| // Copyright IBM Corp. 2014, 2026 |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package rpcapi |
| |
| import ( |
| "testing" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks" |
| "github.com/hashicorp/terraform/internal/stacks/stackstate" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/zclconf/go-cty/cty" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| ) |
| |
| func TestResourceIdentity(t *testing.T) { |
| for name, tc := range map[string]struct { |
| state *stackstate.State |
| |
| expected []*stacks.ListResourceIdentities_Resource |
| expectedErr error |
| }{ |
| "nil state": { |
| state: nil, |
| expected: []*stacks.ListResourceIdentities_Resource{}, |
| }, |
| "empty state": { |
| state: stackstate.NewStateBuilder().Build(), |
| expected: []*stacks.ListResourceIdentities_Resource{}, |
| }, |
| "resource with no identity": { |
| state: stackstate.NewStateBuilder(). |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). |
| SetAddr(mustAbsResourceInstanceObject(t, "component.self.testing_resource.hello")). |
| SetProviderAddr(mustDefaultRootProvider("testing")). |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: mustMarshalJSONAttrs(map[string]any{ |
| "id": "moved", |
| "value": "moved", |
| }), |
| })). |
| Build(), |
| expected: []*stacks.ListResourceIdentities_Resource{}, |
| }, |
| |
| "resource with identity": { |
| state: stackstate.NewStateBuilder(). |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). |
| SetAddr(mustAbsResourceInstanceObject(t, "component.self.testing_resource.hello")). |
| SetProviderAddr(mustDefaultRootProvider("testing")). |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: mustMarshalJSONAttrs(map[string]any{ |
| "id": "moved", |
| "value": "moved", |
| }), |
| IdentityJSON: mustMarshalJSONAttrs(map[string]any{ |
| "id": "hello", |
| }), |
| })). |
| Build(), |
| expected: []*stacks.ListResourceIdentities_Resource{ |
| { |
| ComponentAddr: "component.self", |
| ComponentInstanceAddr: "component.self", |
| ResourceInstanceAddr: "testing_resource.hello", |
| ResourceIdentity: &stacks.DynamicValue{ |
| Msgpack: []byte("\x81\xa2id\xa5hello"), |
| }, |
| }, |
| }, |
| }, |
| |
| "resource with identity and newer identity version": { |
| state: stackstate.NewStateBuilder(). |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). |
| SetAddr(mustAbsResourceInstanceObject(t, "component.self.testing_resource.hello")). |
| SetProviderAddr(mustDefaultRootProvider("testing")). |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: mustMarshalJSONAttrs(map[string]any{ |
| "id": "moved", |
| "value": "moved", |
| }), |
| IdentityJSON: mustMarshalJSONAttrs(map[string]any{ |
| "id": "hello", |
| }), |
| IdentitySchemaVersion: 2, |
| })). |
| Build(), |
| expectedErr: status.Errorf(codes.InvalidArgument, "resource testing_resource.hello has an invalid identity schema version, please update the provider or refresh the state"), |
| }, |
| } { |
| t.Run(name, func(t *testing.T) { |
| |
| identitySchemas := map[addrs.Provider]map[string]providers.IdentitySchema{ |
| addrs.NewDefaultProvider("testing"): { |
| "testing_resource": { |
| Version: 0, |
| Body: &configschema.Object{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Required: true, |
| }, |
| }, |
| Nesting: configschema.NestingSingle, |
| }, |
| }, |
| }, |
| } |
| |
| actual, err := listResourceIdentities(tc.state, identitySchemas) |
| |
| if tc.expectedErr != nil { |
| if err == nil { |
| t.Errorf("expected error %v, got nil", tc.expectedErr) |
| } else if err.Error() != tc.expectedErr.Error() { |
| t.Errorf("expected error %v, got %v", tc.expectedErr, err) |
| } |
| } else { |
| if err != nil { |
| t.Fatalf("expected no error, got %v", err) |
| } |
| |
| if len(actual) != len(tc.expected) { |
| t.Errorf("expected %d resources, got %d", len(tc.expected), len(actual)) |
| } |
| |
| for index, expected := range tc.expected { |
| actual := actual[index] |
| |
| if actual.ComponentAddr != expected.ComponentAddr { |
| t.Errorf("expected component address %s, got %s", expected.ComponentAddr, actual.ComponentAddr) |
| } |
| |
| if actual.ComponentInstanceAddr != expected.ComponentInstanceAddr { |
| t.Errorf("expected component instance address %s, got %s", expected.ComponentInstanceAddr, actual.ComponentInstanceAddr) |
| } |
| |
| if actual.ResourceInstanceAddr != expected.ResourceInstanceAddr { |
| t.Errorf("expected resource instance address %s, got %s", expected.ResourceInstanceAddr, actual.ResourceInstanceAddr) |
| } |
| |
| if string(actual.ResourceIdentity.Msgpack) != string(expected.ResourceIdentity.Msgpack) { |
| t.Errorf("expected resource identity %v, got %v", expected.ResourceIdentity, actual.ResourceIdentity) |
| } |
| } |
| } |
| }) |
| } |
| } |