From 2a3d524a71adbf5fa5374137fc4073c6d4699e43 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Mon, 29 Apr 2024 09:47:26 +0000 Subject: [PATCH] Add more unit tests Signed-off-by: Gabriel Adrian Samfira --- database/sql/enterprise_test.go | 23 +- database/sql/github.go | 22 +- database/sql/github_test.go | 436 +++++++++++++++++++++++++++++ database/sql/organizations_test.go | 23 +- database/sql/repositories_test.go | 23 +- internal/testing/testing.go | 25 ++ runner/repositories_test.go | 3 +- 7 files changed, 516 insertions(+), 39 deletions(-) diff --git a/database/sql/enterprise_test.go b/database/sql/enterprise_test.go index 2a26c11d..1283ead7 100644 --- a/database/sql/enterprise_test.go +++ b/database/sql/enterprise_test.go @@ -29,6 +29,7 @@ import ( "gorm.io/gorm/logger" runnerErrors "github.com/cloudbase/garm-provider-common/errors" + "github.com/cloudbase/garm/auth" dbCommon "github.com/cloudbase/garm/database/common" garmTesting "github.com/cloudbase/garm/internal/testing" "github.com/cloudbase/garm/params" @@ -50,7 +51,9 @@ type EnterpriseTestSuite struct { StoreSQLMocked *sqlDatabase Fixtures *EnterpriseTestFixtures - adminCtx context.Context + adminCtx context.Context + adminUserID string + testCreds params.GithubCredentials secondaryTestCreds params.GithubCredentials githubEndpoint params.GithubEndpoint @@ -84,6 +87,8 @@ func (s *EnterpriseTestSuite) SetupTest() { adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T()) s.adminCtx = adminCtx + s.adminUserID = auth.UserID(adminCtx) + s.Require().NotEmpty(s.adminUserID) s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T()) s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint) @@ -216,8 +221,8 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseInvalidDBPassphrase() { func (s *EnterpriseTestSuite) TestCreateEnterpriseDBCreateErr() { s.Fixtures.SQLMock.ExpectBegin() s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.Fixtures.Enterprises[0].CredentialsName, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.Fixtures.Enterprises[0].CredentialsName, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).AddRow(s.testCreds.ID, s.testCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). WithArgs(s.testCreds.Endpoint.Name). @@ -353,8 +358,8 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBEncryptErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Enterprises[0].ID, s.Fixtures.Enterprises[0].Endpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -378,8 +383,8 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBSaveErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Enterprises[0].ID, s.Fixtures.Enterprises[0].Endpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -409,8 +414,8 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBDecryptingErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Enterprises[0].ID, s.Fixtures.Enterprises[0].Endpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). diff --git a/database/sql/github.go b/database/sql/github.go index 7878c246..7a775e31 100644 --- a/database/sql/github.go +++ b/database/sql/github.go @@ -331,15 +331,13 @@ func (s *sqlDatabase) getGithubCredentialsByName(ctx context.Context, tx *gorm.D Preload("Enterprises") } - if !auth.IsAdmin(ctx) { - userID, err := getUIDFromContext(ctx) - if err != nil { - return GithubCredentials{}, errors.Wrap(err, "fetching github credentials") - } - q = q.Where("user_id = ?", userID) + userID, err := getUIDFromContext(ctx) + if err != nil { + return GithubCredentials{}, errors.Wrap(err, "fetching github credentials") } + q = q.Where("user_id = ?", userID) - err := q.Where("name = ?", name).First(&creds).Error + err = q.Where("name = ?", name).First(&creds).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return GithubCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found") @@ -461,6 +459,10 @@ func (s *sqlDatabase) UpdateGithubCredentials(ctx context.Context, id uint, para if param.PAT != nil { return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update PAT credentials for app") } + default: + // This should never happen, unless there was a bug in the DB migration code, + // or the DB was manually modified. + return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type") } if err != nil { @@ -504,13 +506,13 @@ func (s *sqlDatabase) DeleteGithubCredentials(ctx context.Context, id uint) erro return errors.Wrap(err, "fetching github credentials") } if len(creds.Repositories) > 0 { - return errors.New("cannot delete credentials with repositories") + return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with repositories") } if len(creds.Organizations) > 0 { - return errors.New("cannot delete credentials with organizations") + return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with organizations") } if len(creds.Enterprises) > 0 { - return errors.New("cannot delete credentials with enterprises") + return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with enterprises") } if err := tx.Unscoped().Delete(&creds).Error; err != nil { diff --git a/database/sql/github_test.go b/database/sql/github_test.go index 2f6144e6..0098418b 100644 --- a/database/sql/github_test.go +++ b/database/sql/github_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/suite" runnerErrors "github.com/cloudbase/garm-provider-common/errors" + "github.com/cloudbase/garm/auth" "github.com/cloudbase/garm/database/common" garmTesting "github.com/cloudbase/garm/internal/testing" "github.com/cloudbase/garm/params" @@ -280,6 +281,8 @@ func (s *GithubTestSuite) TestCreateCredentials() { func (s *GithubTestSuite) TestCreateCredentialsFailsOnDuplicateCredentials() { ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + testUser := garmTesting.CreateGARMTestUser(ctx, "testuser", s.db, s.T()) + testUserCtx := auth.PopulateContext(context.Background(), testUser) credParams := params.CreateGithubCredentialsParams{ Name: testCredsName, @@ -294,9 +297,442 @@ func (s *GithubTestSuite) TestCreateCredentialsFailsOnDuplicateCredentials() { _, err := s.db.CreateGithubCredentials(ctx, credParams) s.Require().NoError(err) + // Creating creds with the same parameters should fail for the same user. _, err = s.db.CreateGithubCredentials(ctx, credParams) s.Require().Error(err) s.Require().ErrorIs(err, runnerErrors.ErrDuplicateEntity) + + // Creating creds with the same parameters should work for different users. + _, err = s.db.CreateGithubCredentials(testUserCtx, credParams) + s.Require().NoError(err) +} + +func (s *GithubTestSuite) TestNormalUsersCanOnlySeeTheirOwnCredentialsAdminCanSeeAll() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + testUser := garmTesting.CreateGARMTestUser(ctx, "testuser1", s.db, s.T()) + testUser2 := garmTesting.CreateGARMTestUser(ctx, "testuser2", s.db, s.T()) + testUserCtx := auth.PopulateContext(context.Background(), testUser) + testUser2Ctx := auth.PopulateContext(context.Background(), testUser2) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + credParams.Name = "test-creds2" + creds2, err := s.db.CreateGithubCredentials(testUserCtx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds2) + + credParams.Name = "test-creds3" + creds3, err := s.db.CreateGithubCredentials(testUser2Ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds3) + + credsList, err := s.db.ListGithubCredentials(ctx) + s.Require().NoError(err) + s.Require().Len(credsList, 3) + + credsList, err = s.db.ListGithubCredentials(testUserCtx) + s.Require().NoError(err) + s.Require().Len(credsList, 1) + s.Require().Equal("test-creds2", credsList[0].Name) + + credsList, err = s.db.ListGithubCredentials(testUser2Ctx) + s.Require().NoError(err) + s.Require().Len(credsList, 1) + s.Require().Equal("test-creds3", credsList[0].Name) +} + +func (s *GithubTestSuite) TestGetGithubCredentialsFailsWhenCredentialsDontExist() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + _, err := s.db.GetGithubCredentials(ctx, 1, true) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) + + _, err = s.db.GetGithubCredentialsByName(ctx, "non-existing", true) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestGetGithubCredentialsByNameReturnsOnlyCurrentUserCredentials() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + testUser := garmTesting.CreateGARMTestUser(ctx, "test-user1", s.db, s.T()) + testUserCtx := auth.PopulateContext(context.Background(), testUser) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + creds2, err := s.db.CreateGithubCredentials(testUserCtx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds2) + + creds2Get, err := s.db.GetGithubCredentialsByName(testUserCtx, testCredsName, true) + s.Require().NoError(err) + s.Require().NotNil(creds2) + s.Require().Equal(testCredsName, creds2Get.Name) + s.Require().Equal(creds2.ID, creds2Get.ID) + + credsGet, err := s.db.GetGithubCredentialsByName(ctx, testCredsName, true) + s.Require().NoError(err) + s.Require().NotNil(creds) + s.Require().Equal(testCredsName, credsGet.Name) + s.Require().Equal(creds.ID, credsGet.ID) + + // Admin can get any creds by ID + credsGet, err = s.db.GetGithubCredentials(ctx, creds2.ID, true) + s.Require().NoError(err) + s.Require().NotNil(creds2) + s.Require().Equal(creds2.ID, credsGet.ID) + + // Normal user cannot get other user creds by ID + _, err = s.db.GetGithubCredentials(testUserCtx, creds.ID, true) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestGetGithubCredentials() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + creds2, err := s.db.GetGithubCredentialsByName(ctx, testCredsName, true) + s.Require().NoError(err) + s.Require().NotNil(creds2) + s.Require().Equal(creds.Name, creds2.Name) + s.Require().Equal(creds.ID, creds2.ID) + + creds2, err = s.db.GetGithubCredentials(ctx, creds.ID, true) + s.Require().NoError(err) + s.Require().NotNil(creds2) + s.Require().Equal(creds.Name, creds2.Name) + s.Require().Equal(creds.ID, creds2.ID) +} + +func (s *GithubTestSuite) TestDeleteGithubCredentials() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + err = s.db.DeleteGithubCredentials(ctx, creds.ID) + s.Require().NoError(err) + + _, err = s.db.GetGithubCredentials(ctx, creds.ID, true) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestDeleteGithubCredentialsByNonAdminUser() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + testUser := garmTesting.CreateGARMTestUser(ctx, "test-user4", s.db, s.T()) + testUserCtx := auth.PopulateContext(context.Background(), testUser) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test-creds4", + }, + } + + // Create creds as admin + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + // Deleting non existent creds will return a nil error. For the test user + // the creds created by the admin should not be visible, which leads to not found + // which in turn returns no error. + err = s.db.DeleteGithubCredentials(testUserCtx, creds.ID) + s.Require().NoError(err) + + // Check that the creds created by the admin are still there. + credsGet, err := s.db.GetGithubCredentials(ctx, creds.ID, true) + s.Require().NoError(err) + s.Require().NotNil(credsGet) + s.Require().Equal(creds.ID, credsGet.ID) + + // Create the same creds with the test user. + creds2, err := s.db.CreateGithubCredentials(testUserCtx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds2) + + // Remove creds created by test user. + err = s.db.DeleteGithubCredentials(testUserCtx, creds2.ID) + s.Require().NoError(err) + + // The creds created by the test user should be gone. + _, err = s.db.GetGithubCredentials(testUserCtx, creds2.ID, true) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestDeleteCredentialsFailsIfReposOrgsOrEntitiesUseIt() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + repo, err := s.db.CreateRepository(ctx, "test-owner", "test-repo", creds.Name, "superSecret@123BlaBla", params.PoolBalancerTypeRoundRobin) + s.Require().NoError(err) + s.Require().NotNil(repo) + + err = s.db.DeleteGithubCredentials(ctx, creds.ID) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrBadRequest) + + err = s.db.DeleteRepository(ctx, repo.ID) + s.Require().NoError(err) + + org, err := s.db.CreateOrganization(ctx, "test-org", creds.Name, "superSecret@123BlaBla", params.PoolBalancerTypeRoundRobin) + s.Require().NoError(err) + s.Require().NotNil(org) + + err = s.db.DeleteGithubCredentials(ctx, creds.ID) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrBadRequest) + + err = s.db.DeleteOrganization(ctx, org.ID) + s.Require().NoError(err) + + enterprise, err := s.db.CreateEnterprise(ctx, "test-enterprise", creds.Name, "superSecret@123BlaBla", params.PoolBalancerTypeRoundRobin) + s.Require().NoError(err) + s.Require().NotNil(enterprise) + + err = s.db.DeleteGithubCredentials(ctx, creds.ID) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrBadRequest) + + err = s.db.DeleteEnterprise(ctx, enterprise.ID) + s.Require().NoError(err) + + err = s.db.DeleteGithubCredentials(ctx, creds.ID) + s.Require().NoError(err) + + _, err = s.db.GetGithubCredentials(ctx, creds.ID, true) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestUpdateCredentials() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + newDescription := "new description" + newName := "new-name" + newToken := "new-token" + updateCredParams := params.UpdateGithubCredentialsParams{ + Description: &newDescription, + Name: &newName, + PAT: ¶ms.GithubPAT{ + OAuth2Token: newToken, + }, + } + + updatedCreds, err := s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams) + s.Require().NoError(err) + s.Require().NotNil(updatedCreds) + s.Require().Equal(newDescription, updatedCreds.Description) + s.Require().Equal(newName, updatedCreds.Name) +} + +func (s *GithubTestSuite) TestUpdateGithubCredentialsFailIfWrongCredentialTypeIsPassed() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + updateCredParams := params.UpdateGithubCredentialsParams{ + App: ¶ms.GithubApp{ + AppID: 1, + InstallationID: 2, + PrivateKeyBytes: []byte("test"), + }, + } + + _, err = s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrBadRequest) + s.Require().EqualError(err, "updating github credentials: cannot update app credentials for PAT: invalid request") + + credParamsWithApp := params.CreateGithubCredentialsParams{ + Name: "test-credsApp", + Description: "test credsApp", + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypeApp, + App: params.GithubApp{ + AppID: 1, + InstallationID: 2, + PrivateKeyBytes: []byte("test"), + }, + } + + credsApp, err := s.db.CreateGithubCredentials(ctx, credParamsWithApp) + s.Require().NoError(err) + s.Require().NotNil(credsApp) + + updateCredParams = params.UpdateGithubCredentialsParams{ + PAT: ¶ms.GithubPAT{ + OAuth2Token: "test", + }, + } + + _, err = s.db.UpdateGithubCredentials(ctx, credsApp.ID, updateCredParams) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrBadRequest) + s.Require().EqualError(err, "updating github credentials: cannot update PAT credentials for app: invalid request") +} + +func (s *GithubTestSuite) TestUpdateCredentialsFailsForNonExistingCredentials() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + + updateCredParams := params.UpdateGithubCredentialsParams{ + Description: nil, + } + + _, err := s.db.UpdateGithubCredentials(ctx, 1, updateCredParams) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestUpdateCredentialsFailsIfCredentialsAreOwnedByNonAdminUser() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + testUser := garmTesting.CreateGARMTestUser(ctx, "test-user5", s.db, s.T()) + testUserCtx := auth.PopulateContext(context.Background(), testUser) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test-creds5", + }, + } + + creds, err := s.db.CreateGithubCredentials(ctx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + newDescription := "new description2" + updateCredParams := params.UpdateGithubCredentialsParams{ + Description: &newDescription, + } + + _, err = s.db.UpdateGithubCredentials(testUserCtx, creds.ID, updateCredParams) + s.Require().Error(err) + s.Require().ErrorIs(err, runnerErrors.ErrNotFound) +} + +func (s *GithubTestSuite) TestAdminUserCanUpdateAnyGithubCredentials() { + ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T()) + testUser := garmTesting.CreateGARMTestUser(ctx, "test-user5", s.db, s.T()) + testUserCtx := auth.PopulateContext(context.Background(), testUser) + + credParams := params.CreateGithubCredentialsParams{ + Name: testCredsName, + Description: testCredsDescription, + Endpoint: defaultGithubEndpoint, + AuthType: params.GithubAuthTypePAT, + PAT: params.GithubPAT{ + OAuth2Token: "test-creds5", + }, + } + + creds, err := s.db.CreateGithubCredentials(testUserCtx, credParams) + s.Require().NoError(err) + s.Require().NotNil(creds) + + newDescription := "new description2" + updateCredParams := params.UpdateGithubCredentialsParams{ + Description: &newDescription, + } + + newCreds, err := s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams) + s.Require().NoError(err) + s.Require().Equal(newDescription, newCreds.Description) } func TestGithubTestSuite(t *testing.T) { diff --git a/database/sql/organizations_test.go b/database/sql/organizations_test.go index ae880da7..23736e2b 100644 --- a/database/sql/organizations_test.go +++ b/database/sql/organizations_test.go @@ -29,6 +29,7 @@ import ( "gorm.io/gorm/logger" runnerErrors "github.com/cloudbase/garm-provider-common/errors" + "github.com/cloudbase/garm/auth" dbCommon "github.com/cloudbase/garm/database/common" garmTesting "github.com/cloudbase/garm/internal/testing" "github.com/cloudbase/garm/params" @@ -50,7 +51,9 @@ type OrgTestSuite struct { StoreSQLMocked *sqlDatabase Fixtures *OrgTestFixtures - adminCtx context.Context + adminCtx context.Context + adminUserID string + testCreds params.GithubCredentials secondaryTestCreds params.GithubCredentials githubEndpoint params.GithubEndpoint @@ -85,6 +88,8 @@ func (s *OrgTestSuite) SetupTest() { adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T()) s.adminCtx = adminCtx + s.adminUserID = auth.UserID(adminCtx) + s.Require().NotEmpty(s.adminUserID) s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T()) s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint) @@ -217,8 +222,8 @@ func (s *OrgTestSuite) TestCreateOrganizationInvalidDBPassphrase() { func (s *OrgTestSuite) TestCreateOrganizationDBCreateErr() { s.Fixtures.SQLMock.ExpectBegin() s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.Fixtures.Orgs[0].CredentialsName, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.Fixtures.Orgs[0].CredentialsName, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.testCreds.ID, s.githubEndpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -355,8 +360,8 @@ func (s *OrgTestSuite) TestUpdateOrganizationDBEncryptErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Orgs[0].ID, s.Fixtures.Orgs[0].Endpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -380,8 +385,8 @@ func (s *OrgTestSuite) TestUpdateOrganizationDBSaveErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Orgs[0].ID, s.Fixtures.Orgs[0].Endpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -411,8 +416,8 @@ func (s *OrgTestSuite) TestUpdateOrganizationDBDecryptingErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Orgs[0].ID, s.Fixtures.Orgs[0].Endpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). diff --git a/database/sql/repositories_test.go b/database/sql/repositories_test.go index 5a67abd1..95c5a4e6 100644 --- a/database/sql/repositories_test.go +++ b/database/sql/repositories_test.go @@ -28,6 +28,7 @@ import ( "gorm.io/gorm" "gorm.io/gorm/logger" + "github.com/cloudbase/garm/auth" dbCommon "github.com/cloudbase/garm/database/common" garmTesting "github.com/cloudbase/garm/internal/testing" "github.com/cloudbase/garm/params" @@ -49,7 +50,9 @@ type RepoTestSuite struct { StoreSQLMocked *sqlDatabase Fixtures *RepoTestFixtures - adminCtx context.Context + adminCtx context.Context + adminUserID string + testCreds params.GithubCredentials secondaryTestCreds params.GithubCredentials githubEndpoint params.GithubEndpoint @@ -94,6 +97,8 @@ func (s *RepoTestSuite) SetupTest() { adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T()) s.adminCtx = adminCtx + s.adminUserID = auth.UserID(adminCtx) + s.Require().NotEmpty(s.adminUserID) s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T()) s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint) @@ -233,8 +238,8 @@ func (s *RepoTestSuite) TestCreateRepositoryInvalidDBPassphrase() { func (s *RepoTestSuite) TestCreateRepositoryInvalidDBCreateErr() { s.Fixtures.SQLMock.ExpectBegin() s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.Fixtures.Repos[0].CredentialsName, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.Fixtures.Repos[0].CredentialsName, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.testCreds.ID, s.githubEndpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -393,8 +398,8 @@ func (s *RepoTestSuite) TestUpdateRepositoryDBEncryptErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Repos[0].ID, s.githubEndpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -418,8 +423,8 @@ func (s *RepoTestSuite) TestUpdateRepositoryDBSaveErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Repos[0].ID, s.githubEndpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). @@ -448,8 +453,8 @@ func (s *RepoTestSuite) TestUpdateRepositoryDBDecryptingErr() { WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.Fixtures.Repos[0].ID, s.githubEndpoint.Name)) s.Fixtures.SQLMock. - ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). - WithArgs(s.secondaryTestCreds.Name, 1). + ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")). + WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1). WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}). AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name)) s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")). diff --git a/internal/testing/testing.go b/internal/testing/testing.go index adc9730b..8949f7cf 100644 --- a/internal/testing/testing.go +++ b/internal/testing/testing.go @@ -19,6 +19,7 @@ package testing import ( "context" + "fmt" "os" "path/filepath" "sort" @@ -60,6 +61,30 @@ func ImpersonateAdminContext(ctx context.Context, db common.Store, s *testing.T) return ctx } +func CreateGARMTestUser(ctx context.Context, username string, db common.Store, s *testing.T) params.User { + newUserParams := params.NewUserParams{ + Email: fmt.Sprintf("%s@localhost", username), + Username: username, + Password: "superSecretPassword@123", + IsAdmin: false, + Enabled: true, + } + + user, err := db.CreateUser(ctx, newUserParams) + if err != nil { + if errors.Is(err, runnerErrors.ErrDuplicateEntity) { + user, err = db.GetUser(ctx, newUserParams.Username) + if err != nil { + s.Fatalf("failed to get user by email: %v", err) + } + return user + } + s.Fatalf("failed to create user: %v", err) + } + + return user +} + func CreateDefaultGithubEndpoint(ctx context.Context, db common.Store, s *testing.T) params.GithubEndpoint { endpointParams := params.CreateGithubEndpointParams{ Name: "github.com", diff --git a/runner/repositories_test.go b/runner/repositories_test.go index aa8da725..717e795d 100644 --- a/runner/repositories_test.go +++ b/runner/repositories_test.go @@ -24,7 +24,6 @@ import ( "github.com/stretchr/testify/suite" runnerErrors "github.com/cloudbase/garm-provider-common/errors" - "github.com/cloudbase/garm/auth" "github.com/cloudbase/garm/database" dbCommon "github.com/cloudbase/garm/database/common" garmTesting "github.com/cloudbase/garm/internal/testing" @@ -98,7 +97,7 @@ func (s *RepoTestSuite) SetupTest() { var minIdleRunners uint = 20 providerMock := runnerCommonMocks.NewProvider(s.T()) fixtures := &RepoTestFixtures{ - AdminContext: auth.GetAdminContext(context.Background()), + AdminContext: adminCtx, Store: db, StoreRepos: repos, Providers: map[string]common.Provider{