Skip to content

feat: improve test coverage to 80% and fix CI/CD issues#24

Merged
poyrazK merged 31 commits intomainfrom
feature/coverage-improvement-80
Jan 17, 2026
Merged

feat: improve test coverage to 80% and fix CI/CD issues#24
poyrazK merged 31 commits intomainfrom
feature/coverage-improvement-80

Conversation

@poyrazK
Copy link
Copy Markdown
Owner

@poyrazK poyrazK commented Jan 17, 2026

This PR increases test coverage across multiple handlers to meet the 80% project target, resolves identified security hotspots in the libvirt adapter, and fixes CI/CD workflow version issues.

Closes #23

Summary by CodeRabbit

  • New Features

    • Role creation now accepts Description and Permissions.
    • Stack creation now accepts Template and Parameters.
  • Bug Fixes

    • Standardized error handling across many API endpoints and more consistent HTTP status codes (e.g., deletes → 204).
    • Improved cancellation, timeouts, and graceful shutdown for libvirt-backed operations.
  • Tests

    • Large expansion of error-path and context/cancellation tests across handlers and libvirt.
  • Chores

    • CI workflow action versions and linter configuration adjusted.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings January 17, 2026 13:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 17, 2026

Warning

Rate limit exceeded

@PoyrazK has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0d9a853 and 3b8d335.

📒 Files selected for processing (5)
  • internal/handlers/cron_handler_test.go
  • internal/handlers/database_handler_test.go
  • internal/handlers/function_handler_test.go
  • internal/handlers/gateway_handler_test.go
  • internal/handlers/notify_handler_test.go
📝 Walkthrough

Walkthrough

Adds context propagation and Close semantics across the Libvirt client, adapter, and mocks; threads context through adapter helpers and tests; standardizes handler error responses and expands request structs; adds extensive handler error-path tests; downgrades several GitHub Actions versions and updates CI workflow actions.

Changes

Cohort / File(s) Summary
GitHub Actions workflows
\.github/workflows/*
\.github/workflows/benchmarks.yml, \.github/workflows/ci.yml, \.github/workflows/code-quality.yml, \.github/workflows/codeql.yml, \.github/workflows/contract-tests.yml, \.github/workflows/coverage.yml, \.github/workflows/load-tests.yml, \.github/workflows/security.yml, \.github/workflows/sonar.yml
Downgraded actions/checkout v6→v4 and adjusted other action versions (golangci-lint, codecov, sonarqube); one minor formatting line.
Libvirt client interface
internal/repositories/libvirt/libvirt_client.go
Add context.Context as first parameter for Domain/Network/Storage methods and add Close() error to the interface.
Real & mock libvirt clients
internal/repositories/libvirt/real_client.go, internal/repositories/libvirt/mock_client_test.go
Implement context-aware signatures and ctx.Done checks in real client; update mock methods and Connect signature to accept context.Context.
Libvirt adapter core & tests
internal/repositories/libvirt/adapter.go, internal/repositories/libvirt/adapter_*.go (adapter_context_test.go, adapter_lifecycle_test.go, adapter_network_test.go, adapter_test.go, adapter_unit_test.go, adapter_volume_test.go)
Thread context.Context through adapter methods and helpers (resolveBinds, resolveVolumePath), add Close() on adapter, add taskWaitInterval, and add many context/cancellation and lifecycle tests.
LB proxy / exec wrapper
internal/repositories/libvirt/lb_proxy.go
Add nginxConfigFileName and nginxPidFileName constants and switch to execCommandContext wrapper instead of direct os/exec usage.
Handler error handling & request structs
internal/handlers/rbac_handler.go, internal/handlers/snapshot_handler.go, internal/handlers/stack_handler.go
Standardize invalid-input errors using internal/errors, add errInvalid* constants; extend request structs (CreateRoleRequest adds Description and Permissions; CreateStackRequest adds Template and Parameters).
Extensive handler tests (error paths)
internal/handlers/*_test.go (many files: container, cron, database, function, gateway, image, notify, rbac, secret, security_group, snapshot, stack, storage, subnet, volume, etc.)
Add numerous error-path tests (invalid JSON → 400, service errors → 500, not-found → 404), introduce route/path constants, import internal/errors, and add shared test helpers.
CLI tests minor
cmd/cloud/cli_commands_test.go
Mark unused *http.Request parameters as _ *http.Request in mock handlers (cosmetic).
Noop repo tests
internal/repositories/noop/repos_extra_test.go
New tests exercising Noop*Repository implementations.
GolangCI config
.golangci.yml
Replace per-package exclusions with centralized issues.exclude-rules, change linter configuration and enable/disable select linters.

Sequence Diagram(s)

sequenceDiagram
  participant API as HTTP Handler
  participant Adapter as LibvirtAdapter
  participant Client as LibvirtClient
  participant Libvirt as libvirt daemon

  API->>Adapter: HTTP request (ctx)
  Adapter->>Client: method(ctx, ...)
  Client->>Libvirt: libvirt call (cancellable)
  alt libvirt success
    Libvirt-->>Client: result
    Client-->>Adapter: result
    Adapter-->>API: HTTP 2xx response
  else context canceled / error
    Libvirt-->>Client: error / context.Canceled
    Client-->>Adapter: propagate error
    Adapter-->>API: map to HTTP error (4xx/5xx)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I tunneled through contexts, one hop, two hop, three,
Libvirt now listens to timeouts set by me.
Tests guard the burrow, handlers tidy and neat,
CI rolled its versions back—still steady on its feet.
🥕 Hooray! — says the rabbit, with a carrot to eat.

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Multiple test coverage expansions in handler tests (container, cron, database, function, gateway, image, notify, rbac, secret, security_group, snapshot, stack, storage, subnet, volume) extend beyond the libvirt context issue and appear unrelated to the primary objective. Clarify whether comprehensive handler test coverage improvements are part of the PR scope or should be separated into a distinct issue dedicated to handler test coverage.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: improve test coverage to 80% and fix CI/CD issues' clearly summarizes the main changes, covering both test coverage improvements and CI/CD workflow fixes present throughout the changeset.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #23: context.Context support added to LibvirtClient interface and implementations, ~50+ call sites updated in LibvirtAdapter, context-aware mock implementations added, and comprehensive context cancellation tests included.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR significantly improves test coverage across multiple handlers and repositories to meet the 80% project target. It also addresses security hotspots in the libvirt adapter by adding proper context propagation and cancellation handling, fixes CI/CD workflow version compatibility issues, and improves error handling consistency across HTTP handlers.

Changes:

  • Added comprehensive test coverage for noop repositories and libvirt adapter with context cancellation tests
  • Enhanced error handling in handlers with proper error messages and consistent patterns
  • Fixed CI/CD workflow version issues by downgrading to compatible versions (v4/v6 instead of v6/v9)
  • Added context propagation throughout libvirt client operations for proper cancellation support

Reviewed changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/repositories/noop/repos_extra_test.go New comprehensive tests for noop repository implementations
internal/repositories/libvirt/real_client.go Added context parameter to all operations for proper cancellation
internal/repositories/libvirt/mock_client_test.go Updated mock to support context parameters
internal/repositories/libvirt/libvirt_client.go Updated interface to accept context in all methods
internal/repositories/libvirt/lb_proxy.go Extracted constants and made exec commands testable
internal/repositories/libvirt/adapter*.go Added context propagation and new test files with extensive coverage
internal/handlers/*_handler_test.go Added error path tests and improved coverage
internal/handlers/*_handler.go Improved error messages with consistent patterns
.github/workflows/*.yml Downgraded GitHub Actions versions to compatible releases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cmd/cloud/cli_commands_test.go (1)

65-92: Handle JSON encode errors instead of discarding them.

_ = json.NewEncoder(w).Encode(resp) silently drops errors, which conflicts with the “no silent failures” guideline. Please check and handle the error (e.g., http.Error or set StatusInternalServerError) and apply the same pattern across the other mock handlers in this file.

♻️ Example fix
-		_ = json.NewEncoder(w).Encode(resp)
+		if err := json.NewEncoder(w).Encode(resp); err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return true
+		}
internal/handlers/rbac_handler.go (1)

31-35: Add omitempty on optional JSON fields.

Description and Permissions are optional but currently serialize empty values; add omitempty to align with the JSON-tag guidance.

♻️ Suggested fix
 type CreateRoleRequest struct {
 	Name        string              `json:"name" binding:"required"`
-	Description string              `json:"description"`
-	Permissions []domain.Permission `json:"permissions"`
+	Description string              `json:"description,omitempty"`
+	Permissions []domain.Permission `json:"permissions,omitempty"`
 }

As per coding guidelines, optional fields should include omitempty in json tags.

internal/handlers/stack_handler.go (1)

104-118: Swagger documentation doesn't match implementation.

The Swagger comment specifies @Success 204 but the implementation returns http.StatusOK (200). Align the documentation with the implementation or vice versa.

🔧 Option 1: Update Swagger to match implementation
-// `@Success` 204
+// `@Success` 200 {object} map[string]string
🔧 Option 2: Update implementation to match Swagger
-	httputil.Success(c, http.StatusOK, gin.H{"message": "stack deleted"})
+	c.Status(http.StatusNoContent)
🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 57-61: The workflow step named "Upload Coverage to Codecov" is
pinned to the old uses: codecov/codecov-action@v4; update that to the v5 series
(e.g., uses: codecov/codecov-action@v5 or the specific latest tag like `@v5.5.2`)
in the CI job so the action receives the latest fixes and security updates, and
verify any v5-required input name changes for fields like files or
fail_ci_if_error while making the update.

In @.github/workflows/load-tests.yml:
- Around line 79-83: Replace the deprecated grafana/k6-action@v0.3.0 usage with
the maintained setup and run actions: use grafana/setup-k6-action (v0.3.1) to
install k6 and then grafana/run-k6-action to execute the test; pass the existing
inputs (filename and flags) to the run action so tests/load/api-smoke.js and
flags --vus 10 --duration 30s continue to be used. Locate the block referencing
grafana/k6-action and swap it for a two-step sequence using
grafana/setup-k6-action and grafana/run-k6-action, ensuring versions are updated
to the latest stable release (v0.3.1) and that filename and flags are forwarded
to the run action.

In `@internal/handlers/gateway_handler_test.go`:
- Around line 153-220: The tests TestGatewayHandlerProxySuccess,
TestGatewayHandlerProxyWithoutSlash and TestGatewayHandlerProxyWithSlash
currently mock GetProxy to return a ReverseProxy with only a Director, causing
real network dials and StatusBadGateway; update the mocks to return a
ReverseProxy that targets the httptest.Server (use
httputil.NewSingleHostReverseProxy with the test server's URL parsed via
net/url) OR set a custom Transport on the returned *httputil.ReverseProxy that
routes requests to the test server (or a stub RoundTripper) so the proxy request
succeeds and the success path is exercised; modify the svc.On("GetProxy", ...)
returns accordingly for the functions in these tests.
- Around line 257-279: In TestGatewayHandlerProxyParamWithoutSlash, the returned
ReverseProxy has an empty Director causing nondeterministic dialing; create an
httptest.NewServer that returns 200 and configure the mock GetProxy (on
mockGatewayService) to return a ReverseProxy whose Director sets req.URL.Scheme,
req.URL.Host and req.URL.Path to route to that test server (mirror the setup in
TestGatewayHandlerProxyWithoutSlash/TestGatewayHandlerProxyWithSlash), and then
assert the deterministic expected status (adjust the assert to expect
http.StatusOK if the test server returns 200, or http.StatusBadGateway only if
you intentionally simulate a failed transport).

In `@internal/handlers/image_handler_test.go`:
- Line 261: The test uses an undefined variable pathInvalid in the DELETE
request (c.Request = httptest.NewRequest("DELETE", imagesAPI+pathInvalid, nil)),
causing build failure; define pathInvalid before use (e.g., create pathInvalid
:= "<invalid-path>" or reuse the existing invalid-path variable/name used
earlier around line 154) or replace the reference with the proper literal
invalid path used elsewhere in the test so imagesAPI+pathInvalid is valid;
ensure the variable name matches the one used in other test cases (e.g.,
pathInvalid) and is declared in the same scope as the c.Request call.
- Line 154: The test uses an undefined identifier pathInvalid in the
httptest.NewRequest call (alongside imagesAPI and uploadSuffix); define
pathInvalid as the intended test path constant (e.g., const pathInvalid =
"/invalid" or whatever the test expects) near the other constants, or replace
the identifier inline with the literal string value to match the test scenario
so the call in the test function (the line constructing the POST request)
compiles.

In `@internal/handlers/rbac_handler_test.go`:
- Around line 20-22: The test constant bindPath is incorrect: update the
bindPath constant used in rbac_handler_test.go from "/rbac/bind" to
"/rbac/bindings" so it matches the handler's actual route; locate the bindPath
variable declaration (near rolesPath and testRoleName) and change its value to
"/rbac/bindings" so tests exercise the real routing contract exposed by the
handlers.

In `@internal/handlers/stack_handler_test.go`:
- Around line 179-181: Remove the redundant duplicate assertion in the test:
there are two identical assert.Equal(t, http.StatusOK, w.Code) calls
back-to-back; keep a single assert.Equal(t, http.StatusOK, w.Code) and remove
the duplicate so the test ends with svc.AssertExpectations(t) after one status
check.

In `@internal/handlers/storage_handler_test.go`:
- Around line 188-226: Each error-path subtest (the t.Run blocks named
"UploadServiceError", "DownloadServiceError", "ListServiceError", and
"DeleteServiceError") is missing mock expectation verification; add a defer
svc.AssertExpectations(t) immediately after the svc, handler, r := setup(t) line
in each subtest so that expectations set on the mock service (methods Upload,
Download, ListObjects, DeleteObject) are asserted when the subtest finishes.

In `@internal/repositories/libvirt/adapter_test.go`:
- Around line 160-166: The mock for execCommandContext in
TestLBProxyAdapterOperations still calls the external "true" binary; replace it
with a hermetic test helper process that doesn't depend on PATH by returning an
*exec.Cmd that re-runs the test binary and triggers a TestHelperProcess helper.
Concretely, change the mock to return exec.CommandContext(ctx, os.Args[0],
"-test.run=TestHelperProcess", "--", name, strings.Join(args, " ")) with an env
var like "TEST_HELPER_PROCESS=1", and add a TestHelperProcess(t *testing.T)
function in the test file that checks the env var and exits 0 (no-op) or
simulates desired stdout/stderr/exit codes; keep using the execCommandContext
symbol and ensure TestHelperProcess is used only by tests.

In `@internal/repositories/libvirt/adapter_volume_test.go`:
- Around line 98-105: The test override of mkdirTemp currently ignores errors
from os.WriteFile; update the anonymous function assigned to mkdirTemp in
adapter_volume_test.go to capture the write error (e.g., writeErr :=
os.WriteFile(...)) and if writeErr != nil return the created temp dir and
writeErr (or wrap it) so callers see the failure instead of silently proceeding;
keep the existing defer restoring oldMkdir and ensure the function still returns
the original os.MkdirTemp error if that occurs.
- Around line 64-96: These snapshot tests (TestCreateVolumeSnapshotSuccess and
TestRestoreVolumeSnapshotSuccess) rely on /usr/bin/true via the execCommand stub
and should be skipped on non-Linux platforms; at the start of each test (or in a
shared test helper invoked by both tests) check runtime.GOOS and call
t.Skip("Snapshot tests require Linux with QEMU/KVM") when not "linux", so the
tests are gracefully skipped on unsupported platforms while leaving the existing
execCommand stub and mocks (execCommand, TestCreateVolumeSnapshotSuccess,
TestRestoreVolumeSnapshotSuccess) unchanged.

In `@internal/repositories/libvirt/real_client.go`:
- Around line 166-173: The conversion of needResults (uint32) to int32 in
RealLibvirtClient.NetworkGetDhcpLeases can overflow; before calling
r.conn.NetworkGetDhcpLeases validate needResults against math.MaxInt32 and
return a clear error if it exceeds MaxInt32 (or alternatively clamp to MaxInt32
if preferred), then safely cast to int32 for the call; update the function
signature only if you decide to change the API type instead of the bounds check.
🧹 Nitpick comments (27)
internal/handlers/snapshot_handler_test.go (2)

60-69: Consider more descriptive constant names.

The constants improve maintainability by avoiding magic strings, but some names are cryptic (e.g., testSSnap, testSpath, testSpathID). More descriptive names would improve readability.

💡 Suggested naming improvements
 const (
-	testSSnap             = "test snapshot"
-	testSpath             = "/snapshots"
-	testSpathID           = "/snapshots/"
-	testSRestore          = "/restore"
-	testVolRest           = "restored-volume"
-	snapPathInvalid       = "invalid"
-	snapHeaderContentType = "Content-Type"
-	snapAppJSON           = "application/json"
+	testSnapshotDescription = "test snapshot"
+	testSnapshotsPath       = "/snapshots"
+	testSnapshotsPathWithID = "/snapshots/"
+	testRestorePath         = "/restore"
+	testRestoredVolumeName  = "restored-volume"
+	testInvalidID           = "invalid"
+	headerContentType       = "Content-Type"
+	contentTypeJSON         = "application/json"
 )

188-194: Remove unused parameter from setup function.

The _ *testing.T parameter is unused. Either remove it or use it for test helper functionality.

💡 Proposed fix
-	setup := func(_ *testing.T) (*mockSnapshotService, *SnapshotHandler, *gin.Engine) {
+	setup := func() (*mockSnapshotService, *SnapshotHandler, *gin.Engine) {
 		svc := new(mockSnapshotService)
 		handler := NewSnapshotHandler(svc)
 		r := gin.New()
 		return svc, handler, r
 	}

Then update all calls from setup(t) to setup().

internal/repositories/noop/repos_extra_test.go (1)

12-15: Consider renaming uID to userID for clarity.

The variable uID could be more descriptive. Since it's used as a user identifier in methods like ListByUserID, renaming it to userID would improve readability.

Suggested change
 func TestNoopRepositoriesExtra(t *testing.T) {
 	ctx := context.Background()
-	uID := uuid.New()
+	userID := uuid.New()
 	id := uuid.New()

Then update all usages of uID to userID throughout the file.

internal/handlers/security_group_handler_test.go (1)

465-470: Add gin.SetMode(gin.TestMode) and use t.Helper() for consistency.

The helper function doesn't set gin test mode, but other tests in this file (lines 84, 111, 131, etc.) explicitly call gin.SetMode(gin.TestMode). This inconsistency means tests using this helper will run with verbose debug logging. Additionally, the t parameter is unused—either remove it or use it to call t.Helper() for cleaner test failure stack traces.

♻️ Suggested improvement
-func setupSecurityGroupHandlerTest(_ *testing.T) (*mockSecurityGroupService, *SecurityGroupHandler, *gin.Engine) {
+func setupSecurityGroupHandlerTest(t *testing.T) (*mockSecurityGroupService, *SecurityGroupHandler, *gin.Engine) {
+	t.Helper()
+	gin.SetMode(gin.TestMode)
 	svc := new(mockSecurityGroupService)
 	handler := NewSecurityGroupHandler(svc)
 	r := gin.New()
 	return svc, handler, r
 }
internal/handlers/storage_handler_test.go (2)

158-163: Reuse existing setupStorageHandlerTest helper.

This duplicates setupStorageHandlerTest (lines 80-86) but omits gin.SetMode(gin.TestMode), which suppresses verbose debug output during tests. Reuse the existing helper for consistency.

♻️ Suggested refactor
 func TestStorageHandlerErrorPaths(t *testing.T) {
-	setup := func(_ *testing.T) (*mockStorageService, *StorageHandler, *gin.Engine) {
-		svc := new(mockStorageService)
-		handler := NewStorageHandler(svc)
-		r := gin.New()
-		return svc, handler, r
-	}
-
 	t.Run("UploadMissingBucketOrKey", func(t *testing.T) {
-		_, handler, r := setup(t)
+		_, handler, r := setupStorageHandlerTest(t)

Apply the same change to all subtests that call setup(t).


169-178: Consider condensing verbose comments.

The explanation is helpful but overly detailed for a test file. A single concise comment would suffice.

✏️ Suggested condensed comment
-		// This URL matching might be tricky with params if one is empty.
-		// Actually if I send /storage/b1 without key, it won't match :key path segment usually.
-		// But handler logic says: bucket := c.Param("bucket"), key := c.Param("key").
-		// If I use a route like /storage/:bucket/*key, maybe?
-		// The existing route is /storage/:bucket/:key.
-		// If I request /storage/b1/ then key is empty?
-		// Let's try explicit empty key if possible or verify param logic.
-		// Actually if Params are missing from context (e.g. if I don't use router but call handler directly), it fails.
-		// But using router, if I request /storage/b1/, it might match if trailing slash enabled?
-		// Let's rely on manually setting params for this specific test to ensure we hit the check.
+		// Bypass router and set params directly to test empty key validation.
internal/handlers/image_handler_test.go (2)

344-346: Avoid silent failures with blank identifier assignments.

Per coding guidelines, blank identifier assignments like _, _ = part.Write(...) and _ = writer.Close() should be avoided. In tests, setup failures should cause early test termination to prevent false results.

♻️ Proposed fix
-		_, _ = part.Write([]byte("image content"))
-		_ = writer.Close()
+		_, err := part.Write([]byte("image content"))
+		assert.NoError(t, err)
+		err = writer.Close()
+		assert.NoError(t, err)

As per coding guidelines, silent failures should be avoided in test files.


359-364: Consider marking as test helper and using consistently.

The *testing.T parameter is unused. Adding t.Helper() improves stack traces when tests fail. Also consider using this helper more consistently across other test functions to reduce duplication.

♻️ Proposed enhancement
-func setupImageHandlerTest(_ *testing.T) (*mockImageService, *ImageHandler, *gin.Engine) {
+func setupImageHandlerTest(t *testing.T) (*mockImageService, *ImageHandler, *gin.Engine) {
+	t.Helper()
 	svc := new(mockImageService)
 	handler := NewImageHandler(svc)
 	r := gin.New()
 	return svc, handler, r
 }
internal/handlers/secret_handler_test.go (1)

149-197: Prefer a table-driven structure for these subtests.

This block is already using subtests, but the repeated setup and assertions are a great fit for a single table-driven loop to reduce duplication and keep adding cases cheap. As per coding guidelines.

♻️ Suggested refactor (table-driven)
 func TestSecretHandlerDelete(t *testing.T) {
-	t.Run("SuccessByID", func(t *testing.T) {
-		svc, handler, r := setupSecretHandlerTest(t)
-		r.DELETE(secretsPath+"/:id", handler.Delete)
-		id := uuid.New()
-		svc.On("DeleteSecret", mock.Anything, id).Return(nil)
-		req, _ := http.NewRequest(http.MethodDelete, secretsPath+"/"+id.String(), nil)
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
-		assert.Equal(t, http.StatusOK, w.Code)
-		svc.AssertExpectations(t)
-	})
-
-	t.Run("SuccessByName", func(t *testing.T) {
-		svc, handler, r := setupSecretHandlerTest(t)
-		r.DELETE(secretsPath+"/:id", handler.Delete)
-		id := uuid.New()
-		svc.On("GetSecretByName", mock.Anything, testSecretName).Return(&domain.Secret{ID: id}, nil)
-		svc.On("DeleteSecret", mock.Anything, id).Return(nil)
-		req, _ := http.NewRequest(http.MethodDelete, secretsPath+"/"+testSecretName, nil)
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
-		assert.Equal(t, http.StatusOK, w.Code)
-		svc.AssertExpectations(t)
-	})
-
-	t.Run("GetByNameError", func(t *testing.T) {
-		svc, handler, r := setupSecretHandlerTest(t)
-		r.DELETE(secretsPath+"/:id", handler.Delete)
-		svc.On("GetSecretByName", mock.Anything, testSecretName).Return(nil, errors.New(errors.NotFound, errNotFound))
-		req, _ := http.NewRequest(http.MethodDelete, secretsPath+"/"+testSecretName, nil)
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
-		assert.Equal(t, http.StatusNotFound, w.Code)
-		svc.AssertExpectations(t)
-	})
-
-	t.Run("DeleteError", func(t *testing.T) {
-		svc, handler, r := setupSecretHandlerTest(t)
-		r.DELETE(secretsPath+"/:id", handler.Delete)
-		id := uuid.New()
-		svc.On("DeleteSecret", mock.Anything, id).Return(errors.New(errors.Internal, "internal error"))
-		req, _ := http.NewRequest(http.MethodDelete, secretsPath+"/"+id.String(), nil)
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
-		assert.Equal(t, http.StatusInternalServerError, w.Code)
-		svc.AssertExpectations(t)
-	})
+	cases := []struct {
+		name    string
+		makeURL func(id uuid.UUID) string
+		setup   func(svc *mockSecretService, id uuid.UUID)
+		want    int
+	}{
+		{
+			name:    "SuccessByID",
+			makeURL: func(id uuid.UUID) string { return secretsPath + "/" + id.String() },
+			setup: func(svc *mockSecretService, id uuid.UUID) {
+				svc.On("DeleteSecret", mock.Anything, id).Return(nil)
+			},
+			want: http.StatusOK,
+		},
+		{
+			name:    "SuccessByName",
+			makeURL: func(_ uuid.UUID) string { return secretsPath + "/" + testSecretName },
+			setup: func(svc *mockSecretService, id uuid.UUID) {
+				svc.On("GetSecretByName", mock.Anything, testSecretName).Return(&domain.Secret{ID: id}, nil)
+				svc.On("DeleteSecret", mock.Anything, id).Return(nil)
+			},
+			want: http.StatusOK,
+		},
+		{
+			name:    "GetByNameError",
+			makeURL: func(_ uuid.UUID) string { return secretsPath + "/" + testSecretName },
+			setup: func(svc *mockSecretService, _ uuid.UUID) {
+				svc.On("GetSecretByName", mock.Anything, testSecretName).Return(nil, errors.New(errors.NotFound, errNotFound))
+			},
+			want: http.StatusNotFound,
+		},
+		{
+			name:    "DeleteError",
+			makeURL: func(id uuid.UUID) string { return secretsPath + "/" + id.String() },
+			setup: func(svc *mockSecretService, id uuid.UUID) {
+				svc.On("DeleteSecret", mock.Anything, id).Return(errors.New(errors.Internal, "internal error"))
+			},
+			want: http.StatusInternalServerError,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			svc, handler, r := setupSecretHandlerTest(t)
+			r.DELETE(secretsPath+"/:id", handler.Delete)
+			id := uuid.New()
+			tc.setup(svc, id)
+			req, _ := http.NewRequest(http.MethodDelete, tc.makeURL(id), nil)
+			w := httptest.NewRecorder()
+			r.ServeHTTP(w, req)
+			assert.Equal(t, tc.want, w.Code)
+			svc.AssertExpectations(t)
+		})
+	}
 }
internal/handlers/rbac_handler_test.go (1)

314-497: Prefer a table-driven structure for error-path tests.

This block is a good candidate for a table of {name, method, path, setupMocks, body, wantStatus} to reduce repetition and keep additions consistent.

As per coding guidelines, use table-driven tests in *_test.go files.

internal/handlers/notify_handler_test.go (1)

215-371: Consolidate error subtests into table-driven cases and assert mocks.
These blocks repeat setup/route wiring. A table-driven structure reduces duplication and makes it easy to add svc.AssertExpectations(t) in cases that set expectations. As per coding guidelines, use table-driven tests in *_test.go.

♻️ Example refactor for TestNotifyHandlerTopicErrors
 func TestNotifyHandlerTopicErrors(t *testing.T) {
-	t.Run("CreateInvalidJSON", func(t *testing.T) {
-		_, handler, r := setupNotifyHandlerTest(t)
-		r.POST(topicsPath, handler.CreateTopic)
-		req, _ := http.NewRequest("POST", topicsPath, bytes.NewBufferString("invalid"))
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
-		assert.Equal(t, http.StatusBadRequest, w.Code)
-	})
-
-	t.Run("CreateServiceError", func(t *testing.T) {
-		svc, handler, r := setupNotifyHandlerTest(t)
-		r.POST(topicsPath, handler.CreateTopic)
-		svc.On("CreateTopic", mock.Anything, mock.Anything).Return(nil, errors.New(errors.Internal, "error"))
-		body, _ := json.Marshal(map[string]interface{}{"name": "n"})
-		req, _ := http.NewRequest("POST", topicsPath, bytes.NewBuffer(body))
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
-		assert.Equal(t, http.StatusInternalServerError, w.Code)
-	})
-	// ... remaining subtests ...
+	deleteID := uuid.New()
+	cases := []struct {
+		name     string
+		register func(*gin.Engine, *NotifyHandler)
+		setup    func(*mockNotifyService)
+		request  func() *http.Request
+		want     int
+	}{
+		{
+			name:     "CreateInvalidJSON",
+			register: func(r *gin.Engine, h *NotifyHandler) { r.POST(topicsPath, h.CreateTopic) },
+			request:  func() *http.Request { req, _ := http.NewRequest("POST", topicsPath, bytes.NewBufferString("invalid")); return req },
+			want:     http.StatusBadRequest,
+		},
+		{
+			name:     "CreateServiceError",
+			register: func(r *gin.Engine, h *NotifyHandler) { r.POST(topicsPath, h.CreateTopic) },
+			setup:    func(svc *mockNotifyService) { svc.On("CreateTopic", mock.Anything, mock.Anything).Return(nil, errors.New(errors.Internal, "error")) },
+			request:  func() *http.Request { body, _ := json.Marshal(map[string]interface{}{"name": "n"}); req, _ := http.NewRequest("POST", topicsPath, bytes.NewBuffer(body)); return req },
+			want:     http.StatusInternalServerError,
+		},
+		{
+			name:     "ListServiceError",
+			register: func(r *gin.Engine, h *NotifyHandler) { r.GET(topicsPath, h.ListTopics) },
+			setup:    func(svc *mockNotifyService) { svc.On("ListTopics", mock.Anything).Return(nil, errors.New(errors.Internal, "error")) },
+			request:  func() *http.Request { req, _ := http.NewRequest("GET", topicsPath, nil); return req },
+			want:     http.StatusInternalServerError,
+		},
+		{
+			name:     "DeleteInvalidID",
+			register: func(r *gin.Engine, h *NotifyHandler) { r.DELETE(topicsPath+"/:id", h.DeleteTopic) },
+			request:  func() *http.Request { req, _ := http.NewRequest("DELETE", topicsPath+pathInvalid, nil); return req },
+			want:     http.StatusBadRequest,
+		},
+		{
+			name:     "DeleteServiceError",
+			register: func(r *gin.Engine, h *NotifyHandler) { r.DELETE(topicsPath+"/:id", h.DeleteTopic) },
+			setup:    func(svc *mockNotifyService) { svc.On("DeleteTopic", mock.Anything, deleteID).Return(errors.New(errors.Internal, "error")) },
+			request:  func() *http.Request { req, _ := http.NewRequest("DELETE", topicsPath+"/"+deleteID.String(), nil); return req },
+			want:     http.StatusInternalServerError,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			svc, handler, r := setupNotifyHandlerTest(t)
+			tc.register(r, handler)
+			if tc.setup != nil {
+				tc.setup(svc)
+				defer svc.AssertExpectations(t)
+			}
+			w := httptest.NewRecorder()
+			r.ServeHTTP(w, tc.request())
+			assert.Equal(t, tc.want, w.Code)
+		})
+	}
 }
internal/handlers/gateway_handler_test.go (1)

222-244: Prefer table-driven subtests for error cases.

These error-path tests are already using t.Run; consolidating into a table-driven structure would reduce duplication and align with the project’s testing guideline.

As per coding guidelines, use table-driven tests in test files.

Also applies to: 282-303

internal/handlers/subnet_handler_test.go (1)

135-160: Consider using typed errors from internal/errors for consistency.

This test uses assert.AnError which works for coverage, but other handler tests (e.g., volume_handler_test.go, function_handler_test.go) use errors.New(errors.Internal, "...") from the internal/errors package. Using typed errors ensures the error-to-HTTP-status mapping is properly exercised.

♻️ Suggested improvement
+	"github.com/poyrazk/thecloud/internal/errors"
 	"github.com/stretchr/testify/assert"

...

-	svc.On("CreateSubnet", mock.Anything, vpcID, testSubnetName, testutil.TestSubnetCIDR, mock.Anything).Return(nil, assert.AnError)
+	svc.On("CreateSubnet", mock.Anything, vpcID, testSubnetName, testutil.TestSubnetCIDR, mock.Anything).Return(nil, errors.New(errors.Internal, "service error"))
internal/handlers/function_handler_test.go (1)

218-229: Blank identifier assignments in test setup.

Lines 220-223 use _ = writer.WriteField(...) which suppresses errors. While unlikely to fail in tests, this violates the coding guideline against silent failures. Consider using require.NoError for consistency with other tests in the file that do handle errors.

♻️ Suggested improvement
+	require.NoError(t, writer.WriteField("name", "n"))
+	require.NoError(t, writer.WriteField("runtime", "r"))
+	require.NoError(t, writer.WriteField("handler", "h"))
+	require.NoError(t, writer.Close())
-	_ = writer.WriteField("name", "n")
-	_ = writer.WriteField("runtime", "r")
-	_ = writer.WriteField("handler", "h")
-	_ = writer.Close()
internal/handlers/stack_handler.go (1)

28-33: Consider adding omitempty to the optional Parameters field.

Per coding guidelines, optional fields should include omitempty in their JSON tags. The Parameters field is optional (no binding:"required") but lacks omitempty.

♻️ Suggested improvement
 type CreateStackRequest struct {
 	Name       string            `json:"name" binding:"required"`
 	Template   string            `json:"template" binding:"required"`
-	Parameters map[string]string `json:"parameters"`
+	Parameters map[string]string `json:"parameters,omitempty"`
 }
internal/repositories/libvirt/lb_proxy.go (1)

65-73: Silent failures during cleanup may mask issues.

Lines 69 and 72 use blank identifiers to ignore errors. While cleanup code often uses best-effort semantics, silently ignoring failures violates the coding guideline and may mask debugging-relevant issues.

Consider logging these errors at debug/warn level instead of ignoring them completely.

♻️ Suggested improvement
 	if _, err := os.Stat(pidPath); err == nil {
 		// Stop nginx: nginx -s stop -c ...
 		configPath := filepath.Join(configDir, nginxConfigFileName)
 		cmd := execCommandContext(ctx, "nginx", "-c", configPath, "-g", fmt.Sprintf("pid %s;", pidPath), "-s", "stop")
-		_ = cmd.Run()
+		if err := cmd.Run(); err != nil {
+			// Best-effort: log but continue with cleanup
+			// Consider adding logging here
+		}
 	}

-	_ = os.RemoveAll(configDir)
+	if err := os.RemoveAll(configDir); err != nil {
+		// Best-effort: log but continue
+		// Consider adding logging here
+	}
 	return nil
internal/handlers/container_handler_test.go (1)

170-282: Consolidate error-path cases into a table-driven suite.

These error tests repeat a lot of setup and assertions; a table-driven list of cases would reduce duplication and make adding new cases easier. As per coding guidelines, consider table-driven tests in *_test.go files.

internal/handlers/database_handler_test.go (1)

170-272: Refactor error tests into table-driven cases.

The error-path tests follow the same pattern; a table-driven approach would shrink duplication and improve readability. As per coding guidelines, consider table-driven tests in *_test.go files.

internal/handlers/cron_handler_test.go (2)

139-171: Use pause/resume suffix constants in request URLs for consistency.

This avoids divergence between routes and request construction.

Suggested update
-	req, err := http.NewRequest(http.MethodPost, cronPath+"/"+id.String()+"/pause", nil)
+	req, err := http.NewRequest(http.MethodPost, cronPath+"/"+id.String()+pauseSuffix, nil)
...
-	req, err := http.NewRequest(http.MethodPost, cronPath+"/"+id.String()+"/resume", nil)
+	req, err := http.NewRequest(http.MethodPost, cronPath+"/"+id.String()+resumeSuffix, nil)

190-315: Consider a table-driven structure for error tests.

A table of cases (invalid JSON/ID, service error, not found) would reduce repeated setup and assertions. As per coding guidelines, consider table-driven tests in *_test.go files.

internal/repositories/libvirt/adapter_lifecycle_test.go (1)

337-358: Assert mock expectations in RunTask failure tests.

Without AssertExpectations, the test won’t fail if expected calls aren’t made.

Suggested update
 func TestRunTaskFailures(t *testing.T) {
 	m := new(MockLibvirtClient)
 	a := newTestAdapter(m)
 	ctx := context.Background()
@@
 	m.On("StorageVolCreateXML", ctx, pool, mock.Anything, uint32(0)).Return(libvirt.StorageVol{}, libvirt.Error{Code: 1}).Once()
 	_, err = a.RunTask(ctx, opts)
 	assert.Error(t, err)
+
+	m.AssertExpectations(t)
 }
internal/repositories/libvirt/adapter_volume_test.go (1)

53-62: Add AssertExpectations in failure-path tests.

These tests set expectations but don’t verify them, which can allow false positives.

Suggested update
 func TestStoragePoolNotFound(t *testing.T) {
@@
 	err := a.CreateVolume(ctx, testVolName)
 	assert.Error(t, err)
+	m.AssertExpectations(t)
 }
@@
 func TestDeleteVolumeFailures(t *testing.T) {
@@
 	err = a.DeleteVolume(ctx, testVolName)
 	assert.Error(t, err)
+	m.AssertExpectations(t)
 }

Also applies to: 124-142

internal/repositories/libvirt/adapter_unit_test.go (2)

397-418: Consider handling file operation errors in test setup.

Lines 399, 401-402: The os.CreateTemp error and tmpFile.Close() error are silently ignored. While this is test code, failing early with t.Fatal on setup errors provides clearer diagnostics.

Suggested improvement
 func TestGetInstanceLogs(t *testing.T) {
-	tmpFile, _ := os.CreateTemp("", "log")
+	tmpFile, err := os.CreateTemp("", "log")
+	if err != nil {
+		t.Fatal(err)
+	}
 	defer os.Remove(tmpFile.Name())
 	_, _ = tmpFile.WriteString("log data")
-	_ = tmpFile.Close()
+	if err := tmpFile.Close(); err != nil {
+		t.Fatal(err)
+	}

436-439: Consider verifying NewLibvirtAdapter behavior.

Both return values are discarded, which provides minimal coverage. If the goal is to verify the function doesn't panic, consider at minimum checking the error or adding a comment explaining the intent.

Suggested improvement
 func TestNewLibvirtAdapter(t *testing.T) {
 	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
-	_, _ = NewLibvirtAdapter(logger, "qemu:///system")
+	// This test verifies NewLibvirtAdapter doesn't panic with typical arguments.
+	// Connection failure is expected in test environment without libvirt daemon.
+	_, err := NewLibvirtAdapter(logger, "qemu:///system")
+	// We expect an error since libvirt socket won't be available in test env
+	assert.Error(t, err)
 }
internal/repositories/libvirt/adapter_network_test.go (2)

61-89: Mock expectations may conflict across subtests.

The mock is configured with multiple expectations but all three scenarios use the same mock instance without resetting between scenarios. The mocks for invalidPortName and invalidIPName both set up DomainGetXMLDesc and NetworkGetDhcpLeases with mock.Anything, which may cause unexpected matching. Consider using separate mock instances or table-driven subtests with fresh mocks.

Suggested restructuring with isolated mocks
 func TestSetupPortForwardingErrors(t *testing.T) {
-	m := new(MockLibvirtClient)
-	a := newTestAdapter(m)
-	a.ipWaitInterval = 1 * time.Microsecond
-
-	// 1. IP Wait Failure
-	m.On("DomainLookupByName", mock.Anything, "fail").Return(libvirt.Domain{}, libvirt.Error{Code: 1})
-	a.setupPortForwarding("fail", []string{"80:80"})
+	t.Run("IP Wait Failure", func(t *testing.T) {
+		m := new(MockLibvirtClient)
+		a := newTestAdapter(m)
+		a.ipWaitInterval = 1 * time.Microsecond
+		m.On("DomainLookupByName", mock.Anything, "fail").Return(libvirt.Domain{}, libvirt.Error{Code: 1})
+		a.setupPortForwarding("fail", []string{"80:80"})
+		m.AssertExpectations(t)
+	})
 
-	// 2. Invalid port format
-	...
+	t.Run("Invalid port format", func(t *testing.T) {
+		// ... with fresh mock
+	})

91-98: Consider adding assertion or explicit documentation.

The test verifies that configureIptables doesn't crash when iptables isn't found, but there's no assertion. Consider at least adding a t.Log or comment to make the test's purpose explicit in output.

Suggested improvement
 func TestConfigureIptablesNoPath(t *testing.T) {
-	// This test depends on environment, but we can verify it doesn't crash
+	// Verify configureIptables handles missing iptables gracefully without crashing
 	m := new(MockLibvirtClient)
 	a := newTestAdapter(m)

 	a.configureIptables("test", "1.2.3.4", "80", 8080, 80)
-	// Should log error if iptables not found, but not crash
+	// If we reach here without panic, the test passes
+	// The method should log an error if iptables is not found
 }
internal/repositories/libvirt/adapter.go (1)

130-134: Silent failures on cleanup could mask issues in development.

Lines 131-132 silently ignore errors from DomainUndefine and StorageVolDelete during domain creation failure. While acceptable for cleanup paths, consider at least logging these errors at debug/warn level for troubleshooting.

Suggested improvement
 if err := a.client.DomainCreate(ctx, dom); err != nil {
-	_ = a.client.DomainUndefine(ctx, dom)
-	_ = a.client.StorageVolDelete(ctx, vol, 0)
+	if undefErr := a.client.DomainUndefine(ctx, dom); undefErr != nil {
+		a.logger.Debug("cleanup: failed to undefine domain", "error", undefErr)
+	}
+	if delErr := a.client.StorageVolDelete(ctx, vol, 0); delErr != nil {
+		a.logger.Debug("cleanup: failed to delete volume", "error", delErr)
+	}
 	return "", fmt.Errorf("failed to start domain: %w", err)
 }

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/load-tests.yml
Comment thread internal/handlers/gateway_handler_test.go
Comment thread internal/handlers/gateway_handler_test.go
Comment thread internal/handlers/image_handler_test.go Outdated
Comment thread internal/handlers/storage_handler_test.go
Comment thread internal/repositories/libvirt/adapter_test.go
Comment thread internal/repositories/libvirt/adapter_volume_test.go
Comment thread internal/repositories/libvirt/adapter_volume_test.go
Comment thread internal/repositories/libvirt/real_client.go
Copilot AI review requested due to automatic review settings January 17, 2026 14:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

.github/workflows/load-tests.yml:85

  • The workflow is using path parameter for the k6 action, but the correct parameter name is filename according to the action's documentation. This has been corrected in the change.
          path: tests/load/api-smoke.js

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@internal/handlers/rbac_handler_test.go`:
- Around line 366-373: The tests reference an undefined constant pathInvalid
causing compilation failures; add a missing constant (e.g., const pathInvalid =
"/invalid-id") to the file's constants block so tests like the
"UpdateRoleInvalidID" subtest and the other uses at the locations referenced
compile; update the same constants block where other test constants are declared
so handler.UpdateRole and the other tests using pathInvalid can use it.
🧹 Nitpick comments (4)
internal/handlers/stack_handler_test.go (1)

183-189: Consider setting gin.TestMode in the setup function.

Unlike the happy-path tests, the error-path tests don't set gin.SetMode(gin.TestMode). This omission causes Gin to run in debug mode, producing noisy log output during test execution.

♻️ Proposed fix
 func TestStackHandlerErrorPaths(t *testing.T) {
 	setup := func(_ *testing.T) (*mockStackService, *StackHandler, *gin.Engine) {
+		gin.SetMode(gin.TestMode)
 		svc := new(mockStackService)
 		handler := NewStackHandler(svc)
 		r := gin.New()
 		return svc, handler, r
 	}
internal/handlers/rbac_handler_test.go (1)

314-497: Good comprehensive error path coverage.

The TestRBACHandlerErrorPaths function provides thorough testing of error scenarios including invalid JSON, invalid IDs, and service errors. The use of subtests with t.Run and consistent error creation via errors.New(errors.Internal, "error") follows project conventions.

Consider adding defer svc.AssertExpectations(t) to subtests that set up mock expectations (e.g., CreateRoleServiceError, ListRolesServiceError, etc.) to ensure mocks are called as expected:

♻️ Example improvement for one subtest
 	t.Run("CreateRoleServiceError", func(t *testing.T) {
 		svc, handler, r := setupRBACHandlerTest(t)
+		defer svc.AssertExpectations(t)
 		r.POST(rolesPath, handler.CreateRole)
 		svc.On("CreateRole", mock.Anything, mock.Anything).Return(errors.New(errors.Internal, "error"))
internal/repositories/libvirt/adapter_test.go (2)

165-170: Preserve environment for the helper process.

Overwriting cmd.Env drops existing variables (e.g., PATH, coverage envs). Appending to the current environment is safer and avoids flaky CI behavior.

♻️ Suggested tweak
-		cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
+		cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")

183-196: Consider table-driven subtests for LBProxy operations.

These three subtests share the same setup and assertion pattern; a table-driven loop would reduce repetition. As per coding guidelines, consider consolidating them into a table-driven test.

Comment thread internal/handlers/rbac_handler_test.go
Copilot AI review requested due to automatic review settings January 17, 2026 14:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@internal/handlers/container_handler_test.go`:
- Around line 215-225: In the NotFound subtest, the mock expectation uses an
undefined identifier errNotFound; update the mock return to use the string
literal "not found" instead (locate the t.Run("NotFound") test created by
setupContainerHandlerTest and the svc.On("GetDeployment", mock.Anything,
id).Return(...) call used with handler.GetDeployment) so the errors.New(...)
call receives a valid message.

In `@internal/handlers/rbac_handler_test.go`:
- Around line 325-334: The subtests (e.g., the "CreateRoleServiceError" case
using setupRBACHandlerTest and handler.CreateRole) set service expectations with
svc.On(...) but never assert them; update each such subtest to call
svc.AssertExpectations(t) via t.Cleanup(func(){ svc.AssertExpectations(t) }) (or
defer) immediately after configuring svc.On so the test will fail if the handler
doesn't call the mocked methods — apply the same pattern to the other listed
subtests that set svc.On.
🧹 Nitpick comments (3)
internal/handlers/image_handler_test.go (2)

343-347: Avoid silent failures with blank identifier assignments.

Per coding guidelines, blank identifier assignments like _, _ = part.Write(...) and _ = writer.Close() should be avoided. Use assert.NoError for consistency with the success test at lines 134-137.

♻️ Suggested fix
 		body := new(bytes.Buffer)
 		writer := multipart.NewWriter(body)
 		part, _ := writer.CreateFormFile("file", "image.qcow2")
-		_, _ = part.Write([]byte("image content"))
-		_ = writer.Close()
+		_, err := part.Write([]byte("image content"))
+		assert.NoError(t, err)
+		err = writer.Close()
+		assert.NoError(t, err)

360-365: Unused *testing.T parameter in helper function.

The t parameter is discarded with a blank identifier. Either utilize it (e.g., call t.Helper() to improve stack traces) or remove it from the signature.

♻️ Suggested fix
-func setupImageHandlerTest(_ *testing.T) (*mockImageService, *ImageHandler, *gin.Engine) {
+func setupImageHandlerTest(t *testing.T) (*mockImageService, *ImageHandler, *gin.Engine) {
+	t.Helper()
 	svc := new(mockImageService)
 	handler := NewImageHandler(svc)
 	r := gin.New()
 	return svc, handler, r
 }
internal/handlers/rbac_handler_test.go (1)

315-498: Consider a table-driven structure for the error-path suite.
TestRBACHandlerErrorPaths repeats setup/teardown and request wiring. A table-driven loop would reduce duplication and make it easier to add more cases consistently. As per coding guidelines, prefer table-driven tests in *_test.go.

Comment on lines +215 to +225
t.Run("NotFound", func(t *testing.T) {
svc, handler, r := setupContainerHandlerTest(t)
r.GET(deploymentsPath+"/:id", handler.GetDeployment)
id := uuid.New()
svc.On("GetDeployment", mock.Anything, id).Return(nil, errors.New(errors.NotFound, errNotFound))
req, _ := http.NewRequest(http.MethodGet, deploymentsPath+"/"+id.String(), nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
svc.AssertExpectations(t)
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for errNotFound definition in the httphandlers package and beyond
echo "=== Searching for errNotFound in internal/handlers/ ==="
rg -n 'errNotFound' internal/handlers/

echo ""
echo "=== Searching for errNotFound in entire repo ==="
rg -n 'errNotFound' --type go

echo ""
echo "=== Checking container_handler_test.go file content around lines 215-225 ==="
wc -l internal/handlers/container_handler_test.go

Repository: PoyrazK/thecloud

Length of output: 2475


🏁 Script executed:

#!/bin/bash
# Check the imports and top-level definitions in the test file
echo "=== First 50 lines of container_handler_test.go (imports and constants) ==="
head -50 internal/handlers/container_handler_test.go

echo ""
echo "=== Lines around 215-225 context ==="
sed -n '205,235p' internal/handlers/container_handler_test.go

Repository: PoyrazK/thecloud

Length of output: 2779


Fix undefined identifier errNotFound in test mock expectation.

The constant errNotFound is not defined in container_handler_test.go. Replace with the string literal "not found":

Proposed fix
-		svc.On("GetDeployment", mock.Anything, id).Return(nil, errors.New(errors.NotFound, errNotFound))
+		svc.On("GetDeployment", mock.Anything, id).Return(nil, errors.New(errors.NotFound, "not found"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t.Run("NotFound", func(t *testing.T) {
svc, handler, r := setupContainerHandlerTest(t)
r.GET(deploymentsPath+"/:id", handler.GetDeployment)
id := uuid.New()
svc.On("GetDeployment", mock.Anything, id).Return(nil, errors.New(errors.NotFound, errNotFound))
req, _ := http.NewRequest(http.MethodGet, deploymentsPath+"/"+id.String(), nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
svc.AssertExpectations(t)
})
t.Run("NotFound", func(t *testing.T) {
svc, handler, r := setupContainerHandlerTest(t)
r.GET(deploymentsPath+"/:id", handler.GetDeployment)
id := uuid.New()
svc.On("GetDeployment", mock.Anything, id).Return(nil, errors.New(errors.NotFound, "not found"))
req, _ := http.NewRequest(http.MethodGet, deploymentsPath+"/"+id.String(), nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
svc.AssertExpectations(t)
})
🤖 Prompt for AI Agents
In `@internal/handlers/container_handler_test.go` around lines 215 - 225, In the
NotFound subtest, the mock expectation uses an undefined identifier errNotFound;
update the mock return to use the string literal "not found" instead (locate the
t.Run("NotFound") test created by setupContainerHandlerTest and the
svc.On("GetDeployment", mock.Anything, id).Return(...) call used with
handler.GetDeployment) so the errors.New(...) call receives a valid message.

Comment on lines +325 to +334
t.Run("CreateRoleServiceError", func(t *testing.T) {
svc, handler, r := setupRBACHandlerTest(t)
r.POST(rolesPath, handler.CreateRole)
svc.On("CreateRole", mock.Anything, mock.Anything).Return(errors.New(errors.Internal, "error"))
body, _ := json.Marshal(map[string]interface{}{"name": "n"})
req, _ := http.NewRequest("POST", rolesPath, bytes.NewBuffer(body))
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert mock expectations in subtests that set them.
Several error-path subtests configure svc.On(...) but never call svc.AssertExpectations(t), so a handler that doesn’t call the service would still pass. Consider adding t.Cleanup (or defer) for those subtests.

✅ Suggested pattern
 t.Run("CreateRoleServiceError", func(t *testing.T) {
-    svc, handler, r := setupRBACHandlerTest(t)
+    svc, handler, r := setupRBACHandlerTest(t)
+    t.Cleanup(func() { svc.AssertExpectations(t) })
     r.POST(rolesPath, handler.CreateRole)
     svc.On("CreateRole", mock.Anything, mock.Anything).Return(errors.New(errors.Internal, "error"))
     body, _ := json.Marshal(map[string]interface{}{"name": "n"})
     req, _ := http.NewRequest("POST", rolesPath, bytes.NewBuffer(body))
     w := httptest.NewRecorder()
     r.ServeHTTP(w, req)
     assert.Equal(t, http.StatusInternalServerError, w.Code)
 })

Also applies to: 336-344, 386-396, 407-416, 437-447, 458-467, 478-487, 489-497

🤖 Prompt for AI Agents
In `@internal/handlers/rbac_handler_test.go` around lines 325 - 334, The subtests
(e.g., the "CreateRoleServiceError" case using setupRBACHandlerTest and
handler.CreateRole) set service expectations with svc.On(...) but never assert
them; update each such subtest to call svc.AssertExpectations(t) via
t.Cleanup(func(){ svc.AssertExpectations(t) }) (or defer) immediately after
configuring svc.On so the test will fail if the handler doesn't call the mocked
methods — apply the same pattern to the other listed subtests that set svc.On.

Copilot AI review requested due to automatic review settings January 17, 2026 14:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
60.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(libvirt): Add context.Context support to LibvirtClient interface

2 participants