From 518b96ca28c931a20a47c515fc8862134b303daf Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:51:39 -0700 Subject: [PATCH] Creates respository data when snapshot not found --- Makefile | 2 + README.md | 2 +- src/core/git.go | 14 ++-- src/core/git_test.go | 38 ++++++++-- src/core/handler.go | 30 +++++++- src/core/handler_test.go | 150 ++++++++++++++++++++++++++++++--------- 6 files changed, 185 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index 93471d3..4005375 100644 --- a/Makefile +++ b/Makefile @@ -22,4 +22,6 @@ test: cover: go test -p 1 -coverprofile="coverage.out" ./... + +cover-html: go tool cover -html="coverage.out" diff --git a/README.md b/README.md index 52ba494..0198058 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ You might need to remove mongodb setting from docker-compose.yml if launching th 1. Clone the repository 2. Configure the environment variables in the `.env.test` file -3. `make run:test` to start the application +3. `make run-test` to start the application # Integrated tests diff --git a/src/core/git.go b/src/core/git.go index dc6250c..d5c2537 100644 --- a/src/core/git.go +++ b/src/core/git.go @@ -17,7 +17,7 @@ import ( type IGitService interface { GetRepositoryData(environment string) (*model.RepositoryData, error) - PushChanges(environment string, content string) (string, error) + PushChanges(environment string, content string, message string) (*model.RepositoryData, error) UpdateRepositorySettings(repository string, encryptedToken string, branch string, path string) } @@ -58,7 +58,7 @@ func (g *GitService) GetRepositoryData(environment string) (*model.RepositoryDat }, nil } -func (g *GitService) PushChanges(environment string, content string) (string, error) { +func (g *GitService) PushChanges(environment string, content string, message string) (*model.RepositoryData, error) { // Create an in-memory file system fs := memfs.New() @@ -78,7 +78,7 @@ func (g *GitService) PushChanges(environment string, content string) (string, er w.Add(filePath) // Commit the changes - commit, _ := w.Commit("[switcher-gitops] updated "+environment+".json", g.createCommitOptions()) + commit, _ := w.Commit("[switcher-gitops] "+message, g.createCommitOptions()) // Push the changes err := r.Push(&git.PushOptions{ @@ -87,10 +87,14 @@ func (g *GitService) PushChanges(environment string, content string) (string, er }) if err != nil { - return "", err + return nil, err } - return commit.String(), nil + return &model.RepositoryData{ + CommitHash: commit.String(), + CommitDate: time.Now().Format(time.ANSIC), + Content: content, + }, nil } func (g *GitService) createCommitOptions() *git.CommitOptions { diff --git a/src/core/git_test.go b/src/core/git_test.go index 266f447..52823d6 100644 --- a/src/core/git_test.go +++ b/src/core/git_test.go @@ -126,18 +126,42 @@ func TestPushChanges(t *testing.T) { appConfig.GetEnv("GIT_PATH")) // Test - commitHash, err := gitService.PushChanges("default", "content") + repositoryData, err := gitService.PushChanges("default", "content", "updated default.json") + data, _ := gitService.GetRepositoryData("default") + deleteBranch(branchName) // Assert assert.Nil(t, err) - assert.NotEmpty(t, commitHash) + assert.NotNil(t, repositoryData) - data, _ := gitService.GetRepositoryData("default") - assert.Equal(t, commitHash, data.CommitHash) - assert.Equal(t, "content", data.Content) + assert.Equal(t, repositoryData.CommitHash, data.CommitHash) + assert.Equal(t, repositoryData.CommitDate[0:10], data.CommitDate[0:10]) + assert.Equal(t, repositoryData.Content, data.Content) + }) + + t.Run("Should push changes to repository for a newly created account", func(t *testing.T) { + // Given + branchName := "test-branch-" + time.Now().Format("20060102150405") + createBranch(branchName) - // Clean up + gitService := NewGitService( + appConfig.GetEnv("GIT_REPO_URL"), + utils.Encrypt(appConfig.GetEnv("GIT_TOKEN"), appConfig.GetEnv("GIT_TOKEN_PRIVATE_KEY")), + branchName, + "path/to/snapshots") + + // Test + repositoryData, err := gitService.PushChanges("default", "content", "created default.json") + data, _ := gitService.GetRepositoryData("default") deleteBranch(branchName) + + // Assert + assert.Nil(t, err) + assert.NotNil(t, repositoryData) + + assert.Equal(t, repositoryData.CommitHash, data.CommitHash) + assert.Equal(t, repositoryData.CommitDate[0:10], data.CommitDate[0:10]) + assert.Equal(t, repositoryData.Content, data.Content) }) t.Run("Should fail to push changes to repository - no write access", func(t *testing.T) { @@ -149,7 +173,7 @@ func TestPushChanges(t *testing.T) { appConfig.GetEnv("GIT_PATH")) // Test - commitHash, err := gitService.PushChanges("default", "content") + commitHash, err := gitService.PushChanges("default", "content", "") // Assert assert.NotNil(t, err) diff --git a/src/core/handler.go b/src/core/handler.go index 043550f..73d3c96 100644 --- a/src/core/handler.go +++ b/src/core/handler.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/switcherapi/switcher-gitops/src/config" @@ -89,7 +90,7 @@ func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitServi gitService.UpdateRepositorySettings(account.Repository, account.Token, account.Branch, account.Path) // Fetch repository data - repositoryData, err := gitService.GetRepositoryData(account.Environment) + repositoryData, err := c.getRepositoryData(gitService, account) if err != nil { c.updateDomainStatus(*account, model.StatusError, "Failed to fetch repository data - "+err.Error(), @@ -125,6 +126,16 @@ func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitServi } } +func (c *CoreHandler) getRepositoryData(gitService IGitService, account *model.Account) (*model.RepositoryData, error) { + repositoryData, err := gitService.GetRepositoryData(account.Environment) + + if err != nil && err.Error() == "file not found" { + repositoryData, err = c.createRepositoryData(*account, gitService) + } + + return repositoryData, err +} + func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.RepositoryData, gitService IGitService) { // Update account status: Out of sync account.Domain.LastCommit = repositoryData.CommitHash @@ -234,7 +245,8 @@ func (c *CoreHandler) pushChangesToRepository(account model.Account, snapshot mo snapshotContent.Domain.Version = 0 // Push changes to repository - lastCommit, err := gitService.PushChanges(account.Environment, utils.ToJsonFromObject(snapshotContent)) + repositoryData, err := gitService.PushChanges(account.Environment, utils.ToJsonFromObject(snapshotContent), + fmt.Sprintf("updated %s.json", account.Environment)) if err != nil { return account, err @@ -242,11 +254,23 @@ func (c *CoreHandler) pushChangesToRepository(account model.Account, snapshot mo // Update domain account.Domain.Version = snapshot.Domain.Version - account.Domain.LastCommit = lastCommit + account.Domain.LastCommit = repositoryData.CommitHash return account, nil } +func (c *CoreHandler) createRepositoryData(account model.Account, gitService IGitService) (*model.RepositoryData, error) { + utils.LogInfo("[%s - %s (%s)] Creating repository data", account.ID.Hex(), account.Domain.Name, account.Environment) + + snapshotJsonFromApi, err := c.apiService.FetchSnapshot(account.Domain.ID, account.Environment) + + if err != nil { + return nil, err + } + + return gitService.PushChanges(account.Environment, snapshotJsonFromApi, fmt.Sprintf("created %s.json", account.Environment)) +} + func (c *CoreHandler) isOutSync(account model.Account, lastCommit string, snapshotVersionPayload string) bool { snapshotVersion := c.apiService.NewDataFromJson([]byte(snapshotVersionPayload)).Snapshot.Domain.Version diff --git a/src/core/handler_test.go b/src/core/handler_test.go index c5b36e7..98d33b4 100644 --- a/src/core/handler_test.go +++ b/src/core/handler_test.go @@ -64,7 +64,7 @@ func TestAccountHandlerSyncRepository(t *testing.T) { t.Run("Should sync successfully when API has a newer version", func(t *testing.T) { // Given fakeGitService := NewFakeGitService() - fakeGitService.lastCommit = "111" + fakeGitService.existingData.CommitHash = "111" fakeApiService := NewFakeApiService() coreHandler = NewCoreHandler(coreHandler.accountRepository, fakeApiService, NewComparatorService()) @@ -155,7 +155,7 @@ func TestAccountHandlerSyncRepository(t *testing.T) { t.Run("Should sync successfully when repository is up to date but not synced", func(t *testing.T) { // Given fakeGitService := NewFakeGitService() - fakeGitService.content = `{ + fakeGitService.existingData.Content = `{ "domain": { "group": [{ "name": "Release 1", @@ -196,6 +196,54 @@ func TestAccountHandlerSyncRepository(t *testing.T) { tearDown() }) + + t.Run("Should sync successfully when account was just created", func(t *testing.T) { + // Given + fakeGitService := NewFakeGitService() + fakeGitService.errorGetRepoData = "file not found" + fakeGitService.newData = fakeGitService.existingData + fakeGitService.newData.Content = `{ + "domain": { + "group": [{ + "name": "Release 1", + "description": "Showcase configuration", + "activated": true, + "config": [{ + "key": "MY_SWITCHER", + "description": "", + "activated": true, + "strategies": [], + "components": [ + "switcher-playground" + ] + }] + }] + } + }` + + fakeApiService := NewFakeApiService() + coreHandler = NewCoreHandler(coreHandler.accountRepository, fakeApiService, NewComparatorService()) + + account := givenAccount() + account.Domain.ID = "123-just-created" + accountCreated, _ := coreHandler.accountRepository.Create(&account) + + // Test + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) + + // Wait for goroutine to process + time.Sleep(1 * time.Second) + + // Assert + accountFromDb, _ := coreHandler.accountRepository.FetchByDomainIdEnvironment(accountCreated.Domain.ID, accountCreated.Environment) + assert.Equal(t, model.StatusSynced, accountFromDb.Domain.Status) + assert.Contains(t, accountFromDb.Domain.Message, model.MessageSynced) + assert.Equal(t, "123", accountFromDb.Domain.LastCommit) + assert.Equal(t, 1, accountFromDb.Domain.Version) + assert.NotEqual(t, "", accountFromDb.Domain.LastDate) + + tearDown() + }) } func TestAccountHandlerSyncAPI(t *testing.T) { @@ -231,7 +279,7 @@ func TestAccountHandlerSyncAPI(t *testing.T) { t.Run("Should sync and prune successfully when API is out of sync", func(t *testing.T) { // Given fakeGitService := NewFakeGitService() - fakeGitService.content = `{ + fakeGitService.existingData.Content = `{ "domain": { "group": [] } @@ -265,7 +313,7 @@ func TestAccountHandlerSyncAPI(t *testing.T) { t.Run("Should sync and not prune when API is out of sync", func(t *testing.T) { // Given fakeGitService := NewFakeGitService() - fakeGitService.content = `{ + fakeGitService.existingData.Content = `{ "domain": { "group": [] } @@ -347,7 +395,7 @@ func TestAccountHandlerNotSync(t *testing.T) { t.Run("Should not sync when fetch repository data returns a malformed JSON content", func(t *testing.T) { // Given fakeGitService := NewFakeGitService() - fakeGitService.content = `{ + fakeGitService.existingData.Content = `{ "domain": { "group": [{ "name": "Release 1", @@ -509,6 +557,37 @@ func TestAccountHandlerNotSync(t *testing.T) { tearDown() }) + + t.Run("Should not sync when account was just created - failed to fetch API", func(t *testing.T) { + // Given + fakeGitService := NewFakeGitService() + fakeGitService.errorGetRepoData = "file not found" + fakeGitService.newData = fakeGitService.existingData + + fakeApiService := NewFakeApiService() + fakeApiService.throwErrorSnapshot = true + coreHandler = NewCoreHandler(coreHandler.accountRepository, fakeApiService, NewComparatorService()) + + account := givenAccount() + account.Domain.ID = "123-just-created-error-fetch-api" + accountCreated, _ := coreHandler.accountRepository.Create(&account) + + // Test + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) + + // Wait for goroutine to process + time.Sleep(1 * time.Second) + + // Assert + accountFromDb, _ := coreHandler.accountRepository.FetchByDomainIdEnvironment(accountCreated.Domain.ID, accountCreated.Environment) + assert.Equal(t, model.StatusError, accountFromDb.Domain.Status) + assert.Contains(t, accountFromDb.Domain.Message, "Failed to fetch repository data") + assert.Equal(t, "", accountFromDb.Domain.LastCommit) + assert.Equal(t, 0, accountFromDb.Domain.Version) + assert.NotEqual(t, "", accountFromDb.Domain.LastDate) + + tearDown() + }) } // Helpers @@ -521,9 +600,8 @@ func tearDown() { // Fakes type FakeGitService struct { - lastCommit string - date string - content string + existingData model.RepositoryData + newData model.RepositoryData status string errorPushChanges string errorGetRepoData string @@ -531,26 +609,28 @@ type FakeGitService struct { func NewFakeGitService() *FakeGitService { return &FakeGitService{ - lastCommit: "123", - date: time.Now().Format(time.ANSIC), - content: `{ - "domain": { - "group": [{ - "name": "Release 1", - "description": "Showcase configuration", - "activated": true, - "config": [{ - "key": "MY_SWITCHER", - "description": "", - "activated": false, - "strategies": [], - "components": [ - "switcher-playground" - ] + existingData: model.RepositoryData{ + CommitHash: "123", + CommitDate: time.Now().Format(time.ANSIC), + Content: `{ + "domain": { + "group": [{ + "name": "Release 1", + "description": "Showcase configuration", + "activated": true, + "config": [{ + "key": "MY_SWITCHER", + "description": "", + "activated": false, + "strategies": [], + "components": [ + "switcher-playground" + ] + }] }] - }] - } - }`, + } + }`, + }, status: model.StatusOutSync, } } @@ -560,19 +640,19 @@ func (f *FakeGitService) GetRepositoryData(environment string) (*model.Repositor return nil, errors.New(f.errorGetRepoData) } - return &model.RepositoryData{ - CommitHash: f.lastCommit, - CommitDate: f.date, - Content: f.content, - }, nil + return &f.existingData, nil +} + +func (f *FakeGitService) CreateRepositoryData(environment string, content string) (*model.RepositoryData, error) { + return &f.newData, nil } -func (f *FakeGitService) PushChanges(environment string, content string) (string, error) { +func (f *FakeGitService) PushChanges(environment string, content string, message string) (*model.RepositoryData, error) { if f.errorPushChanges != "" { - return "", errors.New(f.errorPushChanges) + return nil, errors.New(f.errorPushChanges) } - return f.lastCommit, nil + return &f.existingData, nil } func (f *FakeGitService) UpdateRepositorySettings(repository string, token string, branch string, path string) {