package query import ( "database/sql" "database/sql/driver" "errors" "fmt" "regexp" "testing" "github.com/lib/pq" ) var ( membershipsStmt = regexp.QuoteMeta( "SELECT memberships.user_id" + ", memberships.roles" + ", memberships.creation_date" + ", memberships.change_date" + ", memberships.sequence" + ", memberships.resource_owner" + ", memberships.org_id" + ", memberships.iam_id" + ", memberships.project_id" + ", memberships.grant_id" + ", zitadel.projections.projects.name" + ", zitadel.projections.orgs.name" + ", COUNT(*) OVER ()" + " FROM (" + "SELECT members.user_id" + ", members.roles" + ", members.creation_date" + ", members.change_date" + ", members.sequence" + ", members.resource_owner" + ", members.org_id" + ", NULL::STRING AS iam_id" + ", NULL::STRING AS project_id" + ", NULL::STRING AS grant_id" + " FROM zitadel.projections.org_members as members" + " UNION ALL " + "SELECT members.user_id" + ", members.roles" + ", members.creation_date" + ", members.change_date" + ", members.sequence" + ", members.resource_owner" + ", NULL::STRING AS org_id" + ", members.iam_id" + ", NULL::STRING AS project_id" + ", NULL::STRING AS grant_id" + " FROM zitadel.projections.iam_members as members" + " UNION ALL " + "SELECT members.user_id" + ", members.roles" + ", members.creation_date" + ", members.change_date" + ", members.sequence" + ", members.resource_owner" + ", NULL::STRING AS org_id" + ", NULL::STRING AS iam_id" + ", members.project_id" + ", NULL::STRING AS grant_id" + " FROM zitadel.projections.project_members as members" + " UNION ALL " + "SELECT members.user_id" + ", members.roles" + ", members.creation_date" + ", members.change_date" + ", members.sequence" + ", members.resource_owner" + ", NULL::STRING AS org_id" + ", NULL::STRING AS iam_id" + ", members.project_id" + ", members.grant_id" + " FROM zitadel.projections.project_grant_members as members" + ") AS memberships" + " LEFT JOIN zitadel.projections.projects ON memberships.project_id = zitadel.projections.projects.id" + " LEFT JOIN zitadel.projections.orgs ON memberships.org_id = zitadel.projections.orgs.id") membershipCols = []string{ "user_id", "roles", "creation_date", "change_date", "sequence", "resource_owner", "org_id", "iam_id", "project_id", "grant_id", "name", //project name "name", //org name "count", } ) func Test_MembershipPrepares(t *testing.T) { type want struct { sqlExpectations sqlExpectation err checkErr } tests := []struct { name string prepare interface{} want want object interface{} }{ { name: "prepareMembershipsQuery no result", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( membershipsStmt, nil, nil, ), }, object: &Memberships{Memberships: []*Membership{}}, }, { name: "prepareMembershipsQuery one org member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( membershipsStmt, membershipCols, [][]driver.Value{ { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", "org-id", nil, nil, nil, nil, "org-name", }, }, ), }, object: &Memberships{ SearchResponse: SearchResponse{ Count: 1, }, Memberships: []*Membership{ { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", Org: &OrgMembership{OrgID: "org-id", Name: "org-name"}, }, }, }, }, { name: "prepareMembershipsQuery one iam member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( membershipsStmt, membershipCols, [][]driver.Value{ { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", nil, "iam-id", nil, nil, nil, nil, }, }, ), }, object: &Memberships{ SearchResponse: SearchResponse{ Count: 1, }, Memberships: []*Membership{ { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", IAM: &IAMMembership{IAMID: "iam-id", Name: "iam-id"}, }, }, }, }, { name: "prepareMembershipsQuery one project member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( membershipsStmt, membershipCols, [][]driver.Value{ { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", nil, nil, "project-id", nil, "project-name", nil, }, }, ), }, object: &Memberships{ SearchResponse: SearchResponse{ Count: 1, }, Memberships: []*Membership{ { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", Project: &ProjectMembership{ProjectID: "project-id", Name: "project-name"}, }, }, }, }, { name: "prepareMembershipsQuery one project grant member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( membershipsStmt, membershipCols, [][]driver.Value{ { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", nil, nil, "project-id", "grant-id", "project-name", nil, }, }, ), }, object: &Memberships{ SearchResponse: SearchResponse{ Count: 1, }, Memberships: []*Membership{ { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", ProjectGrant: &ProjectGrantMembership{ GrantID: "grant-id", ProjectID: "project-id", ProjectName: "project-name", }, }, }, }, }, { name: "prepareMembershipsQuery one for each member type", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( membershipsStmt, membershipCols, [][]driver.Value{ { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", "org-id", nil, nil, nil, nil, "org-name", }, { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", nil, "iam-id", nil, nil, nil, nil, }, { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", nil, nil, "project-id", nil, "project-name", nil, }, { "user-id", pq.StringArray{"role1", "role2"}, testNow, testNow, uint64(20211202), "ro", nil, nil, "project-id", "grant-id", "project-name", nil, }, }, ), }, object: &Memberships{ SearchResponse: SearchResponse{ Count: 4, }, Memberships: []*Membership{ { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", Org: &OrgMembership{OrgID: "org-id", Name: "org-name"}, }, { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", IAM: &IAMMembership{IAMID: "iam-id", Name: "iam-id"}, }, { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", Project: &ProjectMembership{ProjectID: "project-id", Name: "project-name"}, }, { UserID: "user-id", Roles: []string{"role1", "role2"}, CreationDate: testNow, ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", ProjectGrant: &ProjectGrantMembership{ ProjectID: "project-id", GrantID: "grant-id", ProjectName: "project-name", }, }, }, }, }, { name: "prepareMembershipsQuery sql err", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueryErr( membershipsStmt, 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) }) } }