package projection import ( "testing" "time" "github.com/muhlemmer/gu" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/session" "github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/zerrors" ) func TestSessionProjection_reduces(t *testing.T) { type args struct { event func(t *testing.T) eventstore.Event } tests := []struct { name string args args reduce func(event eventstore.Event) (*handler.Statement, error) want wantReduce }{ { name: "instance reduceSessionAdded", args: args{ event: getEvent(testEvent( session.AddedType, session.AggregateType, []byte(`{ "domain": "domain", "user_agent": { "fingerprint_id": "fp1", "ip": "1.2.3.4", "description": "firefox", "header": { "foo": ["bar"] } } }`), ), session.AddedEventMapper), }, reduce: (&sessionProjection{}).reduceSessionAdded, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "INSERT INTO projections.sessions8 (id, instance_id, creation_date, change_date, resource_owner, state, sequence, creator, user_agent_fingerprint_id, user_agent_description, user_agent_ip, user_agent_header) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", expectedArgs: []interface{}{ "agg-id", "instance-id", anyArg{}, anyArg{}, "ro-id", domain.SessionStateActive, uint64(15), "editor-user", gu.Ptr("fp1"), gu.Ptr("firefox"), "1.2.3.4", []byte(`{"foo":["bar"]}`), }, }, }, }, }, }, { name: "instance reduceUserChecked", args: args{ event: getEvent(testEvent( session.AddedType, session.AggregateType, []byte(`{ "userId": "user-id", "userResourceOwner": "org-id", "checkedAt": "2023-05-04T00:00:00Z" }`), ), session.UserCheckedEventMapper), }, reduce: (&sessionProjection{}).reduceUserChecked, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, user_id, user_resource_owner, user_checked_at) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, "user-id", "org-id", time.Date(2023, time.May, 4, 0, 0, 0, 0, time.UTC), "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reducePasswordChecked", args: args{ event: getEvent(testEvent( session.AddedType, session.AggregateType, []byte(`{ "checkedAt": "2023-05-04T00:00:00Z" }`), ), session.PasswordCheckedEventMapper), }, reduce: (&sessionProjection{}).reducePasswordChecked, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, password_checked_at) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, time.Date(2023, time.May, 4, 0, 0, 0, 0, time.UTC), "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceWebAuthNChecked", args: args{ event: getEvent(testEvent( session.WebAuthNCheckedType, session.AggregateType, []byte(`{ "checkedAt": "2023-05-04T00:00:00Z", "userVerified": true }`), ), eventstore.GenericEventMapper[session.WebAuthNCheckedEvent]), }, reduce: (&sessionProjection{}).reduceWebAuthNChecked, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, webauthn_checked_at, webauthn_user_verified) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, time.Date(2023, time.May, 4, 0, 0, 0, 0, time.UTC), true, "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceIntentChecked", args: args{ event: getEvent(testEvent( session.AddedType, session.AggregateType, []byte(`{ "checkedAt": "2023-05-04T00:00:00Z" }`), ), session.IntentCheckedEventMapper), }, reduce: (&sessionProjection{}).reduceIntentChecked, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, intent_checked_at) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, time.Date(2023, time.May, 4, 0, 0, 0, 0, time.UTC), "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceOTPChecked", args: args{ event: getEvent(testEvent( session.AddedType, session.AggregateType, []byte(`{ "checkedAt": "2023-05-04T00:00:00Z" }`), ), eventstore.GenericEventMapper[session.TOTPCheckedEvent]), }, reduce: (&sessionProjection{}).reduceTOTPChecked, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, totp_checked_at) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, time.Date(2023, time.May, 4, 0, 0, 0, 0, time.UTC), "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceTokenSet", args: args{ event: getEvent(testEvent( session.TokenSetType, session.AggregateType, []byte(`{ "tokenID": "tokenID" }`), ), session.TokenSetEventMapper), }, reduce: (&sessionProjection{}).reduceTokenSet, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, token_id) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, "tokenID", "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceMetadataSet", args: args{ event: getEvent(testEvent( session.MetadataSetType, session.AggregateType, []byte(`{ "metadata": { "key": "dmFsdWU=" } }`), ), session.MetadataSetEventMapper), }, reduce: (&sessionProjection{}).reduceMetadataSet, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, metadata) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, map[string][]byte{ "key": []byte("value"), }, "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceLifetimeSet", args: args{ event: getEvent(testEvent( session.MetadataSetType, session.AggregateType, []byte(`{ "lifetime": 600000000000 }`), ), eventstore.GenericEventMapper[session.LifetimeSetEvent]), }, reduce: (&sessionProjection{}).reduceLifetimeSet, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET (change_date, sequence, expiration) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, anyArg{}, "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceSessionTerminated", args: args{ event: getEvent(testEvent( session.TerminateType, session.AggregateType, []byte(`{}`), ), session.TerminateEventMapper), }, reduce: (&sessionProjection{}).reduceSessionTerminated, want: wantReduce{ aggregateType: eventstore.AggregateType("session"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "DELETE FROM projections.sessions8 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", }, }, }, }, }, }, { name: "instance reduceInstanceRemoved", args: args{ event: getEvent( testEvent( instance.InstanceRemovedEventType, instance.AggregateType, nil, ), instance.InstanceRemovedEventMapper), }, reduce: reduceInstanceRemovedHelper(SessionColumnInstanceID), want: wantReduce{ aggregateType: eventstore.AggregateType("instance"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "DELETE FROM projections.sessions8 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, }, }, }, }, }, { name: "reducePasswordChanged", args: args{ event: getEvent(testEvent( user.HumanPasswordChangedType, user.AggregateType, []byte(`{"secret": { "cryptoType": 0, "algorithm": "enc", "keyID": "id", "crypted": "cGFzc3dvcmQ=" }}`), ), user.HumanPasswordChangedEventMapper), }, reduce: (&sessionProjection{}).reducePasswordChanged, want: wantReduce{ aggregateType: eventstore.AggregateType("user"), sequence: 15, executer: &testExecuter{ executions: []execution{ { expectedStmt: "UPDATE projections.sessions8 SET password_checked_at = $1 WHERE (user_id = $2) AND (instance_id = $3) AND (password_checked_at < $4)", expectedArgs: []interface{}{ nil, "agg-id", "instance-id", anyArg{}, }, }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { event := baseEvent(t) got, err := tt.reduce(event) if !zerrors.IsErrorInvalidArgument(err) { t.Errorf("no wrong event mapping: %v, got: %v", err, got) } event = tt.args.event(t) got, err = tt.reduce(event) assertReduce(t, got, err, SessionsProjectionTable, tt.want) }) } }