From 042c438813855fa506b2bb1bbda3efa41ccdf475 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Mon, 12 Aug 2024 22:32:01 +0200 Subject: [PATCH] feat(v3alpha): read actions (#8357) # Which Problems Are Solved The current v3alpha actions APIs don't exactly adhere to the [new resources API design](https://zitadel.com/docs/apis/v3#standard-resources). # How the Problems Are Solved - **Improved ID access**: The aggregate ID is added to the resource details object, so accessing resource IDs and constructing proto messages for resources is easier - **Explicit Instances**: Optionally, the instance can be explicitly given in each request - **Pagination**: A default search limit and a max search limit are added to the defaults.yaml. They apply to the new v3 APIs (currently only actions). The search query defaults are changed to ascending by creation date, because this makes the pagination results the most deterministic. The creation date is also added to the object details. The bug with updated creation dates is fixed for executions and targets. - **Removed Sequences**: Removed Sequence from object details and ProcessedSequence from search details # Additional Changes Object details IDs are checked in unit test only if an empty ID is expected. Centralizing the details check also makes this internal object more flexible for future evolutions. # Additional Context - Closes #8169 - Depends on https://github.com/zitadel/zitadel/pull/8225 --------- Co-authored-by: Silvan Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com> --- cmd/defaults.yaml | 10 +- cmd/start/start.go | 2 +- docs/docs/apis/_v3_action_service.proto | 4 +- docs/docs/apis/_v3_idp_service.proto | 2 +- internal/api/authz/instance.go | 2 +- .../resources/action/v3alpha/execution.go | 19 +- .../v3alpha/execution_integration_test.go | 185 ++-- .../execution_target_integration_test.go | 335 +++++++ .../grpc/resources/action/v3alpha/query.go | 410 ++++++++ .../action/v3alpha/query_integration_test.go | 898 ++++++++++++++++++ .../grpc/resources/action/v3alpha/server.go | 6 +- .../action/v3alpha/server_integration_test.go | 25 +- .../grpc/resources/action/v3alpha/target.go | 31 +- .../action/v3alpha/target_integration_test.go | 113 +-- .../resources/object/v3alpha/converter.go | 70 +- .../server/middleware/instance_interceptor.go | 62 +- .../middleware/instance_interceptor_test.go | 146 ++- internal/api/grpc/server/server.go | 1 - internal/api/grpc/system/instance.go | 7 - .../middleware/instance_interceptor_test.go | 2 +- internal/command/action_v2_execution_test.go | 331 ++++++- internal/command/action_v2_target.go | 2 - internal/command/action_v2_target_test.go | 12 +- internal/command/auth_request_test.go | 4 +- internal/command/converter.go | 1 + internal/command/device_auth_test.go | 6 +- internal/command/idp_intent_test.go | 2 +- .../instance_custom_login_text_test.go | 2 +- .../instance_custom_message_text_test.go | 2 +- .../instance_debug_notification_file_test.go | 6 +- .../instance_debug_notification_log_test.go | 6 +- internal/command/instance_domain_test.go | 6 +- internal/command/instance_features_test.go | 2 +- internal/command/instance_idp_test.go | 50 +- internal/command/instance_member_test.go | 2 +- .../command/instance_oidc_settings_test.go | 4 +- .../command/instance_policy_domain_test.go | 4 +- .../command/instance_policy_label_test.go | 22 +- .../command/instance_policy_login_test.go | 12 +- .../instance_policy_notification_test.go | 4 +- .../instance_policy_password_age_test.go | 2 +- ...nstance_policy_password_complexity_test.go | 2 +- .../instance_policy_password_lockout_test.go | 2 +- .../command/instance_policy_privacy_test.go | 2 +- internal/command/instance_settings_test.go | 6 +- internal/command/instance_test.go | 4 +- .../command/instance_trusted_domain_test.go | 4 +- internal/command/limits_test.go | 11 +- internal/command/org_action_test.go | 10 +- .../command/org_custom_login_text_test.go | 2 +- .../command/org_custom_message_text_test.go | 2 +- internal/command/org_domain_test.go | 8 +- internal/command/org_flow_test.go | 4 +- internal/command/org_idp_config_test.go | 2 +- internal/command/org_idp_test.go | 50 +- internal/command/org_member_test.go | 2 +- internal/command/org_metadata_test.go | 6 +- internal/command/org_policy_domain_test.go | 6 +- internal/command/org_policy_label_test.go | 20 +- internal/command/org_policy_login_test.go | 8 +- .../command/org_policy_notification_test.go | 6 +- .../command/org_policy_password_age_test.go | 2 +- .../org_policy_password_complexity_test.go | 2 +- internal/command/org_policy_privacy_test.go | 2 +- internal/command/preparation_test.go | 17 + internal/command/project_application_test.go | 8 +- internal/command/project_grant_member_test.go | 2 +- internal/command/project_grant_test.go | 6 +- internal/command/project_member_test.go | 2 +- internal/command/project_role_test.go | 4 +- internal/command/project_test.go | 6 +- internal/command/quota_test.go | 6 +- internal/command/restrictions_test.go | 2 +- internal/command/session_test.go | 2 +- internal/command/sms_config_test.go | 10 +- internal/command/smtp_test.go | 12 +- internal/command/system_features_test.go | 2 +- internal/command/user_grant_test.go | 6 +- internal/command/user_human_avatar_test.go | 4 +- internal/command/user_human_email_test.go | 4 +- internal/command/user_human_init_test.go | 2 +- internal/command/user_human_otp_test.go | 14 +- internal/command/user_human_password_test.go | 8 +- internal/command/user_human_phone_test.go | 6 +- .../command/user_human_refresh_token_test.go | 2 +- internal/command/user_idp_link_test.go | 2 +- internal/command/user_machine_key_test.go | 2 +- internal/command/user_machine_secret_test.go | 4 +- internal/command/user_machine_test.go | 4 +- internal/command/user_metadata_test.go | 6 +- .../user_personal_access_token_test.go | 4 +- internal/command/user_schema_test.go | 10 +- internal/command/user_test.go | 14 +- internal/command/user_v2_email_test.go | 2 +- internal/command/user_v2_human_test.go | 4 +- internal/command/user_v2_passkey_test.go | 22 +- internal/command/user_v2_password_test.go | 2 +- internal/command/user_v2_test.go | 10 +- .../config/systemdefaults/system_defaults.go | 2 + internal/domain/object.go | 9 +- internal/integration/assert.go | 47 +- internal/integration/client.go | 2 +- internal/integration/integration.go | 20 +- internal/query/execution.go | 22 +- internal/query/execution_test.go | 32 +- internal/query/instance.go | 8 +- internal/query/projection/target.go | 2 +- internal/query/target.go | 17 +- internal/query/target_test.go | 38 +- internal/static/i18n/bg.yaml | 1 + internal/static/i18n/cs.yaml | 1 + internal/static/i18n/de.yaml | 1 + internal/static/i18n/en.yaml | 2 + internal/static/i18n/es.yaml | 1 + internal/static/i18n/fr.yaml | 1 + internal/static/i18n/it.yaml | 1 + internal/static/i18n/ja.yaml | 1 + internal/static/i18n/mk.yaml | 1 + internal/static/i18n/nl.yaml | 1 + internal/static/i18n/pl.yaml | 1 + internal/static/i18n/pt.yaml | 1 + internal/static/i18n/ru.yaml | 1 + internal/static/i18n/sv.yaml | 1 + internal/static/i18n/zh.yaml | 1 + proto/zitadel/object/v3alpha/object.proto | 7 + .../action/v3alpha/action_service.proto | 219 ++++- .../resources/action/v3alpha/execution.proto | 6 + .../resources/action/v3alpha/query.proto | 117 +++ .../resources/action/v3alpha/target.proto | 29 +- .../resources/object/v3alpha/object.proto | 79 +- 130 files changed, 3253 insertions(+), 605 deletions(-) create mode 100644 internal/api/grpc/resources/action/v3alpha/execution_target_integration_test.go create mode 100644 internal/api/grpc/resources/action/v3alpha/query.go create mode 100644 internal/api/grpc/resources/action/v3alpha/query_integration_test.go create mode 100644 proto/zitadel/resources/action/v3alpha/query.proto diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index f757bb458..6afbaddbd 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -547,6 +547,10 @@ SystemDefaults: PublicKeyLifetime: 30h # ZITADEL_SYSTEMDEFAULTS_KEYCONFIG_PUBLICKEYLIFETIME # 8766h are 1 year CertificateLifetime: 8766h # ZITADEL_SYSTEMDEFAULTS_KEYCONFIG_CERTIFICATELIFETIME + # DefaultQueryLimit limits the number of items that can be queried in a single v3 API search request without explicitly passing a limit. + DefaultQueryLimit: 100 # ZITADEL_SYSTEMDEFAULTS_DEFAULTQUERYLIMIT + # MaxQueryLimit limits the number of items that can be queried in a single v3 API search request with explicitly passing a limit. + MaxQueryLimit: 1000 # ZITADEL_SYSTEMDEFAULTS_MAXQUERYLIMIT Actions: HTTP: @@ -1056,8 +1060,10 @@ InternalAuthZ: - "events.read" - "milestones.read" - "session.delete" + - "action.target.read" - "action.target.write" - "action.target.delete" + - "action.execution.read" - "action.execution.write" - "userschema.read" - "userschema.write" @@ -1092,8 +1098,8 @@ InternalAuthZ: - "project.grant.member.read" - "events.read" - "milestones.read" - - "execution.target.read" - - "execution.read" + - "action.target.read" + - "action.execution.read" - "userschema.read" - Role: "IAM_ORG_MANAGER" Permissions: diff --git a/cmd/start/start.go b/cmd/start/start.go index 1a65ea3f2..d542e8be6 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -436,7 +436,7 @@ func startAPIs( if err := apis.RegisterService(ctx, feature_v2.CreateServer(commands, queries)); err != nil { return nil, err } - if err := apis.RegisterService(ctx, action_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil { + if err := apis.RegisterService(ctx, action_v3_alpha.CreateServer(config.SystemDefaults, commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil { return nil, err } if err := apis.RegisterService(ctx, user_schema_v3_alpha.CreateServer(commands, queries)); err != nil { diff --git a/docs/docs/apis/_v3_action_service.proto b/docs/docs/apis/_v3_action_service.proto index 5bb2b658c..1e62ddd7d 100644 --- a/docs/docs/apis/_v3_action_service.proto +++ b/docs/docs/apis/_v3_action_service.proto @@ -466,7 +466,7 @@ message DeleteTargetResponse { message SearchTargetsRequest { // list limitations and ordering. - zitadel.resources.object.v3alpha.ListQuery query = 2; + zitadel.resources.object.v3alpha.SearchQuery query = 2; // the field the result is sorted. zitadel.resources.action.v3alpha.TargetFieldName sorting_column = 3 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -526,7 +526,7 @@ message DeleteExecutionResponse { message SearchExecutionsRequest { // list limitations and ordering. - zitadel.resources.object.v3alpha.ListQuery query = 1; + zitadel.resources.object.v3alpha.SearchQuery query = 1; // Define the criteria to query for. repeated zitadel.resources.action.v3alpha.ExecutionSearchFilter filters = 2; } diff --git a/docs/docs/apis/_v3_idp_service.proto b/docs/docs/apis/_v3_idp_service.proto index 3a33d407e..26f734f68 100644 --- a/docs/docs/apis/_v3_idp_service.proto +++ b/docs/docs/apis/_v3_idp_service.proto @@ -292,7 +292,7 @@ message DeleteIDPResponse { message SearchIDPsRequest { optional zitadel.object.v3alpha.RequestContext ctx = 1; // list limitations and ordering. - zitadel.resources.object.v3alpha.ListQuery query = 2; + zitadel.resources.object.v3alpha.SearchQuery query = 2; // the field the result is sorted. zitadel.resources.idp.v3alpha.IDPFieldName sorting_column = 3 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { diff --git a/internal/api/authz/instance.go b/internal/api/authz/instance.go index 9610283a5..8721a75f3 100644 --- a/internal/api/authz/instance.go +++ b/internal/api/authz/instance.go @@ -29,7 +29,7 @@ type Instance interface { type InstanceVerifier interface { InstanceByHost(ctx context.Context, host, publicDomain string) (Instance, error) - InstanceByID(ctx context.Context) (Instance, error) + InstanceByID(ctx context.Context, id string) (Instance, error) } type instance struct { diff --git a/internal/api/grpc/resources/action/v3alpha/execution.go b/internal/api/grpc/resources/action/v3alpha/execution.go index 668b6b526..794827970 100644 --- a/internal/api/grpc/resources/action/v3alpha/execution.go +++ b/internal/api/grpc/resources/action/v3alpha/execution.go @@ -4,7 +4,7 @@ import ( "context" "github.com/zitadel/zitadel/internal/api/authz" - settings_object "github.com/zitadel/zitadel/internal/api/grpc/settings/object/v3alpha" + resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/repository/execution" @@ -14,7 +14,7 @@ import ( ) func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) { - if err := checkExecutionEnabled(ctx); err != nil { + if err := checkActionsEnabled(ctx); err != nil { return nil, err } reqTargets := req.GetExecution().GetTargets() @@ -34,24 +34,21 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque set := &command.SetExecution{ Targets: targets, } - owner := &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: authz.GetInstance(ctx).InstanceID(), - } var err error var details *domain.ObjectDetails + instanceID := authz.GetInstance(ctx).InstanceID() switch t := req.GetCondition().GetConditionType().(type) { case *action.Condition_Request: cond := executionConditionFromRequest(t.Request) - details, err = s.command.SetExecutionRequest(ctx, cond, set, owner.Id) + details, err = s.command.SetExecutionRequest(ctx, cond, set, instanceID) case *action.Condition_Response: cond := executionConditionFromResponse(t.Response) - details, err = s.command.SetExecutionResponse(ctx, cond, set, owner.Id) + details, err = s.command.SetExecutionResponse(ctx, cond, set, instanceID) case *action.Condition_Event: cond := executionConditionFromEvent(t.Event) - details, err = s.command.SetExecutionEvent(ctx, cond, set, owner.Id) + details, err = s.command.SetExecutionEvent(ctx, cond, set, instanceID) case *action.Condition_Function: - details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, owner.Id) + details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, instanceID) default: err = zerrors.ThrowInvalidArgument(nil, "ACTION-5r5Ju", "Errors.Execution.ConditionInvalid") } @@ -59,7 +56,7 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque return nil, err } return &action.SetExecutionResponse{ - Details: settings_object.DomainToDetailsPb(details, owner), + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), }, nil } diff --git a/internal/api/grpc/resources/action/v3alpha/execution_integration_test.go b/internal/api/grpc/resources/action/v3alpha/execution_integration_test.go index 9713a3c57..3056d450c 100644 --- a/internal/api/grpc/resources/action/v3alpha/execution_integration_test.go +++ b/internal/api/grpc/resources/action/v3alpha/execution_integration_test.go @@ -13,7 +13,7 @@ import ( "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" - settings_object "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha" + resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" ) func executionTargetsSingleTarget(id string) []*action.ExecutionTargetType { @@ -25,8 +25,9 @@ func executionTargetsSingleInclude(include *action.Condition) []*action.Executio } func TestServer_SetExecution_Request(t *testing.T) { - ensureFeatureEnabled(t) - targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) tests := []struct { name string @@ -51,7 +52,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, { name: "no condition, error", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -66,7 +67,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, { name: "method, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -85,7 +86,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, { name: "method, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -101,18 +102,18 @@ func TestServer_SetExecution_Request(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, }, { name: "service, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -131,7 +132,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, { name: "service, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -147,18 +148,18 @@ func TestServer_SetExecution_Request(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, }, { name: "all, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -174,11 +175,11 @@ func TestServer_SetExecution_Request(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -187,8 +188,8 @@ func TestServer_SetExecution_Request(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // We want to have the same response no matter how often we call the function - Client.SetExecution(tt.ctx, tt.req) - got, err := Client.SetExecution(tt.ctx, tt.req) + Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) + got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return @@ -196,7 +197,7 @@ func TestServer_SetExecution_Request(t *testing.T) { require.NoError(t, err) - integration.AssertSettingsDetails(t, tt.want.Details, got.Details) + integration.AssertResourceDetails(t, tt.want.Details, got.Details) // cleanup to not impact other requests Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition()) @@ -205,8 +206,9 @@ func TestServer_SetExecution_Request(t *testing.T) { } func TestServer_SetExecution_Request_Include(t *testing.T) { - ensureFeatureEnabled(t) - targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) executionCond := &action.Condition{ ConditionType: &action.Condition_Request{ Request: &action.RequestExecution{ @@ -216,7 +218,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, }, } - Tester.SetExecution(CTX, t, + Tester.SetExecution(isolatedIAMOwnerCTX, t, executionCond, executionTargetsSingleTarget(targetResp.GetDetails().GetId()), ) @@ -230,7 +232,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, }, } - Tester.SetExecution(CTX, t, + Tester.SetExecution(isolatedIAMOwnerCTX, t, circularExecutionService, executionTargetsSingleInclude(executionCond), ) @@ -243,7 +245,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, }, } - Tester.SetExecution(CTX, t, + Tester.SetExecution(isolatedIAMOwnerCTX, t, circularExecutionMethod, executionTargetsSingleInclude(circularExecutionService), ) @@ -257,7 +259,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }{ { name: "method, circular error", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: circularExecutionService, Execution: &action.Execution{ @@ -268,7 +270,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, { name: "method, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -280,23 +282,22 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, }, Execution: &action.Execution{ - Targets: executionTargetsSingleInclude(executionCond), }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, }, { name: "service, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Request{ @@ -308,16 +309,15 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, }, Execution: &action.Execution{ - Targets: executionTargetsSingleInclude(executionCond), }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -326,15 +326,15 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // We want to have the same response no matter how often we call the function - Client.SetExecution(tt.ctx, tt.req) - got, err := Client.SetExecution(tt.ctx, tt.req) + Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) + got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) - integration.AssertSettingsDetails(t, tt.want.Details, got.Details) + integration.AssertResourceDetails(t, tt.want.Details, got.Details) // cleanup to not impact other requests Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition()) @@ -343,8 +343,9 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { } func TestServer_SetExecution_Response(t *testing.T) { - ensureFeatureEnabled(t) - targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) tests := []struct { name string @@ -369,7 +370,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, { name: "no condition, error", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -384,7 +385,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, { name: "method, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -403,7 +404,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, { name: "method, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -419,18 +420,18 @@ func TestServer_SetExecution_Response(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, }, { name: "service, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -449,7 +450,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, { name: "service, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -465,18 +466,18 @@ func TestServer_SetExecution_Response(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, }, { name: "all, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -492,11 +493,11 @@ func TestServer_SetExecution_Response(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -505,15 +506,15 @@ func TestServer_SetExecution_Response(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // We want to have the same response no matter how often we call the function - Client.SetExecution(tt.ctx, tt.req) - got, err := Client.SetExecution(tt.ctx, tt.req) + Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) + got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) - integration.AssertSettingsDetails(t, tt.want.Details, got.Details) + integration.AssertResourceDetails(t, tt.want.Details, got.Details) // cleanup to not impact other requests Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition()) @@ -522,8 +523,9 @@ func TestServer_SetExecution_Response(t *testing.T) { } func TestServer_SetExecution_Event(t *testing.T) { - ensureFeatureEnabled(t) - targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) tests := []struct { name string @@ -550,7 +552,7 @@ func TestServer_SetExecution_Event(t *testing.T) { }, { name: "no condition, error", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Event{ @@ -568,7 +570,7 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "event, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Event{ @@ -586,7 +588,7 @@ func TestServer_SetExecution_Event(t *testing.T) { */ { name: "event, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Event{ @@ -602,11 +604,11 @@ func TestServer_SetExecution_Event(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -616,7 +618,7 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "group, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Event{ @@ -634,7 +636,7 @@ func TestServer_SetExecution_Event(t *testing.T) { */ { name: "group, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Event{ @@ -650,18 +652,18 @@ func TestServer_SetExecution_Event(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, }, { name: "all, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Event{ @@ -677,11 +679,11 @@ func TestServer_SetExecution_Event(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -690,15 +692,15 @@ func TestServer_SetExecution_Event(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // We want to have the same response no matter how often we call the function - Client.SetExecution(tt.ctx, tt.req) - got, err := Client.SetExecution(tt.ctx, tt.req) + Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) + got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) - integration.AssertSettingsDetails(t, tt.want.Details, got.Details) + integration.AssertResourceDetails(t, tt.want.Details, got.Details) // cleanup to not impact other requests Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition()) @@ -707,8 +709,9 @@ func TestServer_SetExecution_Event(t *testing.T) { } func TestServer_SetExecution_Function(t *testing.T) { - ensureFeatureEnabled(t) - targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) tests := []struct { name string @@ -733,7 +736,7 @@ func TestServer_SetExecution_Function(t *testing.T) { }, { name: "no condition, error", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Response{ @@ -748,7 +751,7 @@ func TestServer_SetExecution_Function(t *testing.T) { }, { name: "function, not existing", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Function{ @@ -763,7 +766,7 @@ func TestServer_SetExecution_Function(t *testing.T) { }, { name: "function, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.SetExecutionRequest{ Condition: &action.Condition{ ConditionType: &action.Condition_Function{ @@ -775,11 +778,11 @@ func TestServer_SetExecution_Function(t *testing.T) { }, }, want: &action.SetExecutionResponse{ - Details: &settings_object.Details{ - ChangeDate: timestamppb.Now(), + Details: &resource_object.Details{ + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -788,15 +791,15 @@ func TestServer_SetExecution_Function(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // We want to have the same response no matter how often we call the function - Client.SetExecution(tt.ctx, tt.req) - got, err := Client.SetExecution(tt.ctx, tt.req) + Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) + got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) - integration.AssertSettingsDetails(t, tt.want.Details, got.Details) + integration.AssertResourceDetails(t, tt.want.Details, got.Details) // cleanup to not impact other requests Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition()) diff --git a/internal/api/grpc/resources/action/v3alpha/execution_target_integration_test.go b/internal/api/grpc/resources/action/v3alpha/execution_target_integration_test.go new file mode 100644 index 000000000..0fe042eb0 --- /dev/null +++ b/internal/api/grpc/resources/action/v3alpha/execution_target_integration_test.go @@ -0,0 +1,335 @@ +//go:build integration + +package action_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" + + "github.com/zitadel/zitadel/internal/api/grpc/server/middleware" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/integration" + object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" + resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" +) + +func TestServer_ExecutionTarget(t *testing.T) { + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + + fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget" + + tests := []struct { + name string + ctx context.Context + dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) (func(), error) + clean func(context.Context) + req *action.GetTargetRequest + want *action.GetTargetResponse + wantErr bool + }{ + { + name: "GetTarget, request and response, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) { + + orgID := Tester.Organisation.ID + projectID := "" + userID := Tester.Users.Get(instanceID, integration.IAMOwner).ID + + // create target for target changes + targetCreatedName := fmt.Sprint("GetTarget", time.Now().UnixNano()+1) + targetCreatedURL := "https://nonexistent" + + targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false) + + // request received by target + wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request} + changedRequest := &action.GetTargetRequest{Id: targetCreated.GetDetails().GetId()} + // replace original request with different targetID + urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusOK, changedRequest) + targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, false) + Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetDetails().GetId())) + + // expected response from the GetTarget + expectedResponse := &action.GetTargetResponse{ + Target: &action.GetTarget{ + Config: &action.Target{ + Name: targetCreatedName, + Endpoint: targetCreatedURL, + TargetType: &action.Target_RestCall{ + RestCall: &action.SetRESTCall{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + Details: targetCreated.GetDetails(), + }, + } + // has to be set separately because of the pointers + response.Target = &action.GetTarget{ + Details: targetCreated.GetDetails(), + Config: &action.Target{ + Name: targetCreatedName, + TargetType: &action.Target_RestCall{ + RestCall: &action.SetRESTCall{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + Endpoint: targetCreatedURL, + }, + } + + // content for partial update + changedResponse := &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Id: targetCreated.GetDetails().GetId(), + }, + }, + } + + // response received by target + wantResponse := &middleware.ContextInfoResponse{ + FullMethod: fullMethod, + InstanceID: instanceID, + OrgID: orgID, + ProjectID: projectID, + UserID: userID, + Request: changedRequest, + Response: expectedResponse, + } + // after request with different targetID, return changed response + targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusOK, changedResponse) + targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, false) + Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetDetails().GetId())) + + return func() { + closeRequest() + closeResponse() + }, nil + }, + clean: func(ctx context.Context) { + Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod)) + Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod)) + }, + req: &action.GetTargetRequest{ + Id: "something", + }, + want: &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Id: "changed", + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instanceID, + }, + }, + }, + }, + }, + { + name: "GetTarget, request, interrupt", + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) { + + fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget" + orgID := Tester.Organisation.ID + projectID := "" + userID := Tester.Users.Get(instanceID, integration.IAMOwner).ID + + // request received by target + wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request} + urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetRequest{Id: "notchanged"}) + + targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true) + Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetDetails().GetId())) + // GetTarget with used target + request.Id = targetRequest.GetDetails().GetId() + + return func() { + closeRequest() + }, nil + }, + clean: func(ctx context.Context) { + Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod)) + }, + req: &action.GetTargetRequest{}, + wantErr: true, + }, + { + name: "GetTarget, response, interrupt", + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) { + + fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget" + orgID := Tester.Organisation.ID + projectID := "" + userID := Tester.Users.Get(instanceID, integration.IAMOwner).ID + + // create target for target changes + targetCreatedName := fmt.Sprint("GetTarget", time.Now().UnixNano()+1) + targetCreatedURL := "https://nonexistent" + + targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false) + + // GetTarget with used target + request.Id = targetCreated.GetDetails().GetId() + + // expected response from the GetTarget + expectedResponse := &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: targetCreated.GetDetails(), + Config: &action.Target{ + Name: targetCreatedName, + Endpoint: targetCreatedURL, + TargetType: &action.Target_RestCall{ + RestCall: &action.SetRESTCall{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + } + // content for partial update + changedResponse := &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Id: "changed", + }, + }, + } + + // response received by target + wantResponse := &middleware.ContextInfoResponse{ + FullMethod: fullMethod, + InstanceID: instanceID, + OrgID: orgID, + ProjectID: projectID, + UserID: userID, + Request: request, + Response: expectedResponse, + } + // after request with different targetID, return changed response + targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse) + targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, true) + Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetDetails().GetId())) + + return func() { + closeResponse() + }, nil + }, + clean: func(ctx context.Context) { + Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod)) + }, + req: &action.GetTargetRequest{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.dep != nil { + close, err := tt.dep(tt.ctx, tt.req, tt.want) + require.NoError(t, err) + defer close() + } + + got, err := Tester.Client.ActionV3.GetTarget(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + integration.AssertResourceDetails(t, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails()) + require.Equal(t, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig()) + if tt.clean != nil { + tt.clean(tt.ctx) + } + }) + } +} + +func conditionRequestFullMethod(fullMethod string) *action.Condition { + return &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: fullMethod, + }, + }, + }, + } +} + +func conditionResponseFullMethod(fullMethod string) *action.Condition { + return &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Method{ + Method: fullMethod, + }, + }, + }, + } +} + +func testServerCall( + reqBody interface{}, + sleep time.Duration, + statusCode int, + respBody interface{}, +) (string, func()) { + handler := func(w http.ResponseWriter, r *http.Request) { + data, err := json.Marshal(reqBody) + if err != nil { + http.Error(w, "error, marshall: "+err.Error(), http.StatusInternalServerError) + return + } + + sentBody, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "error, read body: "+err.Error(), http.StatusInternalServerError) + return + } + if !reflect.DeepEqual(data, sentBody) { + http.Error(w, "error, equal:\n"+string(data)+"\nsent:\n"+string(sentBody), http.StatusInternalServerError) + return + } + if statusCode != http.StatusOK { + http.Error(w, "error, statusCode", statusCode) + return + } + + time.Sleep(sleep) + + w.Header().Set("Content-Type", "application/json") + resp, err := json.Marshal(respBody) + if err != nil { + http.Error(w, "error", http.StatusInternalServerError) + return + } + if _, err := io.WriteString(w, string(resp)); err != nil { + http.Error(w, "error", http.StatusInternalServerError) + return + } + } + + server := httptest.NewServer(http.HandlerFunc(handler)) + + return server.URL, server.Close +} diff --git a/internal/api/grpc/resources/action/v3alpha/query.go b/internal/api/grpc/resources/action/v3alpha/query.go new file mode 100644 index 000000000..ec7ed8b9c --- /dev/null +++ b/internal/api/grpc/resources/action/v3alpha/query.go @@ -0,0 +1,410 @@ +package action + +import ( + "context" + "strings" + + "google.golang.org/protobuf/types/known/durationpb" + + resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/zerrors" + object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" +) + +const ( + conditionIDAllSegmentCount = 0 + conditionIDRequestResponseServiceSegmentCount = 1 + conditionIDRequestResponseMethodSegmentCount = 2 + conditionIDEventGroupSegmentCount = 1 +) + +func (s *Server) GetTarget(ctx context.Context, req *action.GetTargetRequest) (*action.GetTargetResponse, error) { + if err := checkActionsEnabled(ctx); err != nil { + return nil, err + } + + resp, err := s.query.GetTargetByID(ctx, req.GetId()) + if err != nil { + return nil, err + } + return &action.GetTargetResponse{ + Target: targetToPb(resp), + }, nil +} + +type InstanceContext interface { + GetInstanceId() string + GetInstanceDomain() string +} + +type Context interface { + GetOwner() InstanceContext +} + +func (s *Server) SearchTargets(ctx context.Context, req *action.SearchTargetsRequest) (*action.SearchTargetsResponse, error) { + if err := checkActionsEnabled(ctx); err != nil { + return nil, err + } + queries, err := s.searchTargetsRequestToModel(req) + if err != nil { + return nil, err + } + resp, err := s.query.SearchTargets(ctx, queries) + if err != nil { + return nil, err + } + return &action.SearchTargetsResponse{ + Result: targetsToPb(resp.Targets), + Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse), + }, nil +} + +func (s *Server) SearchExecutions(ctx context.Context, req *action.SearchExecutionsRequest) (*action.SearchExecutionsResponse, error) { + if err := checkActionsEnabled(ctx); err != nil { + return nil, err + } + queries, err := s.searchExecutionsRequestToModel(req) + if err != nil { + return nil, err + } + resp, err := s.query.SearchExecutions(ctx, queries) + if err != nil { + return nil, err + } + return &action.SearchExecutionsResponse{ + Result: executionsToPb(resp.Executions), + Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse), + }, nil +} + +func targetsToPb(targets []*query.Target) []*action.GetTarget { + t := make([]*action.GetTarget, len(targets)) + for i, target := range targets { + t[i] = targetToPb(target) + } + return t +} + +func targetToPb(t *query.Target) *action.GetTarget { + target := &action.GetTarget{ + Details: resource_object.DomainToDetailsPb(&t.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, t.ResourceOwner), + Config: &action.Target{ + Name: t.Name, + Timeout: durationpb.New(t.Timeout), + Endpoint: t.Endpoint, + }, + } + switch t.TargetType { + case domain.TargetTypeWebhook: + target.Config.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}} + case domain.TargetTypeCall: + target.Config.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}} + case domain.TargetTypeAsync: + target.Config.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}} + default: + target.Config.TargetType = nil + } + return target +} + +func (s *Server) searchTargetsRequestToModel(req *action.SearchTargetsRequest) (*query.TargetSearchQueries, error) { + offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query) + if err != nil { + return nil, err + } + queries, err := targetQueriesToQuery(req.Filters) + if err != nil { + return nil, err + } + return &query.TargetSearchQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + SortingColumn: targetFieldNameToSortingColumn(req.SortingColumn), + }, + Queries: queries, + }, nil +} + +func targetQueriesToQuery(queries []*action.TargetSearchFilter) (_ []query.SearchQuery, err error) { + q := make([]query.SearchQuery, len(queries)) + for i, qry := range queries { + q[i], err = targetQueryToQuery(qry) + if err != nil { + return nil, err + } + } + return q, nil +} + +func targetQueryToQuery(filter *action.TargetSearchFilter) (query.SearchQuery, error) { + switch q := filter.Filter.(type) { + case *action.TargetSearchFilter_TargetNameFilter: + return targetNameQueryToQuery(q.TargetNameFilter) + case *action.TargetSearchFilter_InTargetIdsFilter: + return targetInTargetIdsQueryToQuery(q.InTargetIdsFilter) + default: + return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") + } +} + +func targetNameQueryToQuery(q *action.TargetNameFilter) (query.SearchQuery, error) { + return query.NewTargetNameSearchQuery(resource_object.TextMethodPbToQuery(q.Method), q.GetTargetName()) +} + +func targetInTargetIdsQueryToQuery(q *action.InTargetIDsFilter) (query.SearchQuery, error) { + return query.NewTargetInIDsSearchQuery(q.GetTargetIds()) +} + +// targetFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination +func targetFieldNameToSortingColumn(field *action.TargetFieldName) query.Column { + if field == nil { + return query.TargetColumnCreationDate + } + switch *field { + case action.TargetFieldName_TARGET_FIELD_NAME_UNSPECIFIED: + return query.TargetColumnID + case action.TargetFieldName_TARGET_FIELD_NAME_ID: + return query.TargetColumnID + case action.TargetFieldName_TARGET_FIELD_NAME_CREATED_DATE: + return query.TargetColumnCreationDate + case action.TargetFieldName_TARGET_FIELD_NAME_CHANGED_DATE: + return query.TargetColumnChangeDate + case action.TargetFieldName_TARGET_FIELD_NAME_NAME: + return query.TargetColumnName + case action.TargetFieldName_TARGET_FIELD_NAME_TARGET_TYPE: + return query.TargetColumnTargetType + case action.TargetFieldName_TARGET_FIELD_NAME_URL: + return query.TargetColumnURL + case action.TargetFieldName_TARGET_FIELD_NAME_TIMEOUT: + return query.TargetColumnTimeout + case action.TargetFieldName_TARGET_FIELD_NAME_INTERRUPT_ON_ERROR: + return query.TargetColumnInterruptOnError + default: + return query.TargetColumnCreationDate + } +} + +// executionFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination +func executionFieldNameToSortingColumn(field *action.ExecutionFieldName) query.Column { + if field == nil { + return query.ExecutionColumnCreationDate + } + switch *field { + case action.ExecutionFieldName_EXECUTION_FIELD_NAME_UNSPECIFIED: + return query.ExecutionColumnID + case action.ExecutionFieldName_EXECUTION_FIELD_NAME_ID: + return query.ExecutionColumnID + case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CREATED_DATE: + return query.ExecutionColumnCreationDate + case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CHANGED_DATE: + return query.ExecutionColumnChangeDate + default: + return query.ExecutionColumnCreationDate + } +} + +func (s *Server) searchExecutionsRequestToModel(req *action.SearchExecutionsRequest) (*query.ExecutionSearchQueries, error) { + offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query) + if err != nil { + return nil, err + } + queries, err := executionQueriesToQuery(req.Filters) + if err != nil { + return nil, err + } + return &query.ExecutionSearchQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + SortingColumn: executionFieldNameToSortingColumn(req.SortingColumn), + }, + Queries: queries, + }, nil +} + +func executionQueriesToQuery(queries []*action.ExecutionSearchFilter) (_ []query.SearchQuery, err error) { + q := make([]query.SearchQuery, len(queries)) + for i, query := range queries { + q[i], err = executionQueryToQuery(query) + if err != nil { + return nil, err + } + } + return q, nil +} + +func executionQueryToQuery(searchQuery *action.ExecutionSearchFilter) (query.SearchQuery, error) { + switch q := searchQuery.Filter.(type) { + case *action.ExecutionSearchFilter_InConditionsFilter: + return inConditionsQueryToQuery(q.InConditionsFilter) + case *action.ExecutionSearchFilter_ExecutionTypeFilter: + return executionTypeToQuery(q.ExecutionTypeFilter) + case *action.ExecutionSearchFilter_IncludeFilter: + include, err := conditionToInclude(q.IncludeFilter.GetInclude()) + if err != nil { + return nil, err + } + return query.NewIncludeSearchQuery(include) + case *action.ExecutionSearchFilter_TargetFilter: + return query.NewTargetSearchQuery(q.TargetFilter.GetTargetId()) + default: + return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") + } +} + +func executionTypeToQuery(q *action.ExecutionTypeFilter) (query.SearchQuery, error) { + switch q.ExecutionType { + case action.ExecutionType_EXECUTION_TYPE_UNSPECIFIED: + return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified) + case action.ExecutionType_EXECUTION_TYPE_REQUEST: + return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeRequest) + case action.ExecutionType_EXECUTION_TYPE_RESPONSE: + return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeResponse) + case action.ExecutionType_EXECUTION_TYPE_EVENT: + return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeEvent) + case action.ExecutionType_EXECUTION_TYPE_FUNCTION: + return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeFunction) + default: + return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified) + } +} + +func inConditionsQueryToQuery(q *action.InConditionsFilter) (query.SearchQuery, error) { + values := make([]string, len(q.GetConditions())) + for i, condition := range q.GetConditions() { + id, err := conditionToID(condition) + if err != nil { + return nil, err + } + values[i] = id + } + return query.NewExecutionInIDsSearchQuery(values) +} + +func conditionToID(q *action.Condition) (string, error) { + switch t := q.GetConditionType().(type) { + case *action.Condition_Request: + cond := &command.ExecutionAPICondition{ + Method: t.Request.GetMethod(), + Service: t.Request.GetService(), + All: t.Request.GetAll(), + } + return cond.ID(domain.ExecutionTypeRequest), nil + case *action.Condition_Response: + cond := &command.ExecutionAPICondition{ + Method: t.Response.GetMethod(), + Service: t.Response.GetService(), + All: t.Response.GetAll(), + } + return cond.ID(domain.ExecutionTypeResponse), nil + case *action.Condition_Event: + cond := &command.ExecutionEventCondition{ + Event: t.Event.GetEvent(), + Group: t.Event.GetGroup(), + All: t.Event.GetAll(), + } + return cond.ID(), nil + case *action.Condition_Function: + return command.ExecutionFunctionCondition(t.Function.GetName()).ID(), nil + default: + return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") + } +} + +func executionsToPb(executions []*query.Execution) []*action.GetExecution { + e := make([]*action.GetExecution, len(executions)) + for i, execution := range executions { + e[i] = executionToPb(execution) + } + return e +} + +func executionToPb(e *query.Execution) *action.GetExecution { + targets := make([]*action.ExecutionTargetType, len(e.Targets)) + for i := range e.Targets { + switch e.Targets[i].Type { + case domain.ExecutionTargetTypeInclude: + targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Include{Include: executionIDToCondition(e.Targets[i].Target)}} + case domain.ExecutionTargetTypeTarget: + targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Target{Target: e.Targets[i].Target}} + case domain.ExecutionTargetTypeUnspecified: + continue + default: + continue + } + } + + return &action.GetExecution{ + Details: resource_object.DomainToDetailsPb(&e.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, e.ResourceOwner), + Execution: &action.Execution{ + Targets: targets, + }, + } +} + +func executionIDToCondition(include string) *action.Condition { + if strings.HasPrefix(include, domain.ExecutionTypeRequest.String()) { + return includeRequestToCondition(strings.TrimPrefix(include, domain.ExecutionTypeRequest.String())) + } + if strings.HasPrefix(include, domain.ExecutionTypeResponse.String()) { + return includeResponseToCondition(strings.TrimPrefix(include, domain.ExecutionTypeResponse.String())) + } + if strings.HasPrefix(include, domain.ExecutionTypeEvent.String()) { + return includeEventToCondition(strings.TrimPrefix(include, domain.ExecutionTypeEvent.String())) + } + if strings.HasPrefix(include, domain.ExecutionTypeFunction.String()) { + return includeFunctionToCondition(strings.TrimPrefix(include, domain.ExecutionTypeFunction.String())) + } + return nil +} + +func includeRequestToCondition(id string) *action.Condition { + switch strings.Count(id, "/") { + case conditionIDRequestResponseMethodSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}} + case conditionIDRequestResponseServiceSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}} + case conditionIDAllSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}} + default: + return nil + } +} +func includeResponseToCondition(id string) *action.Condition { + switch strings.Count(id, "/") { + case conditionIDRequestResponseMethodSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}} + case conditionIDRequestResponseServiceSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}} + case conditionIDAllSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}} + default: + return nil + } +} + +func includeEventToCondition(id string) *action.Condition { + switch strings.Count(id, "/") { + case conditionIDEventGroupSegmentCount: + if strings.HasSuffix(id, command.EventGroupSuffix) { + return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: strings.TrimSuffix(strings.TrimPrefix(id, "/"), command.EventGroupSuffix)}}}} + } else { + return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: strings.TrimPrefix(id, "/")}}}} + } + case conditionIDAllSegmentCount: + return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}} + default: + return nil + } +} + +func includeFunctionToCondition(id string) *action.Condition { + return &action.Condition{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: strings.TrimPrefix(id, "/")}}} +} diff --git a/internal/api/grpc/resources/action/v3alpha/query_integration_test.go b/internal/api/grpc/resources/action/v3alpha/query_integration_test.go new file mode 100644 index 000000000..b4f757828 --- /dev/null +++ b/internal/api/grpc/resources/action/v3alpha/query_integration_test.go @@ -0,0 +1,898 @@ +//go:build integration + +package action_test + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/integration" + object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" + resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" +) + +func TestServer_GetTarget(t *testing.T) { + _, _, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + type args struct { + ctx context.Context + dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) error + req *action.GetTargetRequest + } + tests := []struct { + name string + args args + want *action.GetTargetResponse + wantErr bool + }{ + { + name: "missing permission", + args: args{ + ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), + req: &action.GetTargetRequest{}, + }, + wantErr: true, + }, + { + name: "not found", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &action.GetTargetRequest{Id: "notexisting"}, + }, + wantErr: true, + }, + { + name: "get, ok", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + request.Id = resp.GetDetails().GetId() + response.Target.Config.Name = name + response.Target.Details = resp.GetDetails() + return nil + }, + req: &action.GetTargetRequest{}, + }, + want: &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{}, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + { + name: "get, async, ok", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false) + request.Id = resp.GetDetails().GetId() + response.Target.Config.Name = name + response.Target.Details = resp.GetDetails() + return nil + }, + req: &action.GetTargetRequest{}, + }, + want: &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestAsync{ + RestAsync: &action.SetRESTAsync{}, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + { + name: "get, webhook interruptOnError, ok", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true) + request.Id = resp.GetDetails().GetId() + response.Target.Config.Name = name + response.Target.Details = resp.GetDetails() + return nil + }, + req: &action.GetTargetRequest{}, + }, + want: &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ + InterruptOnError: true, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + { + name: "get, call, ok", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false) + request.Id = resp.GetDetails().GetId() + response.Target.Config.Name = name + response.Target.Details = resp.GetDetails() + return nil + }, + req: &action.GetTargetRequest{}, + }, + want: &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestCall{ + RestCall: &action.SetRESTCall{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + { + name: "get, call interruptOnError, ok", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true) + request.Id = resp.GetDetails().GetId() + response.Target.Config.Name = name + response.Target.Details = resp.GetDetails() + return nil + }, + req: &action.GetTargetRequest{}, + }, + want: &action.GetTargetResponse{ + Target: &action.GetTarget{ + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestCall{ + RestCall: &action.SetRESTCall{ + InterruptOnError: true, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.dep != nil { + err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want) + require.NoError(t, err) + } + got, getErr := Tester.Client.ActionV3.GetTarget(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, getErr, "Error: "+getErr.Error()) + } else { + assert.NoError(t, getErr) + wantTarget := tt.want.GetTarget() + gotTarget := got.GetTarget() + integration.AssertResourceDetails(t, wantTarget.GetDetails(), gotTarget.GetDetails()) + assert.Equal(t, wantTarget.GetConfig(), gotTarget.GetConfig()) + } + }) + } +} + +func TestServer_ListTargets(t *testing.T) { + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + type args struct { + ctx context.Context + dep func(context.Context, *action.SearchTargetsRequest, *action.SearchTargetsResponse) error + req *action.SearchTargetsRequest + } + tests := []struct { + name string + args args + want *action.SearchTargetsResponse + wantErr bool + }{ + { + name: "missing permission", + args: args{ + ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), + req: &action.SearchTargetsRequest{}, + }, + wantErr: true, + }, + { + name: "list, not found", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &action.SearchTargetsRequest{ + Filters: []*action.TargetSearchFilter{ + {Filter: &action.TargetSearchFilter_InTargetIdsFilter{ + InTargetIdsFilter: &action.InTargetIDsFilter{ + TargetIds: []string{"notfound"}, + }, + }, + }, + }, + }, + }, + want: &action.SearchTargetsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 0, + AppliedLimit: 100, + }, + Result: []*action.GetTarget{}, + }, + }, + { + name: "list single id", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{ + InTargetIdsFilter: &action.InTargetIDsFilter{ + TargetIds: []string{resp.GetDetails().GetId()}, + }, + } + response.Details.Timestamp = resp.GetDetails().GetChanged() + + response.Result[0].Details = resp.GetDetails() + response.Result[0].Config.Name = name + return nil + }, + req: &action.SearchTargetsRequest{ + Filters: []*action.TargetSearchFilter{{}}, + }, + }, + want: &action.SearchTargetsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 1, + AppliedLimit: 100, + }, + Result: []*action.GetTarget{ + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + }, { + name: "list single name", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error { + name := fmt.Sprint(time.Now().UnixNano() + 1) + resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + request.Filters[0].Filter = &action.TargetSearchFilter_TargetNameFilter{ + TargetNameFilter: &action.TargetNameFilter{ + TargetName: name, + }, + } + response.Details.Timestamp = resp.GetDetails().GetChanged() + + response.Result[0].Details = resp.GetDetails() + response.Result[0].Config.Name = name + return nil + }, + req: &action.SearchTargetsRequest{ + Filters: []*action.TargetSearchFilter{{}}, + }, + }, + want: &action.SearchTargetsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 1, + AppliedLimit: 100, + }, + Result: []*action.GetTarget{ + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instanceID, + }, + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + }, + { + name: "list multiple id", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error { + name1 := fmt.Sprint(time.Now().UnixNano() + 1) + name2 := fmt.Sprint(time.Now().UnixNano() + 3) + name3 := fmt.Sprint(time.Now().UnixNano() + 5) + resp1 := Tester.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false) + resp2 := Tester.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true) + resp3 := Tester.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false) + request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{ + InTargetIdsFilter: &action.InTargetIDsFilter{ + TargetIds: []string{resp1.GetDetails().GetId(), resp2.GetDetails().GetId(), resp3.GetDetails().GetId()}, + }, + } + response.Details.Timestamp = resp3.GetDetails().GetChanged() + + response.Result[0].Details = resp1.GetDetails() + response.Result[0].Config.Name = name1 + response.Result[1].Details = resp2.GetDetails() + response.Result[1].Config.Name = name2 + response.Result[2].Details = resp3.GetDetails() + response.Result[2].Config.Name = name3 + return nil + }, + req: &action.SearchTargetsRequest{ + Filters: []*action.TargetSearchFilter{{}}, + }, + }, + want: &action.SearchTargetsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 3, + AppliedLimit: 100, + }, + Result: []*action.GetTarget{ + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instanceID, + }, + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ + InterruptOnError: false, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instanceID, + }, + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestCall{ + RestCall: &action.SetRESTCall{ + InterruptOnError: true, + }, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instanceID, + }, + }, + Config: &action.Target{ + Endpoint: "https://example.com", + TargetType: &action.Target_RestAsync{ + RestAsync: &action.SetRESTAsync{}, + }, + Timeout: durationpb.New(10 * time.Second), + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.dep != nil { + err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want) + require.NoError(t, err) + } + + retryDuration := 5 * time.Second + if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok { + retryDuration = time.Until(ctxDeadline) + } + + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + got, listErr := Tester.Client.ActionV3.SearchTargets(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(ttt, listErr, "Error: "+listErr.Error()) + } else { + assert.NoError(ttt, listErr) + } + if listErr != nil { + return + } + // always first check length, otherwise its failed anyway + assert.Len(ttt, got.Result, len(tt.want.Result)) + for i := range tt.want.Result { + integration.AssertResourceDetails(t, tt.want.Result[i].GetDetails(), got.Result[i].GetDetails()) + assert.Equal(ttt, tt.want.Result[i].GetConfig(), got.Result[i].GetConfig()) + } + integration.AssertResourceListDetails(t, tt.want, got) + }, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result") + }) + } +} + +func TestServer_SearchExecutions(t *testing.T) { + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + + type args struct { + ctx context.Context + dep func(context.Context, *action.SearchExecutionsRequest, *action.SearchExecutionsResponse) error + req *action.SearchExecutionsRequest + } + tests := []struct { + name string + args args + want *action.SearchExecutionsResponse + wantErr bool + }{ + { + name: "missing permission", + args: args{ + ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), + req: &action.SearchExecutionsRequest{}, + }, + wantErr: true, + }, + { + name: "list request single condition", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error { + cond := request.Filters[0].GetInConditionsFilter().GetConditions()[0] + resp := Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetDetails().GetId())) + + response.Details.Timestamp = resp.GetDetails().GetChanged() + // Set expected response with used values for SetExecution + response.Result[0].Details = resp.GetDetails() + response.Result[0].Condition = cond + return nil + }, + req: &action.SearchExecutionsRequest{ + Filters: []*action.ExecutionSearchFilter{{ + Filter: &action.ExecutionSearchFilter_InConditionsFilter{ + InConditionsFilter: &action.InConditionsFilter{ + Conditions: []*action.Condition{{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.session.v2.SessionService/GetSession", + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + want: &action.SearchExecutionsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 1, + AppliedLimit: 100, + }, + Result: []*action.GetExecution{ + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.session.v2.SessionService/GetSession", + }, + }, + }, + }, + Execution: &action.Execution{ + Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()), + }, + }, + }, + }, + }, + { + name: "list request single target", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error { + target := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + // add target as Filter to the request + request.Filters[0] = &action.ExecutionSearchFilter{ + Filter: &action.ExecutionSearchFilter_TargetFilter{ + TargetFilter: &action.TargetFilter{ + TargetId: target.GetDetails().GetId(), + }, + }, + } + cond := &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.management.v1.ManagementService/UpdateAction", + }, + }, + }, + } + targets := executionTargetsSingleTarget(target.GetDetails().GetId()) + resp := Tester.SetExecution(ctx, t, cond, targets) + + response.Details.Timestamp = resp.GetDetails().GetChanged() + + response.Result[0].Details = resp.GetDetails() + response.Result[0].Condition = cond + response.Result[0].Execution.Targets = targets + return nil + }, + req: &action.SearchExecutionsRequest{ + Filters: []*action.ExecutionSearchFilter{{}}, + }, + }, + want: &action.SearchExecutionsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 1, + AppliedLimit: 100, + }, + Result: []*action.GetExecution{ + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + Condition: &action.Condition{}, + Execution: &action.Execution{ + Targets: executionTargetsSingleTarget(""), + }, + }, + }, + }, + }, { + name: "list request single include", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error { + cond := &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.management.v1.ManagementService/GetAction", + }, + }, + }, + } + Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetDetails().GetId())) + request.Filters[0].GetIncludeFilter().Include = cond + + includeCond := &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.management.v1.ManagementService/ListActions", + }, + }, + }, + } + includeTargets := executionTargetsSingleInclude(cond) + resp2 := Tester.SetExecution(ctx, t, includeCond, includeTargets) + + response.Details.Timestamp = resp2.GetDetails().GetChanged() + + response.Result[0].Details = resp2.GetDetails() + response.Result[0].Condition = includeCond + response.Result[0].Execution = &action.Execution{ + Targets: includeTargets, + } + return nil + }, + req: &action.SearchExecutionsRequest{ + Filters: []*action.ExecutionSearchFilter{{ + Filter: &action.ExecutionSearchFilter_IncludeFilter{ + IncludeFilter: &action.IncludeFilter{}, + }, + }}, + }, + }, + want: &action.SearchExecutionsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 1, + AppliedLimit: 100, + }, + Result: []*action.GetExecution{ + { + Details: &resource_object.Details{ + Created: timestamppb.Now(), + Changed: timestamppb.Now(), + }, + }, + }, + }, + }, + { + name: "list multiple conditions", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error { + + cond1 := request.Filters[0].GetInConditionsFilter().GetConditions()[0] + targets1 := executionTargetsSingleTarget(targetResp.GetDetails().GetId()) + resp1 := Tester.SetExecution(ctx, t, cond1, targets1) + response.Result[0].Details = resp1.GetDetails() + response.Result[0].Condition = cond1 + response.Result[0].Execution = &action.Execution{ + Targets: targets1, + } + + cond2 := request.Filters[0].GetInConditionsFilter().GetConditions()[1] + targets2 := executionTargetsSingleTarget(targetResp.GetDetails().GetId()) + resp2 := Tester.SetExecution(ctx, t, cond2, targets2) + response.Result[1].Details = resp2.GetDetails() + response.Result[1].Condition = cond2 + response.Result[1].Execution = &action.Execution{ + Targets: targets2, + } + + cond3 := request.Filters[0].GetInConditionsFilter().GetConditions()[2] + targets3 := executionTargetsSingleTarget(targetResp.GetDetails().GetId()) + resp3 := Tester.SetExecution(ctx, t, cond3, targets3) + response.Result[2].Details = resp3.GetDetails() + response.Result[2].Condition = cond3 + response.Result[2].Execution = &action.Execution{ + Targets: targets3, + } + response.Details.Timestamp = resp3.GetDetails().GetChanged() + return nil + }, + req: &action.SearchExecutionsRequest{ + Filters: []*action.ExecutionSearchFilter{{ + Filter: &action.ExecutionSearchFilter_InConditionsFilter{ + InConditionsFilter: &action.InConditionsFilter{ + Conditions: []*action.Condition{ + { + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.session.v2.SessionService/GetSession", + }, + }, + }, + }, + { + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.session.v2.SessionService/CreateSession", + }, + }, + }, + }, + { + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ + Method: "/zitadel.session.v2.SessionService/SetSession", + }, + }, + }, + }, + }, + }, + }, + }}, + }, + }, + want: &action.SearchExecutionsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 3, + AppliedLimit: 100, + }, + Result: []*action.GetExecution{ + { + Details: &resource_object.Details{ + Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}, + }, + }, { + Details: &resource_object.Details{ + Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}, + }, + }, { + Details: &resource_object.Details{ + Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}, + }, + }, + }, + }, + }, + { + name: "list multiple conditions all types", + args: args{ + ctx: isolatedIAMOwnerCTX, + dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error { + targets := executionTargetsSingleTarget(targetResp.GetDetails().GetId()) + for i, cond := range request.Filters[0].GetInConditionsFilter().GetConditions() { + resp := Tester.SetExecution(ctx, t, cond, targets) + response.Result[i].Details = resp.GetDetails() + response.Result[i].Condition = cond + response.Result[i].Execution = &action.Execution{ + Targets: targets, + } + // filled with info of last sequence + response.Details.Timestamp = resp.GetDetails().GetChanged() + } + + return nil + }, + req: &action.SearchExecutionsRequest{ + Filters: []*action.ExecutionSearchFilter{{ + Filter: &action.ExecutionSearchFilter_InConditionsFilter{ + InConditionsFilter: &action.InConditionsFilter{ + Conditions: []*action.Condition{ + {ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}}, + {ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: "zitadel.session.v2.SessionService"}}}}, + {ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}}, + {ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}}, + {ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: "zitadel.session.v2.SessionService"}}}}, + {ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}}, + {ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: "user.added"}}}}, + {ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: "user"}}}}, + {ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}}, + {ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"}}}, + }, + }, + }, + }}, + }, + }, + want: &action.SearchExecutionsResponse{ + Details: &resource_object.ListDetails{ + TotalResult: 10, + AppliedLimit: 100, + }, + Result: []*action.GetExecution{ + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + {Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.dep != nil { + err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want) + require.NoError(t, err) + } + + retryDuration := 5 * time.Second + if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok { + retryDuration = time.Until(ctxDeadline) + } + + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + got, listErr := Tester.Client.ActionV3.SearchExecutions(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, listErr, "Error: "+listErr.Error()) + } else { + assert.NoError(t, listErr) + } + if listErr != nil { + return + } + // always first check length, otherwise its failed anyway + assert.Len(t, got.Result, len(tt.want.Result)) + for i := range tt.want.Result { + // as not sorted, all elements have to be checked + // workaround as oneof elements can only be checked with assert.EqualExportedValues() + if j, found := containExecution(got.Result, tt.want.Result[i]); found { + assert.EqualExportedValues(t, tt.want.Result[i], got.Result[j]) + } + } + integration.AssertResourceListDetails(t, tt.want, got) + }, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result") + }) + } +} + +func containExecution(executionList []*action.GetExecution, execution *action.GetExecution) (int, bool) { + for i, exec := range executionList { + if reflect.DeepEqual(exec.Details, execution.Details) { + return i, true + } + } + return 0, false +} diff --git a/internal/api/grpc/resources/action/v3alpha/server.go b/internal/api/grpc/resources/action/v3alpha/server.go index 57d0761fd..b80c60d66 100644 --- a/internal/api/grpc/resources/action/v3alpha/server.go +++ b/internal/api/grpc/resources/action/v3alpha/server.go @@ -8,6 +8,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/server" "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/config/systemdefaults" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" @@ -17,6 +18,7 @@ var _ action.ZITADELActionsServer = (*Server)(nil) type Server struct { action.UnimplementedZITADELActionsServer + systemDefaults systemdefaults.SystemDefaults command *command.Commands query *query.Queries ListActionFunctions func() []string @@ -27,6 +29,7 @@ type Server struct { type Config struct{} func CreateServer( + systemDefaults systemdefaults.SystemDefaults, command *command.Commands, query *query.Queries, listActionFunctions func() []string, @@ -34,6 +37,7 @@ func CreateServer( listGRPCServices func() []string, ) *Server { return &Server{ + systemDefaults: systemDefaults, command: command, query: query, ListActionFunctions: listActionFunctions, @@ -62,7 +66,7 @@ func (s *Server) RegisterGateway() server.RegisterGatewayFunc { return action.RegisterZITADELActionsHandler } -func checkExecutionEnabled(ctx context.Context) error { +func checkActionsEnabled(ctx context.Context) error { if authz.GetInstance(ctx).Features().Actions { return nil } diff --git a/internal/api/grpc/resources/action/v3alpha/server_integration_test.go b/internal/api/grpc/resources/action/v3alpha/server_integration_test.go index 483ed2bd3..e286ea94b 100644 --- a/internal/api/grpc/resources/action/v3alpha/server_integration_test.go +++ b/internal/api/grpc/resources/action/v3alpha/server_integration_test.go @@ -13,49 +13,48 @@ import ( "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/internal/integration" - feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2" - action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" + "github.com/zitadel/zitadel/pkg/grpc/feature/v2" ) var ( - CTX context.Context - Tester *integration.Tester - Client action.ZITADELActionsClient + IAMOwnerCTX, SystemCTX context.Context + Tester *integration.Tester ) func TestMain(m *testing.M) { os.Exit(func() int { - ctx, errCtx, cancel := integration.Contexts(5 * time.Minute) + ctx, _, cancel := integration.Contexts(5 * time.Minute) defer cancel() Tester = integration.NewTester(ctx) defer Tester.Done() - Client = Tester.Client.ActionV3 - CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx + IAMOwnerCTX = Tester.WithAuthorization(ctx, integration.IAMOwner) + SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser) + return m.Run() }()) } -func ensureFeatureEnabled(t *testing.T) { - f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{ +func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) { + f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{ Inheritance: true, }) require.NoError(t, err) if f.Actions.GetEnabled() { return } - _, err = Tester.Client.FeatureV2.SetInstanceFeatures(CTX, &feature.SetInstanceFeaturesRequest{ + _, err = Tester.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{ Actions: gu.Ptr(true), }) require.NoError(t, err) retryDuration := time.Minute - if ctxDeadline, ok := CTX.Deadline(); ok { + if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok { retryDuration = time.Until(ctxDeadline) } require.EventuallyWithT(t, func(ttt *assert.CollectT) { - f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{ + f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{ Inheritance: true, }) require.NoError(ttt, err) diff --git a/internal/api/grpc/resources/action/v3alpha/target.go b/internal/api/grpc/resources/action/v3alpha/target.go index 5d33dac91..031cd9947 100644 --- a/internal/api/grpc/resources/action/v3alpha/target.go +++ b/internal/api/grpc/resources/action/v3alpha/target.go @@ -15,45 +15,45 @@ import ( ) func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) { - if err := checkExecutionEnabled(ctx); err != nil { + if err := checkActionsEnabled(ctx); err != nil { return nil, err } add := createTargetToCommand(req) - instance := targetOwnerInstance(ctx) - details, err := s.command.AddTarget(ctx, add, instance.Id) + instanceID := authz.GetInstance(ctx).InstanceID() + details, err := s.command.AddTarget(ctx, add, instanceID) if err != nil { return nil, err } return &action.CreateTargetResponse{ - Details: resource_object.DomainToDetailsPb(details, instance, add.AggregateID), + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), }, nil } func (s *Server) PatchTarget(ctx context.Context, req *action.PatchTargetRequest) (*action.PatchTargetResponse, error) { - if err := checkExecutionEnabled(ctx); err != nil { + if err := checkActionsEnabled(ctx); err != nil { return nil, err } - instance := targetOwnerInstance(ctx) - details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instance.Id) + instanceID := authz.GetInstance(ctx).InstanceID() + details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instanceID) if err != nil { return nil, err } return &action.PatchTargetResponse{ - Details: resource_object.DomainToDetailsPb(details, instance, req.GetId()), + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), }, nil } func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetRequest) (*action.DeleteTargetResponse, error) { - if err := checkExecutionEnabled(ctx); err != nil { + if err := checkActionsEnabled(ctx); err != nil { return nil, err } - instance := targetOwnerInstance(ctx) - details, err := s.command.DeleteTarget(ctx, req.GetId(), instance.Id) + instanceID := authz.GetInstance(ctx).InstanceID() + details, err := s.command.DeleteTarget(ctx, req.GetId(), instanceID) if err != nil { return nil, err } return &action.DeleteTargetResponse{ - Details: resource_object.DomainToDetailsPb(details, instance, req.GetId()), + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), }, nil } @@ -112,10 +112,3 @@ func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget } return target } - -func targetOwnerInstance(ctx context.Context) *object.Owner { - return &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: authz.GetInstance(ctx).InstanceID(), - } -} diff --git a/internal/api/grpc/resources/action/v3alpha/target_integration_test.go b/internal/api/grpc/resources/action/v3alpha/target_integration_test.go index bda54bf86..c94c08067 100644 --- a/internal/api/grpc/resources/action/v3alpha/target_integration_test.go +++ b/internal/api/grpc/resources/action/v3alpha/target_integration_test.go @@ -5,6 +5,7 @@ package action_test import ( "context" "fmt" + object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" "testing" "time" @@ -15,13 +16,13 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/integration" - object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" ) func TestServer_CreateTarget(t *testing.T) { - ensureFeatureEnabled(t) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) tests := []struct { name string ctx context.Context @@ -39,7 +40,7 @@ func TestServer_CreateTarget(t *testing.T) { }, { name: "empty name", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: "", }, @@ -47,7 +48,7 @@ func TestServer_CreateTarget(t *testing.T) { }, { name: "empty type", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), TargetType: nil, @@ -56,7 +57,7 @@ func TestServer_CreateTarget(t *testing.T) { }, { name: "empty webhook url", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), TargetType: &action.Target_RestWebhook{ @@ -67,7 +68,7 @@ func TestServer_CreateTarget(t *testing.T) { }, { name: "empty request response url", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), TargetType: &action.Target_RestCall{ @@ -78,7 +79,7 @@ func TestServer_CreateTarget(t *testing.T) { }, { name: "empty timeout", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), Endpoint: "https://example.com", @@ -91,7 +92,7 @@ func TestServer_CreateTarget(t *testing.T) { }, { name: "async, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), Endpoint: "https://example.com", @@ -101,16 +102,16 @@ func TestServer_CreateTarget(t *testing.T) { Timeout: durationpb.New(10 * time.Second), }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "webhook, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), Endpoint: "https://example.com", @@ -122,16 +123,16 @@ func TestServer_CreateTarget(t *testing.T) { Timeout: durationpb.New(10 * time.Second), }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "webhook, interrupt on error, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), Endpoint: "https://example.com", @@ -143,16 +144,16 @@ func TestServer_CreateTarget(t *testing.T) { Timeout: durationpb.New(10 * time.Second), }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "call, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), Endpoint: "https://example.com", @@ -164,17 +165,17 @@ func TestServer_CreateTarget(t *testing.T) { Timeout: durationpb.New(10 * time.Second), }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "call, interruptOnError, ok", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.Target{ Name: fmt.Sprint(time.Now().UnixNano() + 1), Endpoint: "https://example.com", @@ -186,17 +187,17 @@ func TestServer_CreateTarget(t *testing.T) { Timeout: durationpb.New(10 * time.Second), }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Client.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req}) + got, err := Tester.Client.ActionV3.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req}) if tt.wantErr { require.Error(t, err) return @@ -208,7 +209,8 @@ func TestServer_CreateTarget(t *testing.T) { } func TestServer_PatchTarget(t *testing.T) { - ensureFeatureEnabled(t) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) type args struct { ctx context.Context req *action.PatchTargetRequest @@ -223,7 +225,7 @@ func TestServer_PatchTarget(t *testing.T) { { name: "missing permission", prepare: func(request *action.PatchTargetRequest) error { - targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() + targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() request.Id = targetID return nil }, @@ -244,7 +246,7 @@ func TestServer_PatchTarget(t *testing.T) { return nil }, args: args{ - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.PatchTargetRequest{ Target: &action.PatchTarget{ Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)), @@ -256,12 +258,12 @@ func TestServer_PatchTarget(t *testing.T) { { name: "change name, ok", prepare: func(request *action.PatchTargetRequest) error { - targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() + targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() request.Id = targetID return nil }, args: args{ - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.PatchTargetRequest{ Target: &action.PatchTarget{ Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)), @@ -269,22 +271,22 @@ func TestServer_PatchTarget(t *testing.T) { }, }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "change type, ok", prepare: func(request *action.PatchTargetRequest) error { - targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() + targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() request.Id = targetID return nil }, args: args{ - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.PatchTargetRequest{ Target: &action.PatchTarget{ TargetType: &action.PatchTarget_RestCall{ @@ -296,22 +298,22 @@ func TestServer_PatchTarget(t *testing.T) { }, }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "change url, ok", prepare: func(request *action.PatchTargetRequest) error { - targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() + targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() request.Id = targetID return nil }, args: args{ - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.PatchTargetRequest{ Target: &action.PatchTarget{ Endpoint: gu.Ptr("https://example.com/hooks/new"), @@ -319,22 +321,22 @@ func TestServer_PatchTarget(t *testing.T) { }, }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "change timeout, ok", prepare: func(request *action.PatchTargetRequest) error { - targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() + targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() request.Id = targetID return nil }, args: args{ - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.PatchTargetRequest{ Target: &action.PatchTarget{ Timeout: durationpb.New(20 * time.Second), @@ -342,22 +344,22 @@ func TestServer_PatchTarget(t *testing.T) { }, }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, { name: "change type async, ok", prepare: func(request *action.PatchTargetRequest) error { - targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId() + targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId() request.Id = targetID return nil }, args: args{ - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.PatchTargetRequest{ Target: &action.PatchTarget{ TargetType: &action.PatchTarget_RestAsync{ @@ -367,10 +369,10 @@ func TestServer_PatchTarget(t *testing.T) { }, }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, @@ -380,8 +382,8 @@ func TestServer_PatchTarget(t *testing.T) { err := tt.prepare(tt.args.req) require.NoError(t, err) // We want to have the same response no matter how often we call the function - Client.PatchTarget(tt.args.ctx, tt.args.req) - got, err := Client.PatchTarget(tt.args.ctx, tt.args.req) + Tester.Client.ActionV3.PatchTarget(tt.args.ctx, tt.args.req) + got, err := Tester.Client.ActionV3.PatchTarget(tt.args.ctx, tt.args.req) if tt.wantErr { require.Error(t, err) return @@ -393,8 +395,9 @@ func TestServer_PatchTarget(t *testing.T) { } func TestServer_DeleteTarget(t *testing.T) { - ensureFeatureEnabled(t) - target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + _, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX) + ensureFeatureEnabled(t, isolatedIAMOwnerCTX) + target := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) tests := []struct { name string ctx context.Context @@ -412,7 +415,7 @@ func TestServer_DeleteTarget(t *testing.T) { }, { name: "empty id", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.DeleteTargetRequest{ Id: "", }, @@ -420,22 +423,22 @@ func TestServer_DeleteTarget(t *testing.T) { }, { name: "delete target", - ctx: CTX, + ctx: isolatedIAMOwnerCTX, req: &action.DeleteTargetRequest{ Id: target.GetDetails().GetId(), }, want: &resource_object.Details{ - ChangeDate: timestamppb.Now(), + Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Tester.Instance.InstanceID(), + Id: instanceID, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Client.DeleteTarget(tt.ctx, tt.req) + got, err := Tester.Client.ActionV3.DeleteTarget(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return diff --git a/internal/api/grpc/resources/object/v3alpha/converter.go b/internal/api/grpc/resources/object/v3alpha/converter.go index 41f81b595..c2dc7bcc6 100644 --- a/internal/api/grpc/resources/object/v3alpha/converter.go +++ b/internal/api/grpc/resources/object/v3alpha/converter.go @@ -1,21 +1,77 @@ package object import ( + "fmt" + "google.golang.org/protobuf/types/known/timestamppb" + "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" object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" - resources_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" + resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" ) -func DomainToDetailsPb(objectDetail *domain.ObjectDetails, owner *object.Owner, id string) *resources_object.Details { - details := &resources_object.Details{ - Id: id, - Sequence: objectDetail.Sequence, - Owner: owner, +func DomainToDetailsPb(objectDetail *domain.ObjectDetails, ownerType object.OwnerType, ownerId string) *resource_object.Details { + details := &resource_object.Details{ + Id: objectDetail.ID, + Owner: &object.Owner{ + Type: ownerType, + Id: ownerId, + }, } if !objectDetail.EventDate.IsZero() { - details.ChangeDate = timestamppb.New(objectDetail.EventDate) + details.Changed = timestamppb.New(objectDetail.EventDate) + } + if !objectDetail.CreationDate.IsZero() { + details.Created = timestamppb.New(objectDetail.CreationDate) } return details } + +func ToSearchDetailsPb(request query.SearchRequest, response query.SearchResponse) *resource_object.ListDetails { + details := &resource_object.ListDetails{ + AppliedLimit: request.Limit, + TotalResult: response.Count, + Timestamp: timestamppb.New(response.EventCreatedAt), + } + + return details +} + +func TextMethodPbToQuery(method resource_object.TextFilterMethod) query.TextComparison { + switch method { + case resource_object.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS: + return query.TextEquals + case resource_object.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE: + return query.TextEqualsIgnoreCase + case resource_object.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH: + return query.TextStartsWith + case resource_object.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE: + return query.TextStartsWithIgnoreCase + case resource_object.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS: + return query.TextContains + default: + return -1 + } +} + +func SearchQueryPbToQuery(defaults systemdefaults.SystemDefaults, query *resource_object.SearchQuery) (offset, limit uint64, asc bool, err error) { + limit = defaults.DefaultQueryLimit + asc = true + if query == nil { + return 0, limit, asc, nil + } + offset = query.Offset + if query.Desc { + asc = false + } + if defaults.MaxQueryLimit > 0 && uint64(query.Limit) > defaults.MaxQueryLimit { + return 0, 0, false, zerrors.ThrowInvalidArgumentf(fmt.Errorf("given: %d, allowed: %d", query.Limit, defaults.MaxQueryLimit), "QUERY-4M0fs", "Errors.Query.LimitExceeded") + } + if query.Limit > 0 { + limit = uint64(query.Limit) + } + return offset, limit, asc, nil +} diff --git a/internal/api/grpc/server/middleware/instance_interceptor.go b/internal/api/grpc/server/middleware/instance_interceptor.go index 07ae1ba27..6232ed4cd 100644 --- a/internal/api/grpc/server/middleware/instance_interceptor.go +++ b/internal/api/grpc/server/middleware/instance_interceptor.go @@ -17,6 +17,7 @@ import ( "github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" + object_v3 "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" ) const ( @@ -34,33 +35,67 @@ func InstanceInterceptor(verifier authz.InstanceVerifier, externalDomain string, func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, externalDomain string, translator *i18n.Translator, idFromRequestsServices ...string) (_ interface{}, err error) { interceptorCtx, span := tracing.NewServerInterceptorSpan(ctx) defer func() { span.EndWithError(err) }() + for _, service := range idFromRequestsServices { if !strings.HasPrefix(service, "/") { service = "/" + service } if strings.HasPrefix(info.FullMethod, service) { - withInstanceIDProperty, ok := req.(interface{ GetInstanceId() string }) + withInstanceIDProperty, ok := req.(interface { + GetInstanceId() string + }) if !ok { return handler(ctx, req) } - ctx = authz.WithInstanceID(ctx, withInstanceIDProperty.GetInstanceId()) - instance, err := verifier.InstanceByID(ctx) - if err != nil { - notFoundErr := new(zerrors.NotFoundError) - if errors.As(err, ¬FoundErr) { - notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil) - } - return nil, status.Error(codes.NotFound, err.Error()) - } - return handler(authz.WithInstance(ctx, instance), req) + return addInstanceByID(interceptorCtx, req, handler, verifier, translator, withInstanceIDProperty.GetInstanceId()) } } + explicitInstanceRequest, ok := req.(interface { + GetInstance() *object_v3.Instance + }) + if ok { + instance := explicitInstanceRequest.GetInstance() + if id := instance.GetId(); id != "" { + return addInstanceByID(interceptorCtx, req, handler, verifier, translator, id) + } + if domain := instance.GetDomain(); domain != "" { + return addInstanceByDomain(interceptorCtx, req, handler, verifier, translator, domain) + } + } + return addInstanceByRequestedHost(interceptorCtx, req, handler, verifier, translator, externalDomain) +} + +func addInstanceByID(ctx context.Context, req interface{}, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, translator *i18n.Translator, id string) (interface{}, error) { + instance, err := verifier.InstanceByID(ctx, id) + if err != nil { + notFoundErr := new(zerrors.ZitadelError) + if errors.As(err, ¬FoundErr) { + notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil) + } + return nil, status.Error(codes.NotFound, fmt.Errorf("unable to set instance using id %s: %w", id, notFoundErr).Error()) + } + return handler(authz.WithInstance(ctx, instance), req) +} + +func addInstanceByDomain(ctx context.Context, req interface{}, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, translator *i18n.Translator, domain string) (interface{}, error) { + instance, err := verifier.InstanceByHost(ctx, domain, "") + if err != nil { + notFoundErr := new(zerrors.NotFoundError) + if errors.As(err, ¬FoundErr) { + notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil) + } + return nil, status.Error(codes.NotFound, fmt.Errorf("unable to set instance using domain %s: %w", domain, notFoundErr).Error()) + } + return handler(authz.WithInstance(ctx, instance), req) +} + +func addInstanceByRequestedHost(ctx context.Context, req interface{}, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, translator *i18n.Translator, externalDomain string) (interface{}, error) { requestContext := zitadel_http.DomainContext(ctx) if requestContext.InstanceHost == "" { - logging.WithFields("origin", requestContext.Origin(), "externalDomain", externalDomain).WithError(err).Error("unable to set instance") + logging.WithFields("origin", requestContext.Origin(), "externalDomain", externalDomain).Error("unable to set instance") return nil, status.Error(codes.NotFound, "no instanceHost specified") } - instance, err := verifier.InstanceByHost(interceptorCtx, requestContext.InstanceHost, requestContext.PublicHost) + instance, err := verifier.InstanceByHost(ctx, requestContext.InstanceHost, requestContext.PublicHost) if err != nil { origin := zitadel_http.DomainContext(ctx) logging.WithFields("origin", requestContext.Origin(), "externalDomain", externalDomain).WithError(err).Error("unable to set instance") @@ -72,6 +107,5 @@ func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInf } return nil, status.Error(codes.NotFound, fmt.Sprintf("unable to set instance using origin %s (ExternalDomain is %s)", origin, externalDomain)) } - span.End() return handler(authz.WithInstance(ctx, instance), req) } diff --git a/internal/api/grpc/server/middleware/instance_interceptor_test.go b/internal/api/grpc/server/middleware/instance_interceptor_test.go index 211444a70..cc71de75f 100644 --- a/internal/api/grpc/server/middleware/instance_interceptor_test.go +++ b/internal/api/grpc/server/middleware/instance_interceptor_test.go @@ -13,6 +13,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/feature" + object_v3 "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" ) func Test_setInstance(t *testing.T) { @@ -69,6 +70,135 @@ func Test_setInstance(t *testing.T) { err: false, }, }, + { + "explicit instance unset, hostname not found, error", + args{ + ctx: http_util.WithDomainContext(context.Background(), &http_util.DomainCtx{InstanceHost: "host2"}), + req: &mockRequestWithExplicitInstance{}, + verifier: &mockInstanceVerifier{instanceHost: "host"}, + }, + res{ + want: nil, + err: true, + }, + }, + { + "explicit instance unset, invalid host, error", + args{ + ctx: http_util.WithDomainContext(context.Background(), &http_util.DomainCtx{InstanceHost: "host2"}), + req: &mockRequestWithExplicitInstance{}, + verifier: &mockInstanceVerifier{instanceHost: "host"}, + }, + res{ + want: nil, + err: true, + }, + }, + { + "explicit instance unset, valid host", + args{ + ctx: http_util.WithDomainContext(context.Background(), &http_util.DomainCtx{InstanceHost: "host"}), + req: &mockRequestWithExplicitInstance{}, + verifier: &mockInstanceVerifier{instanceHost: "host"}, + handler: func(ctx context.Context, req interface{}) (interface{}, error) { + return req, nil + }, + }, + res{ + want: &mockRequestWithExplicitInstance{}, + err: false, + }, + }, + { + name: "explicit instance set, id not found, error", + args: args{ + ctx: context.Background(), + req: &mockRequestWithExplicitInstance{ + instance: object_v3.Instance{ + Property: &object_v3.Instance_Id{ + Id: "not existing instance id", + }, + }, + }, + verifier: &mockInstanceVerifier{id: "existing instance id"}, + }, + res: res{ + want: nil, + err: true, + }, + }, + { + name: "explicit instance set, id found, ok", + args: args{ + ctx: context.Background(), + req: &mockRequestWithExplicitInstance{ + instance: object_v3.Instance{ + Property: &object_v3.Instance_Id{ + Id: "existing instance id", + }, + }, + }, + verifier: &mockInstanceVerifier{id: "existing instance id"}, + handler: func(ctx context.Context, req interface{}) (interface{}, error) { + return req, nil + }, + }, + res: res{ + want: &mockRequestWithExplicitInstance{ + instance: object_v3.Instance{ + Property: &object_v3.Instance_Id{ + Id: "existing instance id", + }, + }, + }, + err: false, + }, + }, + { + name: "explicit instance set, domain not found, error", + args: args{ + ctx: context.Background(), + req: &mockRequestWithExplicitInstance{ + instance: object_v3.Instance{ + Property: &object_v3.Instance_Domain{ + Domain: "not existing instance domain", + }, + }, + }, + verifier: &mockInstanceVerifier{instanceHost: "existing instance domain"}, + }, + res: res{ + want: nil, + err: true, + }, + }, + { + name: "explicit instance set, domain found, ok", + args: args{ + ctx: context.Background(), + req: &mockRequestWithExplicitInstance{ + instance: object_v3.Instance{ + Property: &object_v3.Instance_Domain{ + Domain: "existing instance domain", + }, + }, + }, + verifier: &mockInstanceVerifier{instanceHost: "existing instance domain"}, + handler: func(ctx context.Context, req interface{}) (interface{}, error) { + return req, nil + }, + }, + res: res{ + want: &mockRequestWithExplicitInstance{ + instance: object_v3.Instance{ + Property: &object_v3.Instance_Domain{ + Domain: "existing instance domain", + }, + }, + }, + err: false, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -86,7 +216,16 @@ func Test_setInstance(t *testing.T) { type mockRequest struct{} +type mockRequestWithExplicitInstance struct { + instance object_v3.Instance +} + +func (m *mockRequestWithExplicitInstance) GetInstance() *object_v3.Instance { + return &m.instance +} + type mockInstanceVerifier struct { + id string instanceHost string publicHost string } @@ -104,7 +243,12 @@ func (m *mockInstanceVerifier) InstanceByHost(_ context.Context, instanceHost, p return &mockInstance{}, nil } -func (m *mockInstanceVerifier) InstanceByID(context.Context) (authz.Instance, error) { return nil, nil } +func (m *mockInstanceVerifier) InstanceByID(_ context.Context, id string) (authz.Instance, error) { + if id != m.id { + return nil, fmt.Errorf("not found") + } + return &mockInstance{}, nil +} type mockInstance struct{} diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go index 5408ae257..6b17e929b 100644 --- a/internal/api/grpc/server/server.go +++ b/internal/api/grpc/server/server.go @@ -2,7 +2,6 @@ package server import ( "crypto/tls" - grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "google.golang.org/grpc" "google.golang.org/grpc/credentials" diff --git a/internal/api/grpc/system/instance.go b/internal/api/grpc/system/instance.go index d684c37e8..a5dd7b81b 100644 --- a/internal/api/grpc/system/instance.go +++ b/internal/api/grpc/system/instance.go @@ -3,7 +3,6 @@ package system import ( "context" - "github.com/zitadel/zitadel/internal/api/authz" instance_grpc "github.com/zitadel/zitadel/internal/api/grpc/instance" "github.com/zitadel/zitadel/internal/api/grpc/member" "github.com/zitadel/zitadel/internal/api/grpc/object" @@ -151,12 +150,6 @@ func (s *Server) ListDomains(ctx context.Context, req *system_pb.ListDomainsRequ } func (s *Server) AddDomain(ctx context.Context, req *system_pb.AddDomainRequest) (*system_pb.AddDomainResponse, error) { - instance, err := s.query.InstanceByID(ctx) - if err != nil { - return nil, err - } - ctx = authz.WithInstance(ctx, instance) - details, err := s.command.AddInstanceDomain(ctx, req.Domain) if err != nil { return nil, err diff --git a/internal/api/http/middleware/instance_interceptor_test.go b/internal/api/http/middleware/instance_interceptor_test.go index 7a2d7aeb2..51c0fb9a1 100644 --- a/internal/api/http/middleware/instance_interceptor_test.go +++ b/internal/api/http/middleware/instance_interceptor_test.go @@ -242,7 +242,7 @@ func (m *mockInstanceVerifier) InstanceByHost(_ context.Context, instanceHost, p return &mockInstance{}, nil } -func (m *mockInstanceVerifier) InstanceByID(context.Context) (authz.Instance, error) { +func (m *mockInstanceVerifier) InstanceByID(context.Context, string) (authz.Instance, error) { return nil, nil } diff --git a/internal/command/action_v2_execution_test.go b/internal/command/action_v2_execution_test.go index eb6cd21c3..be0592969 100644 --- a/internal/command/action_v2_execution_test.go +++ b/internal/command/action_v2_execution_test.go @@ -203,6 +203,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "request/method", }, }, }, @@ -251,6 +252,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "request/service", }, }, }, @@ -298,6 +300,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "request", }, }, }, @@ -381,6 +384,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "request/method", }, }, }, @@ -464,6 +468,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "request/service", }, }, }, @@ -545,6 +550,86 @@ func TestCommands_SetExecutionRequest(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "request", + }, + }, + }, + { + "push ok, remove all targets", + fields{ + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("request", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + expectPush( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("request", "instance"), + []*execution.Target{}, + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: &ExecutionAPICondition{ + "", + "", + true, + }, + set: &SetExecution{ + Targets: []*execution.Target{}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "request", + }, + }, + }, + { + "push ok, unchanged execution", + fields{ + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("request", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: &ExecutionAPICondition{ + "", + "", + true, + }, + set: &SetExecution{ + Targets: []*execution.Target{{ + Type: domain.ExecutionTargetTypeTarget, + Target: "target", + }}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "request", }, }, }, @@ -641,7 +726,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -876,6 +961,7 @@ func TestCommands_SetExecutionResponse(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "response/method", }, }, }, @@ -915,6 +1001,7 @@ func TestCommands_SetExecutionResponse(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "response/service", }, }, }, @@ -953,6 +1040,86 @@ func TestCommands_SetExecutionResponse(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "response", + }, + }, + }, + { + "push ok, remove all targets", + fields{ + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("response", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + expectPush( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("response", "instance"), + []*execution.Target{}, + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: &ExecutionAPICondition{ + "", + "", + true, + }, + set: &SetExecution{ + Targets: []*execution.Target{}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "response", + }, + }, + }, + { + "push ok, unchanged execution", + fields{ + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("response", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: &ExecutionAPICondition{ + "", + "", + true, + }, + set: &SetExecution{ + Targets: []*execution.Target{{ + Type: domain.ExecutionTargetTypeTarget, + Target: "target", + }}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "response", }, }, }, @@ -1049,7 +1216,7 @@ func TestCommands_SetExecutionResponse(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -1288,6 +1455,7 @@ func TestCommands_SetExecutionEvent(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "event/event", }, }, }, @@ -1327,6 +1495,7 @@ func TestCommands_SetExecutionEvent(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "event/group.*", }, }, }, @@ -1365,6 +1534,86 @@ func TestCommands_SetExecutionEvent(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "event", + }, + }, + }, + { + "push ok, remove all targets", + fields{ + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("event", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + expectPush( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("event", "instance"), + []*execution.Target{}, + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: &ExecutionEventCondition{ + "", + "", + true, + }, + set: &SetExecution{ + Targets: []*execution.Target{}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "event", + }, + }, + }, + { + "push ok, unchanged execution", + fields{ + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("event", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: &ExecutionEventCondition{ + "", + "", + true, + }, + set: &SetExecution{ + Targets: []*execution.Target{{ + Type: domain.ExecutionTargetTypeTarget, + Target: "target", + }}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "event", }, }, }, @@ -1461,7 +1710,7 @@ func TestCommands_SetExecutionEvent(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -1643,6 +1892,80 @@ func TestCommands_SetExecutionFunction(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "function/function", + }, + }, + }, + { + "push ok, remove all targets", + fields{ + actionFunctionExists: existsMock(true), + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("function/function", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + expectPush( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("function/function", "instance"), + []*execution.Target{}, + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: "function", + set: &SetExecution{ + Targets: []*execution.Target{}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "function/function", + }, + }, + }, + { + "push ok, unchanged execution", + fields{ + actionFunctionExists: existsMock(true), + eventstore: expectEventstore( + expectFilter( // execution has targets + eventFromEventPusher( + execution.NewSetEventV2(context.Background(), + execution.NewAggregate("function/function", "instance"), + []*execution.Target{ + {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, + }, + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + cond: "function", + set: &SetExecution{ + Targets: []*execution.Target{{ + Type: domain.ExecutionTargetTypeTarget, + Target: "target", + }}, + }, + resourceOwner: "instance", + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instance", + ID: "function/function", }, }, }, @@ -1732,7 +2055,7 @@ func TestCommands_SetExecutionFunction(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } diff --git a/internal/command/action_v2_target.go b/internal/command/action_v2_target.go index 913bfb229..d1f06b79b 100644 --- a/internal/command/action_v2_target.go +++ b/internal/command/action_v2_target.go @@ -113,7 +113,6 @@ func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resou if err := change.IsValid(); err != nil { return nil, err } - existing, err := c.getTargetWriteModelByID(ctx, change.AggregateID, resourceOwner) if err != nil { return nil, err @@ -121,7 +120,6 @@ func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resou if !existing.State.Exists() { return nil, zerrors.ThrowNotFound(nil, "COMMAND-xj14f2cccn", "Errors.Target.NotFound") } - changedEvent := existing.NewChangedEvent( ctx, TargetAggregateFromWriteModel(&existing.WriteModel), diff --git a/internal/command/action_v2_target_test.go b/internal/command/action_v2_target_test.go index ef60baae4..12f76c462 100644 --- a/internal/command/action_v2_target_test.go +++ b/internal/command/action_v2_target_test.go @@ -202,6 +202,7 @@ func TestCommands_AddTarget(t *testing.T) { id: "id1", details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "id1", }, }, }, @@ -235,6 +236,7 @@ func TestCommands_AddTarget(t *testing.T) { id: "id1", details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "id1", }, }, }, @@ -254,7 +256,7 @@ func TestCommands_AddTarget(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, tt.args.add.AggregateID) - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -416,6 +418,7 @@ func TestCommands_ChangeTarget(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "id1", }, }, }, @@ -485,6 +488,7 @@ func TestCommands_ChangeTarget(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "id1", }, }, }, @@ -528,6 +532,7 @@ func TestCommands_ChangeTarget(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "id1", }, }, }, @@ -545,7 +550,7 @@ func TestCommands_ChangeTarget(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -625,6 +630,7 @@ func TestCommands_DeleteTarget(t *testing.T) { res{ details: &domain.ObjectDetails{ ResourceOwner: "instance", + ID: "id1", }, }, }, @@ -642,7 +648,7 @@ func TestCommands_DeleteTarget(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } diff --git a/internal/command/auth_request_test.go b/internal/command/auth_request_test.go index a3e1db3a2..d097e4f38 100644 --- a/internal/command/auth_request_test.go +++ b/internal/command/auth_request_test.go @@ -629,7 +629,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) { } details, got, err := c.LinkSessionToAuthRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient) require.ErrorIs(t, err, tt.res.wantErr) - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) if err == nil { assert.WithinRange(t, got.AuthTime, testNow, testNow) got.AuthTime = time.Time{} @@ -739,7 +739,7 @@ func TestCommands_FailAuthRequest(t *testing.T) { } details, got, err := c.FailAuthRequest(tt.args.ctx, tt.args.id, tt.args.reason) require.ErrorIs(t, err, tt.res.wantErr) - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) assert.Equal(t, tt.res.authReq, got) }) } diff --git a/internal/command/converter.go b/internal/command/converter.go index 0761f5e19..e4309a54c 100644 --- a/internal/command/converter.go +++ b/internal/command/converter.go @@ -10,6 +10,7 @@ func writeModelToObjectDetails(writeModel *eventstore.WriteModel) *domain.Object Sequence: writeModel.ProcessedSequence, ResourceOwner: writeModel.ResourceOwner, EventDate: writeModel.ChangeDate, + ID: writeModel.AggregateID, } } diff --git a/internal/command/device_auth_test.go b/internal/command/device_auth_test.go index 3191e8dce..2988d058c 100644 --- a/internal/command/device_auth_test.go +++ b/internal/command/device_auth_test.go @@ -115,7 +115,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { } gotDetails, err := c.AddDeviceAuth(tt.args.ctx, tt.args.clientID, tt.args.deviceCode, tt.args.userCode, tt.args.expires, tt.args.scopes, tt.args.audience, tt.args.needRefreshToken) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.wantDetails, gotDetails) + assertObjectDetails(t, tt.wantDetails, gotDetails) }) } } @@ -254,7 +254,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) { } gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, gotDetails, tt.wantDetails) + assertObjectDetails(t, tt.wantDetails, gotDetails) }) } } @@ -376,7 +376,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) { } gotDetails, err := c.CancelDeviceAuth(tt.args.ctx, tt.args.id, tt.args.reason) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, gotDetails, tt.wantDetails) + assertObjectDetails(t, tt.wantDetails, gotDetails) }) } } diff --git a/internal/command/idp_intent_test.go b/internal/command/idp_intent_test.go index efcc9e0dd..832e2e990 100644 --- a/internal/command/idp_intent_test.go +++ b/internal/command/idp_intent_test.go @@ -333,7 +333,7 @@ func TestCommands_CreateIntent(t *testing.T) { } else { assert.Equal(t, tt.res.intentID, "") } - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) }) } } diff --git a/internal/command/instance_custom_login_text_test.go b/internal/command/instance_custom_login_text_test.go index aa2bf3365..9e460a2f7 100644 --- a/internal/command/instance_custom_login_text_test.go +++ b/internal/command/instance_custom_login_text_test.go @@ -6864,7 +6864,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_custom_message_text_test.go b/internal/command/instance_custom_message_text_test.go index 76f463ae0..a579350d7 100644 --- a/internal/command/instance_custom_message_text_test.go +++ b/internal/command/instance_custom_message_text_test.go @@ -191,7 +191,7 @@ func TestCommandSide_SetDefaultMessageText(t *testing.T) { t.Errorf("got wrong err: %v ", err) t.FailNow() } - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } diff --git a/internal/command/instance_debug_notification_file_test.go b/internal/command/instance_debug_notification_file_test.go index 73e8b6bc2..c86d95453 100644 --- a/internal/command/instance_debug_notification_file_test.go +++ b/internal/command/instance_debug_notification_file_test.go @@ -100,7 +100,7 @@ func TestCommandSide_AddDefaultDebugNotificationProviderFile(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -242,7 +242,7 @@ func TestCommandSide_ChangeDebugNotificationProviderFile(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -323,7 +323,7 @@ func TestCommandSide_RemoveDebugNotificationProviderFile(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_debug_notification_log_test.go b/internal/command/instance_debug_notification_log_test.go index bac918602..9190064f6 100644 --- a/internal/command/instance_debug_notification_log_test.go +++ b/internal/command/instance_debug_notification_log_test.go @@ -128,7 +128,7 @@ func TestCommandSide_AddDefaultDebugNotificationProviderLog(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -277,7 +277,7 @@ func TestCommandSide_ChangeDebugNotificationProviderLog(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -359,7 +359,7 @@ func TestCommandSide_RemoveDebugNotificationProviderLog(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_domain_test.go b/internal/command/instance_domain_test.go index aa844cefa..3f5e73aed 100644 --- a/internal/command/instance_domain_test.go +++ b/internal/command/instance_domain_test.go @@ -198,7 +198,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) return } - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -299,7 +299,7 @@ func TestCommandSide_SetPrimaryInstanceDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -425,7 +425,7 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_features_test.go b/internal/command/instance_features_test.go index 4765fc283..6ca59b00e 100644 --- a/internal/command/instance_features_test.go +++ b/internal/command/instance_features_test.go @@ -363,7 +363,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) { } got, err := c.ResetInstanceFeatures(ctx) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.want, got) + assertObjectDetails(t, tt.want, got) }) } } diff --git a/internal/command/instance_idp_test.go b/internal/command/instance_idp_test.go index 0f7998a63..c6181af9f 100644 --- a/internal/command/instance_idp_test.go +++ b/internal/command/instance_idp_test.go @@ -301,7 +301,7 @@ func TestCommandSide_AddInstanceGenericOAuthIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -619,7 +619,7 @@ func TestCommandSide_UpdateInstanceGenericOAuthIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -829,7 +829,7 @@ func TestCommandSide_AddInstanceGenericOIDCIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1070,7 +1070,7 @@ func TestCommandSide_UpdateInstanceGenericOIDCIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1312,7 +1312,7 @@ func TestCommandSide_MigrateInstanceGenericOIDCToAzureADProvider(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1520,7 +1520,7 @@ func TestCommandSide_MigrateInstanceOIDCToGoogleIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1709,7 +1709,7 @@ func TestCommandSide_AddInstanceAzureADIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1929,7 +1929,7 @@ func TestCommandSide_UpdateInstanceAzureADIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2092,7 +2092,7 @@ func TestCommandSide_AddInstanceGitHubIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2284,7 +2284,7 @@ func TestCommandSide_UpdateInstanceGitHubIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2542,7 +2542,7 @@ func TestCommandSide_AddInstanceGitHubEnterpriseIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2832,7 +2832,7 @@ func TestCommandSide_UpdateInstanceGitHubEnterpriseIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2994,7 +2994,7 @@ func TestCommandSide_AddInstanceGitLabIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3184,7 +3184,7 @@ func TestCommandSide_UpdateInstanceGitLabIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3391,7 +3391,7 @@ func TestCommandSide_AddInstanceGitLabSelfHostedIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3628,7 +3628,7 @@ func TestCommandSide_UpdateInstanceGitLabSelfHostedIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3790,7 +3790,7 @@ func TestCommandSide_AddInstanceGoogleIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3980,7 +3980,7 @@ func TestCommandSide_UpdateInstanceGoogleIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -4329,7 +4329,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -4717,7 +4717,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -4926,7 +4926,7 @@ func TestCommandSide_AddInstanceAppleIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5165,7 +5165,7 @@ func TestCommandSide_UpdateInstanceAppleIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5342,7 +5342,7 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5561,7 +5561,7 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5687,7 +5687,7 @@ func TestCommandSide_RegenerateInstanceSAMLProviderCertificate(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_member_test.go b/internal/command/instance_member_test.go index f520bc324..8d254c56b 100644 --- a/internal/command/instance_member_test.go +++ b/internal/command/instance_member_test.go @@ -529,7 +529,7 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_oidc_settings_test.go b/internal/command/instance_oidc_settings_test.go index 353efa581..f2e52f49f 100644 --- a/internal/command/instance_oidc_settings_test.go +++ b/internal/command/instance_oidc_settings_test.go @@ -190,7 +190,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -403,7 +403,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_domain_test.go b/internal/command/instance_policy_domain_test.go index e2b5c2ccc..745d4a7ef 100644 --- a/internal/command/instance_policy_domain_test.go +++ b/internal/command/instance_policy_domain_test.go @@ -106,7 +106,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -340,7 +340,7 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_label_test.go b/internal/command/instance_policy_label_test.go index 3720fba09..636f24975 100644 --- a/internal/command/instance_policy_label_test.go +++ b/internal/command/instance_policy_label_test.go @@ -291,7 +291,7 @@ func TestCommandSide_ActivateDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -446,7 +446,7 @@ func TestCommandSide_AddLogoDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -588,7 +588,7 @@ func TestCommandSide_RemoveLogoDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -743,7 +743,7 @@ func TestCommandSide_AddIconDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -845,7 +845,7 @@ func TestCommandSide_RemoveIconDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1003,7 +1003,7 @@ func TestCommandSide_AddLogoDarkDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1145,7 +1145,7 @@ func TestCommandSide_RemoveLogoDarkDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1300,7 +1300,7 @@ func TestCommandSide_AddIconDarkDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1442,7 +1442,7 @@ func TestCommandSide_RemoveIconDarkDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1597,7 +1597,7 @@ func TestCommandSide_AddFontDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1739,7 +1739,7 @@ func TestCommandSide_RemoveFontDefaultLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_login_test.go b/internal/command/instance_policy_login_test.go index a3f9a936b..500b3b4ab 100644 --- a/internal/command/instance_policy_login_test.go +++ b/internal/command/instance_policy_login_test.go @@ -204,7 +204,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -726,7 +726,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -901,7 +901,7 @@ func TestCommandSide_AddSecondFactorDefaultLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1170,7 +1170,7 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1269,7 +1269,7 @@ func TestCommandSide_AddMultiFactorDefaultLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1397,7 +1397,7 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_notification_test.go b/internal/command/instance_policy_notification_test.go index f864e4275..1423ab147 100644 --- a/internal/command/instance_policy_notification_test.go +++ b/internal/command/instance_policy_notification_test.go @@ -120,7 +120,7 @@ func TestCommandSide_AddDefaultNotificationPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -231,7 +231,7 @@ func TestCommandSide_ChangeDefaultNotificationPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_password_age_test.go b/internal/command/instance_policy_password_age_test.go index 6380325ed..57efa222f 100644 --- a/internal/command/instance_policy_password_age_test.go +++ b/internal/command/instance_policy_password_age_test.go @@ -99,7 +99,7 @@ func TestCommandSide_AddDefaultPasswordAgePolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_password_complexity_test.go b/internal/command/instance_policy_password_complexity_test.go index 0c10c0c7f..25d8dc048 100644 --- a/internal/command/instance_policy_password_complexity_test.go +++ b/internal/command/instance_policy_password_complexity_test.go @@ -127,7 +127,7 @@ func TestCommandSide_AddDefaultPasswordComplexityPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_password_lockout_test.go b/internal/command/instance_policy_password_lockout_test.go index 866399cae..19af09f23 100644 --- a/internal/command/instance_policy_password_lockout_test.go +++ b/internal/command/instance_policy_password_lockout_test.go @@ -103,7 +103,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_policy_privacy_test.go b/internal/command/instance_policy_privacy_test.go index ed09ba0f5..18d384d72 100644 --- a/internal/command/instance_policy_privacy_test.go +++ b/internal/command/instance_policy_privacy_test.go @@ -178,7 +178,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_settings_test.go b/internal/command/instance_settings_test.go index ddb82f709..b52db33fc 100644 --- a/internal/command/instance_settings_test.go +++ b/internal/command/instance_settings_test.go @@ -140,7 +140,7 @@ func TestCommandSide_AddSecretGenerator(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -376,7 +376,7 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -518,7 +518,7 @@ func TestCommandSide_RemoveSecretGenerator(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_test.go b/internal/command/instance_test.go index 2fbed1845..b35e22638 100644 --- a/internal/command/instance_test.go +++ b/internal/command/instance_test.go @@ -1370,7 +1370,7 @@ func TestCommandSide_UpdateInstance(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1507,7 +1507,7 @@ func TestCommandSide_RemoveInstance(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/instance_trusted_domain_test.go b/internal/command/instance_trusted_domain_test.go index f4f0001c7..3caef90f0 100644 --- a/internal/command/instance_trusted_domain_test.go +++ b/internal/command/instance_trusted_domain_test.go @@ -119,7 +119,7 @@ func TestCommands_AddTrustedDomain(t *testing.T) { } got, err := c.AddTrustedDomain(tt.args.ctx, tt.args.trustedDomain) assert.ErrorIs(t, err, tt.want.err) - assert.Equal(t, tt.want.details, got) + assertObjectDetails(t, tt.want.details, got) }) } } @@ -191,7 +191,7 @@ func TestCommands_RemoveTrustedDomain(t *testing.T) { } got, err := c.RemoveTrustedDomain(tt.args.ctx, tt.args.trustedDomain) assert.ErrorIs(t, err, tt.want.err) - assert.Equal(t, tt.want.details, got) + assertObjectDetails(t, tt.want.details, got) }) } } diff --git a/internal/command/limits_test.go b/internal/command/limits_test.go index 53eeab0b8..543f84fa5 100644 --- a/internal/command/limits_test.go +++ b/internal/command/limits_test.go @@ -223,7 +223,7 @@ func TestLimits_SetLimits(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -626,8 +626,11 @@ func TestLimits_SetLimitsBulk(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, gotDetails) - assert.Equal(t, tt.res.wantTarget, gotTargetDetails) + assertObjectDetails(t, tt.res.want, gotDetails) + assert.Len(t, gotTargetDetails, len(tt.res.wantTarget)) + for i, want := range tt.res.wantTarget { + assertObjectDetails(t, want, gotTargetDetails[i]) + } } }) } @@ -748,7 +751,7 @@ func TestLimits_ResetLimits(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_action_test.go b/internal/command/org_action_test.go index e3f1a8afc..94e9bfba2 100644 --- a/internal/command/org_action_test.go +++ b/internal/command/org_action_test.go @@ -129,7 +129,7 @@ func TestCommands_AddAction(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -330,7 +330,7 @@ func TestCommands_ChangeAction(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -463,7 +463,7 @@ func TestCommands_DeactivateAction(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -596,7 +596,7 @@ func TestCommands_ReactivateAction(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -742,7 +742,7 @@ func TestCommands_DeleteAction(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } diff --git a/internal/command/org_custom_login_text_test.go b/internal/command/org_custom_login_text_test.go index dbef76c4a..0e4be6cee 100644 --- a/internal/command/org_custom_login_text_test.go +++ b/internal/command/org_custom_login_text_test.go @@ -6632,7 +6632,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_custom_message_text_test.go b/internal/command/org_custom_message_text_test.go index ffa8d87ae..f102b1093 100644 --- a/internal/command/org_custom_message_text_test.go +++ b/internal/command/org_custom_message_text_test.go @@ -356,7 +356,7 @@ func TestCommandSide_SetCustomMessageText(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_domain_test.go b/internal/command/org_domain_test.go index 80ec08284..df7971095 100644 --- a/internal/command/org_domain_test.go +++ b/internal/command/org_domain_test.go @@ -396,7 +396,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1102,7 +1102,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1287,7 +1287,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1526,7 +1526,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_flow_test.go b/internal/command/org_flow_test.go index 36961bc6e..493630629 100644 --- a/internal/command/org_flow_test.go +++ b/internal/command/org_flow_test.go @@ -112,7 +112,7 @@ func TestCommands_ClearFlow(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } @@ -276,7 +276,7 @@ func TestCommands_SetTriggerActions(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.details, details) + assertObjectDetails(t, tt.res.details, details) } }) } diff --git a/internal/command/org_idp_config_test.go b/internal/command/org_idp_config_test.go index eb38ca962..b2d7d2ba4 100644 --- a/internal/command/org_idp_config_test.go +++ b/internal/command/org_idp_config_test.go @@ -652,7 +652,7 @@ func TestCommands_RemoveIDPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_idp_test.go b/internal/command/org_idp_test.go index bcda24df2..e1fa43e87 100644 --- a/internal/command/org_idp_test.go +++ b/internal/command/org_idp_test.go @@ -310,7 +310,7 @@ func TestCommandSide_AddOrgGenericOAuthIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -639,7 +639,7 @@ func TestCommandSide_UpdateOrgGenericOAuthIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -856,7 +856,7 @@ func TestCommandSide_AddOrgGenericOIDCIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1105,7 +1105,7 @@ func TestCommandSide_UpdateOrgGenericOIDCIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1350,7 +1350,7 @@ func TestCommandSide_MigrateOrgGenericOIDCToAzureADProvider(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1566,7 +1566,7 @@ func TestCommandSide_MigrateOrgOIDCToGoogleIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1761,7 +1761,7 @@ func TestCommandSide_AddOrgAzureADIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1988,7 +1988,7 @@ func TestCommandSide_UpdateOrgAzureADIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2156,7 +2156,7 @@ func TestCommandSide_AddOrgGitHubIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2354,7 +2354,7 @@ func TestCommandSide_UpdateOrgGitHubIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2621,7 +2621,7 @@ func TestCommandSide_AddOrgGitHubEnterpriseIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2921,7 +2921,7 @@ func TestCommandSide_UpdateOrgGitHubEnterpriseIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3088,7 +3088,7 @@ func TestCommandSide_AddOrgGitLabIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3284,7 +3284,7 @@ func TestCommandSide_UpdateOrgGitLabIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3498,7 +3498,7 @@ func TestCommandSide_AddOrgGitLabSelfHostedIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3743,7 +3743,7 @@ func TestCommandSide_UpdateOrgGitLabSelfHostedIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -3910,7 +3910,7 @@ func TestCommandSide_AddOrgGoogleIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -4106,7 +4106,7 @@ func TestCommandSide_UpdateOrgGoogleIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -4466,7 +4466,7 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -4865,7 +4865,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5081,7 +5081,7 @@ func TestCommandSide_AddOrgAppleIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5328,7 +5328,7 @@ func TestCommandSide_UpdateOrgAppleIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5513,7 +5513,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.id, id) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5741,7 +5741,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -5873,7 +5873,7 @@ func TestCommandSide_RegenerateOrgSAMLProviderCertificate(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_member_test.go b/internal/command/org_member_test.go index 2c49f3bd7..4e2e926b5 100644 --- a/internal/command/org_member_test.go +++ b/internal/command/org_member_test.go @@ -841,7 +841,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_metadata_test.go b/internal/command/org_metadata_test.go index e52f2480b..667263007 100644 --- a/internal/command/org_metadata_test.go +++ b/internal/command/org_metadata_test.go @@ -280,7 +280,7 @@ func TestCommandSide_BulkSetOrgMetadata(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -420,7 +420,7 @@ func TestCommandSide_OrgRemoveMetadata(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -618,7 +618,7 @@ func TestCommandSide_BulkRemoveOrgMetadata(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_domain_test.go b/internal/command/org_policy_domain_test.go index 1cb4c080a..24020d802 100644 --- a/internal/command/org_policy_domain_test.go +++ b/internal/command/org_policy_domain_test.go @@ -247,7 +247,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -461,7 +461,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -648,7 +648,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_label_test.go b/internal/command/org_policy_label_test.go index 8fee5e890..c5227cc26 100644 --- a/internal/command/org_policy_label_test.go +++ b/internal/command/org_policy_label_test.go @@ -773,7 +773,7 @@ func TestCommandSide_AddLogoLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -896,7 +896,7 @@ func TestCommandSide_RemoveLogoLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1078,7 +1078,7 @@ func TestCommandSide_AddIconLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1198,7 +1198,7 @@ func TestCommandSide_RemoveIconLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1380,7 +1380,7 @@ func TestCommandSide_AddLogoDarkLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1503,7 +1503,7 @@ func TestCommandSide_RemoveLogoDarkLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1685,7 +1685,7 @@ func TestCommandSide_AddIconDarkLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1803,7 +1803,7 @@ func TestCommandSide_RemoveIconDarkLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1977,7 +1977,7 @@ func TestCommandSide_AddFontLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -2095,7 +2095,7 @@ func TestCommandSide_RemoveFontLabelPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go index 62b418d02..0d1b9f532 100644 --- a/internal/command/org_policy_login_test.go +++ b/internal/command/org_policy_login_test.go @@ -483,7 +483,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -690,7 +690,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -803,7 +803,7 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1393,7 +1393,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_notification_test.go b/internal/command/org_policy_notification_test.go index 5f8f4f49c..17af5dd27 100644 --- a/internal/command/org_policy_notification_test.go +++ b/internal/command/org_policy_notification_test.go @@ -136,7 +136,7 @@ func TestCommandSide_AddNotificationPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -260,7 +260,7 @@ func TestCommandSide_ChangeNotificationPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -357,7 +357,7 @@ func TestCommandSide_RemoveNotificationPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_password_age_test.go b/internal/command/org_policy_password_age_test.go index 610053387..aa76b1b3a 100644 --- a/internal/command/org_policy_password_age_test.go +++ b/internal/command/org_policy_password_age_test.go @@ -368,7 +368,7 @@ func TestCommandSide_RemovePasswordAgePolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_password_complexity_test.go b/internal/command/org_policy_password_complexity_test.go index 955b3bedf..a1443413d 100644 --- a/internal/command/org_policy_password_complexity_test.go +++ b/internal/command/org_policy_password_complexity_test.go @@ -395,7 +395,7 @@ func TestCommandSide_RemovePasswordComplexityPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/org_policy_privacy_test.go b/internal/command/org_policy_privacy_test.go index 147d4d3e5..d12a8bcf4 100644 --- a/internal/command/org_policy_privacy_test.go +++ b/internal/command/org_policy_privacy_test.go @@ -582,7 +582,7 @@ func TestCommandSide_RemovePrivacyPolicy(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/preparation_test.go b/internal/command/preparation_test.go index 9ffd815b3..24ed57792 100644 --- a/internal/command/preparation_test.go +++ b/internal/command/preparation_test.go @@ -7,7 +7,10 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/command/preparation" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" ) @@ -89,3 +92,17 @@ func (mf *MultiFilter) Filter() preparation.FilterToQueryReducer { return mf.filters[mf.count-1](ctx, queryFactory) } } + +func assertObjectDetails(t *testing.T, want, got *domain.ObjectDetails) { + if want == nil { + assert.Nil(t, got) + return + } + assert.Equal(t, got.CreationDate, want.CreationDate) + assert.Equal(t, got.EventDate, want.EventDate) + assert.Equal(t, got.ResourceOwner, want.ResourceOwner) + assert.Equal(t, got.Sequence, want.Sequence) + if want.ID != "" { + assert.Equal(t, got.ID, want.ID) + } +} diff --git a/internal/command/project_application_test.go b/internal/command/project_application_test.go index 66671a5a5..ae2c6c39b 100644 --- a/internal/command/project_application_test.go +++ b/internal/command/project_application_test.go @@ -190,7 +190,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -342,7 +342,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -494,7 +494,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -670,7 +670,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/project_grant_member_test.go b/internal/command/project_grant_member_test.go index 5535cc936..189d1b991 100644 --- a/internal/command/project_grant_member_test.go +++ b/internal/command/project_grant_member_test.go @@ -575,7 +575,7 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/project_grant_test.go b/internal/command/project_grant_test.go index 745072318..42c6875f0 100644 --- a/internal/command/project_grant_test.go +++ b/internal/command/project_grant_test.go @@ -926,7 +926,7 @@ func TestCommandSide_DeactivateProjectGrant(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1123,7 +1123,7 @@ func TestCommandSide_ReactivateProjectGrant(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1386,7 +1386,7 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/project_member_test.go b/internal/command/project_member_test.go index 358ee4382..88b52f63f 100644 --- a/internal/command/project_member_test.go +++ b/internal/command/project_member_test.go @@ -607,7 +607,7 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/project_role_test.go b/internal/command/project_role_test.go index bd8c85aef..2dc7ee35a 100644 --- a/internal/command/project_role_test.go +++ b/internal/command/project_role_test.go @@ -423,7 +423,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -997,7 +997,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/project_test.go b/internal/command/project_test.go index ca8d2b2dd..299ff344f 100644 --- a/internal/command/project_test.go +++ b/internal/command/project_test.go @@ -606,7 +606,7 @@ func TestCommandSide_DeactivateProject(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -782,7 +782,7 @@ func TestCommandSide_ReactivateProject(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1066,7 +1066,7 @@ func TestCommandSide_RemoveProject(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/quota_test.go b/internal/command/quota_test.go index 14d5d5079..5436885c5 100644 --- a/internal/command/quota_test.go +++ b/internal/command/quota_test.go @@ -275,7 +275,7 @@ func TestQuota_AddQuota(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -541,7 +541,7 @@ func TestQuota_SetQuota(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -678,7 +678,7 @@ func TestQuota_RemoveQuota(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/restrictions_test.go b/internal/command/restrictions_test.go index 4dcbc479e..2f1b26d9f 100644 --- a/internal/command/restrictions_test.go +++ b/internal/command/restrictions_test.go @@ -232,7 +232,7 @@ func TestSetRestrictions(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/session_test.go b/internal/command/session_test.go index 46cbaeafc..5fba98514 100644 --- a/internal/command/session_test.go +++ b/internal/command/session_test.go @@ -1464,7 +1464,7 @@ func TestCommands_TerminateSession(t *testing.T) { } got, err := c.TerminateSession(tt.args.ctx, tt.args.sessionID, tt.args.sessionToken) require.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } diff --git a/internal/command/sms_config_test.go b/internal/command/sms_config_test.go index b79ab2551..8d9675194 100644 --- a/internal/command/sms_config_test.go +++ b/internal/command/sms_config_test.go @@ -94,7 +94,7 @@ func TestCommandSide_AddSMSConfigTwilio(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -253,7 +253,7 @@ func TestCommandSide_ChangeSMSConfigTwilio(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -360,7 +360,7 @@ func TestCommandSide_ActivateSMSConfigTwilio(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -474,7 +474,7 @@ func TestCommandSide_DeactivateSMSConfigTwilio(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -581,7 +581,7 @@ func TestCommandSide_RemoveSMSConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/smtp_test.go b/internal/command/smtp_test.go index bbcbd0b36..b6bb7d98a 100644 --- a/internal/command/smtp_test.go +++ b/internal/command/smtp_test.go @@ -332,7 +332,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -736,7 +736,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -852,7 +852,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -968,7 +968,7 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1091,7 +1091,7 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1190,7 +1190,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/system_features_test.go b/internal/command/system_features_test.go index 83d8c8550..d6d0d6b5c 100644 --- a/internal/command/system_features_test.go +++ b/internal/command/system_features_test.go @@ -340,7 +340,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) { } got, err := c.ResetSystemFeatures(context.Background()) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.want, got) + assertObjectDetails(t, tt.want, got) }) } } diff --git a/internal/command/user_grant_test.go b/internal/command/user_grant_test.go index a073c94e1..a5fafde83 100644 --- a/internal/command/user_grant_test.go +++ b/internal/command/user_grant_test.go @@ -1417,7 +1417,7 @@ func TestCommandSide_DeactivateUserGrant(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1625,7 +1625,7 @@ func TestCommandSide_ReactivateUserGrant(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1827,7 +1827,7 @@ func TestCommandSide_RemoveUserGrant(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_human_avatar_test.go b/internal/command/user_human_avatar_test.go index 70552e7e5..61c1f01bc 100644 --- a/internal/command/user_human_avatar_test.go +++ b/internal/command/user_human_avatar_test.go @@ -192,7 +192,7 @@ func TestCommandSide_AddHumanAvatar(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -355,7 +355,7 @@ func TestCommandSide_RemoveHumanAvatar(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_human_email_test.go b/internal/command/user_human_email_test.go index fca5ba260..e18413594 100644 --- a/internal/command/user_human_email_test.go +++ b/internal/command/user_human_email_test.go @@ -688,7 +688,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -949,7 +949,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_human_init_test.go b/internal/command/user_human_init_test.go index 382d2892b..0a0b6060f 100644 --- a/internal/command/user_human_init_test.go +++ b/internal/command/user_human_init_test.go @@ -394,7 +394,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_human_otp_test.go b/internal/command/user_human_otp_test.go index 94a47f6da..197d22251 100644 --- a/internal/command/user_human_otp_test.go +++ b/internal/command/user_human_otp_test.go @@ -962,7 +962,7 @@ func TestCommandSide_RemoveHumanTOTP(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1179,7 +1179,7 @@ func TestCommandSide_AddHumanOTPSMS(t *testing.T) { } got, err := r.AddHumanOTPSMS(tt.args.ctx, tt.args.userID, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -1306,7 +1306,7 @@ func TestCommandSide_AddHumanOTPSMSWithCheckSucceeded(t *testing.T) { } got, err := r.AddHumanOTPSMSWithCheckSucceeded(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.authRequest) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -1421,7 +1421,7 @@ func TestCommandSide_RemoveHumanOTPSMS(t *testing.T) { } got, err := r.RemoveHumanOTPSMS(tt.args.ctx, tt.args.userID, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -2334,7 +2334,7 @@ func TestCommandSide_AddHumanOTPEmail(t *testing.T) { } got, err := r.AddHumanOTPEmail(tt.args.ctx, tt.args.userID, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -2461,7 +2461,7 @@ func TestCommandSide_AddHumanOTPEmailWithCheckSucceeded(t *testing.T) { } got, err := r.AddHumanOTPEmailWithCheckSucceeded(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.authRequest) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -2576,7 +2576,7 @@ func TestCommandSide_RemoveHumanOTPEmail(t *testing.T) { } got, err := r.RemoveHumanOTPEmail(tt.args.ctx, tt.args.userID, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } diff --git a/internal/command/user_human_password_test.go b/internal/command/user_human_password_test.go index 032696749..1eb886828 100644 --- a/internal/command/user_human_password_test.go +++ b/internal/command/user_human_password_test.go @@ -254,7 +254,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -657,7 +657,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1102,7 +1102,7 @@ func TestCommandSide_ChangePassword(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1336,7 +1336,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_human_phone_test.go b/internal/command/user_human_phone_test.go index ae9784cc0..9ee3fcbdd 100644 --- a/internal/command/user_human_phone_test.go +++ b/internal/command/user_human_phone_test.go @@ -609,7 +609,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -791,7 +791,7 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1047,7 +1047,7 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_human_refresh_token_test.go b/internal/command/user_human_refresh_token_test.go index c3b992535..f70afe3ee 100644 --- a/internal/command/user_human_refresh_token_test.go +++ b/internal/command/user_human_refresh_token_test.go @@ -157,7 +157,7 @@ func TestCommands_RevokeRefreshToken(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_idp_link_test.go b/internal/command/user_idp_link_test.go index 67d1c005e..62b30f5fc 100644 --- a/internal/command/user_idp_link_test.go +++ b/internal/command/user_idp_link_test.go @@ -728,7 +728,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_machine_key_test.go b/internal/command/user_machine_key_test.go index 9e50f81e7..5437026c0 100644 --- a/internal/command/user_machine_key_test.go +++ b/internal/command/user_machine_key_test.go @@ -267,7 +267,7 @@ func TestCommands_AddMachineKey(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) receivedKey := len(tt.args.key.PrivateKey) > 0 assert.Equal(t, tt.res.key, receivedKey) } diff --git a/internal/command/user_machine_secret_test.go b/internal/command/user_machine_secret_test.go index ee58b42d8..4c6d16960 100644 --- a/internal/command/user_machine_secret_test.go +++ b/internal/command/user_machine_secret_test.go @@ -139,7 +139,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) assert.Equal(t, tt.args.set.ClientSecret, tt.res.secret.ClientSecret) } }) @@ -297,7 +297,7 @@ func TestCommandSide_RemoveMachineSecret(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_machine_test.go b/internal/command/user_machine_test.go index d6b93bb94..c7b4b8caf 100644 --- a/internal/command/user_machine_test.go +++ b/internal/command/user_machine_test.go @@ -209,7 +209,7 @@ func TestCommandSide_AddMachine(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -384,7 +384,7 @@ func TestCommandSide_ChangeMachine(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_metadata_test.go b/internal/command/user_metadata_test.go index 812ef529d..b3ffa7b82 100644 --- a/internal/command/user_metadata_test.go +++ b/internal/command/user_metadata_test.go @@ -322,7 +322,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -483,7 +483,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -711,7 +711,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_personal_access_token_test.go b/internal/command/user_personal_access_token_test.go index 335a59e70..2341d52b6 100644 --- a/internal/command/user_personal_access_token_test.go +++ b/internal/command/user_personal_access_token_test.go @@ -270,7 +270,7 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) assert.Equal(t, tt.res.token, tt.args.pat.Token) } }) @@ -368,7 +368,7 @@ func TestCommands_RemovePersonalAccessToken(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_schema_test.go b/internal/command/user_schema_test.go index 717620b44..084963116 100644 --- a/internal/command/user_schema_test.go +++ b/internal/command/user_schema_test.go @@ -281,7 +281,7 @@ func TestCommands_CreateUserSchema(t *testing.T) { } gotID, gotDetails, err := c.CreateUserSchema(tt.args.ctx, tt.args.userSchema) assert.Equal(t, tt.res.id, gotID) - assert.Equal(t, tt.res.details, gotDetails) + assertObjectDetails(t, tt.res.details, gotDetails) assert.ErrorIs(t, err, tt.res.err) }) } @@ -620,7 +620,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) { } got, err := c.UpdateUserSchema(tt.args.ctx, tt.args.userSchema) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.details, got) + assertObjectDetails(t, tt.res.details, got) }) } } @@ -713,7 +713,7 @@ func TestCommands_DeactivateUserSchema(t *testing.T) { } got, err := c.DeactivateUserSchema(tt.args.ctx, tt.args.id, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.details, got) + assertObjectDetails(t, tt.res.details, got) }) } } @@ -812,7 +812,7 @@ func TestCommands_ReactivateUserSchema(t *testing.T) { } got, err := c.ReactivateUserSchema(tt.args.ctx, tt.args.id, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.details, got) + assertObjectDetails(t, tt.res.details, got) }) } } @@ -906,7 +906,7 @@ func TestCommands_DeleteUserSchema(t *testing.T) { } got, err := c.DeleteUserSchema(tt.args.ctx, tt.args.id, tt.args.resourceOwner) assert.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.details, got) + assertObjectDetails(t, tt.res.details, got) }) } } diff --git a/internal/command/user_test.go b/internal/command/user_test.go index 5de57a153..9abae187c 100644 --- a/internal/command/user_test.go +++ b/internal/command/user_test.go @@ -498,7 +498,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -648,7 +648,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -797,7 +797,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -947,7 +947,7 @@ func TestCommandSide_LockUser(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1096,7 +1096,7 @@ func TestCommandSide_UnlockUser(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1427,7 +1427,7 @@ func TestCommandSide_RemoveUser(t *testing.T) { t.Errorf("got wrong err: %v ", err) return } - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) }) } } @@ -1562,7 +1562,7 @@ func TestCommands_RevokeAccessToken(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/command/user_v2_email_test.go b/internal/command/user_v2_email_test.go index 5a7b4fb2a..79a53705f 100644 --- a/internal/command/user_v2_email_test.go +++ b/internal/command/user_v2_email_test.go @@ -1831,7 +1831,7 @@ func TestCommands_verifyUserEmailWithGenerator(t *testing.T) { } got, err := c.verifyUserEmailWithGenerator(context.Background(), tt.args.userID, tt.args.code, GetMockSecretGenerator(t)) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, got, tt.want) + assertObjectDetails(t, tt.want, got) }) } } diff --git a/internal/command/user_v2_human_test.go b/internal/command/user_v2_human_test.go index 997124e1f..37cd2837e 100644 --- a/internal/command/user_v2_human_test.go +++ b/internal/command/user_v2_human_test.go @@ -1569,7 +1569,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { return } if tt.res.err == nil { - assert.Equal(t, tt.res.want, tt.args.human.Details) + assertObjectDetails(t, tt.res.want, tt.args.human.Details) assert.Equal(t, tt.res.wantID, tt.args.human.ID) assert.Equal(t, tt.res.wantEmailCode, gu.Value(tt.args.human.EmailCode)) } @@ -2945,7 +2945,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { return } if tt.res.err == nil { - assert.Equal(t, tt.res.want, tt.args.human.Details) + assertObjectDetails(t, tt.res.want, tt.args.human.Details) assert.Equal(t, tt.res.wantEmailCode, tt.args.human.EmailCode) assert.Equal(t, tt.res.wantPhoneCode, tt.args.human.PhoneCode) } diff --git a/internal/command/user_v2_passkey_test.go b/internal/command/user_v2_passkey_test.go index 5a12cb9dc..aa2ded6d7 100644 --- a/internal/command/user_v2_passkey_test.go +++ b/internal/command/user_v2_passkey_test.go @@ -537,7 +537,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { } got, err := c.AddUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.want, got) + assertObjectDetails(t, tt.want, got) }) } } @@ -645,7 +645,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { } got, err := c.AddUserPasskeyCodeURLTemplate(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, tt.args.urlTmpl) require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.want, got) + assertObjectDetails(t, tt.want, got) }) } } @@ -736,8 +736,13 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCodeReturn(context.Background(), tt.args.userID, tt.args.resourceOwner, alg) - require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.want, got) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + return + } + assert.Equal(t, tt.want.CodeID, got.CodeID) + assert.Equal(t, tt.want.Code, got.Code) + assertObjectDetails(t, tt.want.ObjectDetails, got.ObjectDetails) }) } } @@ -896,8 +901,13 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { idGenerator: tt.fields.idGenerator, } got, err := c.addUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, "", false) - require.ErrorIs(t, err, tt.wantErr) - assert.Equal(t, tt.want, got) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + return + } + assert.Equal(t, tt.want.CodeID, got.CodeID) + assert.Equal(t, tt.want.Code, got.Code) + assertObjectDetails(t, tt.want.ObjectDetails, got.ObjectDetails) }) } } diff --git a/internal/command/user_v2_password_test.go b/internal/command/user_v2_password_test.go index a87037625..2575e9442 100644 --- a/internal/command/user_v2_password_test.go +++ b/internal/command/user_v2_password_test.go @@ -601,7 +601,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { } got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType) require.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.details, got) + assertObjectDetails(t, tt.res.details, got) assert.Equal(t, tt.res.code, gotPlainCode) }) } diff --git a/internal/command/user_v2_test.go b/internal/command/user_v2_test.go index 0ee9fe694..e156c92f0 100644 --- a/internal/command/user_v2_test.go +++ b/internal/command/user_v2_test.go @@ -263,7 +263,7 @@ func TestCommandSide_LockUserV2(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -516,7 +516,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -819,7 +819,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1074,7 +1074,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } @@ -1366,7 +1366,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assert.Equal(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, got) } }) } diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 1d89115c8..f6d39befe 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -14,6 +14,8 @@ type SystemDefaults struct { DomainVerification DomainVerification Notifications Notifications KeyConfig KeyConfig + DefaultQueryLimit uint64 + MaxQueryLimit uint64 } type SecretGenerators struct { diff --git a/internal/domain/object.go b/internal/domain/object.go index a9e90a6ca..9ebffae58 100644 --- a/internal/domain/object.go +++ b/internal/domain/object.go @@ -5,7 +5,12 @@ import ( ) type ObjectDetails struct { - Sequence uint64 - EventDate time.Time + Sequence uint64 + // EventDate is the date of the last event that changed the object + EventDate time.Time + // CreationDate is the date of the first event that created the object + CreationDate time.Time ResourceOwner string + // ID is the Aggregate ID of the resource + ID string } diff --git a/internal/integration/assert.go b/internal/integration/assert.go index 682a82ff7..b3e63fd29 100644 --- a/internal/integration/assert.go +++ b/internal/integration/assert.go @@ -11,7 +11,6 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" resources_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" - settings_object "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha" ) // Details is the interface that covers both v1 and v2 proto generated object details. @@ -37,6 +36,10 @@ type ListDetailsMsg[L ListDetails] interface { GetDetails() L } +type ResourceListDetailsMsg interface { + GetDetails() *resources_object.ListDetails +} + // AssertDetails asserts values in a message's object Details, // if the object Details in expected is a non-nil value. // It targets API v2 messages that have the `GetDetails()` method. @@ -67,28 +70,21 @@ func AssertDetails[D Details, M DetailsMsg[D]](t testing.TB, expected, actual M) } func AssertResourceDetails(t testing.TB, expected *resources_object.Details, actual *resources_object.Details) { - assert.NotZero(t, actual.GetSequence()) - - if expected.GetChangeDate() != nil { + if expected.GetChanged() != nil { wantChangeDate := time.Now() - gotChangeDate := actual.GetChangeDate().AsTime() + gotChangeDate := actual.GetChanged().AsTime() assert.WithinRange(t, gotChangeDate, wantChangeDate.Add(-time.Minute), wantChangeDate.Add(time.Minute)) } - + if expected.GetCreated() != nil { + wantCreatedDate := time.Now() + gotCreatedDate := actual.GetCreated().AsTime() + assert.WithinRange(t, gotCreatedDate, wantCreatedDate.Add(-time.Minute), wantCreatedDate.Add(time.Minute)) + } assert.Equal(t, expected.GetOwner(), actual.GetOwner()) assert.NotEmpty(t, actual.GetId()) -} - -func AssertSettingsDetails(t testing.TB, expected *settings_object.Details, actual *settings_object.Details) { - assert.NotZero(t, actual.GetSequence()) - - if expected.GetChangeDate() != nil { - wantChangeDate := time.Now() - gotChangeDate := actual.GetChangeDate().AsTime() - assert.WithinRange(t, gotChangeDate, wantChangeDate.Add(-time.Minute), wantChangeDate.Add(time.Minute)) + if expected.GetId() != "" { + assert.Equal(t, expected.GetId(), actual.GetId()) } - - assert.Equal(t, expected.GetOwner(), actual.GetOwner()) } func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t testing.TB, expected, actual D) { @@ -107,6 +103,23 @@ func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t testing.TB, expecte } } +func AssertResourceListDetails[D ResourceListDetailsMsg](t testing.TB, expected, actual D) { + wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails() + if wantDetails == nil { + assert.Nil(t, gotDetails) + return + } + + assert.Equal(t, wantDetails.GetTotalResult(), gotDetails.GetTotalResult()) + assert.Equal(t, wantDetails.GetAppliedLimit(), gotDetails.GetAppliedLimit()) + + if wantDetails.GetTimestamp() != nil { + gotCD := gotDetails.GetTimestamp().AsTime() + wantCD := time.Now() + assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute)) + } +} + // EqualProto is inspired by [assert.Equal], only that it tests equality of a proto message. // A message diff is printed on the error test log if the messages are not equal. // diff --git a/internal/integration/client.go b/internal/integration/client.go index 381968261..9e5dc63dd 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -137,7 +137,7 @@ func (t *Tester) UseIsolatedInstance(tt *testing.T, iamOwnerCtx, systemCtx conte }) assert.NoError(collectT, importErr) }, 2*time.Minute, 100*time.Millisecond, "instance not ready") - return primaryDomain, instanceId, adminUser.GetUserId(), newCtx + return primaryDomain, instanceId, adminUser.GetUserId(), t.updateInstanceAndOrg(newCtx, fmt.Sprintf("%s:%d", primaryDomain, t.Config.ExternalPort)) } func (s *Tester) CreateHumanUser(ctx context.Context) *user.AddHumanUserResponse { diff --git a/internal/integration/integration.go b/internal/integration/integration.go index 807f976a3..18836be8f 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -204,14 +204,7 @@ func (s *Tester) createLoginClient(ctx context.Context) { func (s *Tester) createMachineUser(ctx context.Context, username string, userType UserType) (context.Context, *query.User) { var err error - - s.Instance, err = s.Queries.InstanceByHost(ctx, s.Host(), "") - logging.OnError(err).Fatal("query instance") - ctx = authz.WithInstance(ctx, s.Instance) - - s.Organisation, err = s.Queries.OrgByID(ctx, true, s.Instance.DefaultOrganisationID()) - logging.OnError(err).Fatal("query organisation") - + ctx = s.updateInstanceAndOrg(ctx, s.Host()) usernameQuery, err := query.NewUserUsernameSearchQuery(username, query.TextEquals) logging.OnError(err).Fatal("user query") user, err := s.Queries.GetUser(ctx, true, usernameQuery) @@ -427,3 +420,14 @@ func runQuotaServer(ctx context.Context, bodies chan []byte) (*httptest.Server, mockServer.Start() return mockServer, nil } + +func (s *Tester) updateInstanceAndOrg(ctx context.Context, domain string) context.Context { + var err error + s.Instance, err = s.Queries.InstanceByHost(ctx, domain, "") + logging.OnError(err).Fatal("query instance") + ctx = authz.WithInstance(ctx, s.Instance) + + s.Organisation, err = s.Queries.OrgByID(ctx, true, s.Instance.DefaultOrganisationID()) + logging.OnError(err).Fatal("query organisation") + return ctx +} diff --git a/internal/query/execution.go b/internal/query/execution.go index ff501f820..5ce5e36a9 100644 --- a/internal/query/execution.go +++ b/internal/query/execution.go @@ -28,6 +28,10 @@ var ( name: projection.ExecutionIDCol, table: executionTable, } + ExecutionColumnCreationDate = Column{ + name: projection.ExecutionCreationDateCol, + table: executionTable, + } ExecutionColumnChangeDate = Column{ name: projection.ExecutionChangeDateCol, table: executionTable, @@ -36,11 +40,6 @@ var ( name: projection.ExecutionInstanceIDCol, table: executionTable, } - ExecutionColumnSequence = Column{ - name: projection.ExecutionSequenceCol, - table: executionTable, - } - executionTargetsTable = table{ name: projection.ExecutionTable + "_" + projection.ExecutionTargetSuffix, instanceIDCol: projection.ExecutionTargetInstanceIDCol, @@ -79,7 +78,6 @@ func (e *Executions) SetState(s *State) { } type Execution struct { - ID string domain.ObjectDetails Targets []*exec.Target @@ -210,12 +208,12 @@ func (q *Queries) TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string return execution, err } -func prepareExecutionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) { +func prepareExecutionQuery(context.Context, prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) { return sq.Select( ExecutionColumnInstanceID.identifier(), ExecutionColumnID.identifier(), + ExecutionColumnCreationDate.identifier(), ExecutionColumnChangeDate.identifier(), - ExecutionColumnSequence.identifier(), executionTargetsListCol.identifier(), ).From(executionTable.identifier()). Join("(" + executionTargetsQuery + ") AS " + executionTargetsTableAlias.alias + " ON " + @@ -226,12 +224,12 @@ func prepareExecutionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu scanExecution } -func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) { +func prepareExecutionsQuery(context.Context, prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) { return sq.Select( ExecutionColumnInstanceID.identifier(), ExecutionColumnID.identifier(), + ExecutionColumnCreationDate.identifier(), ExecutionColumnChangeDate.identifier(), - ExecutionColumnSequence.identifier(), executionTargetsListCol.identifier(), countColumn.identifier(), ).From(executionTable.identifier()). @@ -256,8 +254,8 @@ func scanExecution(row *sql.Row) (*Execution, error) { err := row.Scan( &execution.ResourceOwner, &execution.ID, + &execution.CreationDate, &execution.EventDate, - &execution.Sequence, &targets, ) if err != nil { @@ -315,8 +313,8 @@ func scanExecutions(rows *sql.Rows) (*Executions, error) { err := rows.Scan( &execution.ResourceOwner, &execution.ID, + &execution.CreationDate, &execution.EventDate, - &execution.Sequence, &targets, &count, ) diff --git a/internal/query/execution_test.go b/internal/query/execution_test.go index b989d539a..ee6bdc4d9 100644 --- a/internal/query/execution_test.go +++ b/internal/query/execution_test.go @@ -16,8 +16,8 @@ import ( var ( prepareExecutionsStmt = `SELECT projections.executions1.instance_id,` + ` projections.executions1.id,` + + ` projections.executions1.creation_date,` + ` projections.executions1.change_date,` + - ` projections.executions1.sequence,` + ` execution_targets.targets,` + ` COUNT(*) OVER ()` + ` FROM projections.executions1` + @@ -32,16 +32,16 @@ var ( prepareExecutionsCols = []string{ "instance_id", "id", + "creation_date", "change_date", - "sequence", "targets", "count", } prepareExecutionStmt = `SELECT projections.executions1.instance_id,` + ` projections.executions1.id,` + + ` projections.executions1.creation_date,` + ` projections.executions1.change_date,` + - ` projections.executions1.sequence,` + ` execution_targets.targets` + ` FROM projections.executions1` + ` JOIN (` + @@ -55,8 +55,8 @@ var ( prepareExecutionCols = []string{ "instance_id", "id", + "creation_date", "change_date", - "sequence", "targets", } ) @@ -96,7 +96,7 @@ func Test_ExecutionPrepares(t *testing.T) { "ro", "id", testNow, - uint64(20211109), + testNow, []byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`), }, }, @@ -108,11 +108,11 @@ func Test_ExecutionPrepares(t *testing.T) { }, Executions: []*Execution{ { - ID: "id", ObjectDetails: domain.ObjectDetails{ EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211109, + ID: "id", }, Targets: []*exec.Target{ {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, @@ -134,14 +134,14 @@ func Test_ExecutionPrepares(t *testing.T) { "ro", "id-1", testNow, - uint64(20211109), + testNow, []byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`), }, { "ro", "id-2", testNow, - uint64(20211110), + testNow, []byte(`[{"position" : 2, "target" : "target"}, {"position" : 1, "include" : "include"}]`), }, }, @@ -153,11 +153,11 @@ func Test_ExecutionPrepares(t *testing.T) { }, Executions: []*Execution{ { - ID: "id-1", ObjectDetails: domain.ObjectDetails{ + ID: "id-1", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211109, }, Targets: []*exec.Target{ {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, @@ -165,11 +165,11 @@ func Test_ExecutionPrepares(t *testing.T) { }, }, { - ID: "id-2", ObjectDetails: domain.ObjectDetails{ + ID: "id-2", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211110, }, Targets: []*exec.Target{ {Type: domain.ExecutionTargetTypeInclude, Target: "include"}, @@ -225,17 +225,17 @@ func Test_ExecutionPrepares(t *testing.T) { "ro", "id", testNow, - uint64(20211109), + testNow, []byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`), }, ), }, object: &Execution{ - ID: "id", ObjectDetails: domain.ObjectDetails{ + ID: "id", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211109, }, Targets: []*exec.Target{ {Type: domain.ExecutionTargetTypeTarget, Target: "target"}, diff --git a/internal/query/instance.go b/internal/query/instance.go index d547ae538..fb60946d3 100644 --- a/internal/query/instance.go +++ b/internal/query/instance.go @@ -216,14 +216,12 @@ func (q *Queries) InstanceByHost(ctx context.Context, instanceHost, publicHost s return instance, err } -func (q *Queries) InstanceByID(ctx context.Context) (_ authz.Instance, err error) { +func (q *Queries) InstanceByID(ctx context.Context, id string) (_ authz.Instance, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - - instanceID := authz.GetInstance(ctx).InstanceID() instance, scan := scanAuthzInstance() - err = q.client.QueryRowContext(ctx, scan, instanceByIDQuery, instanceID) - logging.OnError(err).WithField("instance_id", instanceID).Warn("instance by ID") + err = q.client.QueryRowContext(ctx, scan, instanceByIDQuery, id) + logging.OnError(err).WithField("instance_id", id).Warn("instance by ID") return instance, err } diff --git a/internal/query/projection/target.go b/internal/query/projection/target.go index 7b7c46d25..d39a75b6d 100644 --- a/internal/query/projection/target.go +++ b/internal/query/projection/target.go @@ -97,7 +97,7 @@ func (p *targetProjection) reduceTargetAdded(event eventstore.Event) (*handler.S handler.NewCol(TargetInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(TargetResourceOwnerCol, e.Aggregate().ResourceOwner), handler.NewCol(TargetIDCol, e.Aggregate().ID), - handler.NewCol(TargetCreationDateCol, e.CreationDate()), + handler.NewCol(TargetCreationDateCol, handler.OnlySetValueOnInsert(TargetTable, e.CreationDate())), handler.NewCol(TargetChangeDateCol, e.CreationDate()), handler.NewCol(TargetSequenceCol, e.Sequence()), handler.NewCol(TargetNameCol, e.Name), diff --git a/internal/query/target.go b/internal/query/target.go index c5d8f893a..8d926a699 100644 --- a/internal/query/target.go +++ b/internal/query/target.go @@ -39,10 +39,6 @@ var ( name: projection.TargetInstanceIDCol, table: targetTable, } - TargetColumnSequence = Column{ - name: projection.TargetSequenceCol, - table: targetTable, - } TargetColumnName = Column{ name: projection.TargetNameCol, table: targetTable, @@ -75,7 +71,6 @@ func (t *Targets) SetState(s *State) { } type Target struct { - ID string domain.ObjectDetails Name string @@ -123,12 +118,12 @@ func NewTargetInIDsSearchQuery(values []string) (SearchQuery, error) { return NewInTextQuery(TargetColumnID, values) } -func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Targets, error)) { +func prepareTargetsQuery(context.Context, prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Targets, error)) { return sq.Select( TargetColumnID.identifier(), + TargetColumnCreationDate.identifier(), TargetColumnChangeDate.identifier(), TargetColumnResourceOwner.identifier(), - TargetColumnSequence.identifier(), TargetColumnName.identifier(), TargetColumnTargetType.identifier(), TargetColumnTimeout.identifier(), @@ -144,9 +139,9 @@ func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil target := new(Target) err := rows.Scan( &target.ID, + &target.CreationDate, &target.EventDate, &target.ResourceOwner, - &target.Sequence, &target.Name, &target.TargetType, &target.Timeout, @@ -173,12 +168,12 @@ func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil } } -func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Target, error)) { +func prepareTargetQuery(context.Context, prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Target, error)) { return sq.Select( TargetColumnID.identifier(), + TargetColumnCreationDate.identifier(), TargetColumnChangeDate.identifier(), TargetColumnResourceOwner.identifier(), - TargetColumnSequence.identifier(), TargetColumnName.identifier(), TargetColumnTargetType.identifier(), TargetColumnTimeout.identifier(), @@ -190,9 +185,9 @@ func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuild target := new(Target) err := row.Scan( &target.ID, + &target.CreationDate, &target.EventDate, &target.ResourceOwner, - &target.Sequence, &target.Name, &target.TargetType, &target.Timeout, diff --git a/internal/query/target_test.go b/internal/query/target_test.go index 61a61e9e4..1b6edd1ad 100644 --- a/internal/query/target_test.go +++ b/internal/query/target_test.go @@ -15,9 +15,9 @@ import ( var ( prepareTargetsStmt = `SELECT projections.targets1.id,` + + ` projections.targets1.creation_date,` + ` projections.targets1.change_date,` + ` projections.targets1.resource_owner,` + - ` projections.targets1.sequence,` + ` projections.targets1.name,` + ` projections.targets1.target_type,` + ` projections.targets1.timeout,` + @@ -27,9 +27,9 @@ var ( ` FROM projections.targets1` prepareTargetsCols = []string{ "id", + "creation_date", "change_date", "resource_owner", - "sequence", "name", "target_type", "timeout", @@ -39,9 +39,9 @@ var ( } prepareTargetStmt = `SELECT projections.targets1.id,` + + ` projections.targets1.creation_date,` + ` projections.targets1.change_date,` + ` projections.targets1.resource_owner,` + - ` projections.targets1.sequence,` + ` projections.targets1.name,` + ` projections.targets1.target_type,` + ` projections.targets1.timeout,` + @@ -50,9 +50,9 @@ var ( ` FROM projections.targets1` prepareTargetCols = []string{ "id", + "creation_date", "change_date", "resource_owner", - "sequence", "name", "target_type", "timeout", @@ -95,8 +95,8 @@ func Test_TargetPrepares(t *testing.T) { { "id", testNow, + testNow, "ro", - uint64(20211109), "target-name", domain.TargetTypeWebhook, 1 * time.Second, @@ -112,11 +112,11 @@ func Test_TargetPrepares(t *testing.T) { }, Targets: []*Target{ { - ID: "id", ObjectDetails: domain.ObjectDetails{ + ID: "id", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211109, }, Name: "target-name", TargetType: domain.TargetTypeWebhook, @@ -138,8 +138,8 @@ func Test_TargetPrepares(t *testing.T) { { "id-1", testNow, + testNow, "ro", - uint64(20211109), "target-name1", domain.TargetTypeWebhook, 1 * time.Second, @@ -149,8 +149,8 @@ func Test_TargetPrepares(t *testing.T) { { "id-2", testNow, + testNow, "ro", - uint64(20211110), "target-name2", domain.TargetTypeWebhook, 1 * time.Second, @@ -160,8 +160,8 @@ func Test_TargetPrepares(t *testing.T) { { "id-3", testNow, + testNow, "ro", - uint64(20211110), "target-name3", domain.TargetTypeAsync, 1 * time.Second, @@ -177,11 +177,11 @@ func Test_TargetPrepares(t *testing.T) { }, Targets: []*Target{ { - ID: "id-1", ObjectDetails: domain.ObjectDetails{ + ID: "id-1", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211109, }, Name: "target-name1", TargetType: domain.TargetTypeWebhook, @@ -190,11 +190,11 @@ func Test_TargetPrepares(t *testing.T) { InterruptOnError: true, }, { - ID: "id-2", ObjectDetails: domain.ObjectDetails{ + ID: "id-2", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211110, }, Name: "target-name2", TargetType: domain.TargetTypeWebhook, @@ -203,11 +203,11 @@ func Test_TargetPrepares(t *testing.T) { InterruptOnError: false, }, { - ID: "id-3", ObjectDetails: domain.ObjectDetails{ + ID: "id-3", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211110, }, Name: "target-name3", TargetType: domain.TargetTypeAsync, @@ -263,8 +263,8 @@ func Test_TargetPrepares(t *testing.T) { []driver.Value{ "id", testNow, + testNow, "ro", - uint64(20211109), "target-name", domain.TargetTypeWebhook, 1 * time.Second, @@ -274,11 +274,11 @@ func Test_TargetPrepares(t *testing.T) { ), }, object: &Target{ - ID: "id", ObjectDetails: domain.ObjectDetails{ + ID: "id", EventDate: testNow, + CreationDate: testNow, ResourceOwner: "ro", - Sequence: 20211109, }, Name: "target-name", TargetType: domain.TargetTypeWebhook, diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index 1161a4928..fd7bfee66 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -509,6 +509,7 @@ Errors: SQLStatement: SQL изразът не може да бъде създаден InvalidRequest: Заявката е невалидна TooManyNestingLevels: Твърде много нива на влагане на заявката (макс. 20) + LimitExceeded: Ограничението на заявката е превишено Quota: AlreadyExists: Вече съществува квота за тази единица NotFound: Не е намерена квота за тази единица diff --git a/internal/static/i18n/cs.yaml b/internal/static/i18n/cs.yaml index 3383021b4..986bc6327 100644 --- a/internal/static/i18n/cs.yaml +++ b/internal/static/i18n/cs.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: SQL příkaz nemohl být vytvořen InvalidRequest: Požadavek je neplatný TooManyNestingLevels: Příliš mnoho úrovní vnoření dotazů (max. 20) + LimitExceeded: Překročen limit výsledků Quota: AlreadyExists: Kvóta pro tuto jednotku již existuje NotFound: Kvóta pro tuto jednotku nenalezena diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index e9ebccf3b..ec7d5976b 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: SQL Statement konnte nicht erstellt werden InvalidRequest: Anfrage ist ungültig TooManyNestingLevels: Zu viele Abfrageverschachtelungsebenen (maximal 20) + LimitExceeded: Limit überschritten Quota: AlreadyExists: Das Kontingent existiert bereits für diese Einheit NotFound: Kontingent für diese Einheit nicht gefunden diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index a35cfc043..b1bf5907c 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: SQL Statement could not be created InvalidRequest: Request is invalid TooManyNestingLevels: Too many query nesting levels (Max 20) + LimitExceeded: Limit exceeded Quota: AlreadyExists: Quota already exists for this unit NotFound: Quota not found for this unit @@ -586,6 +587,7 @@ Errors: Impersonation: PolicyDisabled: Impersonation is disabled in the instance security policy + AggregateTypes: action: Action instance: Instance diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index b4ba11cfa..cec5cfedd 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: La sentencia SQL no pudo crearse InvalidRequest: La solicitud no es válida TooManyNestingLevels: Demasiados niveles de anidamiento de consultas (máximo 20) + LimitExceeded: Se ha superado el límite de resultados Quota: AlreadyExists: La cuota ya existe para esta unidad NotFound: Cuota no encontrada para esta unidad diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index e10df340d..987172570 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: L'instruction SQL n'a pas pu être créée InvalidRequest: La requête n'est pas valide TooManyNestingLevels: Trop de niveaux d'imbrication de requêtes (maximum 20) + LimitExceeded: Limite dépassée Quota: AlreadyExists: Contingent existe déjà pour cette unité NotFound: Contingent non trouvé pour cette unité diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index a853e2874..73180b982 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: Lo statement SQL non può essere creato InvalidRequest: La richiesta non è valida TooManyNestingLevels: Troppi livelli di nidificazione delle query (massimo 20) + LimitExceeded: Limite superato Quota: AlreadyExists: La quota esiste già per questa unità NotFound: Quota non trovata per questa unità diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 725cdcc7a..3b8b4cbb9 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -484,6 +484,7 @@ Errors: SQLStatement: SQLステートメントの作成に失敗しました InvalidRequest: 無効なリクエストです TooManyNestingLevels: クエリのネスト レベルが多すぎます (最大 20) + LimitExceeded: 制限を超えました Quota: AlreadyExists: このユニットにはすでにクォータが存在しています NotFound: このユニットにはクォータが見つかりません diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index d7aabafe3..cec3cbb50 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -494,6 +494,7 @@ Errors: SQLStatement: SQL наредбата не може да се креира InvalidRequest: Барањето е невалидно TooManyNestingLevels: Премногу нивоа на вгнездување на барања (макс 20) + LimitExceeded: Превишена граница Quota: AlreadyExists: Веќе постои квота за оваа единица NotFound: Квотата не е пронајдена за оваа единица diff --git a/internal/static/i18n/nl.yaml b/internal/static/i18n/nl.yaml index fa5e3b6ce..062ee7f5c 100644 --- a/internal/static/i18n/nl.yaml +++ b/internal/static/i18n/nl.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: SQL Statement kon niet worden gemaakt InvalidRequest: Verzoek is ongeldig TooManyNestingLevels: Te veel query nesting niveaus (Max 20) + LimitExceeded: Limiet overschreden Quota: AlreadyExists: Quota bestaat al voor deze eenheid NotFound: Quota niet gevonden voor deze eenheid diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index c6081de5a..4705320d8 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: Instrukcja SQL nie mogła zostać utworzona InvalidRequest: Żądanie jest nieprawidłowe TooManyNestingLevels: Zbyt wiele poziomów zagnieżdżenia zapytań (maks. 20) + LimitExceeded: Limit przekroczony Quota: AlreadyExists: Limit już istnieje dla tej jednostki NotFound: Nie znaleziono limitu dla tej jednostki diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index e980b4ea2..acb69e2c0 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -494,6 +494,7 @@ Errors: SQLStatement: Não foi possível criar a instrução SQL InvalidRequest: O pedido é inválido TooManyNestingLevels: muitos níveis de aninhamento de consulta (máx. 20) + LimitExceeded: Limite excedido Quota: AlreadyExists: Cota já existe para esta unidade NotFound: Cota não encontrada para esta unidade diff --git a/internal/static/i18n/ru.yaml b/internal/static/i18n/ru.yaml index b2aa62d28..36918b9c1 100644 --- a/internal/static/i18n/ru.yaml +++ b/internal/static/i18n/ru.yaml @@ -488,6 +488,7 @@ Errors: SQLStatement: SQL-запрос не может быть создан InvalidRequest: Запрос недействителен TooManyNestingLevels: слишком много уровней вложенности запросов (максимум 20) + LimitExceeded: Превышен лимит Quota: AlreadyExists: Квота для данного объекта уже существует NotFound: Квота для данного объекта не найдена diff --git a/internal/static/i18n/sv.yaml b/internal/static/i18n/sv.yaml index d995df06f..ee0a6a3b0 100644 --- a/internal/static/i18n/sv.yaml +++ b/internal/static/i18n/sv.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: SQL-satsen kunde inte skapas InvalidRequest: Begäran är ogiltig TooManyNestingLevels: För många nivåer av frågenästning (Max 20) + LimitExceeded: Gränsen överskreds Quota: AlreadyExists: Kvota finns redan för denna enhet NotFound: Kvota hittades inte för denna enhet diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index b4fa6e90b..9d22c3089 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -495,6 +495,7 @@ Errors: SQLStatement: 无法创建 SQL 语句 InvalidRequest: 请求无效 TooManyNestingLevels: 查询嵌套级别过多(最多 20 个) + LimitExceeded: 限制已超出 Quota: AlreadyExists: 这个单位的配额已经存在 NotFound: 没有找到该单位的配额 diff --git a/proto/zitadel/object/v3alpha/object.proto b/proto/zitadel/object/v3alpha/object.proto index fba08fa5b..5c067ddc1 100644 --- a/proto/zitadel/object/v3alpha/object.proto +++ b/proto/zitadel/object/v3alpha/object.proto @@ -20,3 +20,10 @@ message Owner { string id = 2; } +message Instance { + oneof property { + option (validate.required) = true; + string id = 1; + string domain = 2; + } +} diff --git a/proto/zitadel/resources/action/v3alpha/action_service.proto b/proto/zitadel/resources/action/v3alpha/action_service.proto index a8ae67b95..08d57e93e 100644 --- a/proto/zitadel/resources/action/v3alpha/action_service.proto +++ b/proto/zitadel/resources/action/v3alpha/action_service.proto @@ -10,10 +10,13 @@ import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; + import "zitadel/resources/action/v3alpha/target.proto"; import "zitadel/resources/action/v3alpha/execution.proto"; +import "zitadel/resources/action/v3alpha/query.proto"; import "zitadel/resources/object/v3alpha/object.proto"; -import "zitadel/settings/object/v3alpha/object.proto"; +import "zitadel/object/v3alpha/object.proto"; + option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action"; @@ -44,7 +47,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "${ZITADEL_DOMAIN}"; base_path: "/resources/v3alpha/actions"; external_docs: { @@ -57,8 +60,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; - token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; + authorization_url: "${ZITADEL_DOMAIN}/oauth/v2/authorize"; + token_url: "${ZITADEL_DOMAIN}/oauth/v2/token"; scopes: { scope: { key: "openid"; @@ -189,6 +192,67 @@ service ZITADELActions { }; } + // Target by ID + // + // Returns the target identified by the requested ID. + rpc GetTarget (GetTargetRequest) returns (GetTargetResponse) { + option (google.api.http) = { + get: "/targets/{id}" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "action.target.read" + } + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + responses: { + key: "200" + value: { + description: "Target successfully retrieved"; + } + }; + }; + } + + // Search targets + // + // Search all matching targets. By default all targets of the instance are returned. + // Make sure to include a limit and sorting for pagination. + rpc SearchTargets (SearchTargetsRequest) returns (SearchTargetsResponse) { + option (google.api.http) = { + post: "/targets/_search", + body: "filters" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "action.target.read" + } + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + responses: { + key: "200"; + value: { + description: "A list of all targets matching the query"; + }; + }; + responses: { + key: "400"; + value: { + description: "invalid list query"; + schema: { + json_schema: { + ref: "#/definitions/rpcStatus"; + }; + }; + }; + }; + }; + } + // Sets an execution to call a target or include the targets of another execution. // // Setting an empty list of targets will remove all targets from the execution, making it a noop. @@ -222,6 +286,43 @@ service ZITADELActions { }; } + // Search executions + // + // Search all matching executions. By default all executions of the instance are returned that have at least one execution target. + // Make sure to include a limit and sorting for pagination. + rpc SearchExecutions (SearchExecutionsRequest) returns (SearchExecutionsResponse) { + option (google.api.http) = { + post: "/executions/_search" + body: "filters" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "action.execution.read" + } + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + responses: { + key: "200"; + value: { + description: "A list of all non noop executions matching the query"; + }; + }; + responses: { + key: "400"; + value: { + description: "invalid list query"; + schema: { + json_schema: { + ref: "#/definitions/rpcStatus"; + }; + }; + }; + }; + }; + } + // List all available functions // // List all available functions which can be used as condition for executions. @@ -294,7 +395,16 @@ service ZITADELActions { } message CreateTargetRequest { - Target target = 1; + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + Target target = 2 [ + (validate.rules).message = { + required: true + } + ]; } message CreateTargetResponse { @@ -302,16 +412,24 @@ message CreateTargetResponse { } message PatchTargetRequest { - string id = 1 [ + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + string id = 2 [ (validate.rules).string = {min_len: 1, max_len: 200}, - (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { min_length: 1, max_length: 200, example: "\"69629026806489455\""; } ]; - PatchTarget target = 2; + PatchTarget target = 3 [ + (validate.rules).message = { + required: true + } + ]; } message PatchTargetResponse { @@ -319,9 +437,13 @@ message PatchTargetResponse { } message DeleteTargetRequest { - string id = 1 [ + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + string id = 2 [ (validate.rules).string = {min_len: 1, max_len: 200}, - (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { min_length: 1, max_length: 200, @@ -334,13 +456,84 @@ message DeleteTargetResponse { zitadel.resources.object.v3alpha.Details details = 1; } +message GetTargetRequest { + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + string id = 2 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + min_length: 1, + max_length: 200, + example: "\"69629026806489455\""; + } + ]; +} + +message GetTargetResponse { + GetTarget target = 1; +} + +message SearchTargetsRequest { + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + // list limitations and ordering. + optional zitadel.resources.object.v3alpha.SearchQuery query = 2; + // The field the result is sorted by. The default is the creation date. Beware that if you change this, your result pagination might be inconsistent. + optional TargetFieldName sorting_column = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"TARGET_FIELD_NAME_CREATION_DATE\"" + } + ]; + // Define the criteria to query for. + repeated TargetSearchFilter filters = 4; +} + +message SearchTargetsResponse { + zitadel.resources.object.v3alpha.ListDetails details = 1; + repeated GetTarget result = 2; +} + message SetExecutionRequest { - Condition condition = 1; - Execution execution = 2; + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + Condition condition = 2; + Execution execution = 3; } message SetExecutionResponse { - zitadel.settings.object.v3alpha.Details details = 1; + zitadel.resources.object.v3alpha.Details details = 1; +} + +message SearchExecutionsRequest { + optional zitadel.object.v3alpha.Instance instance = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"domain from HOST or :authority header\"" + } + ]; + // list limitations and ordering. + optional zitadel.resources.object.v3alpha.SearchQuery query = 2; + // The field the result is sorted by. The default is the creation date. Beware that if you change this, your result pagination might be inconsistent. + optional ExecutionFieldName sorting_column = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "\"EXECUTION_FIELD_NAME_CREATION_DATE\"" + } + ]; + // Define the criteria to query for. + repeated ExecutionSearchFilter filters = 4; +} + +message SearchExecutionsResponse { + zitadel.resources.object.v3alpha.ListDetails details = 1; + repeated GetExecution result = 2; } message ListExecutionFunctionsRequest{} diff --git a/proto/zitadel/resources/action/v3alpha/execution.proto b/proto/zitadel/resources/action/v3alpha/execution.proto index f666b4c49..375ab02b8 100644 --- a/proto/zitadel/resources/action/v3alpha/execution.proto +++ b/proto/zitadel/resources/action/v3alpha/execution.proto @@ -21,6 +21,12 @@ message Execution { repeated ExecutionTargetType targets = 1; } +message GetExecution { + zitadel.resources.object.v3alpha.Details details = 1; + Condition condition = 2; + Execution execution = 3; +} + message ExecutionTargetType { oneof type { option (validate.required) = true; diff --git a/proto/zitadel/resources/action/v3alpha/query.proto b/proto/zitadel/resources/action/v3alpha/query.proto new file mode 100644 index 000000000..fb5154308 --- /dev/null +++ b/proto/zitadel/resources/action/v3alpha/query.proto @@ -0,0 +1,117 @@ +syntax = "proto3"; + +package zitadel.resources.action.v3alpha; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action"; + +import "google/api/field_behavior.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; +import "validate/validate.proto"; + +import "zitadel/resources/object/v3alpha/object.proto"; +import "zitadel/resources/action/v3alpha/execution.proto"; + +message ExecutionSearchFilter { + oneof filter { + option (validate.required) = true; + + InConditionsFilter in_conditions_filter = 1; + ExecutionTypeFilter execution_type_filter = 2; + TargetFilter target_filter = 3; + IncludeFilter include_filter = 4; + } +} + +message InConditionsFilter { + // Defines the conditions to query for. + repeated Condition conditions = 1; +} + +message ExecutionTypeFilter { + // Defines the type to query for. + ExecutionType execution_type = 1; +} + +message TargetFilter { + // Defines the id to query for. + string target_id = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "the id of the targets to include" + example: "\"69629023906488334\""; + } + ]; +} + +message IncludeFilter { + // Defines the include to query for. + Condition include = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "the id of the include" + example: "\"request.zitadel.session.v2.SessionService\""; + } + ]; +} + +message TargetSearchFilter { + oneof filter { + option (validate.required) = true; + + TargetNameFilter target_name_filter = 1; + InTargetIDsFilter in_target_ids_filter = 2; + } +} + +message TargetNameFilter { + // Defines the name of the target to query for. + string target_name = 1 [ + (validate.rules).string = {max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + max_length: 200; + example: "\"ip_allow_list\""; + } + ]; + // Defines which text comparison method used for the name query. + zitadel.resources.object.v3alpha.TextFilterMethod method = 2 [ + (validate.rules).enum.defined_only = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "defines which text equality method is used"; + } + ]; +} + +message InTargetIDsFilter { + // Defines the ids to query for. + repeated string target_ids = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "the ids of the targets to include" + example: "[\"69629023906488334\",\"69622366012355662\"]"; + } + ]; +} + +enum ExecutionType { + EXECUTION_TYPE_UNSPECIFIED = 0; + EXECUTION_TYPE_REQUEST = 1; + EXECUTION_TYPE_RESPONSE = 2; + EXECUTION_TYPE_EVENT = 3; + EXECUTION_TYPE_FUNCTION = 4; +} + +enum TargetFieldName { + TARGET_FIELD_NAME_UNSPECIFIED = 0; + TARGET_FIELD_NAME_ID = 1; + TARGET_FIELD_NAME_CREATED_DATE = 2; + TARGET_FIELD_NAME_CHANGED_DATE = 3; + TARGET_FIELD_NAME_NAME = 4; + TARGET_FIELD_NAME_TARGET_TYPE = 5; + TARGET_FIELD_NAME_URL = 6; + TARGET_FIELD_NAME_TIMEOUT = 7; + TARGET_FIELD_NAME_INTERRUPT_ON_ERROR = 8; +} + +enum ExecutionFieldName { + EXECUTION_FIELD_NAME_UNSPECIFIED = 0; + EXECUTION_FIELD_NAME_ID = 1; + EXECUTION_FIELD_NAME_CREATED_DATE = 2; + EXECUTION_FIELD_NAME_CHANGED_DATE = 3; +} diff --git a/proto/zitadel/resources/action/v3alpha/target.proto b/proto/zitadel/resources/action/v3alpha/target.proto index 20843a530..cb1ff8588 100644 --- a/proto/zitadel/resources/action/v3alpha/target.proto +++ b/proto/zitadel/resources/action/v3alpha/target.proto @@ -10,12 +10,17 @@ import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; +import "zitadel/resources/object/v3alpha/object.proto"; + option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action"; message Target { string name = 1 [ + (validate.rules).string = {min_len: 1, max_len: 1000}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"ip_allow_list\""; + min_length: 1 + max_length: 1000 } ]; // Defines the target type and how the response of the target is treated. @@ -27,21 +32,34 @@ message Target { } // Timeout defines the duration until ZITADEL cancels the execution. google.protobuf.Duration timeout = 5 [ + (validate.rules).duration = {gte: {}, lte: {seconds: 270}}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "if the target doesn't respond before this timeout expires, the the connection is closed and the action fails"; example: "\"10s\""; } ]; string endpoint = 6 [ + (validate.rules).string = {min_len: 1, max_len: 1000}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "\"https://example.com/hooks/ip_check\""; + example: "\"https://example.com/hooks/ip_check\"" + min_length: 1 + max_length: 1000 } ]; } +message GetTarget { + zitadel.resources.object.v3alpha.Details details = 1; + Target config = 2; +} + message PatchTarget { optional string name = 1 [ + (validate.rules).string = {min_len: 1, max_len: 1000}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "\"ip_allow_list\""; + example: "\"ip_allow_list\"" + min_length: 1 + max_length: 1000 } ]; // Defines the target type and how the response of the target is treated. @@ -52,13 +70,18 @@ message PatchTarget { } // Timeout defines the duration until ZITADEL cancels the execution. optional google.protobuf.Duration timeout = 5 [ + (validate.rules).duration = {gte: {}, lte: {seconds: 270}}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "if the target doesn't respond before this timeout expires, the the connection is closed and the action fails"; example: "\"10s\""; } ]; optional string endpoint = 6 [ + (validate.rules).string = {min_len: 1, max_len: 1000}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "\"https://example.com/hooks/ip_check\""; + example: "\"https://example.com/hooks/ip_check\"" + min_length: 1 + max_length: 1000 } ]; } diff --git a/proto/zitadel/resources/object/v3alpha/object.proto b/proto/zitadel/resources/object/v3alpha/object.proto index 65b3ef0c9..17d536b5e 100644 --- a/proto/zitadel/resources/object/v3alpha/object.proto +++ b/proto/zitadel/resources/object/v3alpha/object.proto @@ -17,27 +17,66 @@ message Details { example: "\"69629012906488334\""; } ]; - - //sequence represents the order of events. It's always counting - // - // on read: the sequence of the last event reduced by the projection - // - // on manipulation: the timestamp of the event(s) added by the manipulation - uint64 sequence = 2 [ - (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "\"2\""; - } - ]; - //change_date is the timestamp when the object was changed - // - // on read: the timestamp of the last event reduced by the projection - // - // on manipulation: the timestamp of the event(s) added by the manipulation - google.protobuf.Timestamp change_date = 3; - //resource_owner represents the context an object belongs to - zitadel.object.v3alpha.Owner owner = 4 [ + //the timestamp of the first event applied to the object. + google.protobuf.Timestamp created = 3; + //the timestamp of the last event applied to the object. + google.protobuf.Timestamp changed = 4; + //the parent object representing the returned objects context. + zitadel.object.v3alpha.Owner owner = 5 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"69629023906488334\""; } ]; -} \ No newline at end of file +} + +message SearchQuery { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "General List Query" + description: "Object unspecific list filters like offset, limit and asc/desc." + } + }; + uint64 offset = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"0\""; + } + ]; + uint32 limit = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "100"; + description: "Maximum amount of events returned. If not configured otherwise, the default is 100, the maximum is 1000. If the limit exceeds the maximum, ZITADEL throws an error."; + } + ]; + // If desc is true, the result is sorted by in descending order. Beware that if desc is true or the sorting column is not the creation date, the pagination results might be inconsistent. + bool desc = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "default is ascending, because together with the creation date sorting column, this returns the most deterministic pagination results."; + } + ]; +} + +message ListDetails { + uint64 applied_limit = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "100"; + } + ]; + uint64 total_result = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"2\""; + } + ]; + google.protobuf.Timestamp timestamp = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "the last time the projection got updated" + } + ]; +} + +enum TextFilterMethod { + TEXT_FILTER_METHOD_EQUALS = 0; + TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE = 1; + TEXT_FILTER_METHOD_STARTS_WITH = 2; + TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE = 3; + TEXT_FILTER_METHOD_CONTAINS = 4; +}