From 6d94975a857cdb31b0a06f4da36f3ae2328db205 Mon Sep 17 00:00:00 2001 From: Silvan Date: Tue, 16 Nov 2021 14:04:22 +0100 Subject: [PATCH] fix(queries): actions prepare funcs (#2689) * chore(queries): test suite for prepare stmt funcs * test(queries): prepare project funcs * refactor: add comments * test: simlify expected sql, added possibility to add args to expected queries * test(queries): prepare funcs in org * chore(backend): correct modules * test(queries): org domain prepare funcs * test: correct name * refactor: file name * refactor: add table to login policy columns * chore(prepare_test): only add row to result if columns * test(queries): login policy prepare funcs * chore: add comments for configs * test(queries): prepare idp funcs * fix(queries): add table to password complexity policy cols * test(queries): password complexity policy prepare funcs * fix(queries): add table to password age policy cols * test(queries): password age policy prepare func * fix(queries): set cols on lockout policy * test(queries): lockout policy prepare funs * fix(queries): set table on privacy policy cols * test(queries): privacy policy prepare funcs * fix(queries): set table on org iam policy cols * fix(queries): correct table in org iam policy cols * test(queries): org iam policy prepare funcs * test(queries): prepare project grant funcs * refactor(queries): prepareProjectRoleQuery as func * test(queries): prepare project role funcs * test(queries): project grant check for nulls in joins * fix(queries): allow null values in project grant * refactor(queries): make toQuery private * test(queries): action prepare funcs * refactor: rename prepareFlowQuery to prepareFlowsQuery * test: generic count only if count in cols * refactor: remove param in prepareFlowQuery * fix(queries): correct left joins in action flows * test(queries): action flow prepare funcs --- internal/query/action.go | 2 +- internal/query/action_flow.go | 126 ++++--- internal/query/action_flow_test.go | 515 +++++++++++++++++++++++++++++ internal/query/action_test.go | 350 ++++++++++++++++++++ internal/query/idp.go | 2 +- internal/query/org.go | 2 +- internal/query/org_domain.go | 2 +- internal/query/prepare_test.go | 4 +- internal/query/project.go | 2 +- internal/query/project_grant.go | 2 +- internal/query/project_role.go | 2 +- internal/query/search_query.go | 10 +- 12 files changed, 955 insertions(+), 64 deletions(-) create mode 100644 internal/query/action_flow_test.go create mode 100644 internal/query/action_test.go diff --git a/internal/query/action.go b/internal/query/action.go index 7db8bcf098..099d519590 100644 --- a/internal/query/action.go +++ b/internal/query/action.go @@ -86,7 +86,7 @@ type ActionSearchQueries struct { func (q *ActionSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/action_flow.go b/internal/query/action_flow.go index 7409a1c185..273aafde4c 100644 --- a/internal/query/action_flow.go +++ b/internal/query/action_flow.go @@ -6,6 +6,7 @@ import ( "time" sq "github.com/Masterminds/squirrel" + "github.com/lib/pq" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" @@ -39,18 +40,17 @@ var ( ) type Flow struct { - ID string - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - Sequence uint64 + CreationDate time.Time //TODO: add in projection + ChangeDate time.Time //TODO: add in projection + ResourceOwner string //TODO: add in projection + Sequence uint64 //TODO: add in projection Type domain.FlowType TriggerActions map[domain.TriggerType][]*Action } func (q *Queries) GetFlow(ctx context.Context, flowType domain.FlowType, orgID string) (*Flow, error) { - query, scan := q.prepareFlowQuery(flowType) + query, scan := prepareFlowQuery() stmt, args, err := query.Where( sq.Eq{ FlowsTriggersColumnFlowType.identifier(): flowType, @@ -68,7 +68,7 @@ func (q *Queries) GetFlow(ctx context.Context, flowType domain.FlowType, orgID s } func (q *Queries) GetActionsByFlowAndTriggerType(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType, orgID string) ([]*Action, error) { - stmt, scan := q.prepareTriggerActionsQuery() + stmt, scan := prepareTriggerActionsQuery() query, args, err := stmt.Where( sq.Eq{ FlowsTriggersColumnFlowType.identifier(): flowType, @@ -88,42 +88,48 @@ func (q *Queries) GetActionsByFlowAndTriggerType(ctx context.Context, flowType d } func (q *Queries) GetFlowTypesOfActionID(ctx context.Context, actionID string) ([]domain.FlowType, error) { - stmt, args, err := sq.StatementBuilder. - Select(FlowsTriggersColumnFlowType.identifier()). - From(flowsTriggersTable.identifier()). - Where(sq.Eq{ + stmt, scan := prepareFlowTypesQuery() + query, args, err := stmt.Where( + sq.Eq{ FlowsTriggersColumnActionID.identifier(): actionID, - }). - PlaceholderFormat(sq.Dollar). - ToSql() + }, + ).ToSql() if err != nil { return nil, errors.ThrowInvalidArgument(err, "QUERY-Dh311", "Errors.Query.InvalidRequest") } - rows, err := q.client.QueryContext(ctx, stmt, args...) + rows, err := q.client.QueryContext(ctx, query, args...) if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Bhj4w", "Errors.Internal") } - flowTypes := make([]domain.FlowType, 0) - for rows.Next() { - var flowType domain.FlowType - err := rows.Scan( - &flowType, - ) - if err != nil { - return nil, err - } - flowTypes = append(flowTypes, flowType) - } - if err := rows.Close(); err != nil { - return nil, errors.ThrowInternal(err, "QUERY-Fbgnh", "Errors.Query.CloseRows") - } - - return flowTypes, nil + return scan(rows) } -func (q *Queries) prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action, error)) { +func prepareFlowTypesQuery() (sq.SelectBuilder, func(*sql.Rows) ([]domain.FlowType, error)) { + return sq.Select( + FlowsTriggersColumnFlowType.identifier(), + ). + From(flowsTriggersTable.identifier()). + PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) ([]domain.FlowType, error) { + types := []domain.FlowType{} + for rows.Next() { + var flowType domain.FlowType + err := rows.Scan( + &flowType, + ) + if err != nil { + return nil, err + } + types = append(types, flowType) + } + return types, nil + } + +} + +func prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action, error)) { return sq.Select( ActionColumnID.identifier(), ActionColumnCreationDate.identifier(), @@ -133,8 +139,6 @@ func (q *Queries) prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows ActionColumnSequence.identifier(), ActionColumnName.identifier(), ActionColumnScript.identifier(), - FlowsTriggersColumnTriggerType.identifier(), - FlowsTriggersColumnTriggerSequence.identifier(), ). From(flowsTriggersTable.name). LeftJoin(join(ActionColumnID, FlowsTriggersColumnActionID)). @@ -143,8 +147,6 @@ func (q *Queries) prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows actions := make([]*Action, 0) for rows.Next() { action := new(Action) - var triggerType domain.TriggerType - var triggerSequence int err := rows.Scan( &action.ID, &action.CreationDate, @@ -154,8 +156,6 @@ func (q *Queries) prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows &action.Sequence, &action.Name, &action.Script, - &triggerType, - &triggerSequence, ) if err != nil { return nil, err @@ -171,7 +171,7 @@ func (q *Queries) prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows } } -func (q *Queries) prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Rows) (*Flow, error)) { +func prepareFlowQuery() (sq.SelectBuilder, func(*sql.Rows) (*Flow, error)) { return sq.Select( ActionColumnID.identifier(), ActionColumnCreationDate.identifier(), @@ -183,35 +183,59 @@ func (q *Queries) prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, ActionColumnScript.identifier(), FlowsTriggersColumnTriggerType.identifier(), FlowsTriggersColumnTriggerSequence.identifier(), + FlowsTriggersColumnFlowType.identifier(), ). From(flowsTriggersTable.name). LeftJoin(join(ActionColumnID, FlowsTriggersColumnActionID)). PlaceholderFormat(sq.Dollar), func(rows *sql.Rows) (*Flow, error) { flow := &Flow{ - Type: flowType, TriggerActions: make(map[domain.TriggerType][]*Action), } for rows.Next() { - action := new(Action) - var triggerType domain.TriggerType - var triggerSequence int + // action := new(Action) + var ( + actionID sql.NullString + actionCreationDate pq.NullTime + actionChangeDate pq.NullTime + actionResourceOwner sql.NullString + actionSequence sql.NullInt64 + actionName sql.NullString + actionScript sql.NullString + + triggerType domain.TriggerType + triggerSequence int + flowType domain.FlowType + ) err := rows.Scan( - &action.ID, - &action.CreationDate, - &action.ChangeDate, - &action.ResourceOwner, + &actionID, + &actionCreationDate, + &actionChangeDate, + &actionResourceOwner, //&action.State, //TODO: state in next release - &action.Sequence, - &action.Name, - &action.Script, + &actionSequence, + &actionName, + &actionScript, &triggerType, &triggerSequence, + &flowType, ) if err != nil { return nil, err } - flow.TriggerActions[triggerType] = append(flow.TriggerActions[triggerType], action) + flow.Type = flowType + if !actionID.Valid { + continue + } + flow.TriggerActions[triggerType] = append(flow.TriggerActions[triggerType], &Action{ + ID: actionID.String, + CreationDate: actionCreationDate.Time, + ChangeDate: actionChangeDate.Time, + ResourceOwner: actionResourceOwner.String, + Sequence: uint64(actionSequence.Int64), + Name: actionName.String, + Script: actionScript.String, + }) } if err := rows.Close(); err != nil { diff --git a/internal/query/action_flow_test.go b/internal/query/action_flow_test.go new file mode 100644 index 0000000000..9b9963479a --- /dev/null +++ b/internal/query/action_flow_test.go @@ -0,0 +1,515 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/caos/zitadel/internal/domain" +) + +func Test_FlowPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareFlowQuery no result", + prepare: prepareFlowQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.flows_triggers.trigger_type,`+ + ` zitadel.projections.flows_triggers.trigger_sequence,`+ + ` zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + nil, + nil, + ), + }, + object: &Flow{TriggerActions: map[domain.TriggerType][]*Action{}}, + }, + { + name: "prepareFlowQuery one action", + prepare: prepareFlowQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.flows_triggers.trigger_type,`+ + ` zitadel.projections.flows_triggers.trigger_sequence,`+ + ` zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "name", + "script", + "trigger_type", + "trigger_sequence", + "flow_type", + }, + [][]driver.Value{ + { + "action-id", + testNow, + testNow, + "ro", + uint64(20211115), + "action-name", + "script", + domain.TriggerTypePreCreation, + uint64(20211109), + domain.FlowTypeExternalAuthentication, + }, + }, + ), + }, + object: &Flow{ + Type: domain.FlowTypeExternalAuthentication, + TriggerActions: map[domain.TriggerType][]*Action{ + domain.TriggerTypePreCreation: { + { + ID: "action-id", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211115, + Name: "action-name", + Script: "script", + }, + }, + }, + }, + }, + { + name: "prepareFlowQuery multiple actions", + prepare: prepareFlowQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.flows_triggers.trigger_type,`+ + ` zitadel.projections.flows_triggers.trigger_sequence,`+ + ` zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "name", + "script", + "trigger_type", + "trigger_sequence", + "flow_type", + }, + [][]driver.Value{ + { + "action-id-pre", + testNow, + testNow, + "ro", + uint64(20211115), + "action-name-pre", + "script", + domain.TriggerTypePreCreation, + uint64(20211109), + domain.FlowTypeExternalAuthentication, + }, + { + "action-id-post", + testNow, + testNow, + "ro", + uint64(20211115), + "action-name-post", + "script", + domain.TriggerTypePostCreation, + uint64(20211109), + domain.FlowTypeExternalAuthentication, + }, + }, + ), + }, + object: &Flow{ + Type: domain.FlowTypeExternalAuthentication, + TriggerActions: map[domain.TriggerType][]*Action{ + domain.TriggerTypePreCreation: { + { + ID: "action-id-pre", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211115, + Name: "action-name-pre", + Script: "script", + }, + }, + domain.TriggerTypePostCreation: { + { + ID: "action-id-post", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211115, + Name: "action-name-post", + Script: "script", + }, + }, + }, + }, + }, + { + name: "prepareFlowQuery no action", + prepare: prepareFlowQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.flows_triggers.trigger_type,`+ + ` zitadel.projections.flows_triggers.trigger_sequence,`+ + ` zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "name", + "script", + "trigger_type", + "trigger_sequence", + "flow_type", + }, + [][]driver.Value{ + { + nil, + nil, + nil, + nil, + nil, + nil, + nil, + domain.TriggerTypePostCreation, + uint64(20211109), + domain.FlowTypeExternalAuthentication, + }, + }, + ), + }, + object: &Flow{ + Type: domain.FlowTypeExternalAuthentication, + TriggerActions: map[domain.TriggerType][]*Action{}, + }, + }, + { + name: "prepareFlowQuery sql err", + prepare: prepareFlowQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.flows_triggers.trigger_type,`+ + ` zitadel.projections.flows_triggers.trigger_sequence,`+ + ` zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + { + name: "prepareTriggerActionsQuery no result", + prepare: prepareTriggerActionsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + nil, + nil, + ), + }, + object: []*Action{}, + }, + { + name: "prepareTriggerActionsQuery one result", + prepare: prepareTriggerActionsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "name", + "script", + }, + [][]driver.Value{ + { + "action-id", + testNow, + testNow, + "ro", + uint64(20211115), + "action-name", + "script", + }, + }, + ), + }, + object: []*Action{ + { + ID: "action-id", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211115, + Name: "action-name", + Script: "script", + }, + }, + }, + { + name: "prepareTriggerActionsQuery multiple results", + prepare: prepareTriggerActionsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "name", + "script", + }, + [][]driver.Value{ + { + "action-id-1", + testNow, + testNow, + "ro", + uint64(20211115), + "action-name-1", + "script", + }, + { + "action-id-2", + testNow, + testNow, + "ro", + uint64(20211115), + "action-name-2", + "script", + }, + }, + ), + }, + object: []*Action{ + { + ID: "action-id-1", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211115, + Name: "action-name-1", + Script: "script", + }, + { + ID: "action-id-2", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211115, + Name: "action-name-2", + Script: "script", + }, + }, + }, + { + name: "prepareTriggerActionsQuery sql err", + prepare: prepareTriggerActionsQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script`+ + ` FROM zitadel.projections.flows_triggers`+ + ` LEFT JOIN zitadel.projections.actions ON zitadel.projections.flows_triggers.action_id = zitadel.projections.actions.id`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + { + name: "prepareFlowTypesQuery no result", + prepare: prepareFlowTypesQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`), + nil, + nil, + ), + }, + object: []domain.FlowType{}, + }, + { + name: "prepareFlowTypesQuery one result", + prepare: prepareFlowTypesQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`), + []string{ + "flow_type", + }, + [][]driver.Value{ + { + domain.FlowTypeExternalAuthentication, + }, + }, + ), + }, + object: []domain.FlowType{ + domain.FlowTypeExternalAuthentication, + }, + }, + { + name: "prepareFlowTypesQuery multiple results", + prepare: prepareFlowTypesQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`), + []string{ + "flow_type", + }, + [][]driver.Value{ + { + domain.FlowTypeExternalAuthentication, + }, + { + domain.FlowTypeUnspecified, + }, + }, + ), + }, + object: []domain.FlowType{ + domain.FlowTypeExternalAuthentication, + domain.FlowTypeUnspecified, + }, + }, + { + name: "prepareFlowTypesQuery sql err", + prepare: prepareFlowTypesQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.flows_triggers.flow_type`+ + ` FROM zitadel.projections.flows_triggers`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/action_test.go b/internal/query/action_test.go new file mode 100644 index 0000000000..dfda629a97 --- /dev/null +++ b/internal/query/action_test.go @@ -0,0 +1,350 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + "time" + + "github.com/caos/zitadel/internal/domain" + errs "github.com/caos/zitadel/internal/errors" +) + +func Test_ActionPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareActionsQuery no result", + prepare: prepareActionsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.actions`), + nil, + nil, + ), + }, + object: &Actions{Actions: []*Action{}}, + }, + { + name: "prepareActionsQuery one result", + prepare: prepareActionsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.actions`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "action_state", + "name", + "script", + "timeout", + "allowed_to_fail", + "count", + }, + [][]driver.Value{ + { + "id", + testNow, + testNow, + "ro", + uint64(20211109), + domain.ActionStateActive, + "action-name", + "script", + 1 * time.Second, + true, + }, + }, + ), + }, + object: &Actions{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Actions: []*Action{ + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + State: domain.ActionStateActive, + Sequence: 20211109, + Name: "action-name", + Script: "script", + Timeout: 1 * time.Second, + AllowedToFail: true, + }, + }, + }, + }, + { + name: "prepareActionsQuery multiple result", + prepare: prepareActionsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.actions`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "action_state", + "name", + "script", + "timeout", + "allowed_to_fail", + "count", + }, + [][]driver.Value{ + { + "id-1", + testNow, + testNow, + "ro", + uint64(20211109), + domain.ActionStateActive, + "action-name-1", + "script", + 1 * time.Second, + true, + }, + { + "id-2", + testNow, + testNow, + "ro", + uint64(20211109), + domain.ActionStateActive, + "action-name-2", + "script", + 1 * time.Second, + true, + }, + }, + ), + }, + object: &Actions{ + SearchResponse: SearchResponse{ + Count: 2, + }, + Actions: []*Action{ + { + ID: "id-1", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + State: domain.ActionStateActive, + Sequence: 20211109, + Name: "action-name-1", + Script: "script", + Timeout: 1 * time.Second, + AllowedToFail: true, + }, + { + ID: "id-2", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + State: domain.ActionStateActive, + Sequence: 20211109, + Name: "action-name-2", + Script: "script", + Timeout: 1 * time.Second, + AllowedToFail: true, + }, + }, + }, + }, + { + name: "prepareActionsQuery sql err", + prepare: prepareActionsQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.actions`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + { + name: "prepareActionQuery no result", + prepare: prepareActionQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail`+ + ` FROM zitadel.projections.actions`), + nil, + nil, + ), + err: func(err error) (error, bool) { + if !errs.IsNotFound(err) { + return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false + } + return nil, true + }, + }, + object: (*Action)(nil), + }, + { + name: "prepareActionQuery found", + prepare: prepareActionQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail`+ + ` FROM zitadel.projections.actions`), + []string{ + "id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "action_state", + "name", + "script", + "timeout", + "allowed_to_fail", + }, + []driver.Value{ + "id", + testNow, + testNow, + "ro", + uint64(20211109), + domain.ActionStateActive, + "action-name", + "script", + 1 * time.Second, + true, + }, + ), + }, + object: &Action{ + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + State: domain.ActionStateActive, + Sequence: 20211109, + Name: "action-name", + Script: "script", + Timeout: 1 * time.Second, + AllowedToFail: true, + }, + }, + { + name: "prepareActionQuery sql err", + prepare: prepareActionQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.actions.id,`+ + ` zitadel.projections.actions.creation_date,`+ + ` zitadel.projections.actions.change_date,`+ + ` zitadel.projections.actions.resource_owner,`+ + ` zitadel.projections.actions.sequence,`+ + ` zitadel.projections.actions.action_state,`+ + ` zitadel.projections.actions.name,`+ + ` zitadel.projections.actions.script,`+ + ` zitadel.projections.actions.timeout,`+ + ` zitadel.projections.actions.allowed_to_fail`+ + ` FROM zitadel.projections.actions`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/idp.go b/internal/query/idp.go index 2d6a6f3ec2..83a16cb492 100644 --- a/internal/query/idp.go +++ b/internal/query/idp.go @@ -247,7 +247,7 @@ func NewIDPNameSearchQuery(method TextComparison, value string) (SearchQuery, er func (q *IDPSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/org.go b/internal/query/org.go index d39b95023a..4e9716228b 100644 --- a/internal/query/org.go +++ b/internal/query/org.go @@ -75,7 +75,7 @@ type OrgSearchQueries struct { func (q *OrgSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/org_domain.go b/internal/query/org_domain.go index 6176bebd22..b02ef6874e 100644 --- a/internal/query/org_domain.go +++ b/internal/query/org_domain.go @@ -35,7 +35,7 @@ type OrgDomainSearchQueries struct { func (q *OrgDomainSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/prepare_test.go b/internal/query/prepare_test.go index 5665409220..7b820089e2 100644 --- a/internal/query/prepare_test.go +++ b/internal/query/prepare_test.go @@ -93,7 +93,9 @@ func mockQueries(stmt string, cols []string, rows [][]driver.Value, args ...driv result := sqlmock.NewRows(cols) count := uint64(len(rows)) for _, row := range rows { - row = append(row, count) + if cols[len(cols)-1] == "count" { + row = append(row, count) + } result.AddRow(row...) } q.WillReturnRows(result) diff --git a/internal/query/project.go b/internal/query/project.go index 449707b249..b13448da32 100644 --- a/internal/query/project.go +++ b/internal/query/project.go @@ -170,7 +170,7 @@ func (r *ProjectSearchQueries) AppendPermissionQueries(permissions []string) err func (q *ProjectSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/project_grant.go b/internal/query/project_grant.go index 7dfeb4d964..32225ef035 100644 --- a/internal/query/project_grant.go +++ b/internal/query/project_grant.go @@ -227,7 +227,7 @@ func (q *ProjectGrantSearchQueries) AppendPermissionQueries(permissions []string func (q *ProjectGrantSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/project_role.go b/internal/query/project_role.go index 7b972b7f61..e2ded091a8 100644 --- a/internal/query/project_role.go +++ b/internal/query/project_role.go @@ -199,7 +199,7 @@ func (r *ProjectRoleSearchQueries) AppendRoleKeysQuery(keys []string) error { func (q *ProjectRoleSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { - query = q.ToQuery(query) + query = q.toQuery(query) } return query } diff --git a/internal/query/search_query.go b/internal/query/search_query.go index 509764a5d1..bae4cb4b70 100644 --- a/internal/query/search_query.go +++ b/internal/query/search_query.go @@ -43,7 +43,7 @@ func (req *SearchRequest) toQuery(query sq.SelectBuilder) sq.SelectBuilder { const sqlPlaceholder = "?" type SearchQuery interface { - ToQuery(sq.SelectBuilder) sq.SelectBuilder + toQuery(sq.SelectBuilder) sq.SelectBuilder } type TextQuery struct { @@ -72,7 +72,7 @@ func NewTextQuery(col Column, value string, compare TextComparison) (*TextQuery, }, nil } -func (q *TextQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { +func (q *TextQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { where, args := q.comp() return query.Where(where, args...) } @@ -169,7 +169,7 @@ func NewNumberQuery(c Column, value interface{}, compare NumberComparison) (*Num }, nil } -func (q *NumberQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { +func (q *NumberQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { where, args := q.comp() return query.Where(where, args...) } @@ -240,7 +240,7 @@ func NewListQuery(column Column, value []interface{}, compare ListComparison) (* }, nil } -func (q *ListQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { +func (q *ListQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { where, args := q.comp() return query.Where(where, args...) } @@ -282,7 +282,7 @@ func NewBoolQuery(c Column, value bool) (*BoolQuery, error) { }, nil } -func (q *BoolQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { +func (q *BoolQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { where, args := q.comp() return query.Where(where, args...) }