From 3b3ea83ecda58a9fded1bac2b27df504e5cbcd87 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 16 Apr 2026 23:51:01 +0000 Subject: [PATCH] chore: add database error handling tests for whitelist service and handler --- .../crowdsec_whitelist_handler_test.go | 92 +++++++++++++ .../crowdsec_whitelist_service_test.go | 123 ++++++++++++++++++ 2 files changed, 215 insertions(+) diff --git a/backend/internal/api/handlers/crowdsec_whitelist_handler_test.go b/backend/internal/api/handlers/crowdsec_whitelist_handler_test.go index dc03065b..7f603dea 100644 --- a/backend/internal/api/handlers/crowdsec_whitelist_handler_test.go +++ b/backend/internal/api/handlers/crowdsec_whitelist_handler_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "net/http" "net/http/httptest" "testing" @@ -173,3 +174,94 @@ func TestAddWhitelist_400_MissingField(t *testing.T) { require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, "ip_or_cidr is required", resp["error"]) } + +func TestListWhitelists_DBError(t *testing.T) { + t.Parallel() + _, r, db := setupWhitelistHandler(t) + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/whitelist", nil) + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + var resp map[string]interface{} + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, "failed to list whitelist entries", resp["error"]) +} + +func TestAddWhitelist_DBError(t *testing.T) { + t.Parallel() + _, r, db := setupWhitelistHandler(t) + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + body := `{"ip_or_cidr":"1.2.3.4","reason":"test"}` + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/whitelist", bytes.NewBufferString(body)) + req.Header.Set("Content-Type", "application/json") + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + var resp map[string]interface{} + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, "failed to add whitelist entry", resp["error"]) +} + +func TestAddWhitelist_ReloadFailure(t *testing.T) { + t.Parallel() + h, r, _ := setupWhitelistHandler(t) + mock := &mockCmdExecWhitelist{reloadErr: errors.New("cscli failed")} + h.CmdExec = mock + + body := `{"ip_or_cidr":"3.3.3.3","reason":"reload test"}` + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/whitelist", bytes.NewBufferString(body)) + req.Header.Set("Content-Type", "application/json") + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.True(t, mock.reloadCalled) +} + +func TestDeleteWhitelist_DBError(t *testing.T) { + t.Parallel() + _, r, db := setupWhitelistHandler(t) + svc := services.NewCrowdSecWhitelistService(db, "") + entry, err := svc.Add(t.Context(), "4.4.4.4", "will close db") + require.NoError(t, err) + + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/crowdsec/whitelist/"+entry.UUID, nil) + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + var resp map[string]interface{} + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, "failed to delete whitelist entry", resp["error"]) +} + +func TestDeleteWhitelist_ReloadFailure(t *testing.T) { + t.Parallel() + h, r, db := setupWhitelistHandler(t) + mock := &mockCmdExecWhitelist{reloadErr: errors.New("cscli failed")} + h.CmdExec = mock + + svc := services.NewCrowdSecWhitelistService(db, "") + entry, err := svc.Add(t.Context(), "5.5.5.5", "reload test") + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/crowdsec/whitelist/"+entry.UUID, nil) + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusNoContent, w.Code) + assert.True(t, mock.reloadCalled) +} diff --git a/backend/internal/services/crowdsec_whitelist_service_test.go b/backend/internal/services/crowdsec_whitelist_service_test.go index 9d52016f..1d1d0cf0 100644 --- a/backend/internal/services/crowdsec_whitelist_service_test.go +++ b/backend/internal/services/crowdsec_whitelist_service_test.go @@ -177,3 +177,126 @@ func TestAdd_ValidIPv6_Success(t *testing.T) { assert.Len(t, entries, 1) assert.Equal(t, "2001:db8::1", entries[0].IPOrCIDR) } + +func TestCrowdSecWhitelistService_List_DBError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + svc := services.NewCrowdSecWhitelistService(db, "") + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + _, err = svc.List(context.Background()) + assert.Error(t, err) +} + +func TestCrowdSecWhitelistService_Add_DBCreateError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + svc := services.NewCrowdSecWhitelistService(db, "") + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + _, err = svc.Add(context.Background(), "1.2.3.4", "test") + assert.Error(t, err) + assert.NotErrorIs(t, err, services.ErrInvalidIPOrCIDR) + assert.NotErrorIs(t, err, services.ErrDuplicateEntry) +} + +func TestCrowdSecWhitelistService_Delete_DBError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + svc := services.NewCrowdSecWhitelistService(db, "") + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + err = svc.Delete(context.Background(), "some-uuid") + assert.Error(t, err) + assert.NotErrorIs(t, err, services.ErrWhitelistNotFound) +} + +func TestCrowdSecWhitelistService_WriteYAML_DBError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + tmpDir := t.TempDir() + svc := services.NewCrowdSecWhitelistService(db, tmpDir) + sqlDB, err := db.DB() + require.NoError(t, err) + _ = sqlDB.Close() + + err = svc.WriteYAML(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "query entries") +} + +func TestCrowdSecWhitelistService_WriteYAML_MkdirError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + // Use a path under /dev/null which cannot have subdirectories + svc := services.NewCrowdSecWhitelistService(db, "/dev/null/impossible") + + err := svc.WriteYAML(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "create dir") +} + +func TestCrowdSecWhitelistService_WriteYAML_WriteFileError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + tmpDir := t.TempDir() + svc := services.NewCrowdSecWhitelistService(db, tmpDir) + + // Create a directory where the .tmp file would be written, causing WriteFile to fail + dir := filepath.Join(tmpDir, "config", "parsers", "s02-enrich") + require.NoError(t, os.MkdirAll(dir, 0o750)) + tmpTarget := filepath.Join(dir, "charon-whitelist.yaml.tmp") + require.NoError(t, os.MkdirAll(tmpTarget, 0o750)) + + err := svc.WriteYAML(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "write temp") +} + +func TestCrowdSecWhitelistService_Add_WriteYAMLWarning(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + // dataDir that will cause MkdirAll to fail inside WriteYAML (non-fatal) + svc := services.NewCrowdSecWhitelistService(db, "/dev/null/impossible") + + entry, err := svc.Add(context.Background(), "2.2.2.2", "yaml warn test") + require.NoError(t, err) + assert.Equal(t, "2.2.2.2", entry.IPOrCIDR) +} + +func TestCrowdSecWhitelistService_Delete_WriteYAMLWarning(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + // First add with empty dataDir so it succeeds + svcAdd := services.NewCrowdSecWhitelistService(db, "") + entry, err := svcAdd.Add(context.Background(), "3.3.3.3", "to delete") + require.NoError(t, err) + + // Now create a service with a broken dataDir and delete + svcDel := services.NewCrowdSecWhitelistService(db, "/dev/null/impossible") + err = svcDel.Delete(context.Background(), entry.UUID) + require.NoError(t, err) +} + +func TestCrowdSecWhitelistService_WriteYAML_RenameError(t *testing.T) { + t.Parallel() + db := openWhitelistTestDB(t) + tmpDir := t.TempDir() + svc := services.NewCrowdSecWhitelistService(db, tmpDir) + + // Create target as a directory so rename (atomic replace) fails + dir := filepath.Join(tmpDir, "config", "parsers", "s02-enrich") + require.NoError(t, os.MkdirAll(dir, 0o750)) + target := filepath.Join(dir, "charon-whitelist.yaml") + require.NoError(t, os.MkdirAll(target, 0o750)) + + err := svc.WriteYAML(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "rename") +}