package instance import ( "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/zitadel/cmd/build" z_oidc "github.com/zitadel/zitadel/internal/api/oidc" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/config/systemdefaults" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" authn "github.com/zitadel/zitadel/pkg/grpc/authn/v2beta" filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta" instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta" "github.com/zitadel/zitadel/pkg/grpc/object/v2" "golang.org/x/text/language" "google.golang.org/protobuf/types/known/timestamppb" ) func Test_InstancesToPb(t *testing.T) { instances := []*query.Instance{ { ID: "instance1", Name: "Instance One", Domains: []*query.InstanceDomain{ { Domain: "example.com", IsPrimary: true, IsGenerated: false, Sequence: 1, CreationDate: time.Unix(123, 0), ChangeDate: time.Unix(124, 0), InstanceID: "instance1", }, }, Sequence: 1, CreationDate: time.Unix(123, 0), ChangeDate: time.Unix(124, 0), }, } want := []*instance.Instance{ { Id: "instance1", Name: "Instance One", Domains: []*instance.Domain{ { Domain: "example.com", Primary: true, Generated: false, Details: &object.Details{ Sequence: 1, ChangeDate: ×tamppb.Timestamp{Seconds: 124}, CreationDate: ×tamppb.Timestamp{Seconds: 123}, ResourceOwner: "instance1", }, }, }, Version: build.Version(), Details: &object.Details{ Sequence: 1, ChangeDate: ×tamppb.Timestamp{Seconds: 124}, CreationDate: ×tamppb.Timestamp{Seconds: 123}, ResourceOwner: "instance1", }, }, } got := InstancesToPb(instances) assert.Equal(t, want, got) } func Test_ListInstancesRequestToModel(t *testing.T) { t.Parallel() searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1", "instance2") require.Nil(t, err) tt := []struct { testName string inputRequest *instance.ListInstancesRequest maxQueryLimit uint64 expectedResult *query.InstanceSearchQueries expectedError error }{ { testName: "when query limit exceeds max query limit should return invalid argument error", maxQueryLimit: 1, inputRequest: &instance.ListInstancesRequest{ Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true}, SortingColumn: instance.FieldName_FIELD_NAME_ID, Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}}, }, expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"), }, { testName: "when valid request should return instance search query model", inputRequest: &instance.ListInstancesRequest{ Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true}, SortingColumn: instance.FieldName_FIELD_NAME_ID, Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}}, }, expectedResult: &query.InstanceSearchQueries{ SearchRequest: query.SearchRequest{ Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceColumnID, }, Queries: []query.SearchQuery{searchInstanceByID}, }, }, } for _, tc := range tt { t.Run(tc.testName, func(t *testing.T) { t.Parallel() sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tc.maxQueryLimit} got, err := ListInstancesRequestToModel(tc.inputRequest, sysDefaults) assert.Equal(t, tc.expectedError, err) assert.Equal(t, tc.expectedResult, got) }) } } func Test_fieldNameToInstanceColumn(t *testing.T) { t.Parallel() tests := []struct { name string fieldName instance.FieldName want query.Column }{ { name: "ID field", fieldName: instance.FieldName_FIELD_NAME_ID, want: query.InstanceColumnID, }, { name: "Name field", fieldName: instance.FieldName_FIELD_NAME_NAME, want: query.InstanceColumnName, }, { name: "Creation Date field", fieldName: instance.FieldName_FIELD_NAME_CREATION_DATE, want: query.InstanceColumnCreationDate, }, { name: "Unknown field", fieldName: instance.FieldName(99), want: query.Column{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := fieldNameToInstanceColumn(tt.fieldName) assert.Equal(t, tt.want, got) }) } } func Test_instanceQueryToModel(t *testing.T) { t.Parallel() searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1") require.Nil(t, err) searchInstanceByDomain, err := query.NewInstanceDomainsListSearchQuery("example.com") require.Nil(t, err) tests := []struct { name string searchQuery *instance.Query want query.SearchQuery wantErr bool }{ { name: "ID Query", searchQuery: &instance.Query{ Query: &instance.Query_IdQuery{ IdQuery: &instance.IdsQuery{ Ids: []string{"instance1"}, }, }, }, want: searchInstanceByID, wantErr: false, }, { name: "Domain Query", searchQuery: &instance.Query{ Query: &instance.Query_DomainQuery{ DomainQuery: &instance.DomainsQuery{ Domains: []string{"example.com"}, }, }, }, want: searchInstanceByDomain, wantErr: false, }, { name: "Invalid Query", searchQuery: &instance.Query{ Query: nil, }, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := instanceQueryToModel(tt.searchQuery) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) } } func Test_CreateInstancePbToSetupInstance(t *testing.T) { t.Parallel() nowTS := timestamppb.Now() tests := []struct { name string req *instance.CreateInstanceRequest defaultInstance command.InstanceSetup externalDomain string want *command.InstanceSetup }{ { name: "Set instance name, custom domain and organization name", req: &instance.CreateInstanceRequest{ InstanceName: " TestInstance", CustomDomain: "test.com ", FirstOrgName: " org ", }, defaultInstance: command.InstanceSetup{}, externalDomain: "external.com", want: &command.InstanceSetup{ InstanceName: "TestInstance", CustomDomain: "test.com", Org: command.InstanceOrgSetup{ Name: "org", }, }, }, { name: "Set machine user with PAT", req: &instance.CreateInstanceRequest{ Owner: &instance.CreateInstanceRequest_Machine_{ Machine: &instance.CreateInstanceRequest_Machine{ UserName: "machine-user", Name: "Machine Name", PersonalAccessToken: &instance.CreateInstanceRequest_PersonalAccessToken{ ExpirationDate: nowTS, }, MachineKey: &instance.CreateInstanceRequest_MachineKey{ Type: authn.KeyType(authn.KeyType_KEY_TYPE_JSON), ExpirationDate: nowTS, }, }, }, }, defaultInstance: command.InstanceSetup{ Org: command.InstanceOrgSetup{}, }, externalDomain: "external.com", want: &command.InstanceSetup{ Org: command.InstanceOrgSetup{ Machine: &command.AddMachine{ Machine: &command.Machine{ Username: "machine-user", Name: "Machine Name", }, Pat: &command.AddPat{ ExpirationDate: nowTS.AsTime(), Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}, }, MachineKey: &command.AddMachineKey{ Type: domain.AuthNKeyTypeJSON, ExpirationDate: nowTS.AsTime(), }, }, }, }, }, { name: "Set machine user with default machine PAT", req: &instance.CreateInstanceRequest{ Owner: &instance.CreateInstanceRequest_Machine_{ Machine: &instance.CreateInstanceRequest_Machine{ UserName: "machine-user", Name: "Machine Name", PersonalAccessToken: &instance.CreateInstanceRequest_PersonalAccessToken{}, MachineKey: &instance.CreateInstanceRequest_MachineKey{}, }, }, }, defaultInstance: command.InstanceSetup{ Org: command.InstanceOrgSetup{ Machine: &command.AddMachine{ Pat: &command.AddPat{ ExpirationDate: nowTS.AsTime(), }, MachineKey: &command.AddMachineKey{ Type: domain.AuthNKeyTypeJSON, ExpirationDate: nowTS.AsTime(), }, }, }, }, externalDomain: "external.com", want: &command.InstanceSetup{ Org: command.InstanceOrgSetup{ Machine: &command.AddMachine{ Machine: &command.Machine{ Username: "machine-user", Name: "Machine Name", }, Pat: &command.AddPat{ ExpirationDate: nowTS.AsTime(), Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}, }, MachineKey: &command.AddMachineKey{ Type: domain.AuthNKeyTypeJSON, ExpirationDate: nowTS.AsTime(), }, }, }, }, }, { name: "Set human user", req: &instance.CreateInstanceRequest{ Owner: &instance.CreateInstanceRequest_Human_{ Human: &instance.CreateInstanceRequest_Human{ UserName: "human-user ", Email: &instance.CreateInstanceRequest_Email{ Email: "john.doe@example.com", }, Profile: &instance.CreateInstanceRequest_Profile{ FirstName: "John ", LastName: " Doe", PreferredLanguage: "it ", }, Password: &instance.CreateInstanceRequest_Password{}, }, }, }, defaultInstance: command.InstanceSetup{ Org: command.InstanceOrgSetup{Human: &command.AddHuman{}}, }, externalDomain: "external.com", want: &command.InstanceSetup{ Org: command.InstanceOrgSetup{ Human: &command.AddHuman{ Username: "human-user", FirstName: "John", LastName: "Doe", Email: command.Email{ Address: "john.doe@example.com", }, PreferredLanguage: language.Italian, }, }, }, }, { name: "Set default language", req: &instance.CreateInstanceRequest{ DefaultLanguage: " en ", }, defaultInstance: command.InstanceSetup{}, externalDomain: "external.com", want: &command.InstanceSetup{ DefaultLanguage: language.English, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := CreateInstancePbToSetupInstance(tt.req, tt.defaultInstance, tt.externalDomain) assert.Equal(t, tt.want, got) }) } }