From 16f457e809b913a5df430f918a0c601837a1750c Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sat, 10 Aug 2024 15:55:56 -0700 Subject: [PATCH 1/2] Integrated APIServcice to handler --- .vscode/settings.json | 3 + src/controller/controller_test.go | 4 +- src/core/api.go | 8 ++ src/core/core_test.go | 5 +- src/core/handler.go | 27 +++- src/core/handler_test.go | 216 ++++++++++++++++++++++-------- src/model/account.go | 1 - src/repository/repository_test.go | 2 +- src/utils/util_test.go | 2 +- 9 files changed, 197 insertions(+), 71 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8600625 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "go.testFlags": ["-v"], +} \ No newline at end of file diff --git a/src/controller/controller_test.go b/src/controller/controller_test.go index d0ff2bd..0b86e14 100644 --- a/src/controller/controller_test.go +++ b/src/controller/controller_test.go @@ -65,7 +65,7 @@ var accountV1 = model.Account{ Name: "Switcher GitOps", Version: "123", LastCommit: "123", - Status: "active", + Status: model.StatusSynced, Message: "Synced successfully", }, Settings: model.Settings{ @@ -83,7 +83,7 @@ var accountV2 = model.Account{ Name: "Switcher GitOps", Version: "123", LastCommit: "123", - Status: "active", + Status: model.StatusSynced, }, Settings: model.Settings{ Active: false, diff --git a/src/core/api.go b/src/core/api.go index 7a7dcb1..efed8bc 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -9,6 +9,7 @@ import ( "time" "github.com/golang-jwt/jwt" + "github.com/switcherapi/switcher-gitops/src/model" ) type GraphQLRequest struct { @@ -17,6 +18,7 @@ type GraphQLRequest struct { type IAPIService interface { FetchSnapshot(domainId string, environment string) (string, error) + NewDataFromJson(jsonData []byte) model.Data } type ApiService struct { @@ -31,6 +33,12 @@ func NewApiService(apiKey string, apiUrl string) *ApiService { } } +func (c *ApiService) NewDataFromJson(jsonData []byte) model.Data { + var data model.Data + json.Unmarshal(jsonData, &data) + return data +} + func (a *ApiService) FetchSnapshot(domainId string, environment string) (string, error) { // Generate a bearer token token := generateBearerToken(a.ApiKey, domainId) diff --git a/src/core/core_test.go b/src/core/core_test.go index 02ed4b4..4a3abf8 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -30,8 +30,9 @@ func setup() { accountRepository := repository.NewAccountRepositoryMongo(mongoDb) gitService := NewGitService("repoURL", "token", "main") + apiService := NewApiService("apiKey", "") comparatorService := NewComparatorService() - coreHandler = NewCoreHandler(accountRepository, gitService, comparatorService) + coreHandler = NewCoreHandler(accountRepository, gitService, apiService, comparatorService) } func shutdown() { @@ -73,7 +74,7 @@ func givenAccount() model.Account { Version: "", LastCommit: "", LastDate: "", - Status: "", + Status: model.StatusOutSync, Message: "", }, Settings: model.Settings{ diff --git a/src/core/handler.go b/src/core/handler.go index ac2b86f..0e998b2 100644 --- a/src/core/handler.go +++ b/src/core/handler.go @@ -11,14 +11,16 @@ import ( type CoreHandler struct { AccountRepository repository.AccountRepository GitService IGitService + ApiService IAPIService ComparatorService IComparatorService status int } -func NewCoreHandler(repo repository.AccountRepository, gitService IGitService, comparatorService IComparatorService) *CoreHandler { +func NewCoreHandler(repo repository.AccountRepository, gitService IGitService, apiService IAPIService, comparatorService IComparatorService) *CoreHandler { return &CoreHandler{ AccountRepository: repo, GitService: gitService, + ApiService: apiService, ComparatorService: comparatorService, } } @@ -73,7 +75,15 @@ func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.Reposi c.AccountRepository.Update(&account) // Check for changes - diff := c.checkForChanges(repositoryData.Content) + diff, err := c.checkForChanges(account.Domain.ID, account.Environment, repositoryData.Content) + + if err != nil { + // Update account status: Error + account.Domain.Status = model.StatusError + account.Domain.Message = "Error syncing up" + c.AccountRepository.Update(&account) + return + } // Apply changes c.applyChanges(account, diff) @@ -84,12 +94,17 @@ func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.Reposi c.AccountRepository.Update(&account) } -func (c *CoreHandler) checkForChanges(content string) model.DiffResult { +func (c *CoreHandler) checkForChanges(domainId string, environment string, content string) (model.DiffResult, error) { // Get Snapshot from API + snapshotJsonFromApi, err := c.ApiService.FetchSnapshot(domainId, environment) + + if err != nil { + return model.DiffResult{}, err + } // Convert API JSON to model.Snapshot - jsonLeft := []byte(content) - left := c.ComparatorService.NewSnapshotFromJson(jsonLeft) + snapshotApi := c.ApiService.NewDataFromJson([]byte(snapshotJsonFromApi)) + left := snapshotApi.Snapshot // Convert content to model.Snapshot jsonRight := []byte(content) @@ -100,7 +115,7 @@ func (c *CoreHandler) checkForChanges(content string) model.DiffResult { diffChanged := c.ComparatorService.CheckSnapshotDiff(left, right, CHANGED) diffDeleted := c.ComparatorService.CheckSnapshotDiff(left, right, DELETED) - return c.ComparatorService.MergeResults([]model.DiffResult{diffNew, diffChanged, diffDeleted}) + return c.ComparatorService.MergeResults([]model.DiffResult{diffNew, diffChanged, diffDeleted}), nil } func (c *CoreHandler) applyChanges(account model.Account, diff model.DiffResult) { diff --git a/src/core/handler_test.go b/src/core/handler_test.go index d52dc8b..5b6305d 100644 --- a/src/core/handler_test.go +++ b/src/core/handler_test.go @@ -2,6 +2,7 @@ package core import ( "context" + "encoding/json" "sync" "testing" "time" @@ -13,7 +14,8 @@ import ( func TestInitCoreHandlerCoroutine(t *testing.T) { // Given fakeGitService := NewFakeGitService() - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, NewComparatorService()) + fakeApiService := NewFakeApiService(false) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) account := givenAccount() coreHandler.AccountRepository.Create(&account) @@ -28,62 +30,96 @@ func TestInitCoreHandlerCoroutine(t *testing.T) { tearDown() } -func TestStartAccountHandlerInactiveAccount(t *testing.T) { - // Given - account := givenAccount() - account.Settings.Active = false - coreHandler.AccountRepository.Create(&account) - - // Prepare goroutine signals - var wg sync.WaitGroup - wg.Add(1) - - // Test - go coreHandler.StartAccountHandler(account, make(chan bool), &wg) - - // Wait for the goroutine to run and terminate - time.Sleep(1 * time.Second) - wg.Wait() - - // Assert - accountFromDb, _ := coreHandler.AccountRepository.FetchByDomainId(account.Domain.ID) - assert.Equal(t, "", accountFromDb.Domain.Message) - assert.Equal(t, "", accountFromDb.Domain.LastCommit) - - tearDown() -} - func TestStartAccountHandler(t *testing.T) { - // Given - fakeGitService := NewFakeGitService() - fakeGitService.status = model.StatusSynced - fakeGitService.message = "Synced successfully" - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, NewComparatorService()) - - account := givenAccount() - coreHandler.AccountRepository.Create(&account) - - // Prepare goroutine signals - var wg sync.WaitGroup - quit := make(chan bool) - wg.Add(1) - - // Test - go coreHandler.StartAccountHandler(account, quit, &wg) - - // Wait for the goroutine to run and terminate - time.Sleep(1 * time.Second) - quit <- true - wg.Wait() - - // Assert - accountFromDb, _ := coreHandler.AccountRepository.FetchByDomainId(account.Domain.ID) - assert.Equal(t, model.StatusSynced, accountFromDb.Domain.Status) - assert.Equal(t, "Synced successfully", accountFromDb.Domain.Message) - assert.Equal(t, "123", accountFromDb.Domain.LastCommit) - assert.NotEqual(t, "", accountFromDb.Domain.LastDate) - - tearDown() + t.Run("Should not sync when account is not active", func(t *testing.T) { + // Given + account := givenAccount() + account.Settings.Active = false + coreHandler.AccountRepository.Create(&account) + + // Prepare goroutine signals + var wg sync.WaitGroup + wg.Add(1) + + // Test + go coreHandler.StartAccountHandler(account, make(chan bool), &wg) + + // Wait for the goroutine to run and terminate + time.Sleep(1 * time.Second) + wg.Wait() + + // Assert + accountFromDb, _ := coreHandler.AccountRepository.FetchByDomainId(account.Domain.ID) + assert.Equal(t, model.StatusOutSync, accountFromDb.Domain.Status) + assert.Equal(t, "", accountFromDb.Domain.Message) + assert.Equal(t, "", accountFromDb.Domain.LastCommit) + + tearDown() + }) + + t.Run("Should sync successfully when repository is out of sync", func(t *testing.T) { + // Given + fakeGitService := NewFakeGitService() + fakeApiService := NewFakeApiService(false) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + + account := givenAccount() + coreHandler.AccountRepository.Create(&account) + + // Prepare goroutine signals + var wg sync.WaitGroup + quit := make(chan bool) + wg.Add(1) + + // Test + go coreHandler.StartAccountHandler(account, quit, &wg) + + // Wait for the goroutine to run and terminate + time.Sleep(1 * time.Second) + quit <- true + wg.Wait() + + // Assert + accountFromDb, _ := coreHandler.AccountRepository.FetchByDomainId(account.Domain.ID) + assert.Equal(t, model.StatusSynced, accountFromDb.Domain.Status) + assert.Equal(t, "Synced successfully", accountFromDb.Domain.Message) + assert.Equal(t, "123", accountFromDb.Domain.LastCommit) + assert.NotEqual(t, "", accountFromDb.Domain.LastDate) + + tearDown() + }) + + t.Run("Should not sync when API returns an error", func(t *testing.T) { + // Given + fakeGitService := NewFakeGitService() + fakeApiService := NewFakeApiService(true) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + + account := givenAccount() + coreHandler.AccountRepository.Create(&account) + + // Prepare goroutine signals + var wg sync.WaitGroup + quit := make(chan bool) + wg.Add(1) + + // Test + go coreHandler.StartAccountHandler(account, quit, &wg) + + // Wait for the goroutine to run and terminate + time.Sleep(1 * time.Second) + quit <- true + wg.Wait() + + // Assert + accountFromDb, _ := coreHandler.AccountRepository.FetchByDomainId(account.Domain.ID) + assert.Equal(t, model.StatusError, accountFromDb.Domain.Status) + assert.Equal(t, "Error syncing up", accountFromDb.Domain.Message) + assert.Equal(t, "123", accountFromDb.Domain.LastCommit) + assert.NotEqual(t, "", accountFromDb.Domain.LastDate) + + tearDown() + }) } // Helpers @@ -107,8 +143,25 @@ func NewFakeGitService() *FakeGitService { return &FakeGitService{ lastCommit: "123", date: time.Now().Format(time.ANSIC), - content: "Content", - status: model.StatusOutSync, + 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, } } @@ -124,3 +177,50 @@ func (f *FakeGitService) CheckForChanges(account model.Account, lastCommit strin date string, content string) (status string, message string) { return f.status, f.message } + +type FakeApiService struct { + throwError bool + response string +} + +func NewFakeApiService(throwError bool) *FakeApiService { + return &FakeApiService{ + throwError: throwError, + response: `{ + "data": { + "domain": { + "name": "Switcher GitOps", + "version": "1", + "group": [{ + "name": "Release 1", + "description": "Showcase configuration", + "activated": true, + "config": [{ + "key": "MY_SWITCHER", + "description": "", + "activated": true, + "strategies": [], + "components": [ + "switcher-playground" + ] + }] + }] + } + } + }`, + } +} + +func (f *FakeApiService) FetchSnapshot(domainId string, environment string) (string, error) { + if f.throwError { + return "", assert.AnError + } + + return f.response, nil +} + +func (f *FakeApiService) NewDataFromJson(jsonData []byte) model.Data { + var data model.Data + json.Unmarshal(jsonData, &data) + return data +} diff --git a/src/model/account.go b/src/model/account.go index df3805c..d7a2978 100644 --- a/src/model/account.go +++ b/src/model/account.go @@ -7,7 +7,6 @@ const ( ) const ( - StatusCreated = "Created" StatusSynced = "Synced" StatusOutSync = "OutSync" StatusError = "Error" diff --git a/src/repository/repository_test.go b/src/repository/repository_test.go index 090ffa5..c5bda3e 100644 --- a/src/repository/repository_test.go +++ b/src/repository/repository_test.go @@ -46,7 +46,7 @@ func givenAccount(active bool) model.Account { Name: "Switcher GitOps", Version: "123", LastCommit: "123", - Status: "active", + Status: model.StatusSynced, Message: "Synced successfully", }, Settings: model.Settings{ diff --git a/src/utils/util_test.go b/src/utils/util_test.go index 399398f..c66b13c 100644 --- a/src/utils/util_test.go +++ b/src/utils/util_test.go @@ -43,7 +43,7 @@ func givenAccount(active bool) model.Account { Name: "Switcher GitOps", Version: "123", LastCommit: "123", - Status: "active", + Status: model.StatusSynced, Message: "Synced successfully", }, Settings: model.Settings{ From 053fc20ae6a04b5f0a1d28d62a956526a9968cea Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sat, 10 Aug 2024 16:04:57 -0700 Subject: [PATCH 2/2] chore: Added integrated test for API NewDataFromJson --- src/core/api_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/api_test.go b/src/core/api_test.go index 14c060d..7ad8513 100644 --- a/src/core/api_test.go +++ b/src/core/api_test.go @@ -22,6 +22,16 @@ func TestFetchSnapshot(t *testing.T) { assert.Contains(t, snapshot, "config", "Missing config in snapshot") }) + t.Run("Should return data from snapshot", func(t *testing.T) { + apiService := NewApiService(config.GetEnv("SWITCHER_API_JWT_SECRET"), config.GetEnv("SWITCHER_API_URL")) + snapshot, _ := apiService.FetchSnapshot(config.GetEnv("API_DOMAIN_ID"), "default") + data := apiService.NewDataFromJson([]byte(snapshot)) + + assert.NotNil(t, data.Snapshot.Domain, "domain", "Missing domain in data") + assert.NotNil(t, data.Snapshot.Domain.Group, "group", "Missing groups in data") + assert.NotNil(t, data.Snapshot.Domain.Group[0].Config, "config", "Missing config in data") + }) + t.Run("Should return error - invalid API key", func(t *testing.T) { apiService := NewApiService("INVALID_KEY", config.GetEnv("SWITCHER_API_URL")) snapshot, _ := apiService.FetchSnapshot(config.GetEnv("API_DOMAIN_ID"), "default")