Skip to content

Migrate AuthService to Real Unit Tests#67

Merged
poyrazK merged 6 commits intomainfrom
feature/auth-service-real-unit-tests
Feb 25, 2026
Merged

Migrate AuthService to Real Unit Tests#67
poyrazK merged 6 commits intomainfrom
feature/auth-service-real-unit-tests

Conversation

@poyrazK
Copy link
Copy Markdown
Owner

@poyrazK poyrazK commented Feb 25, 2026

This PR replaces mock-based unit tests for AuthService with tests using real database dependencies via testcontainers.

Summary by CodeRabbit

  • New Features

    • Added configurable account lockout duration, allowing lockout timing to be adjusted at runtime.
  • Tests

    • Restructured authentication tests: added comprehensive lockout integration tests and dynamic test data generation.
    • Consolidated and cleaned up test database teardown for more reliable test runs.
    • Removed several legacy unit tests as part of the test suite reorganization.

Copilot AI review requested due to automatic review settings February 25, 2026 13:08
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 25, 2026

Warning

Rate limit exceeded

@poyrazK has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 41 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 4b2dfbc and 30dfd14.

📒 Files selected for processing (1)
  • internal/core/services/auth_test.go
📝 Walkthrough

Walkthrough

Add configurable lockout duration to AuthService, remove several legacy unit test files, expand and reorganize auth integration/unit tests (including real lockout tests), and make test DB cleanup dynamic and resilient.

Changes

Cohort / File(s) Summary
Auth service implementation
internal/core/services/auth.go
Added lockoutDuration field, defaultLockout constant, initialized default in NewAuthService, and added SetLockoutDuration(d time.Duration) (thread-safe) to make lockout period configurable. Lockout logic now references the configurable duration.
Consolidated test suite
internal/core/services/auth_test.go
Significantly updated: replaced static emails with UUID-based emails, added imports (reflect, uuid, time), introduced TestAuthService_LockoutLogicReal with subtests covering lockout, recovery, and expiration, and adjusted multiple tests to use dynamic data and timing-based assertions.
Deleted legacy tests
internal/core/services/auth_internal_test.go, internal/core/services/auth_lockout_unit_test.go, internal/core/services/auth_unit_test.go
Removed entire test files (unit and internal tests), including mocks and multiple subtests for register/login/validate and lockout unit scenarios.
Test setup / DB cleanup
internal/core/services/setup_test.go
Replaced hardcoded table list with dynamic query of public schema base tables (excluding schema_migrations), collect table names, and perform a single TRUNCATE ... CASCADE RESTART IDENTITY. Added defensive error handling and early returns when no tables found or on query errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through code with nimble feet,
Made lockouts tunable — neat and fleet.
Tests reorganized, old files cleared away,
Tables truncated so tests can play,
A happy rabbit coding night and day 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Migrate AuthService to Real Unit Tests' accurately summarizes the main change: replacing mock-based unit tests with real database-backed tests using testcontainers.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/auth-service-real-unit-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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 migrates the AuthService tests from mock-based unit tests to integration tests using real database dependencies via testcontainers. The migration improves test realism while maintaining comprehensive coverage of the authentication workflows.

Changes:

  • Deleted auth_internal_test.go which contained mock-based tests for the AuthService
  • Enhanced auth_test.go with structured subtests for Register, Login, and new error scenarios using real database connections
  • Added mixed testing approach for error scenarios, combining real UserRepo with mocked services to test rollback and error handling

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

File Description
internal/core/services/auth_test.go Expanded with real DB tests for Register (Success, DuplicateEmail, WeakPassword), Login (Success, InvalidPassword, UserNotFound), Lockout, ValidateUser, and added error scenarios for tenant/identity failures; updated existing tests with unique emails using uuid
internal/core/services/auth_internal_test.go Removed mock-based internal tests; functionality migrated to auth_test.go with real database

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

Comment thread internal/core/services/auth_test.go Outdated
func TestAuthServiceValidateToken(t *testing.T) {
_, svc, _, identitySvc := setupAuthServiceTest(t)
ctx := context.Background()
user, _ := svc.Register(ctx, "val_" + uuid.NewString() + "@test.com", testPassword, "Val User")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The error from svc.Register is being ignored with _. If registration fails, the ValidateUser test will not function correctly since it assumes a user exists. Consider using require.NoError to ensure the test setup is valid.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +127 to +130
mockIdentity := new(MockIdentityService)
mockAudit := new(MockAuditService)
mockTenant := new(MockTenantService)
svc := services.NewAuthService(userRepo, mockIdentity, mockAudit, mockTenant)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The PR description states this is migrating from mock-based unit tests to tests using real database dependencies. However, the TestAuthService_ErrorScenarios test still uses mocks for IdentityService, AuditService, and TenantService. While this mixed approach is valid for testing specific error scenarios, consider documenting why mocks are still needed for these specific cases or whether these scenarios could also use real services for consistency.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
t.Run("Login_IdentityFailure", func(t *testing.T) {
email := "idfail_" + uuid.NewString() + "@test.com"
user := &domain.User{ID: uuid.New(), Email: email}
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The error from bcrypt.GenerateFromPassword is being ignored with _. If password hashing fails, the test will insert a user with an invalid password hash, leading to confusing test failures. Consider using require.NoError to ensure password hashing succeeds in the test setup.

Suggested change
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
hash, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
require.NoError(t, err)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
assert.Contains(t, err.Error(), "failed to create personal tenant")

// Verify user was rolled back (deleted)
user, _ := userRepo.GetByEmail(ctx, email)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The error from userRepo.GetByEmail is being ignored with _. If this call fails for reasons other than "user not found" (e.g., database connection error), the test could incorrectly pass when it should fail. Consider checking the error to distinguish between "user not found" (expected) and actual errors (unexpected), or use require.NoError if nil is expected after successful rollback.

Suggested change
user, _ := userRepo.GetByEmail(ctx, email)
user, err := userRepo.GetByEmail(ctx, email)
require.NoError(t, err)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
apiKey, err := identitySvc.CreateKey(ctx, user.ID, "session")
require.NoError(t, err)
func TestAuthService_ErrorScenarios(t *testing.T) {
db := setupDB(t)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The TestAuthService_ErrorScenarios test doesn't call cleanDB(t, db) before running subtests. This could lead to test pollution if the database has leftover data from previous tests, potentially causing flaky tests. Consider adding cleanDB(t, db) after line 125 to ensure a clean slate for the error scenarios tests.

Suggested change
db := setupDB(t)
db := setupDB(t)
cleanDB(t, db)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
assert.NotEmpty(t, token)
assert.Equal(t, email, user.Email)
email := "login_" + uuid.NewString() + "@example.com"
_, _ = svc.Register(ctx, email, testPassword, "Login User")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The error from svc.Register is being ignored with _. If registration fails, this test should fail since the rest of the test assumes a user exists for login testing. Consider using require.NoError to ensure the test setup is valid.

Suggested change
_, _ = svc.Register(ctx, email, testPassword, "Login User")
_, err := svc.Register(ctx, email, testPassword, "Login User")
require.NoError(t, err)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
_, svc, _, _ := setupAuthServiceTest(t)
ctx := context.Background()
email := "locked_" + uuid.NewString() + "@test.com"
_, _ = svc.Register(ctx, email, testPassword, "Locked User")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The error from svc.Register is being ignored with _. If registration fails, the lockout test will not function correctly since it assumes a user exists. Consider using require.NoError to ensure the test setup is valid.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +152 to +157
_ = userRepo.Create(ctx, user)

mockIdentity.On("CreateKey", mock.Anything, user.ID, "Default Key").
Return(nil, errors.New("key failed")).Once()

_, _, err := svc.Login(ctx, email, testPassword)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The error from userRepo.Create is being ignored with _. If user creation fails, the Login_IdentityFailure test will not function correctly since it assumes a user exists in the database for login testing. Consider using require.NoError to ensure the test setup is valid.

Suggested change
_ = userRepo.Create(ctx, user)
mockIdentity.On("CreateKey", mock.Anything, user.ID, "Default Key").
Return(nil, errors.New("key failed")).Once()
_, _, err := svc.Login(ctx, email, testPassword)
err := userRepo.Create(ctx, user)
require.NoError(t, err)
mockIdentity.On("CreateKey", mock.Anything, user.ID, "Default Key").
Return(nil, errors.New("key failed")).Once()
_, _, err = svc.Login(ctx, email, testPassword)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated

t.Run("DuplicateEmail", func(t *testing.T) {
email := "dup_" + uuid.NewString() + "@test.com"
_, _ = svc.Register(ctx, email, testPassword, "User")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The first svc.Register call in the DuplicateEmail test is ignoring both the returned user and error with _. If the first registration fails, the test will not behave as expected. Consider using require.NoError to ensure the first registration succeeds before testing the duplicate scenario.

Copilot uses AI. Check for mistakes.
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: 6

Caution

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

⚠️ Outside diff range comments (2)
internal/core/services/auth_test.go (2)

40-69: ⚠️ Potential issue | 🟠 Major

Tests are not table-driven.

TestAuthServiceRegister (and the other test functions below) use t.Run for subtests but don't follow the table-driven pattern ([]struct{ name, input, expected … } driven by a for _, tc := range tests loop). This is required by the project's test standards.

♻️ Example table-driven shape for RegisterTests
-func TestAuthServiceRegister(t *testing.T) {
-	_, svc, userRepo, _ := setupAuthServiceTest(t)
-	ctx := context.Background()
-
-	t.Run("Success", func(t *testing.T) { … })
-	t.Run("DuplicateEmail", func(t *testing.T) { … })
-	t.Run("WeakPassword", func(t *testing.T) { … })
-}
+func TestAuthServiceRegister(t *testing.T) {
+	tests := []struct {
+		name        string
+		email       func() string
+		password    string
+		wantErr     bool
+		errContains string
+	}{
+		{name: "Success",        email: func() string { return "reg_" + uuid.NewString() + "@example.com" }, password: testPassword},
+		{name: "DuplicateEmail", email: func() string { return "dup_" + uuid.NewString() + "@test.com"   }, password: testPassword, wantErr: true, errContains: "already exists"},
+		{name: "WeakPassword",   email: func() string { return "weak_" + uuid.NewString() + "@test.com"  }, password: "123",         wantErr: true, errContains: "too weak"},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) { … })
+	}
+}

As per coding guidelines: "Use table-driven tests in test files" (**/*_test.go).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 40 - 69, Convert
TestAuthServiceRegister to a table-driven test: create a []struct slice with
fields like name, email, password, displayName, expectError, expectErrContains,
and any pre-test setup flags, then loop over it with for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { ... }) }; inside each case call
svc.Register(ctx, tc.email, tc.password, tc.displayName) and assert based on
tc.expectError (for success also verify persistence via userRepo.GetByEmail(ctx,
tc.email) and compare IDs); ensure existing "DuplicateEmail" case is implemented
by seeding the user before running that test case and reuse symbols
TestAuthServiceRegister, svc.Register, userRepo.GetByEmail and testPassword to
locate the code to change.

22-38: ⚠️ Potential issue | 🟠 Major

Entire test suite violates the "no real external dependencies in unit tests" guideline.

Every test wired through setupAuthServiceTest creates real pgx pools, real repositories, and real service implementations. The PR title frames this as intentional, but it directly contradicts the project's stated standards.

All service-layer tests must use testify/mock objects for the repository layer rather than live Postgres connections. The TestAuthService_ErrorScenarios block (lines 124–161) is the only test that partially does this, but it still uses a real postgres.UserRepo.

♻️ Sketch of the correct pattern for the setup helper
-func setupAuthServiceTest(t *testing.T) (*pgxpool.Pool, *services.AuthService, *postgres.UserRepo, *services.IdentityService) {
-	t.Helper()
-	db := setupDB(t)
-	cleanDB(t, db)
-
-	userRepo := postgres.NewUserRepo(db)
-	auditRepo := postgres.NewAuditRepository(db)
-	identityRepo := postgres.NewIdentityRepository(db)
-	tenantRepo := postgres.NewTenantRepo(db)
-
-	auditSvc := services.NewAuditService(auditRepo)
-	identitySvc := services.NewIdentityService(identityRepo, auditSvc, slog.Default())
-	tenantSvc := services.NewTenantService(tenantRepo, userRepo, slog.Default())
-	svc := services.NewAuthService(userRepo, identitySvc, auditSvc, tenantSvc)
-
-	return db, svc, userRepo, identitySvc
-}
+func setupAuthServiceTest(t *testing.T) (*services.AuthService, *MockUserRepo, *MockIdentityService, *MockAuditService, *MockTenantService) {
+	t.Helper()
+	mockUserRepo    := new(MockUserRepo)
+	mockIdentity    := new(MockIdentityService)
+	mockAudit       := new(MockAuditService)
+	mockTenant      := new(MockTenantService)
+	svc := services.NewAuthService(mockUserRepo, mockIdentity, mockAudit, mockTenant)
+	return svc, mockUserRepo, mockIdentity, mockAudit, mockTenant
+}

As per coding guidelines: "Do not test with real external dependencies in unit tests - use mocks instead" (**/*_test.go) and "Mock repositories in service tests" (internal/core/services/**/*_test.go).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 22 - 38, The test helper
setupAuthServiceTest currently creates real DB connections and concrete repo
implementations (postgres.NewUserRepo, postgres.NewAuditRepository,
postgres.NewIdentityRepository, postgres.NewTenantRepo) and wires real services
into services.NewAuthService; change it to use testify/mock-based repository
mocks instead: replace the postgres.* repo constructions with mock
implementations (or testify/mock-generated mocks) and inject those mock repo
interfaces into AuditService/IdentityService/TenantService/AuthService in the
setup helper, and update TestAuthService_ErrorScenarios to use the mock UserRepo
rather than postgres.UserRepo so all service tests avoid real pgx pools and real
DB dependencies.
♻️ Duplicate comments (1)
internal/core/services/auth_test.go (1)

163-254: Real-dependency violations inherited from setupAuthServiceTest.

TestAuthServiceRevokeToken, TestAuthServiceRotateToken, TestAuthServiceLogout, and TestAuthServiceTokenRotationIntegration all inherit the real-DB setup. The root cause is already flagged above at setupAuthServiceTest. No additional distinct issues in this block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 163 - 254, These tests
(TestAuthServiceRevokeToken, TestAuthServiceRotateToken, TestAuthServiceLogout,
TestAuthServiceTokenRotationIntegration) are unintentionally using the real DB
because they call setupAuthServiceTest; update the tests to use a test-friendly
setup that uses an in-memory or mocked datastore (e.g., add a new helper
setupAuthServiceTestWithMock or add an option to setupAuthServiceTest to return
a mock DB), then update each test to call the mock setup (replace
setupAuthServiceTest(...) with the mock variant) so the IdentityService and
AuthService operate against the fake DB; ensure the new helper returns the same
tuple types (db, svc, ..., identitySvc) or adjust the tests to use the returned
mock types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/services/auth_test.go`:
- Around line 56-62: The DuplicateEmail subtest currently discards the error
from the first call to svc.Register which can mask setup failures; change the
setup to assert the first registration succeeded (use require.NoError on the
first svc.Register call and capture its result) so any DB or setup error
surfaces immediately before calling svc.Register a second time and asserting the
duplicate error.
- Around line 114-121: In TestAuthServiceValidateUser, don't ignore the error
returned by svc.Register: capture the error (e.g., user, err :=
svc.Register(...)) and assert/require no error before using user.ID so the test
fails fast instead of panicking; update the test to call require.NoError(t, err)
immediately after svc.Register and only then call svc.ValidateUser and access
user.ID.
- Around line 124-132: TestAuthService_ErrorScenarios opens its own DB with
setupDB(t) but never calls cleanDB, so test data from subtests like
Register_TenantFailure_RollbackSuccess and Login_IdentityFailure can leak; fix
by ensuring DB isolation—either replace the manual setup with
setupAuthServiceTest (which calls cleanDB) or call cleanDB(db) (or defer
cleanDB(db)) around setupDB(t) so the DB is cleaned before/after the test and
rows do not persist across tests.
- Around line 75-76: The tests TestAuthServiceLogin and TestAuthServiceLockout
silently ignore svc.Register results; instead capture the returned user and
error from svc.Register (e.g., user, err := svc.Register(...)) and assert/handle
the error immediately (use require.NoError(t, err) or t.Fatalf on err) before
proceeding; do this for both occurrences (the Register call in
TestAuthServiceLogin and the similar call in TestAuthServiceLockout) so setup
failures fail the test rather than continuing against a non-existent user.
- Around line 142-144: The test currently discards the error from
userRepo.GetByEmail which can hide DB errors; change the call in the rollback
verification to capture the error (user, err := userRepo.GetByEmail(ctx,
email)), then assert the error is the expected "not found" condition (or assert
err == nil if your repo returns nil, nil for missing users) and assert user is
nil; update the assertions around userRepo.GetByEmail and assert.Nil to check
err explicitly so the test fails on DB/query errors rather than silently
passing.
- Around line 150-152: The test silently ignores errors from userRepo.Create
which can mask failures (e.g., zero UUID causing DB constraint violations) and
lead svc.Login to fail for the wrong reason; update the test to check and
require no error from userRepo.Create (fail the test immediately if err != nil)
and ensure the created user has a non-zero TenantID (assign a valid UUID to
user.TenantID before calling userRepo.Create) so the Login_IdentityFailure
subtest exercises the intended identity failure path.

---

Outside diff comments:
In `@internal/core/services/auth_test.go`:
- Around line 40-69: Convert TestAuthServiceRegister to a table-driven test:
create a []struct slice with fields like name, email, password, displayName,
expectError, expectErrContains, and any pre-test setup flags, then loop over it
with for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ... }) };
inside each case call svc.Register(ctx, tc.email, tc.password, tc.displayName)
and assert based on tc.expectError (for success also verify persistence via
userRepo.GetByEmail(ctx, tc.email) and compare IDs); ensure existing
"DuplicateEmail" case is implemented by seeding the user before running that
test case and reuse symbols TestAuthServiceRegister, svc.Register,
userRepo.GetByEmail and testPassword to locate the code to change.
- Around line 22-38: The test helper setupAuthServiceTest currently creates real
DB connections and concrete repo implementations (postgres.NewUserRepo,
postgres.NewAuditRepository, postgres.NewIdentityRepository,
postgres.NewTenantRepo) and wires real services into services.NewAuthService;
change it to use testify/mock-based repository mocks instead: replace the
postgres.* repo constructions with mock implementations (or
testify/mock-generated mocks) and inject those mock repo interfaces into
AuditService/IdentityService/TenantService/AuthService in the setup helper, and
update TestAuthService_ErrorScenarios to use the mock UserRepo rather than
postgres.UserRepo so all service tests avoid real pgx pools and real DB
dependencies.

---

Duplicate comments:
In `@internal/core/services/auth_test.go`:
- Around line 163-254: These tests (TestAuthServiceRevokeToken,
TestAuthServiceRotateToken, TestAuthServiceLogout,
TestAuthServiceTokenRotationIntegration) are unintentionally using the real DB
because they call setupAuthServiceTest; update the tests to use a test-friendly
setup that uses an in-memory or mocked datastore (e.g., add a new helper
setupAuthServiceTestWithMock or add an option to setupAuthServiceTest to return
a mock DB), then update each test to call the mock setup (replace
setupAuthServiceTest(...) with the mock variant) so the IdentityService and
AuthService operate against the fake DB; ensure the new helper returns the same
tuple types (db, svc, ..., identitySvc) or adjust the tests to use the returned
mock types.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9177c5f and 60fc6d7.

📒 Files selected for processing (2)
  • internal/core/services/auth_internal_test.go
  • internal/core/services/auth_test.go
💤 Files with no reviewable changes (1)
  • internal/core/services/auth_internal_test.go

Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +56 to +62
t.Run("DuplicateEmail", func(t *testing.T) {
email := "dup_" + uuid.NewString() + "@test.com"
_, _ = svc.Register(ctx, email, testPassword, "User")
_, err := svc.Register(ctx, email, testPassword, "User")
require.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
})
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 | 🟠 Major

Silent failure masks a false-positive in the DuplicateEmail subtest.

If the first Register call silently fails (e.g., DB error), the second call won't be a duplicate and the test will wrongly pass. Use require.NoError so the setup failure is surfaced immediately.

-		_, _ = svc.Register(ctx, email, testPassword, "User")
+		_, err := svc.Register(ctx, email, testPassword, "User")
+		require.NoError(t, err)

As per coding guidelines: "Do not use silent failures - avoid blank identifier assignment like _ = someFunc()" (**/*.go).

📝 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("DuplicateEmail", func(t *testing.T) {
email := "dup_" + uuid.NewString() + "@test.com"
_, _ = svc.Register(ctx, email, testPassword, "User")
_, err := svc.Register(ctx, email, testPassword, "User")
require.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
})
t.Run("DuplicateEmail", func(t *testing.T) {
email := "dup_" + uuid.NewString() + "@test.com"
_, err := svc.Register(ctx, email, testPassword, "User")
require.NoError(t, err)
_, err := svc.Register(ctx, email, testPassword, "User")
require.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 56 - 62, The DuplicateEmail
subtest currently discards the error from the first call to svc.Register which
can mask setup failures; change the setup to assert the first registration
succeeded (use require.NoError on the first svc.Register call and capture its
result) so any DB or setup error surfaces immediately before calling
svc.Register a second time and asserting the duplicate error.

Comment thread internal/core/services/auth_test.go Outdated
Comment thread internal/core/services/auth_test.go Outdated
Comment thread internal/core/services/auth_test.go Outdated
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +142 to +144
// Verify user was rolled back (deleted)
user, _ := userRepo.GetByEmail(ctx, email)
assert.Nil(t, user)
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 | 🟠 Major

Partial silent failure on userRepo.GetByEmail in rollback verification.

user, _ discards the error. If GetByEmail returns a DB error rather than nil, nil, assert.Nil(t, user) will still pass (the returned pointer is nil on error) but for the wrong reason — you haven't confirmed the user was deleted vs. the query itself failing.

-		user, _ := userRepo.GetByEmail(ctx, email)
+		user, err := userRepo.GetByEmail(ctx, email)
+		require.NoError(t, err)
 		assert.Nil(t, user)

As per coding guidelines: "Do not use silent failures - avoid blank identifier assignment like _ = someFunc()" (**/*.go).

📝 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
// Verify user was rolled back (deleted)
user, _ := userRepo.GetByEmail(ctx, email)
assert.Nil(t, user)
// Verify user was rolled back (deleted)
user, err := userRepo.GetByEmail(ctx, email)
require.NoError(t, err)
assert.Nil(t, user)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 142 - 144, The test
currently discards the error from userRepo.GetByEmail which can hide DB errors;
change the call in the rollback verification to capture the error (user, err :=
userRepo.GetByEmail(ctx, email)), then assert the error is the expected "not
found" condition (or assert err == nil if your repo returns nil, nil for missing
users) and assert user is nil; update the assertions around userRepo.GetByEmail
and assert.Nil to check err explicitly so the test fails on DB/query errors
rather than silently passing.

Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +150 to +152
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
user.PasswordHash = string(hash)
_ = userRepo.Create(ctx, user)
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 | 🟠 Major

Silent failure on userRepo.Create hides the root cause for the Login_IdentityFailure subtest.

If Create fails (e.g., because user.TenantID is a zero UUID and violates a NOT NULL constraint), the subsequent svc.Login call will fail because the user doesn't exist, not because of the identity service error — giving a false-positive on err but testing the wrong failure path.

-		_ = userRepo.Create(ctx, user)
+		require.NoError(t, userRepo.Create(ctx, user))

Additionally, user.TenantID is a zero UUID. Verify the users table allows a zero UUID for that column, or assign a real tenant ID.

As per coding guidelines: "Do not use silent failures - avoid blank identifier assignment like _ = someFunc()" (**/*.go).

📝 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
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
user.PasswordHash = string(hash)
_ = userRepo.Create(ctx, user)
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
user.PasswordHash = string(hash)
require.NoError(t, userRepo.Create(ctx, user))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 150 - 152, The test
silently ignores errors from userRepo.Create which can mask failures (e.g., zero
UUID causing DB constraint violations) and lead svc.Login to fail for the wrong
reason; update the test to check and require no error from userRepo.Create (fail
the test immediately if err != nil) and ensure the created user has a non-zero
TenantID (assign a valid UUID to user.TenantID before calling userRepo.Create)
so the Login_IdentityFailure subtest exercises the intended identity failure
path.

Copilot AI review requested due to automatic review settings February 25, 2026 13:46
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 3 out of 3 changed files in this pull request and generated 9 comments.


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

Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +76 to +78
email := "login_" + uuid.NewString() + "@example.com"
_, _ = svc.Register(ctx, email, testPassword, "Login User")

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The setup Register call's error is ignored, which can make the subtests fail or report misleading errors. Use require.NoError for the setup registration so the test fails fast if prerequisites aren't met.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines 100 to 102
email := "locked_" + uuid.NewString() + "@test.com"
_, _ = svc.Register(ctx, email, testPassword, "Locked User")

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Registration error is ignored here; if it fails, the lockout assertions become unreliable. Use require.NoError on Register since it's a prerequisite for the test.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
apiKey, err := identitySvc.CreateKey(ctx, user.ID, "session")
require.NoError(t, err)
func TestAuthService_ErrorScenarios(t *testing.T) {
db := setupDB(t)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

This test sets up a DB but doesn't call cleanDB(t, db). Other service tests use cleanDB after setupDB for isolation; without it, data can accumulate across tests when DATABASE_URL points to a shared DB. Consider calling cleanDB(t, db) here too.

Suggested change
db := setupDB(t)
db := setupDB(t)
defer cleanDB(t, db)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +151 to +153
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
user.PasswordHash = string(hash)
_ = userRepo.Create(ctx, user)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

bcrypt.GenerateFromPassword and userRepo.Create errors are ignored. If hashing or insert fails, this test may pass/fail for the wrong reason. Use require.NoError for both operations to ensure the failure is specifically due to CreateKey.

Suggested change
hash, _ := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
user.PasswordHash = string(hash)
_ = userRepo.Create(ctx, user)
hash, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
require.NoError(t, err)
user.PasswordHash = string(hash)
err = userRepo.Create(ctx, user)
require.NoError(t, err)

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +252 to +254
// Wait for expiration
time.Sleep(150 * time.Millisecond)

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Using a fixed time.Sleep for lockout expiration is prone to flakes under load/slow CI. Prefer require.Eventually (with a timeout and polling interval) to wait until Login succeeds after the lockout window.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +58 to +60
email := "dup_" + uuid.NewString() + "@test.com"
_, _ = svc.Register(ctx, email, testPassword, "User")
_, err := svc.Register(ctx, email, testPassword, "User")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

In this test, the first Register call's error is ignored. If registration fails (e.g. DB/migrations issue), the second call may fail for the wrong reason and the test becomes flaky. Use require.NoError on the initial Register and then assert the duplicate error on the second call.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines 117 to 119
ctx := context.Background()
user, _ := svc.Register(ctx, "val_"+uuid.NewString()+"@test.com", testPassword, "Val User")

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Register error is ignored and the returned user is used immediately; if Register returns (nil, err) this will panic on user.ID. Capture the error with require.NoError before using user.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
assert.Contains(t, err.Error(), "failed to create personal tenant")

// Verify user was rolled back (deleted)
user, _ := userRepo.GetByEmail(ctx, email)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The GetByEmail error is ignored; if it fails, asserting user == nil can hide real DB issues. Use require.NoError for the lookup before asserting the rollback behavior.

Suggested change
user, _ := userRepo.GetByEmail(ctx, email)
user, err := userRepo.GetByEmail(ctx, email)
require.NoError(t, err)

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +57
// SetLockoutDuration overrides the default lockout duration. Useful for testing.
func (s *AuthService) SetLockoutDuration(d time.Duration) {
s.mu.Lock()
defer s.mu.Unlock()
s.lockoutDuration = d
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

SetLockoutDuration accepts non-positive durations, which effectively disables lockout (duration <= 0 makes lockout time <= now). Consider validating d > 0 (or documenting/handling 0 explicitly) to avoid accidental misconfiguration.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 25, 2026 14:12
@poyrazK poyrazK force-pushed the feature/auth-service-real-unit-tests branch from fa24baa to 5bb5478 Compare February 25, 2026 14:12
@poyrazK poyrazK force-pushed the feature/auth-service-real-unit-tests branch from 5bb5478 to dcc85b4 Compare February 25, 2026 14:16
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 4 out of 4 changed files in this pull request and generated 5 comments.


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


// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Failed login attempts are silently ignored with blank identifiers in the lockout trigger loop. If any of these Login calls don't fail as expected, the test will continue without detecting the issue. Following the established pattern in this codebase (as seen throughout this file), errors should be checked with require.Error to ensure the test logic is sound.

Suggested change
_, _, _ = svc.Login(ctx, email3, "wrong")
_, _, err := svc.Login(ctx, email3, "wrong")
require.Error(t, err)

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +263
func TestAuthService_LockoutLogicReal(t *testing.T) {
_, svc, _, _ := setupAuthServiceTest(t)
ctx := context.Background()
email := "locked_" + uuid.NewString() + "@example.com"
pass := testPassword
_, err := svc.Register(ctx, email, pass, "Locked User")
require.NoError(t, err)

t.Run("Lockout after 5 attempts", func(t *testing.T) {
// 5 failed attempts
for i := 0; i < 5; i++ {
_, _, err := svc.Login(ctx, email, "wrong-password")
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid email or password")
}

// 6th attempt should be locked out
_, _, err = svc.Login(ctx, email, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "account is locked")
})

t.Run("Success clears failures", func(t *testing.T) {
email2 := "clean_" + uuid.NewString() + "@example.com"
_, err := svc.Register(ctx, email2, pass, "Clean User")
require.NoError(t, err)

// 2 failed attempts
for i := 0; i < 2; i++ {
_, _, err := svc.Login(ctx, email2, "wrong")
require.Error(t, err)
}

// Success
_, _, err = svc.Login(ctx, email2, pass)
require.NoError(t, err)
})

t.Run("Lockout Expiration", func(t *testing.T) {
email3 := "expire_" + uuid.NewString() + "@example.com"
_, _ = svc.Register(ctx, email3, pass, "Expire User")

// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}

// Verify locked
_, _, err := svc.Login(ctx, email3, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "locked")

// Manually expire lockout via reflection
v := reflect.ValueOf(svc).Elem()
lockoutsField := v.FieldByName("lockouts")
lockoutsField.SetMapIndex(reflect.ValueOf(email3), reflect.ValueOf(time.Now().Add(-1*time.Minute)))

// Login should now work
_, _, err = svc.Login(ctx, email3, pass)
require.NoError(t, err)
})
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The AuthService instance is shared across all three sub-tests, creating a test isolation issue. The lockout state (failedAttempts and lockouts maps) is stored in memory in the AuthService, so state from one sub-test can leak into another. Each sub-test should call setupAuthServiceTest to get a fresh service instance, ensuring proper test isolation. This is especially critical for lockout testing where the state directly affects test outcomes.

Copilot uses AI. Check for mistakes.
Comment on lines +201 to +263

func TestAuthService_LockoutLogicReal(t *testing.T) {
_, svc, _, _ := setupAuthServiceTest(t)
ctx := context.Background()
email := "locked_" + uuid.NewString() + "@example.com"
pass := testPassword
_, err := svc.Register(ctx, email, pass, "Locked User")
require.NoError(t, err)

t.Run("Lockout after 5 attempts", func(t *testing.T) {
// 5 failed attempts
for i := 0; i < 5; i++ {
_, _, err := svc.Login(ctx, email, "wrong-password")
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid email or password")
}

// 6th attempt should be locked out
_, _, err = svc.Login(ctx, email, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "account is locked")
})

t.Run("Success clears failures", func(t *testing.T) {
email2 := "clean_" + uuid.NewString() + "@example.com"
_, err := svc.Register(ctx, email2, pass, "Clean User")
require.NoError(t, err)

// 2 failed attempts
for i := 0; i < 2; i++ {
_, _, err := svc.Login(ctx, email2, "wrong")
require.Error(t, err)
}

// Success
_, _, err = svc.Login(ctx, email2, pass)
require.NoError(t, err)
})

t.Run("Lockout Expiration", func(t *testing.T) {
email3 := "expire_" + uuid.NewString() + "@example.com"
_, _ = svc.Register(ctx, email3, pass, "Expire User")

// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}

// Verify locked
_, _, err := svc.Login(ctx, email3, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "locked")

// Manually expire lockout via reflection
v := reflect.ValueOf(svc).Elem()
lockoutsField := v.FieldByName("lockouts")
lockoutsField.SetMapIndex(reflect.ValueOf(email3), reflect.ValueOf(time.Now().Add(-1*time.Minute)))

// Login should now work
_, _, err = svc.Login(ctx, email3, pass)
require.NoError(t, err)
})
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The new lockout test duplicates functionality already covered by auth_lockout_unit_test.go. The existing unit test file (auth_lockout_unit_test.go) already contains mock-based tests for lockout logic that test the same scenarios: lockout after 5 attempts and success clearing failures. Adding this integration test creates redundancy without adding meaningful coverage beyond what the existing unit test provides, unless the purpose is to replace the mock-based test entirely (but the mock-based test is still present).

Suggested change
func TestAuthService_LockoutLogicReal(t *testing.T) {
_, svc, _, _ := setupAuthServiceTest(t)
ctx := context.Background()
email := "locked_" + uuid.NewString() + "@example.com"
pass := testPassword
_, err := svc.Register(ctx, email, pass, "Locked User")
require.NoError(t, err)
t.Run("Lockout after 5 attempts", func(t *testing.T) {
// 5 failed attempts
for i := 0; i < 5; i++ {
_, _, err := svc.Login(ctx, email, "wrong-password")
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid email or password")
}
// 6th attempt should be locked out
_, _, err = svc.Login(ctx, email, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "account is locked")
})
t.Run("Success clears failures", func(t *testing.T) {
email2 := "clean_" + uuid.NewString() + "@example.com"
_, err := svc.Register(ctx, email2, pass, "Clean User")
require.NoError(t, err)
// 2 failed attempts
for i := 0; i < 2; i++ {
_, _, err := svc.Login(ctx, email2, "wrong")
require.Error(t, err)
}
// Success
_, _, err = svc.Login(ctx, email2, pass)
require.NoError(t, err)
})
t.Run("Lockout Expiration", func(t *testing.T) {
email3 := "expire_" + uuid.NewString() + "@example.com"
_, _ = svc.Register(ctx, email3, pass, "Expire User")
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}
// Verify locked
_, _, err := svc.Login(ctx, email3, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "locked")
// Manually expire lockout via reflection
v := reflect.ValueOf(svc).Elem()
lockoutsField := v.FieldByName("lockouts")
lockoutsField.SetMapIndex(reflect.ValueOf(email3), reflect.ValueOf(time.Now().Add(-1*time.Minute)))
// Login should now work
_, _, err = svc.Login(ctx, email3, pass)
require.NoError(t, err)
})
}

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +254 to +257
// Manually expire lockout via reflection
v := reflect.ValueOf(svc).Elem()
lockoutsField := v.FieldByName("lockouts")
lockoutsField.SetMapIndex(reflect.ValueOf(email3), reflect.ValueOf(time.Now().Add(-1*time.Minute)))
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Using reflection to manipulate private fields (lockouts map) violates encapsulation and makes the test brittle. If the internal implementation of AuthService changes (e.g., field renaming or using a different data structure), this test will break. Additionally, this approach bypasses the actual expiration logic that would be used in production. A better approach would be to either: (1) mock the time function if one exists, (2) use a shorter lockout duration in a test-specific service, or (3) accept that testing lockout expiration in integration tests may require waiting or is better suited to unit tests with mocks.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +242 to +247
_, _ = svc.Register(ctx, email3, pass, "Expire User")

// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Test setup errors are silently ignored with blank identifiers. If Register fails in line 242, the subsequent test logic will not behave as expected. Following the established pattern in this codebase (as seen in lines 79, 98, 114, 132, 158, 182), setup operations should use require.NoError to ensure test validity.

Suggested change
_, _ = svc.Register(ctx, email3, pass, "Expire User")
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}
_, err := svc.Register(ctx, email3, pass, "Expire User")
require.NoError(t, err)
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 25, 2026 14:30
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 5 out of 5 changed files in this pull request and generated 4 comments.


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

Comment on lines +130 to +132

// Truncate all tables in one command with CASCADE to handle foreign keys
truncateQuery := "TRUNCATE " + strings.Join(tables, ", ") + " RESTART IDENTITY CASCADE"
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

cleanDB now uses strings.Join(...) but setup_test.go doesn’t import the strings package, so the test suite won’t compile. Add the missing import (and ensure it’s used only once to satisfy gofmt/goimports).

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +124
`
rows, err := db.Query(ctx, query)
if err != nil {
t.Logf("Warning: failed to query tables for cleanup: %v", err)
return
}
defer rows.Close()

var tables []string
for rows.Next() {
var table string
if err := rows.Scan(&table); err == nil {
tables = append(tables, table)
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

cleanDB silently ignores table discovery/cleanup failures (it logs a warning and returns). Since cleanDB is invoked at the start of many DB-backed tests, skipping cleanup can leave stale data and cause flaky, order-dependent failures. Prefer failing the test on query/truncate errors (e.g., require.NoError), and also handle rows.Scan/rows.Err() instead of ignoring them so cleanup is deterministic.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +254 to +258
// Manually expire lockout via reflection
v := reflect.ValueOf(svc).Elem()
lockoutsField := v.FieldByName("lockouts")
lockoutsField.SetMapIndex(reflect.ValueOf(email3), reflect.ValueOf(time.Now().Add(-1*time.Minute)))

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The lockout-expiration subtest uses reflection to mutate AuthService.lockouts from package services_test. Because lockouts is an unexported field, reflect.Value.FieldByName(...).SetMapIndex will panic (the field isn’t settable from another package). To test expiration, introduce a testable time source (e.g., inject a clock/now func into AuthService) or expose a narrow test helper API so the test can advance time without reflection.

Copilot uses AI. Check for mistakes.
Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +242 to +250
_, _ = svc.Register(ctx, email3, pass, "Expire User")

// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}

// Verify locked
_, _, err := svc.Login(ctx, email3, pass)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

In the lockout-expiration subtest, errors from Register/Login are discarded (_ = / _, _, _ =). If registration fails or the failed-attempt loop doesn’t behave as expected, the test can pass/fail for the wrong reason and mask regressions. Use require.NoError/require.Error for these setup steps so the test only proceeds when the preconditions are met.

Suggested change
_, _ = svc.Register(ctx, email3, pass, "Expire User")
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}
// Verify locked
_, _, err := svc.Login(ctx, email3, pass)
_, err := svc.Register(ctx, email3, pass, "Expire User")
require.NoError(t, err)
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, err = svc.Login(ctx, email3, "wrong")
require.Error(t, err)
}
// Verify locked
_, _, err = svc.Login(ctx, email3, pass)

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 25, 2026 16:38
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: 4

Caution

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

⚠️ Outside diff range comments (1)
internal/core/services/auth_test.go (1)

20-36: ⚠️ Potential issue | 🟠 Major

All tests use real Postgres repositories — coding guidelines require mocks for service tests.

The setupAuthServiceTest function wires real postgres.* repositories instead of mock implementations. The coding guidelines are explicit:

  • **/*_test.go: "Do not test with real external dependencies in unit tests - use mocks instead"
  • **/*_test.go: "Use testify/mock for creating mock objects in tests"
  • internal/core/services/**/*_test.go: "Mock repositories in service tests"

The PR description states the intent is to replace mock-based tests with real-DB tests. If the team wants integration tests alongside unit tests, these should live in a separate file (e.g., auth_integration_test.go) with a build tag, while mock-based unit tests are retained. Deleting the mock-based tests removes the fast, isolated feedback loop that unit tests provide.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 20 - 36, The test helper
setupAuthServiceTest currently constructs real Postgres repositories
(postgres.NewUserRepo, postgres.NewAuditRepository,
postgres.NewIdentityRepository, postgres.NewTenantRepo) so replace those with
testify/mock-based mock implementations for unit tests: create mocks for
UserRepo, AuditRepository, IdentityRepository, TenantRepo (or use existing
mocks) and pass them into services.NewAuthService, services.NewIdentityService,
and services.NewTenantService; if you intend to keep real-DB tests, move this
real-repo wiring into a separate integration test file (e.g.,
auth_integration_test.go) with an appropriate build tag so unit tests continue
to use the mock-based setup and remain fast and isolated.
🧹 Nitpick comments (4)
internal/core/services/setup_test.go (1)

131-136: Quote table identifiers to avoid breakage with special characters or reserved words.

Table names are concatenated directly into the SQL string. While they come from information_schema, quoting them with double-quotes is standard Postgres hygiene and prevents breakage if a table name ever contains a hyphen, space, or is a reserved keyword.

Suggested fix
+	// Quote each table name for safety
+	quoted := make([]string, len(tables))
+	for i, tbl := range tables {
+		quoted[i] = `"` + strings.ReplaceAll(tbl, `"`, `""`) + `"`
+	}
+
 	// Truncate all tables in one command with CASCADE to handle foreign keys
-	truncateQuery := "TRUNCATE " + strings.Join(tables, ", ") + " RESTART IDENTITY CASCADE"
+	truncateQuery := "TRUNCATE " + strings.Join(quoted, ", ") + " RESTART IDENTITY CASCADE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/setup_test.go` around lines 131 - 136, The truncate
SQL concatenates table names directly (tables -> truncateQuery) which can break
for hyphens/reserved words; update the code that builds truncateQuery so each
identifier in tables is wrapped in double quotes and any internal double quotes
are escaped (replace " with ""), then join the quoted names with ", " and use
that in db.Exec(ctx, truncateQuery); ensure the quoting logic is applied where
truncateQuery is constructed so TRUNCATE "table1", "table-2" RESTART IDENTITY
CASCADE is produced.
internal/core/services/auth.go (1)

40-50: Constructor has 4 dependencies — coding guidelines require a params struct.

NewAuthService accepts 4 positional dependencies. As per coding guidelines, "Use a params struct for service constructors with 3 or more dependencies" (internal/core/services/*.go).

Suggested params struct
+// AuthServiceParams holds the dependencies for AuthService.
+type AuthServiceParams struct {
+	UserRepo  ports.UserRepository
+	APIKeySvc ports.IdentityService
+	AuditSvc  ports.AuditService
+	TenantSvc ports.TenantService
+}
+
-func NewAuthService(userRepo ports.UserRepository, apiKeySvc ports.IdentityService, auditSvc ports.AuditService, tenantSvc ports.TenantService) *AuthService {
+func NewAuthService(p AuthServiceParams) *AuthService {
 	return &AuthService{
-		userRepo:        userRepo,
-		apiKeySvc:       apiKeySvc,
-		auditSvc:        auditSvc,
-		tenantSvc:       tenantSvc,
+		userRepo:        p.UserRepo,
+		apiKeySvc:       p.APIKeySvc,
+		auditSvc:        p.AuditSvc,
+		tenantSvc:       p.TenantSvc,
 		failedAttempts:  make(map[string]int),
 		lockouts:        make(map[string]time.Time),
 		lockoutDuration: defaultLockout,
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth.go` around lines 40 - 50, The constructor
NewAuthService currently takes four positional dependencies; create a params
struct (e.g., AuthServiceParams) containing fields for UserRepository,
IdentityService, AuditService, and TenantService (typed from ports), change
NewAuthService to accept a single AuthServiceParams parameter and initialize the
AuthService fields from params (preserving failedAttempts, lockouts,
lockoutDuration defaults), and update all call sites to pass the new params
struct; ensure the struct and constructor names (AuthServiceParams,
NewAuthService, AuthService) are used so callers and DI code can be updated
consistently.
internal/core/services/auth_test.go (2)

38-91: Tests are not table-driven.

TestAuthServiceRegister, TestAuthServiceLogin, TestAuthServiceLoginInvalidCredentials, and TestAuthServiceLoginUserNotFound each test a single scenario. The guidelines require table-driven tests. For example, the login variants (valid credentials, invalid password, user not found) are natural candidates for a single table-driven test. As per coding guidelines, "Use table-driven tests in test files" (**/*_test.go).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 38 - 91, Refactor the
standalone tests into table-driven tests: convert TestAuthServiceRegister into a
table-driven TestAuthServiceRegister with cases for successful register and any
edge cases, and merge TestAuthServiceLogin,
TestAuthServiceLoginInvalidCredentials, and TestAuthServiceLoginUserNotFound
into a single table-driven TestAuthServiceLogin that iterates cases (valid
credentials -> expect user+token, invalid password -> expect error, user not
found -> expect error). For each case use setupAuthServiceTest to get svc, call
svc.Register where needed, then call svc.Login or svc.Register with inputs from
the test case, and assert expectations (presence/absence of token, error vs nil,
user email/name) inside t.Run subtests; keep referencing svc.Register, svc.Login
and setupAuthServiceTest to locate the logic.

210-216: Magic number 5 duplicates the lockout threshold.

The literal 5 on lines 212 and 249 mirrors maxFailedAttempts from auth.go but isn't referenced by name. If the constant changes, these tests silently test the wrong boundary. Since maxFailedAttempts is unexported and in a different test package (services_test), consider either exporting it or defining a test-local constant.

As per coding guidelines, "Do not use magic numbers - use named constants instead" (**/*.go).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 210 - 216, Replace the
hard-coded literal "5" used to mirror the lockout threshold in the auth tests
with a named test-local constant (e.g., const testMaxFailedAttempts = 5) and use
that constant in the t.Run("Lockout after 5 attempts") loop and any other
assertions that depend on the lockout threshold; this avoids coupling to the
unexported maxFailedAttempts in auth.go while removing the magic number from
calls to svc.Login and related assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/services/auth_test.go`:
- Around line 240-264: The "Lockout Expiration" test uses a fragile real-time
sleep (svc.SetLockoutDuration(100 * time.Millisecond) then time.Sleep(150 *
time.Millisecond)) which can flake under CI; change the test to be deterministic
by either (preferred) using the service's clock abstraction if available (make
the test advance the fake clock and call svc.Login/register with that clock) or,
if adding a clock is out of scope, adjust the timings to a safer margin (e.g.,
SetLockoutDuration(1 * time.Millisecond) and Sleep(10 * time.Millisecond) or
increase the sleep to several multiples of the duration) and assert accordingly;
reference svc.SetLockoutDuration, svc.Register, svc.Login and the "Lockout
Expiration" test when making the change.
- Line 6: Remove the unused reflect import in the test file: delete the
"reflect" entry from the import block (or, if reflect was intended to be used,
add the missing usage where tests reference reflection). Ensure no remaining
references to reflect exist so the test package builds cleanly.
- Around line 248-251: The test currently discards svc.Login returns in the
lockout loop; capture the returned error (e.g., _, _, err := svc.Login(ctx,
email3, "wrong")) and assert it is the expected authentication failure (not a
DB/transport error) so the loop actually exercises the auth-failure path and
increments the lockout counter; if you have a specific sentinel like
ErrInvalidCredentials or auth.ErrInvalidCredentials, assert equality to that,
otherwise fail the test on any unexpected error to avoid silent setup failures.

In `@internal/core/services/setup_test.go`:
- Around line 119-125: The rows.Scan error is being silently ignored in the
loop; update the test to handle scan errors instead of discarding them. Inside
the loop that uses rows.Next() and calls rows.Scan(&table), check if err != nil
and fail the test (e.g., use t.Fatalf or t.Fatalf-like helper) with the error
message, otherwise append to tables; this ensures the scan failure in the test
is surfaced. Target the loop using rows.Next(), rows.Scan and the tables slice
in the test function in setup_test.go.

---

Outside diff comments:
In `@internal/core/services/auth_test.go`:
- Around line 20-36: The test helper setupAuthServiceTest currently constructs
real Postgres repositories (postgres.NewUserRepo, postgres.NewAuditRepository,
postgres.NewIdentityRepository, postgres.NewTenantRepo) so replace those with
testify/mock-based mock implementations for unit tests: create mocks for
UserRepo, AuditRepository, IdentityRepository, TenantRepo (or use existing
mocks) and pass them into services.NewAuthService, services.NewIdentityService,
and services.NewTenantService; if you intend to keep real-DB tests, move this
real-repo wiring into a separate integration test file (e.g.,
auth_integration_test.go) with an appropriate build tag so unit tests continue
to use the mock-based setup and remain fast and isolated.

---

Nitpick comments:
In `@internal/core/services/auth_test.go`:
- Around line 38-91: Refactor the standalone tests into table-driven tests:
convert TestAuthServiceRegister into a table-driven TestAuthServiceRegister with
cases for successful register and any edge cases, and merge
TestAuthServiceLogin, TestAuthServiceLoginInvalidCredentials, and
TestAuthServiceLoginUserNotFound into a single table-driven TestAuthServiceLogin
that iterates cases (valid credentials -> expect user+token, invalid password ->
expect error, user not found -> expect error). For each case use
setupAuthServiceTest to get svc, call svc.Register where needed, then call
svc.Login or svc.Register with inputs from the test case, and assert
expectations (presence/absence of token, error vs nil, user email/name) inside
t.Run subtests; keep referencing svc.Register, svc.Login and
setupAuthServiceTest to locate the logic.
- Around line 210-216: Replace the hard-coded literal "5" used to mirror the
lockout threshold in the auth tests with a named test-local constant (e.g.,
const testMaxFailedAttempts = 5) and use that constant in the t.Run("Lockout
after 5 attempts") loop and any other assertions that depend on the lockout
threshold; this avoids coupling to the unexported maxFailedAttempts in auth.go
while removing the magic number from calls to svc.Login and related assertions.

In `@internal/core/services/auth.go`:
- Around line 40-50: The constructor NewAuthService currently takes four
positional dependencies; create a params struct (e.g., AuthServiceParams)
containing fields for UserRepository, IdentityService, AuditService, and
TenantService (typed from ports), change NewAuthService to accept a single
AuthServiceParams parameter and initialize the AuthService fields from params
(preserving failedAttempts, lockouts, lockoutDuration defaults), and update all
call sites to pass the new params struct; ensure the struct and constructor
names (AuthServiceParams, NewAuthService, AuthService) are used so callers and
DI code can be updated consistently.

In `@internal/core/services/setup_test.go`:
- Around line 131-136: The truncate SQL concatenates table names directly
(tables -> truncateQuery) which can break for hyphens/reserved words; update the
code that builds truncateQuery so each identifier in tables is wrapped in double
quotes and any internal double quotes are escaped (replace " with ""), then join
the quoted names with ", " and use that in db.Exec(ctx, truncateQuery); ensure
the quoting logic is applied where truncateQuery is constructed so TRUNCATE
"table1", "table-2" RESTART IDENTITY CASCADE is produced.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60fc6d7 and 4b2dfbc.

📒 Files selected for processing (6)
  • internal/core/services/auth.go
  • internal/core/services/auth_internal_test.go
  • internal/core/services/auth_lockout_unit_test.go
  • internal/core/services/auth_test.go
  • internal/core/services/auth_unit_test.go
  • internal/core/services/setup_test.go
💤 Files with no reviewable changes (3)
  • internal/core/services/auth_internal_test.go
  • internal/core/services/auth_unit_test.go
  • internal/core/services/auth_lockout_unit_test.go

Comment thread internal/core/services/auth_test.go Outdated
Comment on lines +240 to +264
t.Run("Lockout Expiration", func(t *testing.T) {
email3 := "expire_" + uuid.NewString() + "@example.com"
_, err := svc.Register(ctx, email3, pass, "Expire User")
require.NoError(t, err)

// Set a very short lockout
svc.SetLockoutDuration(100 * time.Millisecond)

// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}

// Verify locked
_, _, err = svc.Login(ctx, email3, pass)
require.Error(t, err)
assert.Contains(t, err.Error(), "locked")

// Wait for expiration
time.Sleep(150 * time.Millisecond)

// Login should now work
_, _, err = svc.Login(ctx, email3, pass)
require.NoError(t, err)
})
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

Sleep-based lockout expiration test is inherently flaky.

time.Sleep(150 * time.Millisecond) with a 100ms lockout leaves only a 50ms margin. Under CI load (especially with race detection enabled), this can flake. Consider using a shorter lockout (e.g., 1 * time.Millisecond) with a longer sleep, or better yet, use a clock abstraction that can be advanced deterministically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 240 - 264, The "Lockout
Expiration" test uses a fragile real-time sleep (svc.SetLockoutDuration(100 *
time.Millisecond) then time.Sleep(150 * time.Millisecond)) which can flake under
CI; change the test to be deterministic by either (preferred) using the
service's clock abstraction if available (make the test advance the fake clock
and call svc.Login/register with that clock) or, if adding a clock is out of
scope, adjust the timings to a safer margin (e.g., SetLockoutDuration(1 *
time.Millisecond) and Sleep(10 * time.Millisecond) or increase the sleep to
several multiples of the duration) and assert accordingly; reference
svc.SetLockoutDuration, svc.Register, svc.Login and the "Lockout Expiration"
test when making the change.

Comment on lines +248 to +251
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}
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 | 🟠 Major

Silent triple-discard masks setup failures in the lockout loop.

Line 250 discards all three return values from svc.Login. If Login fails for an unexpected reason (e.g., DB connectivity), the loop won't actually trigger the lockout counter, and the subsequent assertions will pass or fail for the wrong reason.

 		for i := 0; i < 5; i++ {
-			_, _, _ = svc.Login(ctx, email3, "wrong")
+			_, _, err := svc.Login(ctx, email3, "wrong")
+			require.Error(t, err)
 		}

As per coding guidelines, "Do not use silent failures - avoid blank identifier assignment like _ = someFunc()" (**/*.go).

📝 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
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, _ = svc.Login(ctx, email3, "wrong")
}
// Trigger lockout
for i := 0; i < 5; i++ {
_, _, err := svc.Login(ctx, email3, "wrong")
require.Error(t, err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/auth_test.go` around lines 248 - 251, The test
currently discards svc.Login returns in the lockout loop; capture the returned
error (e.g., _, _, err := svc.Login(ctx, email3, "wrong")) and assert it is the
expected authentication failure (not a DB/transport error) so the loop actually
exercises the auth-failure path and increments the lockout counter; if you have
a specific sentinel like ErrInvalidCredentials or auth.ErrInvalidCredentials,
assert equality to that, otherwise fail the test on any unexpected error to
avoid silent setup failures.

Comment on lines +119 to 125
var tables []string
for rows.Next() {
var table string
if err := rows.Scan(&table); err == nil {
tables = append(tables, table)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Silent scan error violates coding guidelines.

Line 122 silently discards the rows.Scan error. While unlikely to fail here, the guideline is clear.

Suggested fix
 	for rows.Next() {
 		var table string
-		if err := rows.Scan(&table); err == nil {
-			tables = append(tables, table)
-		}
+		if err := rows.Scan(&table); err != nil {
+			t.Logf("Warning: failed to scan table name: %v", err)
+			continue
+		}
+		tables = append(tables, table)
 	}

As per coding guidelines, "Do not use silent failures - avoid blank identifier assignment like _ = someFunc()" (**/*.go).

📝 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
var tables []string
for rows.Next() {
var table string
if err := rows.Scan(&table); err == nil {
tables = append(tables, table)
}
}
var tables []string
for rows.Next() {
var table string
if err := rows.Scan(&table); err != nil {
t.Logf("Warning: failed to scan table name: %v", err)
continue
}
tables = append(tables, table)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/services/setup_test.go` around lines 119 - 125, The rows.Scan
error is being silently ignored in the loop; update the test to handle scan
errors instead of discarding them. Inside the loop that uses rows.Next() and
calls rows.Scan(&table), check if err != nil and fail the test (e.g., use
t.Fatalf or t.Fatalf-like helper) with the error message, otherwise append to
tables; this ensures the scan failure in the test is surfaced. Target the loop
using rows.Next(), rows.Scan and the tables slice in the test function in
setup_test.go.

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 6 out of 6 changed files in this pull request and generated 2 comments.


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

Comment on lines +131 to +132
// Truncate all tables in one command with CASCADE to handle foreign keys
truncateQuery := "TRUNCATE " + strings.Join(tables, ", ") + " RESTART IDENTITY CASCADE"
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The table names from information_schema are concatenated directly into a SQL query without proper identifier quoting. While PostgreSQL's information_schema.tables returns safe table names, it's a best practice to quote identifiers to prevent potential SQL injection if table naming conventions change or special characters are introduced. Consider using proper identifier quoting (e.g., wrapping each table name in double quotes) or using pgx's identifier handling.

Suggested change
// Truncate all tables in one command with CASCADE to handle foreign keys
truncateQuery := "TRUNCATE " + strings.Join(tables, ", ") + " RESTART IDENTITY CASCADE"
// Truncate all tables in one command with CASCADE to handle foreign keys.
// Quote identifiers to avoid issues with special characters or reserved words.
quotedTables := make([]string, 0, len(tables))
for _, tbl := range tables {
safeName := `"` + strings.ReplaceAll(tbl, `"`, `""`) + `"`
quotedTables = append(quotedTables, safeName)
}
truncateQuery := "TRUNCATE " + strings.Join(quotedTables, ", ") + " RESTART IDENTITY CASCADE"

Copilot uses AI. Check for mistakes.
require.NoError(t, err)

// Set a very short lockout
svc.SetLockoutDuration(100 * time.Millisecond)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Modifying the shared service instance's lockout duration in a subtest could cause issues if subtests are run in parallel in the future. Consider creating a fresh service instance for this subtest or resetting the lockout duration at the end of the subtest to avoid potential test pollution.

Copilot uses AI. Check for mistakes.
@poyrazK poyrazK merged commit 5465657 into main Feb 25, 2026
41 checks passed
@poyrazK poyrazK deleted the feature/auth-service-real-unit-tests branch April 12, 2026 15:56
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.

2 participants