From 98bb6d554c4b828ca53252896e27921b2b8d018b Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Fri, 10 Jun 2022 14:59:18 +0530 Subject: [PATCH 01/17] Add testing for idp groups --- pkg/service/user.go | 2 + pkg/service/user_test.go | 90 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/pkg/service/user.go b/pkg/service/user.go index f7e5bef..1aeb754 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -445,6 +445,7 @@ func (s *userService) Create(ctx context.Context, user *userv3.User) (*userv3.Us if err != nil { tx.Rollback() _log.Warn("unable to commit changes", err) + return &userv3.User{}, err } rl, err := s.ap.GetRecoveryLink(ctx, id) @@ -727,6 +728,7 @@ func (s *userService) Update(ctx context.Context, user *userv3.User) (*userv3.Us if err != nil { tx.Rollback() _log.Warn("unable to commit changes", err) + return &userv3.User{}, fmt.Errorf("unable to update user '%v'", name) } CreateUserAuditEvent(ctx, s.al, s.db, AuditActionUpdate, user.GetMetadata().GetName(), usr.ID, rolesBefore, rolesAfter, groupsBefore, groupsAfter) diff --git a/pkg/service/user_test.go b/pkg/service/user_test.go index 21edbc7..1c84e78 100644 --- a/pkg/service/user_test.go +++ b/pkg/service/user_test.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "strings" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -12,6 +13,7 @@ import ( commonv3 "github.com/paralus/paralus/proto/types/commonpb/v3" v3 "github.com/paralus/paralus/proto/types/commonpb/v3" userv3 "github.com/paralus/paralus/proto/types/userpb/v3" + userrpcv3 "github.com/paralus/paralus/proto/rpc/user" ) func performUserBasicChecks(t *testing.T, user *userv3.User, uuuid string) { @@ -127,7 +129,7 @@ func TestCreateUserWithRole(t *testing.T) { role.Project = &pruuid } if tc.namespace { - var ns string = "ns" + var ns = "ns" role.Namespace = &ns } mock.ExpectQuery(fmt.Sprintf(`INSERT INTO "%v"`, tc.dbname)). @@ -239,6 +241,92 @@ func TestUpdateUserWithGroup(t *testing.T) { performBasicAuthProviderChecks(t, *ap, 0, 1, 0, 0) } +func TestUpdateUserWithIdpGroupPassed(t *testing.T) { + // Having idp groups passed down should not affect, it should come from db + db, mock := getDB(t) + defer db.Close() + + ap := &mockAuthProvider{} + mazc := mockAuthzClient{} + us := NewUserService(ap, db, &mazc, nil, common.CliConfigDownloadData{}, getLogger(), true) + + // performing update + uuuid := addUserFullFetchExpectation(mock) + puuid, ouuid := addParterOrgFetchExpectation(mock) + mock.ExpectBegin() + _ = addUserRoleMappingsUpdateExpectation(mock, uuuid) + addUserGroupMappingsUpdateExpectation(mock, uuuid) + ruuid := addResourceRoleFetchExpectation(mock, "project") + pruuid := addFetchExpectation(mock, "project") + mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountresourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) + // addFetchExpectation(mock, "group") + // mock.ExpectQuery(`INSERT INTO "authsrv_groupaccount"`). + // WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) + mock.ExpectCommit() + + var ns = "ns" + user := &userv3.User{ + Metadata: &v3.Metadata{Partner: "partner-" + puuid, Organization: "org-" + ouuid, Name: "user-" + uuuid}, + Spec: &userv3.UserSpec{ + IdpGroups: []string{"group"}, + ProjectNamespaceRoles: []*userv3.ProjectNamespaceRole{{Project: idnamea(pruuid, "project"), Namespace: &ns, Role: idname(ruuid, "role")}}, + }, + } + user, err := us.Update(context.Background(), user) + if err != nil { + t.Fatal("could not create user:", err) + } + performUserBasicChecks(t, user, uuuid) + if user.GetMetadata().GetName() != "user-"+uuuid { + t.Errorf("expected name 'user-%v'; got '%v'", uuuid, user.GetMetadata().GetName()) + } + performBasicAuthProviderChecks(t, *ap, 0, 1, 0, 0) +} + +func TestUpdateUserWithIdpGroupFetched(t *testing.T) { + // Having idp groups passed down should not affect, it should come from db + db, mock := getDB(t) + defer db.Close() + + ap := &mockAuthProvider{} + mazc := mockAuthzClient{} + us := NewUserService(ap, db, &mazc, nil, common.CliConfigDownloadData{}, getLogger(), true) + + // performing update + uuuid := addUserFullFetchExpectationWithIdpGroups(mock) + puuid, ouuid := addParterOrgFetchExpectation(mock) + mock.ExpectBegin() + _ = addUserRoleMappingsUpdateExpectation(mock, uuuid) + addUserGroupMappingsUpdateExpectation(mock, uuuid) + ruuid := addResourceRoleFetchExpectation(mock, "project") + pruuid := addFetchExpectation(mock, "project") + mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountresourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) + addFetchExpectation(mock, "group") + mock.ExpectQuery(`INSERT INTO "authsrv_groupaccount"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) + mock.ExpectCommit() + + var ns = "ns" + user := &userv3.User{ + Metadata: &v3.Metadata{Partner: "partner-" + puuid, Organization: "org-" + ouuid, Name: "user-" + uuuid}, + Spec: &userv3.UserSpec{ + IdpGroups: []string{"group"}, + ProjectNamespaceRoles: []*userv3.ProjectNamespaceRole{{Project: idnamea(pruuid, "project"), Namespace: &ns, Role: idname(ruuid, "role")}}, + }, + } + user, err := us.Update(context.Background(), user) + if err != nil { + t.Fatal("could not create user:", err) + } + performUserBasicChecks(t, user, uuuid) + if user.GetMetadata().GetName() != "user-"+uuuid { + t.Errorf("expected name 'user-%v'; got '%v'", uuuid, user.GetMetadata().GetName()) + } + performBasicAuthProviderChecks(t, *ap, 0, 1, 0, 0) +} + func TestUpdateUserInvalid(t *testing.T) { db, mock := getDB(t) defer db.Close() From d6768a3f126074c873ec3d907cecc62b3b8ec5a4 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Fri, 10 Jun 2022 13:06:13 +0530 Subject: [PATCH 02/17] Add tests for projects in user listing --- pkg/service/user_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/service/user_test.go b/pkg/service/user_test.go index 1c84e78..8354230 100644 --- a/pkg/service/user_test.go +++ b/pkg/service/user_test.go @@ -556,6 +556,9 @@ func TestUserList(t *testing.T) { {"simple list", "", 50, 20, "", "", "", "", []string{}, ""}, {"simple list with type", "", 50, 20, "", "", "", "", []string{}, "password"}, {"sorted list", "", 50, 20, "email", "asc", "", "", []string{}, ""}, + {"sorted list with ALL projects", "", 50, 20, "email", "asc", "", "", []string{"ALL"}, ""}, + {"sorted list with single project", "", 50, 20, "email", "asc", "", "", []string{"project1"}, ""}, + {"sorted list with projects", "", 50, 20, "email", "asc", "", "", []string{"project1", "project2"}, ""}, {"sorted list without dir", "", 50, 20, "email", "", "", "", []string{}, ""}, {"sorted list with q", "filter-query", 50, 20, "email", "asc", "", "", []string{}, ""}, {"sorted list with role", "", 50, 20, "email", "asc", "role-name", "", []string{}, ""}, @@ -593,6 +596,12 @@ func TestUserList(t *testing.T) { if tc.group != "" { addFetchExpectation(mock, "group") } + for _, p := range tc.projects { + if p == "ALL" { + continue + } + addFetchIdByNameExpectation(mock, "project", p) + } if tc.role != "" || tc.group != "" || len(tc.projects) != 0 { addSentryLookupExpectation(mock, []string{uuuid1, uuuid2}, puuid, ouuid) mock.ExpectQuery(`SELECT "identities"."id", .*WHERE .identities.id IN .'` + uuuid1 + `', '` + uuuid2 + `'.. ` + q + order + `LIMIT ` + fmt.Sprint(tc.limit) + ` OFFSET ` + fmt.Sprint(tc.offset)). @@ -632,6 +641,7 @@ func TestUserList(t *testing.T) { Role: tc.role, Group: tc.group, Type: tc.utype, + Project: strings.Join(tc.projects, ","), } userlist, err := us.List(context.Background(), query.WithOptions(qo)) From 761d157bcf6729e1adcd2918e70e657b14198d05 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Fri, 10 Jun 2022 13:06:23 +0530 Subject: [PATCH 03/17] Add tests for forgot password --- pkg/service/test_utils.go | 14 ++++++++++++++ pkg/service/user_test.go | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/pkg/service/test_utils.go b/pkg/service/test_utils.go index 9e124de..47f5e6a 100644 --- a/pkg/service/test_utils.go +++ b/pkg/service/test_utils.go @@ -72,6 +72,13 @@ func addFetchIdExpectation(mock sqlmock.Sqlmock, resource string) string { return uid } +func addFetchIdByNameExpectation(mock sqlmock.Sqlmock, resource, name string) string { + uid := uuid.New().String() + mock.ExpectQuery(`SELECT "` + resource + `"."id" FROM "authsrv_` + resource + `" AS "` + resource + `" WHERE .name = '` + name + `'.`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uid)) + return uid +} + func addFetchExpectation(mock sqlmock.Sqlmock, resource string) string { uid := uuid.New().String() mock.ExpectQuery(`SELECT "` + resource + `"."id".* FROM "authsrv_` + resource + `" AS "` + resource + `"`). @@ -152,6 +159,13 @@ func addUserFullFetchExpectation(mock sqlmock.Sqlmock) string { return uid } +func addUserFullFetchExpectationWithIdpGroups(mock sqlmock.Sqlmock) string { + uid := uuid.New().String() + mock.ExpectQuery(`SELECT "identities"."id", "identities"."schema_id", "identities"."traits", "identities"."created_at", "identities"."updated_at", "identities"."state", "identities"."state_changed_at", "identities"."nid", "identity_credential"."id" AS "identity_credential__id", "identity_credential"."identity_id" AS "identity_credential__identity_id", "identity_credential"."identity_credential_type_id" AS "identity_credential__identity_credential_type_id", "identity_credential__identity_credential_type"."id" AS "identity_credential__identity_credential_type__id", "identity_credential__identity_credential_type"."name" AS "identity_credential__identity_credential_type__name" FROM "identities" LEFT JOIN "identity_credentials" AS "identity_credential" ON ."identity_credential"."identity_id" = "identities"."id". LEFT JOIN "identity_credential_types" AS "identity_credential__identity_credential_type" ON ."identity_credential__identity_credential_type"."id" = "identity_credential"."identity_credential_type_id". WHERE .traits ->> 'email' = 'user-` + uid + `'.`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "traits", "identity_credential__identity_credential_type__name"}).AddRow(uid, []byte(`{"email":"user-`+uid+`", "first_name": "John", "last_name": "Doe", "description": "The OG user.", "idp_group": "BigShot"}`), "password")) + return uid +} + func addUsersGroupFetchExpectation(mock sqlmock.Sqlmock, user string) string { uid := uuid.New().String() mock.ExpectQuery(`SELECT "group"."id".* FROM "authsrv_group" AS "group" JOIN authsrv_groupaccount ON authsrv_groupaccount.group_id="group".id WHERE .authsrv_groupaccount.account_id = '` + user + `'`). diff --git a/pkg/service/user_test.go b/pkg/service/user_test.go index 8354230..ef0eecf 100644 --- a/pkg/service/user_test.go +++ b/pkg/service/user_test.go @@ -731,3 +731,23 @@ func TestUserDeleteSelf(t *testing.T) { t.Fatal("user able to delete their own account") } } + +func TestUserForgotPassword(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ap := &mockAuthProvider{} + mazc := mockAuthzClient{} + us := NewUserService(ap, db, &mazc, nil, common.CliConfigDownloadData{}, getLogger(), true) + + uuuid := addUserFetchExpectation(mock) + + fpreq := &userrpcv3.ForgotPasswordRequest{Username: "user-"+uuuid} + fpresp, err := us.ForgotPassword(context.Background(), fpreq) + if err != nil { + t.Fatal("could not fetch password recovery link:", err) + } + if !strings.HasPrefix(fpresp.RecoveryLink, "https://recoverme.testing/") { + t.Error("invalid recovery url generated") + } +} From dded075b1b3ff11416be2f12ac2e96b77342f08b Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Mon, 13 Jun 2022 14:02:39 +0530 Subject: [PATCH 04/17] Explicitly fail on empty role in user mapping --- pkg/service/user.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/service/user.go b/pkg/service/user.go index 1aeb754..5233222 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -125,6 +125,9 @@ func (s *userService) createUserRoleRelations(ctx context.Context, db bun.IDB, u continue } role := pnr.GetRole() + if role == "" { + return &userv3.User{}, nil, fmt.Errorf("cannot use empty role") + } entity, err := dao.GetByName(ctx, db, role, &models.Role{}) if err != nil { return &userv3.User{}, nil, fmt.Errorf("unable to find role '%v'", role) From ae7db385ea0fee10b75d5ca043a51a7b0970b7d4 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Mon, 13 Jun 2022 14:50:32 +0530 Subject: [PATCH 05/17] Add tests for RetrieveCliConfig --- pkg/service/test_utils.go | 2 +- pkg/service/user_test.go | 91 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/pkg/service/test_utils.go b/pkg/service/test_utils.go index 47f5e6a..ff2743d 100644 --- a/pkg/service/test_utils.go +++ b/pkg/service/test_utils.go @@ -82,7 +82,7 @@ func addFetchIdByNameExpectation(mock sqlmock.Sqlmock, resource, name string) st func addFetchExpectation(mock sqlmock.Sqlmock, resource string) string { uid := uuid.New().String() mock.ExpectQuery(`SELECT "` + resource + `"."id".* FROM "authsrv_` + resource + `" AS "` + resource + `"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uid, "role-name")) + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uid, resource+"-name")) return uid } diff --git a/pkg/service/user_test.go b/pkg/service/user_test.go index ef0eecf..3075dd8 100644 --- a/pkg/service/user_test.go +++ b/pkg/service/user_test.go @@ -10,10 +10,10 @@ import ( "github.com/google/uuid" "github.com/paralus/paralus/pkg/common" "github.com/paralus/paralus/pkg/query" + userrpcv3 "github.com/paralus/paralus/proto/rpc/user" commonv3 "github.com/paralus/paralus/proto/types/commonpb/v3" v3 "github.com/paralus/paralus/proto/types/commonpb/v3" userv3 "github.com/paralus/paralus/proto/types/userpb/v3" - userrpcv3 "github.com/paralus/paralus/proto/rpc/user" ) func performUserBasicChecks(t *testing.T, user *userv3.User, uuuid string) { @@ -742,7 +742,7 @@ func TestUserForgotPassword(t *testing.T) { uuuid := addUserFetchExpectation(mock) - fpreq := &userrpcv3.ForgotPasswordRequest{Username: "user-"+uuuid} + fpreq := &userrpcv3.ForgotPasswordRequest{Username: "user-" + uuuid} fpresp, err := us.ForgotPassword(context.Background(), fpreq) if err != nil { t.Fatal("could not fetch password recovery link:", err) @@ -751,3 +751,90 @@ func TestUserForgotPassword(t *testing.T) { t.Error("invalid recovery url generated") } } + +func TestUserRetrieveCliConfigGet(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ap := &mockAuthProvider{} + mazc := mockAuthzClient{} + ks := NewApiKeyService(db, getLogger()) + us := NewUserService(ap, db, &mazc, ks, common.CliConfigDownloadData{}, getLogger(), true) + + uuuid := uuid.NewString() + auuid := uuid.NewString() + mock.ExpectQuery(`SELECT sap.* FROM "sentry_account_permission" AS "sap" JOIN authsrv_project as proj ON \(proj.id = sap.project_id\) AND \(proj.default = TRUE\) WHERE \(account_id = '` + uuuid + `'\) LIMIT 1`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"account_id"}).AddRow(uuuid)) + _ = addFetchExpectation(mock, "project") + _ = addFetchExpectation(mock, "organization") + _ = addFetchExpectation(mock, "partner") + + mock.ExpectQuery(`SELECT "apikey"."id", "apikey"."name",.* FROM "authsrv_apikey" AS "apikey" WHERE \(name = 'user-` + uuuid + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"key", "secret"}).AddRow(auuid, "apikey-"+auuid)) + + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + resp, err := us.RetrieveCliConfig(context.Background(), req) + if err != nil { + t.Fatal("could not fetch cli config:", err) + } + if resp.ApiKey != auuid { + t.Error("incorrect apikey generated") + } + if resp.ApiSecret != "apikey-"+auuid { + t.Error("incorrect apisecret generated") + } + if resp.Project != "project-name" { + t.Error("invalid project name") + } + if resp.Organization != "organization-name" { + t.Error("invalid organization name") + } + if resp.Partner != "partner-name" { + t.Error("invalid partner name") + } +} + +func TestUserRetrieveCliConfigCreate(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ap := &mockAuthProvider{} + mazc := mockAuthzClient{} + ks := NewApiKeyService(db, getLogger()) + us := NewUserService(ap, db, &mazc, ks, common.CliConfigDownloadData{}, getLogger(), true) + + uuuid := uuid.NewString() + auuid := uuid.NewString() + mock.ExpectQuery(`SELECT sap.* FROM "sentry_account_permission" AS "sap" JOIN authsrv_project as proj ON \(proj.id = sap.project_id\) AND \(proj.default = TRUE\) WHERE \(account_id = '` + uuuid + `'\) LIMIT 1`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"account_id"}).AddRow(uuuid)) + _ = addFetchExpectation(mock, "project") + _ = addFetchExpectation(mock, "organization") + _ = addFetchExpectation(mock, "partner") + + mock.ExpectQuery(`SELECT "apikey"."id", "apikey"."name",.* FROM "authsrv_apikey" AS "apikey" WHERE \(name = 'user-` + uuuid + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"})) + + mock.ExpectQuery(`INSERT INTO "authsrv_apikey"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(auuid)) + + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + resp, err := us.RetrieveCliConfig(context.Background(), req) + if err != nil { + t.Fatal("could not fetch cli config:", err) + } + if len(resp.ApiKey) == 0 { + t.Error("no apikey generated") + } + if len(resp.ApiSecret) == 0 { + t.Error("no apisecret generated") + } + if resp.Project != "project-name" { + t.Error("invalid project name") + } + if resp.Organization != "organization-name" { + t.Error("invalid organization name") + } + if resp.Partner != "partner-name" { + t.Error("invalid partner name") + } +} From e6a02705a73300e804e73f345e6fc767bc8f7432 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Mon, 13 Jun 2022 15:25:32 +0530 Subject: [PATCH 06/17] Add apikey tests --- pkg/service/apikey_test.go | 122 +++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 pkg/service/apikey_test.go diff --git a/pkg/service/apikey_test.go b/pkg/service/apikey_test.go new file mode 100644 index 0000000..0fe9a91 --- /dev/null +++ b/pkg/service/apikey_test.go @@ -0,0 +1,122 @@ +package service + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/uuid" + userrpcv3 "github.com/paralus/paralus/proto/rpc/user" +) + +func TestApiKeyCreate(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ak := NewApiKeyService(db, getLogger()) + uuuid := uuid.NewString() + auuid := uuid.NewString() + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + + // mocks + mock.ExpectQuery(`INSERT INTO "authsrv_apikey"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(auuid, "apikey-"+auuid)) + + resp, err := ak.Create(context.Background(), req) + if err != nil { + t.Error("unable to create apikey:", err) + } + if resp.ID != uuid.MustParse(auuid) { + t.Errorf("incorrect id for apikey; expected '%v', got '%v'", uuid.MustParse(auuid), resp.ID) + } + if resp.Name != "apikey-"+auuid { + t.Errorf("incorrect name for apikey; expected '%v', got '%v'", "apikey-"+auuid, resp.Name) + } +} + +func TestApiKeyDelete(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ak := NewApiKeyService(db, getLogger()) + uuuid := uuid.NewString() + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + + // mocks + mock.ExpectExec(`UPDATE "authsrv_apikey" AS "apikey" SET trash = TRUE WHERE \(account_id = 'user-` + uuuid + `'\) AND \(key = '` + uuuid + `'\)`). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _, err := ak.Delete(context.Background(), req) + if err != nil { + t.Error("unable to delete apikey:", err) + } +} + +func TestApiKeyGet(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ak := NewApiKeyService(db, getLogger()) + uuuid := uuid.NewString() + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + + // mocks + mock.ExpectQuery(`SELECT "apikey"."id", "apikey"."name", .*FROM "authsrv_apikey" AS "apikey" WHERE \(name = 'user-` + uuuid + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uuuid, "user-"+uuuid)) + + resp, err := ak.Get(context.Background(), req) + if err != nil { + t.Error("unable to get apikey:", err) + } + if resp.ID != uuid.MustParse(uuuid) { + t.Errorf("incorrect id for apikey; expected '%v', got '%v'", uuid.MustParse(uuuid), resp.ID) + } + if resp.Name != "user-"+uuuid { + t.Errorf("incorrect name for apikey; expected '%v', got '%v'", "apikey-"+uuuid, resp.Name) + } +} + +func TestApiKeyGetByKey(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ak := NewApiKeyService(db, getLogger()) + uuuid := uuid.NewString() + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + + // mocks + mock.ExpectQuery(`SELECT "apikey"."id", "apikey"."name", .*FROM "authsrv_apikey" AS "apikey" WHERE \(key = '` + uuuid + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uuuid, "user-"+uuuid)) + + resp, err := ak.GetByKey(context.Background(), req) + if err != nil { + t.Error("unable to get apikey:", err) + } + if resp.ID != uuid.MustParse(uuuid) { + t.Errorf("incorrect id for apikey; expected '%v', got '%v'", uuid.MustParse(uuuid), resp.ID) + } + if resp.Name != "user-"+uuuid { + t.Errorf("incorrect name for apikey; expected '%v', got '%v'", "apikey-"+uuuid, resp.Name) + } +} + +func TestApiKeyList(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + ak := NewApiKeyService(db, getLogger()) + uuuid := uuid.NewString() + req := &userrpcv3.ApiKeyRequest{Username: "user-" + uuuid, Id: uuuid} + + // mocks + mock.ExpectQuery(`SELECT "apikey"."id", "apikey"."name", .*FROM "authsrv_apikey" AS "apikey" WHERE \(account_id = 'user-` + uuuid + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uuuid, "user-"+uuuid)) + + resp, err := ak.List(context.Background(), req) + if err != nil { + t.Error("unable to list apikey:", err) + } + if resp.Items[0].Name != "user-"+uuuid { + t.Errorf("incorrect name for apikey; expected '%v', got '%v'", "apikey-"+uuuid, resp.Items[0].Name) + } +} From 8154da9944a5775f28ac5b1306620a805c13a384 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 15:58:44 +0530 Subject: [PATCH 07/17] Add initial set of tests for audit_log_query --- pkg/service/audit_log_query.go | 2 +- pkg/service/audit_log_query_test.go | 201 ++++++++++++++++++++++++++++ pkg/service/elastic_search_query.go | 12 +- pkg/service/relay_audit_query.go | 2 +- 4 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 pkg/service/audit_log_query_test.go diff --git a/pkg/service/audit_log_query.go b/pkg/service/audit_log_query.go index 68c5f72..344a4c9 100644 --- a/pkg/service/audit_log_query.go +++ b/pkg/service/audit_log_query.go @@ -11,7 +11,7 @@ import ( ) type AuditLogService struct { - auditQuery *ElasticSearchQuery + auditQuery ElasticSearchQuery } func NewAuditLogService(url string, auditPattern string, logPrefix string) (*AuditLogService, error) { diff --git a/pkg/service/audit_log_query_test.go b/pkg/service/audit_log_query_test.go new file mode 100644 index 0000000..338dfd7 --- /dev/null +++ b/pkg/service/audit_log_query_test.go @@ -0,0 +1,201 @@ +package service + +import ( + "bytes" + "encoding/json" + "testing" + + v1 "github.com/paralus/paralus/proto/rpc/audit" + v3 "github.com/paralus/paralus/proto/types/commonpb/v3" +) + +type md struct { + Source []string `json:"_source"` + Aggs struct { + GroupByProject struct { + Aggs struct { + GroupByType struct { + Terms struct { + Field string `json:"field"` + Size int `json:"size"` + } `json:"terms"` + } `json:"group_by_type"` + GroupByUsername struct { + Terms struct { + Field string `json:"field"` + Size int `json:"size"` + } `json:"terms"` + } `json:"group_by_username"` + } `json:"aggs"` + Terms struct { + Field string `json:"field"` + Size int `json:"size"` + } `json:"terms"` + } `json:"group_by_project"` + GroupByType struct { + Terms struct { + Field string `json:"field"` + } `json:"terms"` + } `json:"group_by_type"` + GroupByUsername struct { + Terms struct { + Field string `json:"field"` + } `json:"terms"` + } `json:"group_by_username"` + } `json:"aggs"` + Query struct { + Bool struct { + Filter struct { + Range struct { + JSONTimestamp struct { + Gte string `json:"gte"` + Lt string `json:"lt"` + } `json:"json.timestamp"` + } `json:"range"` + } `json:"filter"` + Must []struct { + Term struct { + JSONCategory string `json:"json.category"` + } `json:"term,omitempty"` + Terms struct { + JSONProject []string `json:"json.project"` + } `json:"terms,omitempty"` + QueryString struct { + Query string `json:"query"` + } `json:"query_string,omitempty"` + } `json:"must"` + } `json:"bool"` + } `json:"query"` + Size int `json:"size"` + Sort struct { + JSONTimestamp struct { + Order string `json:"order"` + } `json:"json.timestamp"` + } `json:"sort"` +} + +type mockElasticSearchQuery struct { + msg []bytes.Buffer +} + +func (m *mockElasticSearchQuery) Handle(msg bytes.Buffer) (map[string]interface{}, error) { + m.msg = append(m.msg, msg) + return map[string]interface{}{}, nil +} + +func TestGetAuditLogByProjectsSimple(t *testing.T) { + esq := &mockElasticSearchQuery{} + al := &AuditLogService{auditQuery: esq} + req := v1.AuditLogSearchRequest{ + Filter: &v1.AuditLogQueryFilter{ + QueryString: "query-string", + Projects: []string{"project-one", "project-two"}, + Timefrom: "now-1h", + Type: "fake-type", + User: "fake-user", + Client: "fake-client", + DashboardData: true, + }, + } + _, err := al.GetAuditLogByProjects(&req) + if err != nil { + t.Error("unable to get audit logs") + } + if len(esq.msg) != 1 { + t.Fatalf("incorrect number of searches; expected '%v', got '%v'", 1, len(esq.msg)) + } + m := &md{} + err = json.Unmarshal(esq.msg[0].Bytes(), m) + if err != nil { + t.Fatal("unable to unmarshall es request") + } + + if m.Source[0] != "json" { + t.Errorf("incorrect source; expected '%v', got '%v'", "json", m.Source[0]) + } + + if m.Aggs.GroupByType.Terms.Field != "json.type" { + t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.type", m.Aggs.GroupByType.Terms.Field) + } + + if m.Aggs.GroupByUsername.Terms.Field != "json.actor.account.username" { + t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.actor.account.username", m.Aggs.GroupByUsername.Terms.Field) + } + + // FIXME: will this cause issues as json has no ordering? + if m.Query.Bool.Must[0].Term.JSONCategory != "AUDIT" { + t.Errorf("incorrect category; expected '%v', got '%v'", "AUDIT", m.Query.Bool.Must[0].Term.JSONCategory) + } + + // TODO: add checks for user, client and type + if m.Query.Bool.Must[5].QueryString.Query != "query-string" { + t.Errorf("incorrect category; expected '%v', got '%v'", "query-string", m.Query.Bool.Must[5].QueryString.Query) + } + + if len(m.Query.Bool.Must[4].Terms.JSONProject) != 2 { + t.Errorf("incorrect number of project filters; expected '%v', got '%v'", 2, len(m.Query.Bool.Must[4].Terms.JSONProject)) + } + + if m.Query.Bool.Must[4].Terms.JSONProject[0] != "project-one" { + t.Errorf("incorrect number of project filters; expected '%v', got '%v'", "project-one", m.Query.Bool.Must[4].Terms.JSONProject[0]) + } + + if m.Query.Bool.Filter.Range.JSONTimestamp.Gte != "now-1h" { + t.Errorf("incorrect time range; expected '%v', got '%v'", "now-1h", m.Query.Bool.Filter.Range.JSONTimestamp.Gte) + } + if m.Query.Bool.Filter.Range.JSONTimestamp.Lt != "now" { + t.Errorf("incorrect time range; expected '%v', got '%v'", "now", m.Query.Bool.Filter.Range.JSONTimestamp.Lt) + } +} + +func TestGetAuditLogByProjectsNoProject(t *testing.T) { + esq := &mockElasticSearchQuery{} + al := &AuditLogService{auditQuery: esq} + req := v1.AuditLogSearchRequest{ + Metadata: &v3.Metadata{UrlScope: "url/project"}, + Filter: &v1.AuditLogQueryFilter{ + QueryString: "query-string", + }, + } + _, err := al.GetAuditLog(&req) + if err != nil { + t.Error("unable to get audit logs", err) + } + if len(esq.msg) != 1 { + t.Fatalf("incorrect number of searches; expected '%v', got '%v'", 1, len(esq.msg)) + } + m := &md{} + err = json.Unmarshal(esq.msg[0].Bytes(), m) + if err != nil { + t.Fatal("unable to unmarshall es request") + } + + if m.Source[0] != "json" { + t.Errorf("incorrect source; expected '%v', got '%v'", "json", m.Source[0]) + } + + if m.Aggs.GroupByType.Terms.Field != "json.type" { + t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.type", m.Aggs.GroupByType.Terms.Field) + } + + if m.Aggs.GroupByUsername.Terms.Field != "json.actor.account.username" { + t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.actor.account.username", m.Aggs.GroupByUsername.Terms.Field) + } + + // FIXME: will this cause issues as json has no ordering? + if m.Query.Bool.Must[0].Term.JSONCategory != "AUDIT" { + t.Errorf("incorrect category; expected '%v', got '%v'", "AUDIT", m.Query.Bool.Must[0].Term.JSONCategory) + } + + if m.Query.Bool.Must[2].QueryString.Query != "query-string" { + t.Errorf("incorrect category; expected '%v', got '%v'", "query-string", m.Query.Bool.Must[2].QueryString.Query) + } + + if len(m.Query.Bool.Must[1].Terms.JSONProject) != 1 { + t.Errorf("incorrect number of project filters; expected '%v', got '%v'", 1, len(m.Query.Bool.Must[1].Terms.JSONProject)) + } + + if m.Query.Bool.Must[1].Terms.JSONProject[0] != "project" { + t.Errorf("incorrect number of project filters; expected '%v', got '%v'", "project", m.Query.Bool.Must[1].Terms.JSONProject[0]) + } +} diff --git a/pkg/service/elastic_search_query.go b/pkg/service/elastic_search_query.go index 7b59232..660f1fb 100644 --- a/pkg/service/elastic_search_query.go +++ b/pkg/service/elastic_search_query.go @@ -9,14 +9,18 @@ import ( v6Client "github.com/elastic/go-elasticsearch" ) -type ElasticSearchQuery struct { +type elasticSearchQuery struct { url string indexPattern string logPrefix string esClient *v6Client.Client } -func NewElasticSearchQuery(url string, indexPattern string, logPrefix string) (*ElasticSearchQuery, error) { +type ElasticSearchQuery interface { + Handle(bytes.Buffer) (map[string]interface{}, error) +} + +func NewElasticSearchQuery(url string, indexPattern string, logPrefix string) (ElasticSearchQuery, error) { cfg := v6Client.Config{ Addresses: []string{ url, @@ -33,7 +37,7 @@ func NewElasticSearchQuery(url string, indexPattern string, logPrefix string) (* // return nil, err // } // _log.Infow(logPrefix+":Connected to elastic search ", "cluster", res, "index", indexPattern) - esQuery := &ElasticSearchQuery{ + esQuery := &elasticSearchQuery{ url: url, indexPattern: indexPattern, logPrefix: logPrefix, @@ -43,7 +47,7 @@ func NewElasticSearchQuery(url string, indexPattern string, logPrefix string) (* } //Handle Fires the search query -func (q *ElasticSearchQuery) Handle(msg bytes.Buffer) (map[string]interface{}, error) { +func (q *elasticSearchQuery) Handle(msg bytes.Buffer) (map[string]interface{}, error) { _log.Debugw("Searching elastic search: ", "index", q.indexPattern, "url", q.url, "q", q) res, err := q.esClient.Search( q.esClient.Search.WithContext(context.Background()), diff --git a/pkg/service/relay_audit_query.go b/pkg/service/relay_audit_query.go index 4d7df16..f4dbf34 100644 --- a/pkg/service/relay_audit_query.go +++ b/pkg/service/relay_audit_query.go @@ -9,7 +9,7 @@ import ( ) type RelayAuditService struct { - relayQuery *ElasticSearchQuery + relayQuery ElasticSearchQuery } func NewRelayAuditService(url string, auditPattern string, logPrefix string) (*RelayAuditService, error) { From f70156749fe2a9a156068d421d0c9c808495196e Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 18:17:46 +0530 Subject: [PATCH 08/17] Simplify audit log tests --- pkg/service/audit_log_query_test.go | 70 +++-------------------------- 1 file changed, 7 insertions(+), 63 deletions(-) diff --git a/pkg/service/audit_log_query_test.go b/pkg/service/audit_log_query_test.go index 338dfd7..abb8971 100644 --- a/pkg/service/audit_log_query_test.go +++ b/pkg/service/audit_log_query_test.go @@ -3,6 +3,7 @@ package service import ( "bytes" "encoding/json" + "strings" "testing" v1 "github.com/paralus/paralus/proto/rpc/audit" @@ -109,42 +110,9 @@ func TestGetAuditLogByProjectsSimple(t *testing.T) { if err != nil { t.Fatal("unable to unmarshall es request") } - - if m.Source[0] != "json" { - t.Errorf("incorrect source; expected '%v', got '%v'", "json", m.Source[0]) - } - - if m.Aggs.GroupByType.Terms.Field != "json.type" { - t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.type", m.Aggs.GroupByType.Terms.Field) - } - - if m.Aggs.GroupByUsername.Terms.Field != "json.actor.account.username" { - t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.actor.account.username", m.Aggs.GroupByUsername.Terms.Field) - } - - // FIXME: will this cause issues as json has no ordering? - if m.Query.Bool.Must[0].Term.JSONCategory != "AUDIT" { - t.Errorf("incorrect category; expected '%v', got '%v'", "AUDIT", m.Query.Bool.Must[0].Term.JSONCategory) - } - - // TODO: add checks for user, client and type - if m.Query.Bool.Must[5].QueryString.Query != "query-string" { - t.Errorf("incorrect category; expected '%v', got '%v'", "query-string", m.Query.Bool.Must[5].QueryString.Query) - } - - if len(m.Query.Bool.Must[4].Terms.JSONProject) != 2 { - t.Errorf("incorrect number of project filters; expected '%v', got '%v'", 2, len(m.Query.Bool.Must[4].Terms.JSONProject)) - } - - if m.Query.Bool.Must[4].Terms.JSONProject[0] != "project-one" { - t.Errorf("incorrect number of project filters; expected '%v', got '%v'", "project-one", m.Query.Bool.Must[4].Terms.JSONProject[0]) - } - - if m.Query.Bool.Filter.Range.JSONTimestamp.Gte != "now-1h" { - t.Errorf("incorrect time range; expected '%v', got '%v'", "now-1h", m.Query.Bool.Filter.Range.JSONTimestamp.Gte) - } - if m.Query.Bool.Filter.Range.JSONTimestamp.Lt != "now" { - t.Errorf("incorrect time range; expected '%v', got '%v'", "now", m.Query.Bool.Filter.Range.JSONTimestamp.Lt) + expected := `{"_source":["json"],"aggs":{"group_by_project":{"aggs":{"group_by_type":{"terms":{"field":"json.type","size":1000}},"group_by_username":{"terms":{"field":"json.actor.account.username","size":1000}}},"terms":{"field":"json.project","size":1000}},"group_by_type":{"terms":{"field":"json.type"}},"group_by_username":{"terms":{"field":"json.actor.account.username"}}},"query":{"bool":{"filter":{"range":{"json.timestamp":{"gte":"now-1h","lt":"now"}}},"must":[{"term":{"json.category":"AUDIT"}},{"term":{"json.type":"fake-type"}},{"term":{"json.actor.account.username":"fake-user"}},{"term":{"json.client.type":"fake-client"}},{"terms":{"json.project":["project-one","project-two"]}},{"query_string":{"query":"query-string"}}]}},"size":0,"sort":{"json.timestamp":{"order":"desc"}}}` + if strings.TrimSpace(esq.msg[0].String()) != expected { + t.Errorf("incorrect es query; expected '%v', got '%v'", expected, strings.TrimSpace(esq.msg[0].String())) } } @@ -170,32 +138,8 @@ func TestGetAuditLogByProjectsNoProject(t *testing.T) { t.Fatal("unable to unmarshall es request") } - if m.Source[0] != "json" { - t.Errorf("incorrect source; expected '%v', got '%v'", "json", m.Source[0]) - } - - if m.Aggs.GroupByType.Terms.Field != "json.type" { - t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.type", m.Aggs.GroupByType.Terms.Field) - } - - if m.Aggs.GroupByUsername.Terms.Field != "json.actor.account.username" { - t.Errorf("incorrect group_by_type field; expected '%v', got '%v'", "json.actor.account.username", m.Aggs.GroupByUsername.Terms.Field) - } - - // FIXME: will this cause issues as json has no ordering? - if m.Query.Bool.Must[0].Term.JSONCategory != "AUDIT" { - t.Errorf("incorrect category; expected '%v', got '%v'", "AUDIT", m.Query.Bool.Must[0].Term.JSONCategory) - } - - if m.Query.Bool.Must[2].QueryString.Query != "query-string" { - t.Errorf("incorrect category; expected '%v', got '%v'", "query-string", m.Query.Bool.Must[2].QueryString.Query) - } - - if len(m.Query.Bool.Must[1].Terms.JSONProject) != 1 { - t.Errorf("incorrect number of project filters; expected '%v', got '%v'", 1, len(m.Query.Bool.Must[1].Terms.JSONProject)) - } - - if m.Query.Bool.Must[1].Terms.JSONProject[0] != "project" { - t.Errorf("incorrect number of project filters; expected '%v', got '%v'", "project", m.Query.Bool.Must[1].Terms.JSONProject[0]) + expected := `{"_source":["json"],"aggs":{"group_by_type":{"terms":{"field":"json.type"}},"group_by_username":{"terms":{"field":"json.actor.account.username"}}},"query":{"bool":{"must":[{"term":{"json.category":"AUDIT"}},{"terms":{"json.project":["project"]}},{"query_string":{"query":"query-string"}}]}},"size":500,"sort":{"json.timestamp":{"order":"desc"}}}` + if strings.TrimSpace(esq.msg[0].String()) != expected { + t.Errorf("incorrect es query; expected '%v', got '%v'", expected, strings.TrimSpace(esq.msg[0].String())) } } From a40ef14cf62066f609766ca48d91a587c45f200e Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 18:23:33 +0530 Subject: [PATCH 09/17] Add tests for relay audit logs --- pkg/service/relay_audit_query_test.go | 145 ++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 pkg/service/relay_audit_query_test.go diff --git a/pkg/service/relay_audit_query_test.go b/pkg/service/relay_audit_query_test.go new file mode 100644 index 0000000..85aad70 --- /dev/null +++ b/pkg/service/relay_audit_query_test.go @@ -0,0 +1,145 @@ +package service + +import ( + "encoding/json" + "strings" + "testing" + + v1 "github.com/paralus/paralus/proto/rpc/audit" + v3 "github.com/paralus/paralus/proto/types/commonpb/v3" +) + +type rmd struct { + Source []string `json:"_source"` + Aggs struct { + GroupByCluster struct { + Aggs struct { + GroupByNamespace struct { + Terms struct { + Field string `json:"field"` + Size int `json:"size"` + } `json:"terms"` + } `json:"group_by_namespace"` + GroupByUsername struct { + Terms struct { + Field string `json:"field"` + Size int `json:"size"` + } `json:"terms"` + } `json:"group_by_username"` + } `json:"aggs"` + Terms struct { + Field string `json:"field"` + Size int `json:"size"` + } `json:"terms"` + } `json:"group_by_cluster"` + GroupByKind struct { + Terms struct { + Field string `json:"field"` + } `json:"terms"` + } `json:"group_by_kind"` + GroupByMethod struct { + Terms struct { + Field string `json:"field"` + } `json:"terms"` + } `json:"group_by_method"` + GroupByNamespace struct { + Terms struct { + Field string `json:"field"` + } `json:"terms"` + } `json:"group_by_namespace"` + GroupByUsername struct { + Terms struct { + Field string `json:"field"` + } `json:"terms"` + } `json:"group_by_username"` + } `json:"aggs"` + Query struct { + Bool struct { + Filter struct { + Range struct { + JSONTs struct { + Gte string `json:"gte"` + Lt string `json:"lt"` + } `json:"json.ts"` + } `json:"range"` + } `json:"filter"` + Must []struct { + Term struct { + JSONUn string `json:"json.un"` + } `json:"term,omitempty"` + Terms struct { + JSONProject []string `json:"json.project"` + } `json:"terms,omitempty"` + QueryString struct { + Query string `json:"query"` + } `json:"query_string,omitempty"` + } `json:"must"` + } `json:"bool"` + } `json:"query"` + Size int `json:"size"` + Sort struct { + JSONTs struct { + Order string `json:"order"` + } `json:"json.ts"` + } `json:"sort"` +} + +func TestGetRelayAuditLogByProjectsSimple(t *testing.T) { + esq := &mockElasticSearchQuery{} + al := &RelayAuditService{relayQuery: esq} + req := v1.RelayAuditSearchRequest{ + Filter: &v1.RelayAuditQueryFilter{ + QueryString: "query-string", + Projects: []string{"project-one", "project-two"}, + Timefrom: "now-1h", + Type: "fake-type", + User: "fake-user", + Client: "fake-client", + DashboardData: true, + }, + } + _, err := al.GetRelayAuditByProjects(&req) + if err != nil { + t.Error("unable to get audit logs") + } + if len(esq.msg) != 1 { + t.Fatalf("incorrect number of searches; expected '%v', got '%v'", 1, len(esq.msg)) + } + m := &rmd{} + err = json.Unmarshal(esq.msg[0].Bytes(), m) + if err != nil { + t.Fatal("unable to unmarshall es request") + } + expected := `{"_source":["json"],"aggs":{"group_by_cluster":{"aggs":{"group_by_namespace":{"terms":{"field":"json.ns","size":1000}},"group_by_username":{"terms":{"field":"json.un","size":1000}}},"terms":{"field":"json.cn","size":1000}},"group_by_kind":{"terms":{"field":"json.k"}},"group_by_method":{"terms":{"field":"json.m"}},"group_by_namespace":{"terms":{"field":"json.ns"}},"group_by_username":{"terms":{"field":"json.un"}}},"query":{"bool":{"filter":{"range":{"json.ts":{"gte":"now-1h","lt":"now"}}},"must":[{"term":{"json.un":"fake-user"}},{"terms":{"json.project":["project-one","project-two"]}},{"query_string":{"query":"query-string"}}]}},"size":0,"sort":{"json.ts":{"order":"desc"}}}` + if strings.TrimSpace(esq.msg[0].String()) != expected { + t.Errorf("incorrect es query; expected '%v', got '%v'", expected, strings.TrimSpace(esq.msg[0].String())) + } +} + +func TestGetRelayAuditLogByProjectsNoProject(t *testing.T) { + esq := &mockElasticSearchQuery{} + al := &RelayAuditService{relayQuery: esq} + req := v1.RelayAuditSearchRequest{ + Metadata: &v3.Metadata{UrlScope: "url/project"}, + Filter: &v1.RelayAuditQueryFilter{ + QueryString: "query-string", + }, + } + _, err := al.GetRelayAudit(&req) + if err != nil { + t.Error("unable to get audit logs", err) + } + if len(esq.msg) != 1 { + t.Fatalf("incorrect number of searches; expected '%v', got '%v'", 1, len(esq.msg)) + } + m := &rmd{} + err = json.Unmarshal(esq.msg[0].Bytes(), m) + if err != nil { + t.Fatal("unable to unmarshall es request") + } + + expected := `{"_source":["json"],"aggs":{"group_by_cluster":{"terms":{"field":"json.cn"}},"group_by_kind":{"terms":{"field":"json.k"}},"group_by_method":{"terms":{"field":"json.m"}},"group_by_namespace":{"terms":{"field":"json.ns"}},"group_by_username":{"terms":{"field":"json.un"}}},"query":{"bool":{"must":[{"terms":{"json.project":["project"]}},{"query_string":{"query":"query-string"}}]}},"size":500,"sort":{"json.ts":{"order":"desc"}}}` + if strings.TrimSpace(esq.msg[0].String()) != expected { + t.Errorf("incorrect es query; expected '%v', got '%v'", expected, strings.TrimSpace(esq.msg[0].String())) + } +} From 1d4d05c94ca092b4279cfc98af2f26d7d19fedb1 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 18:25:33 +0530 Subject: [PATCH 10/17] Improve relay audit log test --- pkg/service/relay_audit_query_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/service/relay_audit_query_test.go b/pkg/service/relay_audit_query_test.go index 85aad70..1a954e3 100644 --- a/pkg/service/relay_audit_query_test.go +++ b/pkg/service/relay_audit_query_test.go @@ -92,9 +92,13 @@ func TestGetRelayAuditLogByProjectsSimple(t *testing.T) { QueryString: "query-string", Projects: []string{"project-one", "project-two"}, Timefrom: "now-1h", - Type: "fake-type", - User: "fake-user", - Client: "fake-client", + Type: "test-type", + User: "test-user", + Client: "test-client", + Cluster: "test-cluster", + Namespace: "test-namespace", + Kind: "test-kind", + Method: "test-method", DashboardData: true, }, } @@ -110,7 +114,7 @@ func TestGetRelayAuditLogByProjectsSimple(t *testing.T) { if err != nil { t.Fatal("unable to unmarshall es request") } - expected := `{"_source":["json"],"aggs":{"group_by_cluster":{"aggs":{"group_by_namespace":{"terms":{"field":"json.ns","size":1000}},"group_by_username":{"terms":{"field":"json.un","size":1000}}},"terms":{"field":"json.cn","size":1000}},"group_by_kind":{"terms":{"field":"json.k"}},"group_by_method":{"terms":{"field":"json.m"}},"group_by_namespace":{"terms":{"field":"json.ns"}},"group_by_username":{"terms":{"field":"json.un"}}},"query":{"bool":{"filter":{"range":{"json.ts":{"gte":"now-1h","lt":"now"}}},"must":[{"term":{"json.un":"fake-user"}},{"terms":{"json.project":["project-one","project-two"]}},{"query_string":{"query":"query-string"}}]}},"size":0,"sort":{"json.ts":{"order":"desc"}}}` + expected := `{"_source":["json"],"aggs":{"group_by_cluster":{"aggs":{"group_by_namespace":{"terms":{"field":"json.ns","size":1000}},"group_by_username":{"terms":{"field":"json.un","size":1000}}},"terms":{"field":"json.cn","size":1000}},"group_by_kind":{"terms":{"field":"json.k"}},"group_by_method":{"terms":{"field":"json.m"}},"group_by_namespace":{"terms":{"field":"json.ns"}},"group_by_username":{"terms":{"field":"json.un"}}},"query":{"bool":{"filter":{"range":{"json.ts":{"gte":"now-1h","lt":"now"}}},"must":[{"term":{"json.un":"test-user"}},{"term":{"json.cn":"test-cluster"}},{"term":{"json.ns":"test-namespace"}},{"term":{"json.k":"test-kind"}},{"term":{"json.m":"test-method"}},{"terms":{"json.project":["project-one","project-two"]}},{"query_string":{"query":"query-string"}}]}},"size":0,"sort":{"json.ts":{"order":"desc"}}}` if strings.TrimSpace(esq.msg[0].String()) != expected { t.Errorf("incorrect es query; expected '%v', got '%v'", expected, strings.TrimSpace(esq.msg[0].String())) } From bbdfa7c74baa3c67fd3806c840475fb2435fcdf1 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 18:33:11 +0530 Subject: [PATCH 11/17] Drop unused function --- pkg/service/rolepermission.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pkg/service/rolepermission.go b/pkg/service/rolepermission.go index a8ee8af..d471d05 100644 --- a/pkg/service/rolepermission.go +++ b/pkg/service/rolepermission.go @@ -49,21 +49,6 @@ func (s *rolepermissionService) toV3Rolepermission(rolepermission *rolev3.RolePe return rolepermission } -func (s *rolepermissionService) getPartnerOrganization(ctx context.Context, rolepermission *rolev3.RolePermission) (uuid.UUID, uuid.UUID, error) { - partner := rolepermission.GetMetadata().GetPartner() - org := rolepermission.GetMetadata().GetOrganization() - partnerId, err := dao.GetPartnerId(ctx, s.db, partner) - if err != nil { - return uuid.Nil, uuid.Nil, err - } - organizationId, err := dao.GetOrganizationId(ctx, s.db, org) - if err != nil { - return partnerId, uuid.Nil, err - } - return partnerId, organizationId, nil - -} - func (s *rolepermissionService) GetByName(ctx context.Context, rolepermission *rolev3.RolePermission) (*rolev3.RolePermission, error) { name := rolepermission.GetMetadata().GetName() entity, err := dao.GetByName(ctx, s.db, name, &models.ResourcePermission{}) From bcfcd466193c4bdbe5361e141aa6c528c5421f77 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 18:45:40 +0530 Subject: [PATCH 12/17] Add more tests for rolepermissions --- pkg/service/rolepermission_test.go | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/pkg/service/rolepermission_test.go b/pkg/service/rolepermission_test.go index 2153d33..d99d473 100644 --- a/pkg/service/rolepermission_test.go +++ b/pkg/service/rolepermission_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/paralus/paralus/pkg/query" commonv3 "github.com/paralus/paralus/proto/types/commonpb/v3" + v3 "github.com/paralus/paralus/proto/types/commonpb/v3" rolev3 "github.com/paralus/paralus/proto/types/rolepb/v3" ) @@ -55,3 +56,63 @@ func TestRolePermissionList(t *testing.T) { t.Errorf("incorrect role ids returned when listing") } } + +func TestRolePermissionListWithSelectors(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + rs := NewRolepermissionService(db) + + ruuid1 := uuid.New().String() + ruuid2 := uuid.New().String() + puuid := uuid.New().String() + ouuid := uuid.New().String() + + mock.ExpectQuery(`SELECT authsrv_resourcepermission.name as name, authsrv_resourcepermission.scope as scope FROM "authsrv_resourcepermission" WHERE \(scope = 'NAMESPACE'\) AND \(authsrv_resourcepermission.trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}). + AddRow(ruuid1, "role-"+ruuid1)) + + mock.ExpectQuery(`SELECT authsrv_resourcepermission.name as name, authsrv_resourcepermission.scope as scope FROM "authsrv_resourcepermission" WHERE \(scope = 'PROJECT'\) AND \(authsrv_resourcepermission.trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}). + AddRow(ruuid2, "role-"+ruuid2)) + + req := &commonv3.QueryOptions{ + Partner: "partner-" + puuid, + Organization: "org-" + ouuid, + Selector: "namespace", + } + rolelist, err := rs.List(context.Background(), query.WithOptions(req)) + if err != nil { + t.Fatal("could not list rolepermissions:", err) + } + if rolelist.Metadata.Count != 2 { + t.Errorf("incorrect number of rolepermissions returned, expected 2; got %v", rolelist.Metadata.Count) + } + if rolelist.Items[0].Metadata.Name != "role-"+ruuid1 || rolelist.Items[1].Metadata.Name != "role-"+ruuid2 { + t.Errorf("incorrect role ids returned when listing") + } +} + +func TestRolePermissionGetByName(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + rs := NewRolepermissionService(db) + + ruuid := uuid.New().String() + + mock.ExpectQuery(`SELECT "resourcepermission"."id", "resourcepermission"."name".* FROM "authsrv_resourcepermission" AS "resourcepermission"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}). + AddRow(ruuid, "role-"+ruuid)) + + req := &rolev3.RolePermission{ + Metadata: &v3.Metadata{}, + } + role, err := rs.GetByName(context.Background(), req) + if err != nil { + t.Fatal("could not list rolepermissions:", err) + } + if role.Metadata.Name != "role-"+ruuid { + t.Errorf("incorrect role name; expected '%v', got '%v'", "role-"+ruuid, role.Metadata.Name) + } +} From 16b8a530fedd992bbd03177c5ea8b1590e8a703f Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 18:58:37 +0530 Subject: [PATCH 13/17] Update project tests and simplify mocks --- pkg/service/project_test.go | 31 ++++++++----------------------- pkg/service/test_utils.go | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/service/project_test.go b/pkg/service/project_test.go index 0fe8873..d333ec7 100644 --- a/pkg/service/project_test.go +++ b/pkg/service/project_test.go @@ -25,10 +25,8 @@ func TestCreateProject(t *testing.T) { ps := NewProjectService(db, &mazc, getLogger(), true) puuid := uuid.New().String() - ouuid := uuid.New().String() - mock.ExpectQuery(`SELECT "organization"."id", "organization"."name"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(ouuid)) + addFetchExpectation(mock, "organization") mock.ExpectBegin() mock.ExpectQuery(`INSERT INTO "authsrv_project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(puuid)) @@ -109,8 +107,7 @@ func TestProjectDeleteNonExist(t *testing.T) { puuid := uuid.New().String() - mock.ExpectQuery(`SELECT "project"."id", "project"."name", .* FROM "authsrv_project" AS "project" WHERE`). - WithArgs().WillReturnError(fmt.Errorf("no data available")) + addFailingFetchExpecteation(mock, "project") project := &systemv3.Project{ Metadata: &v3.Metadata{Id: puuid, Name: "project-" + puuid}, @@ -128,18 +125,10 @@ func TestProjectGetByName(t *testing.T) { mazc := mockAuthzClient{} ps := NewProjectService(db, &mazc, getLogger(), true) - partuuid := uuid.New().String() - ouuid := uuid.New().String() puuid := uuid.New().String() - - mock.ExpectQuery(`SELECT "project"."id", "project"."name"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "partner_id"}).AddRow(puuid, ouuid, partuuid)) - - mock.ExpectQuery(`SELECT "organization"."id", "organization"."name"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(ouuid)) - - mock.ExpectQuery(`SELECT "partner"."id", "partner"."name"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(partuuid)) + addFetchExpectation(mock, "project") + addFetchExpectation(mock, "organization") + addFetchExpectation(mock, "partner") mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group FROM "authsrv_projectgrouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgrouprole.role_id @@ -176,10 +165,9 @@ func TestProjectGetById(t *testing.T) { mazc := mockAuthzClient{} ps := NewProjectService(db, &mazc, getLogger(), true) - puuid := uuid.New().String() + puuid := uuid.NewString() - mock.ExpectQuery(`SELECT "project"."id", "project"."name", .* FROM "authsrv_project" AS "project" WHERE .*id = '` + puuid + `'`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(puuid, "project-"+puuid)) + addFetchByIdExpectation(mock, "project", puuid) project := &systemv3.Project{ Metadata: &v3.Metadata{Id: puuid, Name: "project-" + puuid}, @@ -198,10 +186,7 @@ func TestProjectUpdate(t *testing.T) { mazc := mockAuthzClient{} ps := NewProjectService(db, &mazc, getLogger(), true) - puuid := uuid.New().String() - - mock.ExpectQuery(`SELECT "project"."id", "project"."name", .* FROM "authsrv_project" AS "project" WHERE`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(puuid, "project-"+puuid)) + puuid := addFetchExpectation(mock, "project") mock.ExpectBegin() mock.ExpectExec(`UPDATE "authsrv_projectgrouprole" AS "projectgrouprole" SET trash = TRUE WHERE`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectQuery(`UPDATE "authsrv_projectgroupnamespacerole" AS "projectgroupnamespacerole" SET trash = TRUE WHERE ."project_id" = '` + puuid + `'. AND .trash = false. RETURNING *`). diff --git a/pkg/service/test_utils.go b/pkg/service/test_utils.go index ff2743d..63324e5 100644 --- a/pkg/service/test_utils.go +++ b/pkg/service/test_utils.go @@ -65,6 +65,16 @@ func idnamea(uid string, resource string) *string { return &name } +func addFetchEmptyExpecteation(mock sqlmock.Sqlmock, resource string) { + mock.ExpectQuery(`SELECT "` + resource + `"."id" FROM "authsrv_` + resource + `" AS "` + resource + `"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"})) +} + +func addFailingFetchExpecteation(mock sqlmock.Sqlmock, resource string) { + mock.ExpectQuery(`SELECT "` + resource + `"."id" FROM "authsrv_` + resource + `" AS "` + resource + `"`). + WithArgs().WillReturnError(fmt.Errorf("no data available")) +} + func addFetchIdExpectation(mock sqlmock.Sqlmock, resource string) string { uid := uuid.New().String() mock.ExpectQuery(`SELECT "` + resource + `"."id" FROM "authsrv_` + resource + `" AS "` + resource + `"`). @@ -72,6 +82,11 @@ func addFetchIdExpectation(mock sqlmock.Sqlmock, resource string) string { return uid } +func addFetchByIdExpectation(mock sqlmock.Sqlmock, resource, uid string) { + mock.ExpectQuery(`SELECT "` + resource + `"."id".* FROM "authsrv_` + resource + `" AS "` + resource + `" WHERE .id = '` + uid + `'.`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uid, resource+"-"+uid)) +} + func addFetchIdByNameExpectation(mock sqlmock.Sqlmock, resource, name string) string { uid := uuid.New().String() mock.ExpectQuery(`SELECT "` + resource + `"."id" FROM "authsrv_` + resource + `" AS "` + resource + `" WHERE .name = '` + name + `'.`). From d3eafc6c9ab223a2878fb0ebcc8c6cacee86216a Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 19:57:44 +0530 Subject: [PATCH 14/17] Improve test coverage for project --- pkg/service/project_test.go | 147 ++++++++++++++++++++++++++++++++++++ pkg/service/test_utils.go | 2 +- 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/pkg/service/project_test.go b/pkg/service/project_test.go index d333ec7..599f73d 100644 --- a/pkg/service/project_test.go +++ b/pkg/service/project_test.go @@ -7,8 +7,10 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/google/uuid" + "github.com/paralus/paralus/pkg/common" v3 "github.com/paralus/paralus/proto/types/commonpb/v3" systemv3 "github.com/paralus/paralus/proto/types/systempb/v3" + userv3 "github.com/paralus/paralus/proto/types/userpb/v3" ) func performProjectBasicChecks(t *testing.T, project *systemv3.Project, puuid string) { @@ -66,6 +68,50 @@ func TestCreateProjectDuplicate(t *testing.T) { } } +func TestCreateProjectFull(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + mazc := mockAuthzClient{} + ps := NewProjectService(db, &mazc, getLogger(), true) + + puuid := uuid.New().String() + + addFetchExpectation(mock, "organization") + addFetchEmptyExpecteation(mock, "project") // not existing project + mock.ExpectBegin() + mock.ExpectQuery(`INSERT INTO "authsrv_project"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(puuid)) + addFetchExpectation(mock, "resourcerole") + addFetchExpectation(mock, "group") + mock.ExpectQuery(`INSERT INTO "authsrv_projectgrouprole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.NewString())) + mock.ExpectQuery(`SELECT "identities"."id".* FROM "identities" WHERE .*traits ->> 'email' = 'test-user'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "traits"}).AddRow(uuid.NewString(), []byte(`{"email":"test-user", "first_name": "John", "last_name": "Doe", "description": "The OG user."}`))) + addFetchExpectation(mock, "resourcerole") + mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountresourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.NewString())) + mock.ExpectCommit() + + te := []string{"test-project", "test-namespace", "test-group", "test-role", "test-user"} + project := &systemv3.Project{ + Metadata: &v3.Metadata{Id: puuid, Name: "project-" + puuid, Organization: "orgname"}, + Spec: &systemv3.ProjectSpec{ + ProjectNamespaceRoles: []*userv3.ProjectNamespaceRole{ + {Project: &te[0], Namespace: &te[1], Group: &te[2], Role: te[3]}, + }, + UserRoles: []*userv3.UserRole{ + {User: te[4], Role: te[3]}, + }, + }, + } + project, err := ps.Create(context.Background(), project) + if err != nil { + t.Fatal("could not create project:", err) + } + performProjectBasicChecks(t, project, puuid) +} + func TestProjectDelete(t *testing.T) { db, mock := getDB(t) defer db.Close() @@ -222,3 +268,104 @@ func TestProjectUpdate(t *testing.T) { t.Fatal("could not update project:", err) } } + +func TestProjectList(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + mazc := mockAuthzClient{} + ps := NewProjectService(db, &mazc, getLogger(), true) + + uid := uuid.NewString() + ouuid := addFetchExpectation(mock, "organization") + puuid := addFetchExpectation(mock, "partner") + mock.ExpectQuery(`SELECT "project"."id", "project"."name", .* FROM "authsrv_project" AS "project" WHERE \(partner_id = '` + puuid + `'\) AND \(organization_id = '` + ouuid + `'\) AND \(trash = false\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uid, "project-"+uid)) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group FROM "authsrv_projectgrouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgrouprole.role_id JOIN authsrv_group ON authsrv_group.id=authsrv_projectgrouprole.group_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectgrouprole.project_id WHERE \(authsrv_projectgrouprole.project_id = '` + uid + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "group"}).AddRow("test-role1", "test-project1", "test-group1")) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group, namespace FROM "authsrv_projectgroupnamespacerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgroupnamespacerole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectgroupnamespacerole.project_id JOIN authsrv_group ON authsrv_group.id=authsrv_projectgroupnamespacerole.group_id WHERE \(authsrv_projectgroupnamespacerole.project_id = '` + uid + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "group", "namespace"}).AddRow("test-role2", "test-project2", "test-group2", "test-namespace2")) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, identities.traits ->> 'email' as user FROM "authsrv_projectaccountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountresourcerole.role_id JOIN identities ON identities.id=authsrv_projectaccountresourcerole.account_id WHERE \(authsrv_projectaccountresourcerole.project_id = '` + uid + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "user"}).AddRow("test-role3", "test-user3")) + + project := &systemv3.Project{ + Metadata: &v3.Metadata{ + Organization: ouuid, + }, + } + pl, err := ps.List(context.Background(), project) + if err != nil { + t.Fatal("could not get project:", err) + } + if pl.Metadata.Count != 1 { + t.Errorf("incorrect number of projects returned; expected '%v', got '%v'", 1, pl.Metadata.Count) + } + if pl.Items[0].Metadata.Name != "project-"+uid { + t.Errorf("incorrect project name; expected '%v', got '%v'", "project-"+uid, pl.Items[0].Metadata.Name) + } + if *pl.Items[0].Spec.ProjectNamespaceRoles[0].Project != "test-project1" { + t.Errorf("incorrect projectnamespacerole; expected '%v', got '%v'", "test-project1", *pl.Items[0].Spec.ProjectNamespaceRoles[0].Project) + } + if *pl.Items[0].Spec.ProjectNamespaceRoles[1].Namespace != "test-namespace2" { + t.Errorf("incorrect projectnamespacerole; expected '%v', got '%v'", "test-namespace2", *pl.Items[0].Spec.ProjectNamespaceRoles[1].Namespace) + } + if pl.Items[0].Spec.UserRoles[0].User != "test-user3" { + t.Errorf("incorrect userrole; expected '%v', got '%v'", "test-user3", pl.Items[0].Spec.UserRoles[0].User) + } +} + +func TestProjectListNonDev(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + mazc := mockAuthzClient{} + ps := NewProjectService(db, &mazc, getLogger(), false) + + uid := uuid.NewString() + ouuid := addFetchExpectation(mock, "organization") + puuid := addFetchExpectation(mock, "partner") + uuuid := addUserFetchExpectation(mock) + mock.ExpectQuery(`SELECT distinct account_id, project_id FROM "sentry_account_permission" AS "sap" WHERE \(sap.partner_id = '` + puuid + `'\) AND \(sap.organization_id = '` + ouuid + `'\) AND \(sap.account_id = '` + uuuid + `'\) AND \(sap.permission_name IN \('project.read', 'ops_star.all'\)\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"account_id", "project_id"}).AddRow(uuuid, uid)) + mock.ExpectQuery(`SELECT "project"."id", "project"."name", .* FROM "authsrv_project" AS "project" WHERE \(project.partner_id = '` + puuid + `'\) AND \(project.organization_id = '` + ouuid + `'\) AND \(project.trash = FALSE\) AND \(project.id IN \('` + uid + `'\)\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(uid, "project-"+uid)) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group FROM "authsrv_projectgrouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgrouprole.role_id JOIN authsrv_group ON authsrv_group.id=authsrv_projectgrouprole.group_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectgrouprole.project_id WHERE \(authsrv_projectgrouprole.project_id = '` + uid + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "group"}).AddRow("test-role1", "test-project1", "test-group1")) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group, namespace FROM "authsrv_projectgroupnamespacerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgroupnamespacerole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectgroupnamespacerole.project_id JOIN authsrv_group ON authsrv_group.id=authsrv_projectgroupnamespacerole.group_id WHERE \(authsrv_projectgroupnamespacerole.project_id = '` + uid + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "group", "namespace"}).AddRow("test-role2", "test-project2", "test-group2", "test-namespace2")) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, identities.traits ->> 'email' as user FROM "authsrv_projectaccountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountresourcerole.role_id JOIN identities ON identities.id=authsrv_projectaccountresourcerole.account_id WHERE \(authsrv_projectaccountresourcerole.project_id = '` + uid + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "user"}).AddRow("test-role3", "test-user3")) + + project := &systemv3.Project{ + Metadata: &v3.Metadata{ + Organization: ouuid, + }, + } + + sd := v3.SessionData{Username: "user-" + uuuid} + ctx := context.WithValue(context.Background(), common.SessionDataKey, &sd) + pl, err := ps.List(ctx, project) + if err != nil { + t.Fatal("could not get project:", err) + } + + if pl.Metadata.Count != 1 { + t.Errorf("incorrect number of projects returned; expected '%v', got '%v'", 1, pl.Metadata.Count) + } + + if pl.Items[0].Metadata.Name != "project-"+uid { + t.Errorf("incorrect project name; expected '%v', got '%v'", "project-"+uid, pl.Items[0].Metadata.Name) + } + + if *pl.Items[0].Spec.ProjectNamespaceRoles[0].Project != "test-project1" { + t.Errorf("incorrect projectnamespacerole; expected '%v', got '%v'", "test-project1", *pl.Items[0].Spec.ProjectNamespaceRoles[0].Project) + } + + if *pl.Items[0].Spec.ProjectNamespaceRoles[1].Namespace != "test-namespace2" { + t.Errorf("incorrect projectnamespacerole; expected '%v', got '%v'", "test-namespace2", *pl.Items[0].Spec.ProjectNamespaceRoles[1].Namespace) + } + + if pl.Items[0].Spec.UserRoles[0].User != "test-user3" { + t.Errorf("incorrect userrole; expected '%v', got '%v'", "test-user3", pl.Items[0].Spec.UserRoles[0].User) + } +} diff --git a/pkg/service/test_utils.go b/pkg/service/test_utils.go index 63324e5..dadc3ab 100644 --- a/pkg/service/test_utils.go +++ b/pkg/service/test_utils.go @@ -88,7 +88,7 @@ func addFetchByIdExpectation(mock sqlmock.Sqlmock, resource, uid string) { } func addFetchIdByNameExpectation(mock sqlmock.Sqlmock, resource, name string) string { - uid := uuid.New().String() + uid := uuid.NewString() mock.ExpectQuery(`SELECT "` + resource + `"."id" FROM "authsrv_` + resource + `" AS "` + resource + `" WHERE .name = '` + name + `'.`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uid)) return uid From 2f069f2d7248b4f9d3c26fcca73f8e706275eed3 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 14 Jun 2022 20:25:06 +0530 Subject: [PATCH 15/17] Add tests for namespaces --- internal/dao/namespaces.go | 4 +- pkg/service/namespace_test.go | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 pkg/service/namespace_test.go diff --git a/internal/dao/namespaces.go b/internal/dao/namespaces.go index 0147e8e..0a3e88b 100644 --- a/internal/dao/namespaces.go +++ b/internal/dao/namespaces.go @@ -14,7 +14,7 @@ func GetProjectNamespaces(ctx context.Context, db bun.IDB, projectID uuid.UUID) var panr []models.ProjectAccountNamespaceRole err := db.NewSelect().Model(&panr).Where("project_id = ?", projectID).Where("trash = ?", false).Scan(ctx) - if err != sql.ErrNoRows { + if err != nil && err != sql.ErrNoRows { return nil, err } for _, nr := range panr { @@ -23,7 +23,7 @@ func GetProjectNamespaces(ctx context.Context, db bun.IDB, projectID uuid.UUID) var pgnr []models.ProjectGroupNamespaceRole err = db.NewSelect().Model(&pgnr).Where("project_id = ?", projectID).Where("trash = ?", false).Scan(ctx) - if err != sql.ErrNoRows { + if err != nil && err != sql.ErrNoRows { return nil, err } for _, nr := range pgnr { diff --git a/pkg/service/namespace_test.go b/pkg/service/namespace_test.go new file mode 100644 index 0000000..772e78f --- /dev/null +++ b/pkg/service/namespace_test.go @@ -0,0 +1,79 @@ +package service + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/uuid" +) + +func TestGetProjectNamespaces(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + puuid := uuid.New() + mock.ExpectQuery(`SELECT "projectaccountnamespacerole"."id", "projectaccountnamespacerole"."name", "projectaccountnamespacerole"."description", "projectaccountnamespacerole"."created_at", "projectaccountnamespacerole"."modified_at", "projectaccountnamespacerole"."trash", "projectaccountnamespacerole"."organization_id", "projectaccountnamespacerole"."partner_id", "projectaccountnamespacerole"."role_id", "projectaccountnamespacerole"."account_id", "projectaccountnamespacerole"."project_id", "projectaccountnamespacerole"."namespace", "projectaccountnamespacerole"."active" FROM "authsrv_projectaccountnamespacerole" AS "projectaccountnamespacerole" WHERE \(project_id = '` + puuid.String() + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"namespace"}).AddRow("namespace1")) + mock.ExpectQuery(`SELECT "projectgroupnamespacerole"."id", "projectgroupnamespacerole"."name", "projectgroupnamespacerole"."description", "projectgroupnamespacerole"."created_at", "projectgroupnamespacerole"."modified_at", "projectgroupnamespacerole"."trash", "projectgroupnamespacerole"."organization_id", "projectgroupnamespacerole"."partner_id", "projectgroupnamespacerole"."role_id", "projectgroupnamespacerole"."group_id", "projectgroupnamespacerole"."project_id", "projectgroupnamespacerole"."namespace", "projectgroupnamespacerole"."active" FROM "authsrv_projectgroupnamespacerole" AS "projectgroupnamespacerole" WHERE \(project_id = '` + puuid.String() + `'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"namespace"}).AddRow("namespace2")) + + ns := NewNamespaceService(db) + nl, err := ns.GetProjectNamespaces(context.Background(), puuid) + if err != nil { + t.Fatal("unable to get namespaces", err) + } + if len(nl) != 2 { + t.Errorf("incorrect number of namespaces; expected '%v', got '%v'", 2, len(nl)) + } + if nl[0] != "namespace1" { + t.Errorf("incorrect namespace name; expected '%v', got '%v'", "namespace1", nl[0]) + } + if nl[1] != "namespace2" { + t.Errorf("incorrect namespace name; expected '%v', got '%v'", "namespace2", nl[1]) + } +} + +func TestGetAccountProjectNamespaces(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + puuid := uuid.New() + uuuid := uuid.New() + mock.ExpectQuery(`SELECT "projectaccountnamespacerole"."id", "projectaccountnamespacerole"."name", "projectaccountnamespacerole"."description", "projectaccountnamespacerole"."created_at", "projectaccountnamespacerole"."modified_at", "projectaccountnamespacerole"."trash", "projectaccountnamespacerole"."organization_id", "projectaccountnamespacerole"."partner_id", "projectaccountnamespacerole"."role_id", "projectaccountnamespacerole"."account_id", "projectaccountnamespacerole"."project_id", "projectaccountnamespacerole"."namespace", "projectaccountnamespacerole"."active" FROM "authsrv_projectaccountnamespacerole" AS "projectaccountnamespacerole" WHERE \(project_id = '` + puuid.String() + `'\) AND \(account_id = '` + uuuid.String() + `'\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"namespace"}).AddRow("namespace1")) + + ns := NewNamespaceService(db) + nl, err := ns.GetAccountProjectNamespaces(context.Background(), puuid, uuuid) + if err != nil { + t.Fatal("unable to get namespaces", err) + } + if len(nl) != 1 { + t.Errorf("incorrect number of namespaces; expected '%v', got '%v'", 1, len(nl)) + } + if nl[0] != "namespace1" { + t.Errorf("incorrect namespace name; expected '%v', got '%v'", "namespace1", nl[0]) + } +} + +func TestGetGroupProjectNamespaces(t *testing.T) { + db, mock := getDB(t) + defer db.Close() + + puuid := uuid.New() + uuuid := uuid.New() + mock.ExpectQuery(`SELECT "projectgroupnamespacerole"."id", "projectgroupnamespacerole"."name", "projectgroupnamespacerole"."description", "projectgroupnamespacerole"."created_at", "projectgroupnamespacerole"."modified_at", "projectgroupnamespacerole"."trash", "projectgroupnamespacerole"."organization_id", "projectgroupnamespacerole"."partner_id", "projectgroupnamespacerole"."role_id", "projectgroupnamespacerole"."group_id", "projectgroupnamespacerole"."project_id", "projectgroupnamespacerole"."namespace", "projectgroupnamespacerole"."active" FROM "authsrv_projectgroupnamespacerole" AS "projectgroupnamespacerole" JOIN authsrv_groupaccount ON projectgroupnamespacerole.group_id=authsrv_groupaccount.group_id WHERE \(project_id = '+` + puuid.String() + `+'\) AND \(authsrv_groupaccount.account_id = '` + uuuid.String() + `'\) AND \(projectgroupnamespacerole.trash = FALSE\) AND \(authsrv_groupaccount.trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"namespace"}).AddRow("namespace1")) + + ns := NewNamespaceService(db) + nl, err := ns.GetGroupProjectNamespaces(context.Background(), puuid, uuuid) + if err != nil { + t.Fatal("unable to get namespaces", err) + } + if len(nl) != 1 { + t.Errorf("incorrect number of namespaces; expected '%v', got '%v'", 1, len(nl)) + } + if nl[0] != "namespace1" { + t.Errorf("incorrect namespace name; expected '%v', got '%v'", "namespace1", nl[0]) + } +} From 981e82ec00bf6d08b5fc3e53942b679ca16f56a0 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Thu, 16 Jun 2022 15:21:08 +0530 Subject: [PATCH 16/17] Fix project related tests --- pkg/service/project_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/service/project_test.go b/pkg/service/project_test.go index 599f73d..5b569c6 100644 --- a/pkg/service/project_test.go +++ b/pkg/service/project_test.go @@ -82,14 +82,16 @@ func TestCreateProjectFull(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery(`INSERT INTO "authsrv_project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(puuid)) - addFetchExpectation(mock, "resourcerole") + mock.ExpectQuery(`SELECT "resourcerole"."id", "resourcerole"."name", "resourcerole"."description", "resourcerole"."created_at", "resourcerole"."modified_at", "resourcerole"."trash", "resourcerole"."organization_id", "resourcerole"."partner_id", "resourcerole"."is_global", "resourcerole"."builtin", "resourcerole"."scope" FROM "authsrv_resourcerole" AS "resourcerole" WHERE \(name = 'test-role'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(puuid, "resourcerole-"+puuid, "namespace")) addFetchExpectation(mock, "group") - mock.ExpectQuery(`INSERT INTO "authsrv_projectgrouprole"`). + mock.ExpectQuery(`INSERT INTO "authsrv_projectgroupnamespacerole"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.NewString())) mock.ExpectQuery(`SELECT "identities"."id".* FROM "identities" WHERE .*traits ->> 'email' = 'test-user'`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "traits"}).AddRow(uuid.NewString(), []byte(`{"email":"test-user", "first_name": "John", "last_name": "Doe", "description": "The OG user."}`))) - addFetchExpectation(mock, "resourcerole") - mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountresourcerole"`). + mock.ExpectQuery(`SELECT "resourcerole"."id", "resourcerole"."name", "resourcerole"."description", "resourcerole"."created_at", "resourcerole"."modified_at", "resourcerole"."trash", "resourcerole"."organization_id", "resourcerole"."partner_id", "resourcerole"."is_global", "resourcerole"."builtin", "resourcerole"."scope" FROM "authsrv_resourcerole" AS "resourcerole" WHERE \(name = 'test-role'\) AND \(trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(puuid, "resourcerole-"+puuid, "namespace")) + mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountnamespacerole"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.NewString())) mock.ExpectCommit() @@ -287,6 +289,8 @@ func TestProjectList(t *testing.T) { WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "group", "namespace"}).AddRow("test-role2", "test-project2", "test-group2", "test-namespace2")) mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, identities.traits ->> 'email' as user FROM "authsrv_projectaccountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountresourcerole.role_id JOIN identities ON identities.id=authsrv_projectaccountresourcerole.account_id WHERE \(authsrv_projectaccountresourcerole.project_id = '` + uid + `'\)`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "user"}).AddRow("test-role3", "test-user3")) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, identities.traits ->> 'email' as user, namespace FROM "authsrv_projectaccountnamespacerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountnamespacerole.role_id JOIN identities ON identities.id=authsrv_projectaccountnamespacerole.account_id WHERE \(authsrv_projectaccountnamespacerole.project_id = '` + uid + `'\) AND \(authsrv_projectaccountnamespacerole.trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "user", "namespace"}).AddRow("test-role4", "test-user4", "test-namespace4")) project := &systemv3.Project{ Metadata: &v3.Metadata{ @@ -312,6 +316,9 @@ func TestProjectList(t *testing.T) { if pl.Items[0].Spec.UserRoles[0].User != "test-user3" { t.Errorf("incorrect userrole; expected '%v', got '%v'", "test-user3", pl.Items[0].Spec.UserRoles[0].User) } + if pl.Items[0].Spec.UserRoles[1].Namespace != "test-namespace4" { + t.Errorf("incorrect userrole; expected '%v', got '%v'", "test-namespace4", pl.Items[0].Spec.UserRoles[1].Namespace) + } } func TestProjectListNonDev(t *testing.T) { @@ -335,6 +342,8 @@ func TestProjectListNonDev(t *testing.T) { WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "group", "namespace"}).AddRow("test-role2", "test-project2", "test-group2", "test-namespace2")) mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, identities.traits ->> 'email' as user FROM "authsrv_projectaccountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountresourcerole.role_id JOIN identities ON identities.id=authsrv_projectaccountresourcerole.account_id WHERE \(authsrv_projectaccountresourcerole.project_id = '` + uid + `'\)`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "user"}).AddRow("test-role3", "test-user3")) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, identities.traits ->> 'email' as user, namespace FROM "authsrv_projectaccountnamespacerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountnamespacerole.role_id JOIN identities ON identities.id=authsrv_projectaccountnamespacerole.account_id WHERE \(authsrv_projectaccountnamespacerole.project_id = '` + uid + `'\) AND \(authsrv_projectaccountnamespacerole.trash = FALSE\)`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "user", "namespace"}).AddRow("test-role4", "test-user4", "test-namespace4")) project := &systemv3.Project{ Metadata: &v3.Metadata{ @@ -368,4 +377,8 @@ func TestProjectListNonDev(t *testing.T) { if pl.Items[0].Spec.UserRoles[0].User != "test-user3" { t.Errorf("incorrect userrole; expected '%v', got '%v'", "test-user3", pl.Items[0].Spec.UserRoles[0].User) } + + if pl.Items[0].Spec.UserRoles[1].Namespace != "test-namespace4" { + t.Errorf("incorrect userrole; expected '%v', got '%v'", "test-namespace4", pl.Items[0].Spec.UserRoles[1].Namespace) + } } From 7cd0156805c8148adcfe599f4747745ba263d9f5 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Fri, 17 Jun 2022 14:32:01 +0530 Subject: [PATCH 17/17] Use /dev/stdout for log default output --- pkg/service/test_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/service/test_utils.go b/pkg/service/test_utils.go index dadc3ab..7834977 100644 --- a/pkg/service/test_utils.go +++ b/pkg/service/test_utils.go @@ -12,7 +12,7 @@ import ( func getLogger() *zap.Logger { ao := audit.AuditOptions{ - LogPath: "stdout", + LogPath: "/dev/stdout", MaxSizeMB: 1, MaxBackups: 10, // Should we let sidecar do rotation? MaxAgeDays: 10, // Make these configurable via env