Skip to content

test: add basic necessary unit tests - #8

Merged
Floffah merged 2 commits into
mainfrom
features/testing
May 22, 2026
Merged

test: add basic necessary unit tests#8
Floffah merged 2 commits into
mainfrom
features/testing

Conversation

@Floffah

@Floffah Floffah commented May 22, 2026

Copy link
Copy Markdown
Owner

What this changes

Unit tests

How I tested this

tests.

Checklist

  • I have read CONTRIBUTING.md and this PR follows the guidelines
  • A human has reviewed the entire diff of this PR, every line of code
  • A human understands the changes and can explain why this approach is correct
  • Tests pass
  • This PR doesn't have AI-generated boilerplate or co-author lines
  • This PR was authored and submitted by an AI agent without human review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

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

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a3cb252-7663-457f-97a7-d3d9caf93c95

📥 Commits

Reviewing files that changed from the base of the PR and between 0630a37 and 5a3f810.

📒 Files selected for processing (1)
  • .github/workflows/housekeeping.yml
📝 Walkthrough

Walkthrough

This PR adds extensive test coverage and refactors backend dependency injection. The backend introduces abstract interfaces for auth, database transactions, and token issuance, refactors the API server to use these abstractions, and adds comprehensive test coverage across API handlers and packages. The frontend establishes a complete test infrastructure using Bun, Testing Library, and MSW, then exercises authentication, token management, and form submission flows. The build configuration is updated to improve web task execution.

Changes

Backend Refactoring and Testing Suite

Layer / File(s) Summary
Backend Dependency Abstractions
internal/pkg/auth/auth.go, internal/pkg/db/orchestra.go, internal/pkg/gitauth/gitauth.go
Introduces Provider interface for auth services, extends TxDB interface with BeginTx for transaction support, and adds TokenIssuer interface for token generation, enabling loose coupling and testability.
API Server Refactoring and Git Token Handler
go.mod, internal/app/api/api.go, internal/app/api/git_access_tokens.go, internal/app/api/git_access_tokens_test.go, internal/app/api/test_helpers_test.go
Updates Server and ServerDeps to use new abstract interfaces (db.TxDB, auth.Provider, gitauth.TokenIssuer), refactors router and Git handler wiring, updates Git token handler to use token issuer, and provides test scaffolding with mock auth and database stubs.
API Handler Tests and Utilities
internal/app/api/users_test.go, internal/app/api/issues_test.go, internal/app/api/repositories_test.go, internal/app/api/util_test.go, internal/app/gitserver/server_test.go
Comprehensive endpoint testing for user retrieval/updates, issue CRUD operations, repository creation/access/updates, API type conversions (user/repository/token/issue), and gitserver request parsing and routing; uses pgxmock for database expectations.
Backend Package Unit and Integration Tests
internal/pkg/gitauth/gitauth_test.go, internal/pkg/gitstore/gitstore_test.go, internal/pkg/repositoryitems/repositoryitems_test.go
Tests token generation/hashing/display, scope normalisation/validation, Git repository creation/content read, path resolution, branch operations, commit history, and reference formatting; includes git binary integration and temporary repository setup.
Frontend Test Environment Setup
web/package.json, web/src/test/setup.ts, web/src/test/server.ts, web/src/test/render.tsx, web/src/test/navigation.ts
Configures Bun test runner with happy-dom, React Testing Library, and MSW; provides renderWithQueryClient() and test utilities for navigation mocking and API request interception; manages test lifecycle with proper cleanup.
Frontend Component Tests
web/src/providers/AuthProvider.test.tsx, web/src/app/(site)/settings/tokens/new/page.test.tsx, web/src/components/blocks/PersonalAccessTokenList.test.tsx, web/src/components/views/CreateIssueForm.test.tsx, web/src/components/views/RepositorySettingsForm.test.tsx, web/src/noop.test.ts
Tests authentication token propagation in API headers, personal access token creation/copy workflow, token list empty state and revocation, issue form submission and validation, repository settings updates, and removes placeholder test.
Build Configuration Updates
justfile
Updates lint/test/format recipes to use bun run --cwd web for consistency, adds web test execution to main test recipe with Go coverage flag, and standardises development task invocation.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • Floffah/catena#1: Introduces the initial issue schema and models that this PR now provides comprehensive API and conversion test coverage for.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is overly sparse and generic ('Unit tests' and 'tests.'), lacking substantive detail about what was tested, why these tests were added, or the testing approach. Expand the description to explain which components/features were tested, why these tests were necessary, and provide concrete examples of test coverage added.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change—adding unit tests—which accurately reflects the primary objective of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch features/testing

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.

@socket-security

socket-security Bot commented May 22, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​happy-dom@​20.9.0751008892100
Addednpm/​@​testing-library/​user-event@​14.6.110010010086100
Addednpm/​@​testing-library/​dom@​10.4.19910010087100
Addednpm/​@​testing-library/​react@​16.3.29910010087100

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
internal/pkg/auth/auth.go (1)

187-191: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against nil auth in sign-in token creation.

This dereferences auth.ClerkUserID without a nil check, so a nil input will panic the request path.

Suggested fix
 func (s *Service) CreateClerkSignInToken(auth *Auth) (string, error) {
+	if auth == nil || strings.TrimSpace(auth.ClerkUserID) == "" {
+		return "", errors.New("auth is required")
+	}
+
 	signInToken, err := s.ClerkSignInToken.Create(context.Background(), &signintoken.CreateParams{
 		UserID:           &auth.ClerkUserID,
 		ExpiresInSeconds: new(int64(20)),
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/pkg/auth/auth.go` around lines 187 - 191, In CreateClerkSignInToken,
guard against a nil or invalid auth before dereferencing auth.ClerkUserID: check
if auth == nil and if auth.ClerkUserID is nil/empty and return a clear error
(instead of proceeding) so the subsequent call to s.ClerkSignInToken.Create does
not panic; update the validation at the start of CreateClerkSignInToken to
return an error when auth or auth.ClerkUserID is missing.
🧹 Nitpick comments (5)
internal/app/api/issues_test.go (1)

84-125: ⚡ Quick win

Assert early short-circuit before DB access in auth/validation failures.

Nice coverage overall. At the moment, Line 89 and Line 110 bake in a repository lookup even when the request is unauthenticated or has a blank title. Switching these subtests to failDB would better protect the contract that auth/validation failures should return early without DB work.

Suggested test tightening
 t.Run("anonymous request returns unauthorized", func(t *testing.T) {
-    mock, err := pgxmock.NewPool()
-    assert.Nil(t, err)
-    defer mock.Close()
-
-    expectRepositoryByOwnerAndName(mock, user.Name, repository.Name, repository)
-
     router := NewRouter(ServerDeps{
-        DB:   mock,
+        DB:   failDB{t: t},
         Auth: testAuthProvider{user: user},
     })
@@
     router.ServeHTTP(response, request)

     assert.That(t, response.Code == http.StatusUnauthorized)
-    assert.Nil(t, mock.ExpectationsWereMet())
 })

 t.Run("blank title returns bad request", func(t *testing.T) {
-    mock, err := pgxmock.NewPool()
-    assert.Nil(t, err)
-    defer mock.Close()
-
-    expectRepositoryByOwnerAndName(mock, user.Name, repository.Name, repository)
-
     router := NewRouter(ServerDeps{
-        DB:   mock,
+        DB:   failDB{t: t},
         Auth: testAuthProvider{user: user},
     })
@@
     assert.That(t, response.Code == http.StatusBadRequest)
-    assert.Nil(t, mock.ExpectationsWereMet())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/app/api/issues_test.go` around lines 84 - 125, The tests "anonymous
request returns unauthorized" and "blank title returns bad request" currently
set expectRepositoryByOwnerAndName(mock, ...) which allows/expects a DB lookup;
change those to a failing DB expectation so the handler cannot hit the DB on
auth/validation failures—replace the expectRepositoryByOwnerAndName calls with a
call to failDB(mock) (or set mock to expect no queries/fail on any query) before
creating the router, keeping the same ServerDeps and assertions, so the tests
assert the code short-circuits before any repository lookup.
web/src/test/setup.ts (1)

15-15: 💤 Low value

Minor: Redundant SyntaxError assignment.

SyntaxError is assigned to globals.SyntaxError twice (lines 15 and 40). The second assignment is unnecessary.

♻️ Proposed fix
 globals.cancelAnimationFrame = window.cancelAnimationFrame.bind(window);
-globals.SyntaxError = SyntaxError;
 globals.IS_REACT_ACT_ENVIRONMENT = true;

Also applies to: 40-40

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/test/setup.ts` at line 15, The file assigns the same global twice
(window.SyntaxError and a later globals.SyntaxError); remove the redundant
duplicate assignment so SyntaxError is exported to globals only once—keep a
single assignment (either window.SyntaxError = SyntaxError or
globals.SyntaxError = SyntaxError) and delete the other occurrence to avoid the
unnecessary duplication.
web/src/components/views/CreateIssueForm.test.tsx (2)

75-106: 💤 Low value

Consider verifying routerRefreshCalls for consistency.

In the success test case (line 72), you verify routerRefreshCalls, but this validation test doesn't check it. Whilst it's correct that no refresh should occur on validation failure, explicitly asserting expect(routerRefreshCalls).toBe(0) would make the intent clearer and catch any unintended refresh calls.

Suggested addition
         expect(await screen.findByText("Title is required")).toBeDefined();
         expect(requestBodies).toEqual([]);
         expect(routerPushCalls).toEqual([]);
+        expect(routerRefreshCalls).toBe(0);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/views/CreateIssueForm.test.tsx` around lines 75 - 106, The
test "requires a title before creating an issue" currently asserts no network
call and no router.push but omits checking router refresh; update the test to
also assert that routerRefreshCalls is zero to ensure no refresh occurs on
validation failure. Locate the test in CreateIssueForm.test.tsx (the test
function with requestBodies and server.use) and add an assertion like
expect(routerRefreshCalls).toBe(0) after the other expects so the test
explicitly verifies the CreateIssueForm component did not trigger a router
refresh on validation error.

10-107: ⚡ Quick win

Consider adding test coverage for error scenarios.

The current tests cover the happy path and validation, which is great for a first pass. However, you might want to add a test case for API error handling (e.g., when the server returns a 500 or 4xx error). This would verify that the form displays appropriate error messages and doesn't navigate on failure.

Example test case:

test("displays error message when API fails", async () => {
    server.use(
        http.post(
            "http://catena.test/v1/repositories/:owner/:repository/issues",
            () => {
                return HttpResponse.json(
                    { message: "Internal server error" },
                    { status: 500 }
                );
            }
        )
    );

    const CreateIssueForm = await import("./CreateIssueForm").then(
        (mod) => mod.default
    );

    renderWithQueryClient(
        <CreateIssueForm ownerName="floffah" repoName="catena" />
    );

    await userEvent.type(screen.getByLabelText("Title"), "Test issue");
    await userEvent.click(
        screen.getByRole("button", { name: "Create issue" })
    );

    expect(await screen.findByText(/error/i)).toBeDefined();
    expect(routerPushCalls).toEqual([]);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/views/CreateIssueForm.test.tsx` around lines 10 - 107, Add
a test that exercises API error handling for CreateIssueForm: use server.use
with http.post to return an error response (e.g., HttpResponse.json({...}, {
status: 500 })), import the CreateIssueForm component as in the existing tests,
render it via renderWithQueryClient, fill the Title/Body (userEvent.type) and
click the Create issue button, then assert that an error message is shown (await
screen.findByText(/error/i) or similar) and that requestBodies remains empty or
routerPushCalls is unchanged (routerPushCalls === []), ensuring the form does
not navigate on failure and matches the pattern used in the other tests.
web/src/components/views/RepositorySettingsForm.test.tsx (1)

23-86: ⚡ Quick win

Consider adding a test for API error handling.

Similar to the CreateIssueForm tests, it would be valuable to verify how the form behaves when the API returns an error. This ensures users receive appropriate feedback when updates fail.

Example test case:

test("displays error message when update fails", async () => {
    server.use(
        http.patch(
            "http://catena.test/v1/repositories/:owner/:repository",
            () => {
                return HttpResponse.json(
                    { message: "Update failed" },
                    { status: 500 }
                );
            }
        )
    );

    const RepositorySettingsForm =
        await import("./RepositorySettingsForm").then((mod) => mod.default);

    renderWithQueryClient(
        <RepositorySettingsForm
            branchNames={["main", "develop"]}
            repository={repository}
        />
    );

    await userEvent.type(
        screen.getByLabelText("Description (optional)"),
        "New description"
    );
    await userEvent.click(
        screen.getByRole("button", { name: "Save Changes" })
    );

    expect(await screen.findByText(/error|failed/i)).toBeDefined();
    expect(routerRefreshCalls).toBe(0);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/views/RepositorySettingsForm.test.tsx` around lines 23 -
86, Add a test that verifies RepositorySettingsForm shows an error message and
does not trigger a router refresh when the update API returns an error: in the
test suite add a new test (e.g., "displays error message when update fails")
that uses server.use with http.patch for the same
"http://catena.test/v1/repositories/:owner/:repository" route to return an
HttpResponse.json({ message: "Update failed" }, { status: 500 }), then import
RepositorySettingsForm, render it with renderWithQueryClient (passing
branchNames and repository), simulate changing the form (type into the
Description field and click the "Save Changes" button), and assert that await
screen.findByText(/error|failed/i) is defined and that routerRefreshCalls
remains 0 to confirm no refresh happened.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/app/api/util_test.go`:
- Around line 79-99: Replace the realistic PAT-style literal used in the
GitAccessToken fixture and its assertion with a clearly synthetic value to avoid
leak-scanner noise: in the GitAccessTokenToAPI test where TokenPrefix is set
(the db.GitAccessToken TokenPrefix field) change "ctn_pat_12345678" to a
non-secret-looking string (e.g. "token_prefix_test") and update the
corresponding assertion that checks got.TokenPrefix to expect that synthetic
value instead.

In `@justfile`:
- Line 30: The go test invocation includes a redundant -cover flag; update the
command string "go test -v -coverprofile=coverage.out -cover ./..." by removing
the standalone -cover so it reads "go test -v -coverprofile=coverage.out ./..."
to rely on -coverprofile for coverage collection.

In `@web/src/providers/AuthProvider.test.tsx`:
- Around line 8-20: The test file sets
process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL at module scope and calls
mock.module("`@clerk/nextjs`", ...) at import time, which runs before your test
preload/setup and can trigger "document is not defined"; remove the module-level
env assignment (process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL) and relocate the
Clerk mock.module call into the test body or a per-test beforeEach so
useAuth/getToken are mocked only during test execution (or create a dedicated
test setup file) rather than at module import.

---

Outside diff comments:
In `@internal/pkg/auth/auth.go`:
- Around line 187-191: In CreateClerkSignInToken, guard against a nil or invalid
auth before dereferencing auth.ClerkUserID: check if auth == nil and if
auth.ClerkUserID is nil/empty and return a clear error (instead of proceeding)
so the subsequent call to s.ClerkSignInToken.Create does not panic; update the
validation at the start of CreateClerkSignInToken to return an error when auth
or auth.ClerkUserID is missing.

---

Nitpick comments:
In `@internal/app/api/issues_test.go`:
- Around line 84-125: The tests "anonymous request returns unauthorized" and
"blank title returns bad request" currently set
expectRepositoryByOwnerAndName(mock, ...) which allows/expects a DB lookup;
change those to a failing DB expectation so the handler cannot hit the DB on
auth/validation failures—replace the expectRepositoryByOwnerAndName calls with a
call to failDB(mock) (or set mock to expect no queries/fail on any query) before
creating the router, keeping the same ServerDeps and assertions, so the tests
assert the code short-circuits before any repository lookup.

In `@web/src/components/views/CreateIssueForm.test.tsx`:
- Around line 75-106: The test "requires a title before creating an issue"
currently asserts no network call and no router.push but omits checking router
refresh; update the test to also assert that routerRefreshCalls is zero to
ensure no refresh occurs on validation failure. Locate the test in
CreateIssueForm.test.tsx (the test function with requestBodies and server.use)
and add an assertion like expect(routerRefreshCalls).toBe(0) after the other
expects so the test explicitly verifies the CreateIssueForm component did not
trigger a router refresh on validation error.
- Around line 10-107: Add a test that exercises API error handling for
CreateIssueForm: use server.use with http.post to return an error response
(e.g., HttpResponse.json({...}, { status: 500 })), import the CreateIssueForm
component as in the existing tests, render it via renderWithQueryClient, fill
the Title/Body (userEvent.type) and click the Create issue button, then assert
that an error message is shown (await screen.findByText(/error/i) or similar)
and that requestBodies remains empty or routerPushCalls is unchanged
(routerPushCalls === []), ensuring the form does not navigate on failure and
matches the pattern used in the other tests.

In `@web/src/components/views/RepositorySettingsForm.test.tsx`:
- Around line 23-86: Add a test that verifies RepositorySettingsForm shows an
error message and does not trigger a router refresh when the update API returns
an error: in the test suite add a new test (e.g., "displays error message when
update fails") that uses server.use with http.patch for the same
"http://catena.test/v1/repositories/:owner/:repository" route to return an
HttpResponse.json({ message: "Update failed" }, { status: 500 }), then import
RepositorySettingsForm, render it with renderWithQueryClient (passing
branchNames and repository), simulate changing the form (type into the
Description field and click the "Save Changes" button), and assert that await
screen.findByText(/error|failed/i) is defined and that routerRefreshCalls
remains 0 to confirm no refresh happened.

In `@web/src/test/setup.ts`:
- Line 15: The file assigns the same global twice (window.SyntaxError and a
later globals.SyntaxError); remove the redundant duplicate assignment so
SyntaxError is exported to globals only once—keep a single assignment (either
window.SyntaxError = SyntaxError or globals.SyntaxError = SyntaxError) and
delete the other occurrence to avoid the unnecessary duplication.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eba1be36-eec3-4bd5-8fe5-bfca0105eec6

📥 Commits

Reviewing files that changed from the base of the PR and between 4854864 and 0630a37.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (29)
  • go.mod
  • internal/app/api/api.go
  • internal/app/api/git_access_tokens.go
  • internal/app/api/git_access_tokens_test.go
  • internal/app/api/issues_test.go
  • internal/app/api/repositories_test.go
  • internal/app/api/test_helpers_test.go
  • internal/app/api/users_test.go
  • internal/app/api/util_test.go
  • internal/app/gitserver/server_test.go
  • internal/pkg/auth/auth.go
  • internal/pkg/db/orchestra.go
  • internal/pkg/gitauth/gitauth.go
  • internal/pkg/gitauth/gitauth_test.go
  • internal/pkg/gitstore/gitstore_test.go
  • internal/pkg/repositoryitems/repositoryitems_test.go
  • internal/pkg/util/strings.go
  • justfile
  • web/package.json
  • web/src/app/(site)/settings/tokens/new/page.test.tsx
  • web/src/components/blocks/PersonalAccessTokenList.test.tsx
  • web/src/components/views/CreateIssueForm.test.tsx
  • web/src/components/views/RepositorySettingsForm.test.tsx
  • web/src/noop.test.ts
  • web/src/providers/AuthProvider.test.tsx
  • web/src/test/navigation.ts
  • web/src/test/render.tsx
  • web/src/test/server.ts
  • web/src/test/setup.ts
💤 Files with no reviewable changes (2)
  • web/src/noop.test.ts
  • internal/pkg/util/strings.go

Comment on lines +79 to +99
got, err := GitAccessTokenToAPI(db.GitAccessToken{
ID: UUIDToPgtype(tokenID),
Name: "Local laptop",
TokenPrefix: "ctn_pat_12345678",
Scopes: []string{gitauth.ScopeRepoRead, gitauth.ScopeRepoWrite},
LastUsedAt: pgtype.Timestamptz{Time: lastUsedAt, Valid: true},
ExpiresAt: pgtype.Timestamptz{Time: expiresAt, Valid: true},
CreatedAt: pgtype.Timestamptz{Time: createdAt, Valid: true},
UpdatedAt: pgtype.Timestamptz{Time: updatedAt, Valid: true},
})

assert.Nil(t, err)
assert.That(t, got.Id == tokenID)
assert.That(t, got.Name == "Local laptop")
assert.That(t, got.TokenPrefix == "ctn_pat_12345678")
assert.That(t, got.LastUsedAt != nil && got.LastUsedAt.Equal(lastUsedAt))
assert.That(t, got.ExpiresAt != nil && got.ExpiresAt.Equal(expiresAt))
assert.That(t, got.RevokedAt == nil)
assert.That(t, len(got.Scopes) == 2)
assert.That(t, got.Scopes[0] == gitauth.ScopeRepoRead)
assert.That(t, got.Scopes[1] == gitauth.ScopeRepoWrite)

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 | ⚡ Quick win

Use non-secret-looking token fixtures to avoid leak-scanner noise.

Line 82 and Line 93 currently use a realistic PAT-style literal and are being flagged by Betterleaks. Swapping to a clearly synthetic value keeps the test intent while reducing security-tool false positives.

Small fixture tweak
-        TokenPrefix: "ctn_pat_12345678",
+        TokenPrefix: "test_token_prefix",
@@
-    assert.That(t, got.TokenPrefix == "ctn_pat_12345678")
+    assert.That(t, got.TokenPrefix == "test_token_prefix")
📝 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
got, err := GitAccessTokenToAPI(db.GitAccessToken{
ID: UUIDToPgtype(tokenID),
Name: "Local laptop",
TokenPrefix: "ctn_pat_12345678",
Scopes: []string{gitauth.ScopeRepoRead, gitauth.ScopeRepoWrite},
LastUsedAt: pgtype.Timestamptz{Time: lastUsedAt, Valid: true},
ExpiresAt: pgtype.Timestamptz{Time: expiresAt, Valid: true},
CreatedAt: pgtype.Timestamptz{Time: createdAt, Valid: true},
UpdatedAt: pgtype.Timestamptz{Time: updatedAt, Valid: true},
})
assert.Nil(t, err)
assert.That(t, got.Id == tokenID)
assert.That(t, got.Name == "Local laptop")
assert.That(t, got.TokenPrefix == "ctn_pat_12345678")
assert.That(t, got.LastUsedAt != nil && got.LastUsedAt.Equal(lastUsedAt))
assert.That(t, got.ExpiresAt != nil && got.ExpiresAt.Equal(expiresAt))
assert.That(t, got.RevokedAt == nil)
assert.That(t, len(got.Scopes) == 2)
assert.That(t, got.Scopes[0] == gitauth.ScopeRepoRead)
assert.That(t, got.Scopes[1] == gitauth.ScopeRepoWrite)
got, err := GitAccessTokenToAPI(db.GitAccessToken{
ID: UUIDToPgtype(tokenID),
Name: "Local laptop",
TokenPrefix: "test_token_prefix",
Scopes: []string{gitauth.ScopeRepoRead, gitauth.ScopeRepoWrite},
LastUsedAt: pgtype.Timestamptz{Time: lastUsedAt, Valid: true},
ExpiresAt: pgtype.Timestamptz{Time: expiresAt, Valid: true},
CreatedAt: pgtype.Timestamptz{Time: createdAt, Valid: true},
UpdatedAt: pgtype.Timestamptz{Time: updatedAt, Valid: true},
})
assert.Nil(t, err)
assert.That(t, got.Id == tokenID)
assert.That(t, got.Name == "Local laptop")
assert.That(t, got.TokenPrefix == "test_token_prefix")
assert.That(t, got.LastUsedAt != nil && got.LastUsedAt.Equal(lastUsedAt))
assert.That(t, got.ExpiresAt != nil && got.ExpiresAt.Equal(expiresAt))
assert.That(t, got.RevokedAt == nil)
assert.That(t, len(got.Scopes) == 2)
assert.That(t, got.Scopes[0] == gitauth.ScopeRepoRead)
assert.That(t, got.Scopes[1] == gitauth.ScopeRepoWrite)
🧰 Tools
🪛 Betterleaks (1.2.0)

[high] 82-82: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 93-93: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/app/api/util_test.go` around lines 79 - 99, Replace the realistic
PAT-style literal used in the GitAccessToken fixture and its assertion with a
clearly synthetic value to avoid leak-scanner noise: in the GitAccessTokenToAPI
test where TokenPrefix is set (the db.GitAccessToken TokenPrefix field) change
"ctn_pat_12345678" to a non-secret-looking string (e.g. "token_prefix_test") and
update the corresponding assertion that checks got.TokenPrefix to expect that
synthetic value instead.

Comment thread justfile

test:
go test -v -coverprofile=coverage.out ./...
go test -v -coverprofile=coverage.out -cover ./...

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 | ⚡ Quick win

Remove redundant -cover flag.

The -cover flag is unnecessary here since -coverprofile=coverage.out already enables coverage collection. The -coverprofile flag implicitly turns on coverage, so explicitly specifying -cover is redundant.

♻️ Proposed fix
-	go test -v -coverprofile=coverage.out -cover ./...
+	go test -v -coverprofile=coverage.out ./...
📝 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
go test -v -coverprofile=coverage.out -cover ./...
go test -v -coverprofile=coverage.out ./...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@justfile` at line 30, The go test invocation includes a redundant -cover
flag; update the command string "go test -v -coverprofile=coverage.out -cover
./..." by removing the standalone -cover so it reads "go test -v
-coverprofile=coverage.out ./..." to rely on -coverprofile for coverage
collection.

Comment on lines +8 to +20
process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL = "http://catena.test";

let token = "initial-token";

mock.module("@clerk/nextjs", () => ({
useAuth() {
return {
getToken: async () => token,
isLoaded: true,
isSignedIn: true,
};
},
}));

@coderabbitai coderabbitai Bot May 22, 2026

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 | 🔴 Critical | 🏗️ Heavy lift

Critical: Module-level mocking causes test setup conflict.

The pipeline failure shows document is not defined at line 63, despite setup.ts establishing DOM globals via --preload. The module-level mock.module() call (line 12) and process.env assignment (line 8) execute during import, which can run before the preload completes or in a different context.

Additionally, line 8 redundantly sets NEXT_PUBLIC_CATENA_INSTANCE_URL (already set in setup.ts).

Solution: Move the Clerk mock into the test body or into a separate per-test setup, and remove the redundant env var assignment.

🔧 Proposed fix

Remove lines 8 and 10-20, and move the mock inside the test:

-process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL = "http://catena.test";
-
 let token = "initial-token";

-mock.module("`@clerk/nextjs`", () => ({
-    useAuth() {
-        return {
-            getToken: async () => token,
-            isLoaded: true,
-            isSignedIn: true,
-        };
-    },
-}));
-
 const authHeaders: (string | null)[] = [];
 
 afterEach(() => {

Then inside the test (after line 30):

 describe("AuthProvider", () => {
     test("adds the latest Clerk token to API requests", async () => {
+        mock.module("`@clerk/nextjs`", () => ({
+            useAuth() {
+                return {
+                    getToken: async () => token,
+                    isLoaded: true,
+                    isSignedIn: true,
+                };
+            },
+        }));
+
         server.use(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/providers/AuthProvider.test.tsx` around lines 8 - 20, The test file
sets process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL at module scope and calls
mock.module("`@clerk/nextjs`", ...) at import time, which runs before your test
preload/setup and can trigger "document is not defined"; remove the module-level
env assignment (process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL) and relocate the
Clerk mock.module call into the test body or a per-test beforeEach so
useAuth/getToken are mocked only during test execution (or create a dedicated
test setup file) rather than at module import.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

rereview

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the setup.ts file to see if NEXT_PUBLIC_CATENA_INSTANCE_URL is set there
cat web/src/test/setup.ts

echo "---"
# Check the current state of the AuthProvider test file
cat web/src/providers/AuthProvider.test.tsx

echo "---"
# Check if env var is set anywhere else in test files
rg "NEXT_PUBLIC_CATENA_INSTANCE_URL" web/src --type=tsx -n -A1 -B1
rg "NEXT_PUBLIC_CATENA_INSTANCE_URL" web/src -n -A1 -B1

Length of output: 5003


@Floffah, after looking at this more carefully, I need to walk back most of my original comment — sorry for the noise! 🐇

Here's what the re-review actually shows:

mock.module at module scope — my concern was wrong. Bun's --preload ./src/test/setup.ts guarantees the setup file (and its DOM globals like document) runs before any test module is loaded. There's no ordering hazard here. Additionally, top-level mock.module() is the correct and idiomatic Bun pattern — it's hoisted before ESM imports resolve, so moving it inside the test body would actually break the mock.

process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL — still a minor nit. setup.ts line 7 already sets this to "http://catena.test", so line 8 in AuthProvider.test.tsx is a redundant duplicate. It's completely harmless since both values are identical, but it could be cleaned up if you want to keep things tidy:

-process.env.NEXT_PUBLIC_CATENA_INSTANCE_URL = "http://catena.test";
-
 let token = "initial-token";

Other than that one minor nit, the current structure of the file is fine. Again, apologies for the misleading original comment!


🧠 Learnings used
Learnt from: Floffah
Repo: Floffah/catena PR: 6
File: web/src/app/(site)/[ownerName]/[repoName]/layout.tsx:35-40
Timestamp: 2026-05-21T14:33:33.229Z
Learning: In the web/ (Next.js) frontend, treat `NEXT_PUBLIC_CATENA_INSTANCE_URL` as the backend/API domain (not the frontend app origin). When building frontend-facing absolute URLs—e.g., Open Graph/Twitter image URLs and `metadataBase` in route layouts/pages—use a separate env var such as `NEXT_PUBLIC_APP_URL` that represents the frontend origin (scheme + host, optionally port). Avoid using `NEXT_PUBLIC_CATENA_INSTANCE_URL` for browser-visible origins.

@Floffah
Floffah merged commit 3dc4ff8 into main May 22, 2026
6 checks passed
@Floffah
Floffah deleted the features/testing branch May 22, 2026 09: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.

1 participant