From 5ccbacc940791e8cb654b3c78d19f8eeef917914 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sun, 15 Sep 2024 11:06:02 -0700 Subject: [PATCH] Added FecthAllByDomainId, fixed handler sync check --- src/controller/account.go | 14 ++++++++++ src/controller/account_test.go | 40 +++++++++++++++++++++++++++-- src/core/handler.go | 47 +++++++++++++++++++++------------- src/core/handler_test.go | 13 +++++----- src/repository/account.go | 28 +++++++++++++++++--- src/repository/account_test.go | 25 ++++++++++++++++-- src/server/app.go | 2 +- src/utils/http.go | 3 +-- 8 files changed, 136 insertions(+), 36 deletions(-) diff --git a/src/controller/account.go b/src/controller/account.go index b78ee70..d5c85f1 100644 --- a/src/controller/account.go +++ b/src/controller/account.go @@ -33,6 +33,7 @@ func NewAccountController(repo repository.AccountRepository, coreHandler *core.C func (controller *AccountController) RegisterRoutes(r *mux.Router) http.Handler { r.NewRoute().Path(controller.RouteAccountPath).Name("CreateAccount").HandlerFunc(controller.CreateAccountHandler).Methods(http.MethodPost) r.NewRoute().Path(controller.RouteAccountPath).Name("UpdateAccount").HandlerFunc(controller.UpdateAccountHandler).Methods(http.MethodPut) + r.NewRoute().Path(controller.RouteAccountPath + "/{domainId}").Name("GelAllAccountsByDomainId").HandlerFunc(controller.FetchAllAccountsByDomainIdHandler).Methods(http.MethodGet) r.NewRoute().Path(controller.RouteAccountPath + "/{domainId}/{enviroment}").Name("GetAccount").HandlerFunc(controller.FetchAccountHandler).Methods(http.MethodGet) r.NewRoute().Path(controller.RouteAccountPath + "/{domainId}/{enviroment}").Name("DeleteAccount").HandlerFunc(controller.DeleteAccountHandler).Methods(http.MethodDelete) @@ -80,6 +81,19 @@ func (controller *AccountController) FetchAccountHandler(w http.ResponseWriter, utils.ResponseJSON(w, account, http.StatusOK) } +func (controller *AccountController) FetchAllAccountsByDomainIdHandler(w http.ResponseWriter, r *http.Request) { + domainId := mux.Vars(r)["domainId"] + + accounts := controller.AccountRepository.FetchAllByDomainId(domainId) + if accounts == nil { + utils.Log(utils.LogLevelError, "Not found accounts for domain: %s", domainId) + utils.ResponseJSON(w, ErrorResponse{Error: "Not found accounts for domain: " + domainId}, http.StatusNotFound) + return + } + + utils.ResponseJSON(w, accounts, http.StatusOK) +} + func (controller *AccountController) UpdateAccountHandler(w http.ResponseWriter, r *http.Request) { var accountRequest model.Account err := json.NewDecoder(r.Body).Decode(&accountRequest) diff --git a/src/controller/account_test.go b/src/controller/account_test.go index 04b4148..9e0f413 100644 --- a/src/controller/account_test.go +++ b/src/controller/account_test.go @@ -90,17 +90,53 @@ func TestFetchAccountHandler(t *testing.T) { assert.Equal(t, http.StatusNotFound, response.Code) assert.Equal(t, "{\"error\":\"Account not found\"}", response.Body.String()) }) + + t.Run("Should fetch all accounts by domain ID", func(t *testing.T) { + // Create an account + accountV1.Domain.ID = "123-controller-fetch-all-accounts" + accountController.CreateAccountHandler(givenAccountRequest(accountV1)) + accountV1.Environment = "staging" + accountController.CreateAccountHandler(givenAccountRequest(accountV1)) + + // Test + payload := []byte("") + req, _ := http.NewRequest(http.MethodGet, accountController.RouteAccountPath+"/"+accountV1.Domain.ID, bytes.NewBuffer(payload)) + response := executeRequest(req) + + // Assert + var accountsResponse []model.Account + err := json.NewDecoder(response.Body).Decode(&accountsResponse) + + assert.Equal(t, http.StatusOK, response.Code) + assert.Nil(t, err) + assert.Equal(t, 2, len(accountsResponse)) + }) + + t.Run("Should not fetch all accounts by domain ID - not found", func(t *testing.T) { + // Test + payload := []byte("") + req, _ := http.NewRequest(http.MethodGet, accountController.RouteAccountPath+"/not-found", bytes.NewBuffer(payload)) + response := executeRequest(req) + + // Assert + assert.Equal(t, http.StatusNotFound, response.Code) + assert.Equal(t, "{\"error\":\"Not found accounts for domain: not-found\"}", response.Body.String()) + }) } func TestUpdateAccountHandler(t *testing.T) { t.Run("Should update an account", func(t *testing.T) { // Create an account accountV1.Domain.ID = "123-controller-update-account" + accountV1.Environment = "default" accountController.CreateAccountHandler(givenAccountRequest(accountV1)) - // Test - accountV2.Domain.ID = accountV1.Domain.ID + // Update the account + accountV2.Domain.ID = "123-controller-update-account" + accountV2.Environment = "default" accountV2.Domain.Message = "Updated successfully" + + // Test payload, _ := json.Marshal(accountV2) req, _ := http.NewRequest(http.MethodPut, accountController.RouteAccountPath, bytes.NewBuffer(payload)) response := executeRequest(req) diff --git a/src/core/handler.go b/src/core/handler.go index 21f58a4..008cdda 100644 --- a/src/core/handler.go +++ b/src/core/handler.go @@ -29,7 +29,7 @@ func NewCoreHandler(accountRepository repository.AccountRepository, apiService I } } -func (c *CoreHandler) InitCoreHandlerCoroutine() (int, error) { +func (c *CoreHandler) InitCoreHandlerGoroutine() (int, error) { // Check if core handler is already running if c.Status == CoreHandlerStatusRunning { return c.Status, nil @@ -39,7 +39,7 @@ func (c *CoreHandler) InitCoreHandlerCoroutine() (int, error) { c.Status = CoreHandlerStatusInit // Load all accounts - accounts, _ := c.AccountRepository.FetchAllActiveAccounts() + accounts := c.AccountRepository.FetchAllActiveAccounts() // Iterate over accounts and start account handlers for _, account := range accounts { @@ -70,7 +70,9 @@ func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitServi // Wait for account to be active if !account.Settings.Active { - utils.Log(utils.LogLevelInfo, "[%s - %s] Account is not active, waiting for activation", accountId, account.Domain.Name) + utils.Log(utils.LogLevelInfo, "[%s - %s (%s)] Account is not active, waiting for activation", + accountId, account.Domain.Name, account.Environment) + c.updateDomainStatus(*account, model.StatusPending, "Account was deactivated") time.Sleep(1 * time.Minute) continue @@ -83,7 +85,9 @@ func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitServi repositoryData, err := gitService.GetRepositoryData(account.Environment) if err != nil { - utils.Log(utils.LogLevelError, "[%s - %s] Failed to fetch repository data - %s", accountId, account.Domain.Name, err.Error()) + utils.Log(utils.LogLevelError, "[%s - %s (%s)] Failed to fetch repository data - %s", + accountId, account.Domain.Name, account.Environment, err.Error()) + c.updateDomainStatus(*account, model.StatusError, "Failed to fetch repository data - "+err.Error()) time.Sleep(1 * time.Minute) continue @@ -93,7 +97,9 @@ func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitServi snapshotVersionPayload, err := c.ApiService.FetchSnapshotVersion(account.Domain.ID, account.Environment) if err != nil { - utils.Log(utils.LogLevelError, "[%s - %s] Failed to fetch snapshot version - %s", accountId, account.Domain.Name, err.Error()) + utils.Log(utils.LogLevelError, "[%s - %s (%s)] Failed to fetch snapshot version - %s", + accountId, account.Domain.Name, account.Environment, err.Error()) + c.updateDomainStatus(*account, model.StatusError, "Failed to fetch snapshot version - "+err.Error()) time.Sleep(1 * time.Minute) continue @@ -111,9 +117,11 @@ func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitServi } func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.RepositoryData, gitService IGitService) { - utils.Log(utils.LogLevelInfo, "[%s - %s] Syncing up", account.ID.Hex(), account.Domain.Name) + utils.Log(utils.LogLevelInfo, "[%s - %s (%s)] Syncing up", account.ID.Hex(), account.Domain.Name, account.Environment) // Update account status: Out of sync + account.Domain.LastCommit = repositoryData.CommitHash + account.Domain.LastDate = repositoryData.CommitDate c.updateDomainStatus(account, model.StatusOutSync, model.MessageSyncingUp) // Check for changes @@ -124,17 +132,19 @@ func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.Reposi return } - utils.Log(utils.LogLevelDebug, "[%s - %s] SnapshotAPI version: %s - SnapshotRepo version: %s", - account.ID.Hex(), account.Domain.Name, fmt.Sprint(snapshotApi.Domain.Version), fmt.Sprint(account.Domain.Version)) + utils.Log(utils.LogLevelDebug, "[%s - %s (%s)] SnapshotAPI version: %s - SnapshotRepo version: %s", + account.ID.Hex(), account.Domain.Name, account.Environment, fmt.Sprint(snapshotApi.Domain.Version), fmt.Sprint(account.Domain.Version)) // Apply changes changeSource := "" if snapshotApi.Domain.Version > account.Domain.Version { changeSource = "Repository" - if c.isRepositoryOutSync(account, repositoryData, diff) { + if c.isRepositoryOutSync(repositoryData, diff) { account, err = c.applyChangesToRepository(account, snapshotApi, gitService) } else { - utils.Log(utils.LogLevelInfo, "[%s - %s] Repository is up to date", account.ID.Hex(), account.Domain.Name) + utils.Log(utils.LogLevelInfo, "[%s - %s (%s)] Repository is up to date", + account.ID.Hex(), account.Domain.Name, account.Environment) + account.Domain.Version = snapshotApi.Domain.Version account.Domain.LastCommit = repositoryData.CommitHash } @@ -144,7 +154,9 @@ func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.Reposi } if err != nil { - utils.Log(utils.LogLevelError, "[%s - %s] Failed to apply changes [%s] - %s", account.ID.Hex(), account.Domain.Name, changeSource, err.Error()) + utils.Log(utils.LogLevelError, "[%s - %s (%s)] Failed to apply changes [%s] - %s", + account.ID.Hex(), account.Domain.Name, account.Environment, changeSource, err.Error()) + c.updateDomainStatus(account, model.StatusError, "Failed to apply changes ["+changeSource+"] - "+err.Error()) return } @@ -178,7 +190,7 @@ func (c *CoreHandler) checkForChanges(account model.Account, content string) (mo } func (c *CoreHandler) applyChangesToAPI(account model.Account, repositoryData *model.RepositoryData, diff model.DiffResult) model.Account { - utils.Log(utils.LogLevelInfo, "[%s - %s] Pushing changes to API", account.ID.Hex(), account.Domain.Name) + utils.Log(utils.LogLevelInfo, "[%s - %s (%s)] Pushing changes to API", account.ID.Hex(), account.Domain.Name, account.Environment) // Removed deleted if force prune is disabled if !account.Settings.ForcePrune { @@ -195,7 +207,7 @@ func (c *CoreHandler) applyChangesToAPI(account model.Account, repositoryData *m } func (c *CoreHandler) applyChangesToRepository(account model.Account, snapshot model.Snapshot, gitService IGitService) (model.Account, error) { - utils.Log(utils.LogLevelInfo, "[%s - %s] Pushing changes to repository", account.ID.Hex(), account.Domain.Name) + utils.Log(utils.LogLevelInfo, "[%s - %s (%s)] Pushing changes to repository", account.ID.Hex(), account.Domain.Name, account.Environment) // Remove version from domain snapshotContent := snapshot @@ -217,17 +229,16 @@ func (c *CoreHandler) applyChangesToRepository(account model.Account, snapshot m func (c *CoreHandler) isOutSync(account model.Account, lastCommit string, snapshotVersionPayload string) bool { snapshotVersion := c.ApiService.NewDataFromJson([]byte(snapshotVersionPayload)).Snapshot.Domain.Version - utils.Log(utils.LogLevelDebug, "[%s - %s] Checking account - Last commit: %s - Domain Version: %d - Snapshot Version: %d", - account.ID.Hex(), account.Domain.Name, account.Domain.LastCommit, account.Domain.Version, snapshotVersion) + utils.Log(utils.LogLevelDebug, "[%s - %s (%s)] Checking account - Last commit: %s - Domain Version: %d - Snapshot Version: %d", + account.ID.Hex(), account.Domain.Name, account.Environment, account.Domain.LastCommit, account.Domain.Version, snapshotVersion) return account.Domain.LastCommit == "" || // First sync account.Domain.LastCommit != lastCommit || // Repository out of sync account.Domain.Version != snapshotVersion // API out of sync } -func (c *CoreHandler) isRepositoryOutSync(account model.Account, repositoryData *model.RepositoryData, diff model.DiffResult) bool { - return account.Domain.Version == 0 || // First/Force-push sync - len(repositoryData.Content) <= 1 || // File is empty +func (c *CoreHandler) isRepositoryOutSync(repositoryData *model.RepositoryData, diff model.DiffResult) bool { + return len(repositoryData.Content) <= 1 || // File is empty len(diff.Changes) > 0 // Changes detected } diff --git a/src/core/handler_test.go b/src/core/handler_test.go index b4dea43..e667e19 100644 --- a/src/core/handler_test.go +++ b/src/core/handler_test.go @@ -12,7 +12,7 @@ import ( "github.com/switcherapi/switcher-gitops/src/model" ) -func TestInitCoreHandlerCoroutine(t *testing.T) { +func TestInitCoreHandlerGoroutine(t *testing.T) { t.Run("Should start account handlers for all active accounts", func(t *testing.T) { // Given fakeApiService := NewFakeApiService() @@ -23,7 +23,7 @@ func TestInitCoreHandlerCoroutine(t *testing.T) { accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - status, err := coreHandler.InitCoreHandlerCoroutine() + status, err := coreHandler.InitCoreHandlerGoroutine() // Terminate the goroutine coreHandler.AccountRepository.DeleteByDomainIdEnvironment(accountCreated.Domain.ID, accountCreated.Environment) @@ -46,8 +46,8 @@ func TestInitCoreHandlerCoroutine(t *testing.T) { accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - coreHandler.InitCoreHandlerCoroutine() - status, _ := coreHandler.InitCoreHandlerCoroutine() + coreHandler.InitCoreHandlerGoroutine() + status, _ := coreHandler.InitCoreHandlerGoroutine() // Terminate the goroutine coreHandler.AccountRepository.DeleteByDomainIdEnvironment(accountCreated.Domain.ID, accountCreated.Environment) @@ -211,7 +211,6 @@ func TestStartAccountHandler(t *testing.T) { account := givenAccount() account.Domain.ID = "123-up-to-date-not-synced" - account.Domain.Version = -1 // Different from the API version accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test @@ -350,7 +349,7 @@ func TestStartAccountHandler(t *testing.T) { 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 check for changes") - assert.Equal(t, "", accountFromDb.Domain.LastCommit) + assert.Equal(t, "123", accountFromDb.Domain.LastCommit) assert.NotEqual(t, "", accountFromDb.Domain.LastDate) tearDown() @@ -379,7 +378,7 @@ func TestStartAccountHandler(t *testing.T) { assert.Equal(t, model.StatusError, accountFromDb.Domain.Status) assert.Contains(t, accountFromDb.Domain.Message, "authorization failed") assert.Contains(t, accountFromDb.Domain.Message, "Failed to apply changes [Repository]") - assert.Equal(t, "", accountFromDb.Domain.LastCommit) + assert.Equal(t, "123", accountFromDb.Domain.LastCommit) assert.NotEqual(t, "", accountFromDb.Domain.LastDate) tearDown() diff --git a/src/repository/account.go b/src/repository/account.go index b0a095b..147bf7b 100644 --- a/src/repository/account.go +++ b/src/repository/account.go @@ -17,7 +17,8 @@ type AccountRepository interface { Create(account *model.Account) (*model.Account, error) FetchByAccountId(accountId string) (*model.Account, error) FetchByDomainIdEnvironment(domainId string, environment string) (*model.Account, error) - FetchAllActiveAccounts() ([]model.Account, error) + FetchAllByDomainId(domainId string) []model.Account + FetchAllActiveAccounts() []model.Account Update(account *model.Account) (*model.Account, error) DeleteByAccountId(accountId string) error DeleteByDomainIdEnvironment(domainId string, environment string) error @@ -69,7 +70,7 @@ func (repo *AccountRepositoryMongo) FetchByDomainIdEnvironment(domainId string, defer cancel() var account model.Account - filter := primitive.M{domainIdFilter: domainId} + filter := primitive.M{domainIdFilter: domainId, environmentFilter: environment} err := collection.FindOne(ctx, filter).Decode(&account) if err != nil { return nil, err @@ -78,7 +79,26 @@ func (repo *AccountRepositoryMongo) FetchByDomainIdEnvironment(domainId string, return &account, nil } -func (repo *AccountRepositoryMongo) FetchAllActiveAccounts() ([]model.Account, error) { +func (repo *AccountRepositoryMongo) FetchAllByDomainId(domainId string) []model.Account { + collection, ctx, cancel := getDbContext(repo) + defer cancel() + + filter := primitive.M{domainIdFilter: domainId} + cursor, _ := collection.Find(ctx, filter) + + var accounts []model.Account + for cursor.Next(ctx) { + var account model.Account + err := cursor.Decode(&account) + if err == nil { + accounts = append(accounts, account) + } + } + + return accounts +} + +func (repo *AccountRepositoryMongo) FetchAllActiveAccounts() []model.Account { collection, ctx, cancel := getDbContext(repo) defer cancel() @@ -94,7 +114,7 @@ func (repo *AccountRepositoryMongo) FetchAllActiveAccounts() ([]model.Account, e } } - return accounts, nil + return accounts } func (repo *AccountRepositoryMongo) Update(account *model.Account) (*model.Account, error) { diff --git a/src/repository/account_test.go b/src/repository/account_test.go index 72ea675..0a7e769 100644 --- a/src/repository/account_test.go +++ b/src/repository/account_test.go @@ -115,12 +115,33 @@ func TestFetchAccount(t *testing.T) { accountRepository.Create(&account2) // Test - accounts, err := accountRepository.FetchAllActiveAccounts() + accounts := accountRepository.FetchAllActiveAccounts() // Assert - assert.Nil(t, err) + assert.NotNil(t, accounts) assert.Equal(t, 1, len(accounts)) }) + + t.Run("Should fetch all accounts by domain ID", func(t *testing.T) { + // Drop collection + mongoDb.Collection("accounts").Drop(context.Background()) + + // Given + account1 := givenAccount(true) + account1.Domain.ID = "123-fetch-all-accounts-by-domain-id" + account2 := givenAccount(true) + account2.Domain.ID = "123-fetch-all-accounts-by-domain-id" + + accountRepository.Create(&account1) + accountRepository.Create(&account2) + + // Test + accounts := accountRepository.FetchAllByDomainId(account1.Domain.ID) + + // Assert + assert.NotNil(t, accounts) + assert.Equal(t, 2, len(accounts)) + }) } func TestUpdateAccount(t *testing.T) { diff --git a/src/server/app.go b/src/server/app.go index e2988c0..5f12072 100644 --- a/src/server/app.go +++ b/src/server/app.go @@ -82,7 +82,7 @@ func initCoreHandler(db *mongo.Database) *core.CoreHandler { ) coreHandler := core.NewCoreHandler(accountRepository, apiService, comparatorService) - coreHandler.InitCoreHandlerCoroutine() + coreHandler.InitCoreHandlerGoroutine() return coreHandler } diff --git a/src/utils/http.go b/src/utils/http.go index f69a2f8..973d882 100644 --- a/src/utils/http.go +++ b/src/utils/http.go @@ -2,7 +2,6 @@ package utils import ( "encoding/json" - "fmt" "net/http" ) @@ -12,7 +11,7 @@ func ResponseJSON(w http.ResponseWriter, data interface{}, status int) { encodedData, err := json.Marshal(data) if err != nil { - fmt.Println("Error encoding JSON:", err) + Log(LogLevelError, "Error encoding JSON: %s", err.Error()) return }