Skip to content

test(web): Add unit tests for daemon web HTTP layer - #109

Merged
sudhirverma merged 4 commits into
mainfrom
test/daemon-web-unit-coverage
Aug 11, 2026
Merged

test(web): Add unit tests for daemon web HTTP layer#109
sudhirverma merged 4 commits into
mainfrom
test/daemon-web-unit-coverage

Conversation

@sudhirverma

@sudhirverma sudhirverma commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add and expand unit tests for packages/daemon/src/daemon/web/ covering grpc-web-control RPC helpers, http-security (CORS/auth/CSRF), validate-body, API schemas, OpenAI-compat routes, and createWebApp HTTP routes.
  • Mock gRPC transport in grpc-web-control.rpc.test.ts for sendStartWebServer / sendStopWebServer without a live daemon.
  • Raise web package line coverage from ~66% to ~78% (grpc-web-control, openai-compat, validate-body now ~90%+).
  • Address CodeRabbit review feedback: stronger constructor/CSRF/CORS assertions, isolated MCP config fixtures, server cleanup in finally blocks.

Commits

  • test(web): Add unit tests for daemon web HTTP layer — initial web-layer unit test coverage
  • Merge branch 'main' into test/daemon-web-unit-coverage — sync with main
  • test(web): address CodeRabbit review feedback on web unit tests — review-driven test hardening (bbe0a4f)
  • fix(test): avoid require() in server.test vi.hoisted for eslint — CI lint fix for lazy config dir init (32e72b2)

Test plan

  • npm run lint -w packages/daemon
  • npm run test -w packages/daemon -- --run src/daemon/web/ (226+ tests)
  • Full npm test (all workspaces)
  • CI green on PR

Expand tests for grpc-web-control RPC helpers, http-security middleware,
OpenAI-compat routes, validate-body helpers, API schemas, and createWebApp
routes to exercise auth, CORS, CSRF, and config validation paths.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Tests
    • Expanded coverage for web API request validation, authentication, CORS, CSRF, and workspace access rules.
    • Added RPC tests for starting and stopping the web server, including errors and cleanup.
    • Added coverage for server connection settings, TLS options, and socket handling.
    • Added comprehensive embedded web server tests covering health, configuration, providers, models, secrets, tools, MCP, sessions, chat, login, and error handling.
    • Added OpenAI-compatible API integration tests for model listing, chat completions, streaming, validation, and tool handling.

Walkthrough

Added broad Vitest coverage for daemon web schemas, validation, security middleware, gRPC control, OpenAI-compatible routes, and embedded-server behavior.

Changes

Daemon web test coverage

Layer / File(s) Summary
Request schemas and workspace validation
packages/daemon/src/daemon/web/api-schemas.test.ts, packages/daemon/src/daemon/web/validate-body.test.ts
Added coverage for request schemas, conditional fields, enums, unknown fields, workspace collection, configuration locations, and bad-request responses.
HTTP security middleware
packages/daemon/src/daemon/web/http-security.test.ts
Added coverage for security resolution, token paths, CORS, bearer and cookie authentication, CSRF checks, and same-origin validation.
Web control transport
packages/daemon/src/daemon/web/grpc-web-control.rpc.test.ts, packages/daemon/src/daemon/web/grpc-web-control.test.ts
Added coverage for gRPC start and stop calls, socket defaults, TLS overrides, error propagation, client closure, and certificate cleanup.
OpenAI compatibility and embedded server flows
packages/daemon/src/daemon/web/openai-compat.test.ts, packages/daemon/src/daemon/web/server.test.ts
Added coverage for OpenAI model listing, chat completions, SSE streaming, tool handling, validation failures, server lifecycle, web routes, login flows, MCP, sessions, static pages, and error responses.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the primary change: adding unit tests for the daemon web HTTP layer.
Description check ✅ Passed The description accurately describes the added web-layer tests, coverage improvements, and test plan.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@sudhirverma sudhirverma changed the title Add unit tests for daemon web HTTP layer test(web): Add unit tests for daemon web HTTP layer Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.78%. Comparing base (26ee9f5) to head (32e72b2).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #109      +/-   ##
==========================================
+ Coverage   63.70%   67.78%   +4.08%     
==========================================
  Files          37       37              
  Lines        4973     4973              
  Branches     1568     1568              
==========================================
+ Hits         3168     3371     +203     
+ Misses       1262     1056     -206     
- Partials      543      546       +3     
Flag Coverage Δ
daemon 67.78% <ø> (+4.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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: 2

🧹 Nitpick comments (6)
packages/daemon/src/daemon/web/http-security.test.ts (2)

593-613: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a negative CSRF case with a mismatched header.

The current test covers "no CSRF" and "matching CSRF". It does not cover a present-but-wrong CSRF header with no allowlisted origin. That path is the main CSRF bypass risk in createAuthMiddleware.

♻️ Proposed extra case
     expect(withCsrf.nextCalled).toBe(true);
+
+    const mismatched = runMiddleware(createAuthMiddleware(token, corsOrigins), {
+      method: 'POST',
+      headers: {
+        cookie: `${API_TOKEN_COOKIE}=${encodeURIComponent(token)}; ${CSRF_COOKIE}=${encodeURIComponent(csrf)}`,
+        [CSRF_HEADER]: 'other-value',
+      },
+    } as Partial<Request>);
+    expect(mismatched.nextCalled).toBe(false);
+    expect(mismatched.statusCode).toBe(403);
🤖 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 `@packages/daemon/src/daemon/web/http-security.test.ts` around lines 593 - 613,
Add a negative assertion in the test “requires CSRF or allowlisted origin for
cookie auth on POST” using the existing CSRF cookie but a mismatched CSRF header
and no allowlisted origin. Verify the middleware does not call next and returns
status 403, alongside the existing missing-token and matching-token cases.

447-451: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tighten two assertions to lock the contract.

Line 449 checks only the file name suffix. The test name states "points under the config directory", so assert the directory too. Line 573 checks the 401 status and body, but not the WWW-Authenticate header that createAuthMiddleware sets. A regression that drops that header stays undetected.

♻️ Proposed assertions
-    expect(getHttpApiTokenPath()).toMatch(/http-api-token$/);
+    expect(getHttpApiTokenPath()).toBe(path.join(getConfigDir(), 'http-api-token'));
     expect(res.statusCode).toBe(401);
+    expect(res.headers['www-authenticate']).toBe('Bearer');
     expect(res.body).toEqual({ error: 'Unauthorized' });

Use the mocked config dir helper already available in this test file if getConfigDir is not imported.

Also applies to: 573-581

🤖 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 `@packages/daemon/src/daemon/web/http-security.test.ts` around lines 447 - 451,
Strengthen the getHttpApiTokenPath test to assert the returned path is inside
the mocked config directory, not only that it ends with http-api-token; use the
existing config-directory helper if needed. In the unauthorized-response test
around createAuthMiddleware, also assert the WWW-Authenticate header is present
with the expected value while preserving the existing 401 status and body
assertions.
packages/daemon/src/daemon/web/server.test.ts (4)

984-990: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Close the server in a finally block.

If the expect at line 988 fails, line 989 never runs. The HTTP server stays open and holds the event loop, so the run can hang or report unrelated failures. Other tests in this file already use try/finally for this.

♻️ Proposed fix
     const { httpServer, baseUrl } = await startTestApp(state);
-    const res = await httpRequest(baseUrl, 'GET', '/api/providers');
-    expect(res.statusCode).toBe(500);
-    await stopTestApp(httpServer);
+    try {
+      const res = await httpRequest(baseUrl, 'GET', '/api/providers');
+      expect(res.statusCode).toBe(500);
+    } finally {
+      await stopTestApp(httpServer);
+    }
🤖 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 `@packages/daemon/src/daemon/web/server.test.ts` around lines 984 - 990, Wrap
the request and status assertion in a try/finally block within the “GET
/api/providers returns 500 when listProviders throws” test, and call
stopTestApp(httpServer) from the finally block so the server is always closed
when assertions or requests fail.

27-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move tmpConfigDir into vi.hoisted and clean it up.

vi.mock calls are hoisted above line 27. The factory runs when ../../core/paths.js is first imported, which happens during the hoisted import of ./server.js. The mocked functions only read tmpConfigDir lazily, so this works today. It breaks with a ReferenceError if any module reads getConfigDir() at import time. vi.hoisted removes that dependency on evaluation order.

Also, tmpConfigDir is never removed. Every run leaves a temp directory behind.

♻️ Proposed change
-const tmpConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'abbenay-server-unit-config-'));
+const { tmpConfigDir } = vi.hoisted(() => {
+  const nodeFs = require('node:fs') as typeof import('node:fs');
+  const nodeOs = require('node:os') as typeof import('node:os');
+  const nodePath = require('node:path') as typeof import('node:path');
+  return {
+    tmpConfigDir: nodeFs.mkdtempSync(nodePath.join(nodeOs.tmpdir(), 'abbenay-server-unit-config-')),
+  };
+});
 vi.mock('../../core/paths.js', async (importOriginal) => {

Add a global teardown for the directory:

+afterAll(() => {
+  fs.rmSync(tmpConfigDir, { recursive: true, force: true });
+});
🤖 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 `@packages/daemon/src/daemon/web/server.test.ts` around lines 27 - 36, Move
tmpConfigDir initialization into vi.hoisted so the mocked getUserConfigPath,
getWorkspaceConfigPath, and getConfigDir functions can safely access it during
module evaluation. Add global teardown for the test suite that recursively
removes tmpConfigDir after tests complete.

831-855: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tests share one mutable config.yaml and depend on execution order.

Several tests in this suite write to path.join(tmpConfigDir, 'config.yaml'): the POST /api/config test at line 541, the provider configure tests at lines 805 and 823, and this test. This test then deletes the file at line 853. Any later test that expects the earlier config content fails. The suite passes only because of the current declaration order.

Give this test its own config directory, or write the file with a unique name and point the mocked getUserConfigPath at a per-test path. That removes the order coupling.

🤖 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 `@packages/daemon/src/daemon/web/server.test.ts` around lines 831 - 855, Update
the GET /api/mcp-servers test to use an isolated per-test configuration path
instead of the shared tmpConfigDir/config.yaml. Point the mocked
getUserConfigPath (or equivalent test configuration) to that unique directory or
filename, and keep cleanup scoped to the isolated config so other tests’
configuration remains unaffected.

102-198: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a typed route-state fixture instead of broad casts.

The routes read secretStore, sessionStore, toolRegistry, mcpClientPool, mcpServer, chat, provider/model, and client/workspace members. Define the mock with Pick<DaemonState, ...> and explicit nested types. Use ClientType.CLI instead of 'CLI' as never.

🤖 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 `@packages/daemon/src/daemon/web/server.test.ts` around lines 102 - 198, Update
createMockState to use a typed Pick<DaemonState, ...> fixture covering the
route-consumed secretStore, sessionStore, toolRegistry, mcpClientPool,
mcpServer, chat, provider/model, and client/workspace members, with explicit
types for nested mocks. Remove the broad unknown cast and type assertions, and
represent CLI clients with ClientType.CLI rather than 'CLI' as never.
🤖 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 `@packages/daemon/src/daemon/web/grpc-web-control.test.ts`:
- Around line 50-55: Update the “constructs a client with default unix socket
when address is omitted” test to capture the gRPC client constructor arguments,
then assert the address is unix://${getDefaultSocketPath()} and
sslTargetNameOverride matches the configured value while preserving the existing
client and close assertions.

In `@packages/daemon/src/daemon/web/http-security.test.ts`:
- Around line 627-638: The test around createAuthMiddleware should exercise the
production middleware chain by running createCorsMiddleware before
authentication, and include https://abbenay.example.com in the configured
corsOrigins so the request reaches auth. Preserve the POST cookie-auth
assertion, and avoid relying on x-forwarded-proto unless the test explicitly
establishes the trusted proxy boundary required by isOriginAllowed.

---

Nitpick comments:
In `@packages/daemon/src/daemon/web/http-security.test.ts`:
- Around line 593-613: Add a negative assertion in the test “requires CSRF or
allowlisted origin for cookie auth on POST” using the existing CSRF cookie but a
mismatched CSRF header and no allowlisted origin. Verify the middleware does not
call next and returns status 403, alongside the existing missing-token and
matching-token cases.
- Around line 447-451: Strengthen the getHttpApiTokenPath test to assert the
returned path is inside the mocked config directory, not only that it ends with
http-api-token; use the existing config-directory helper if needed. In the
unauthorized-response test around createAuthMiddleware, also assert the
WWW-Authenticate header is present with the expected value while preserving the
existing 401 status and body assertions.

In `@packages/daemon/src/daemon/web/server.test.ts`:
- Around line 984-990: Wrap the request and status assertion in a try/finally
block within the “GET /api/providers returns 500 when listProviders throws”
test, and call stopTestApp(httpServer) from the finally block so the server is
always closed when assertions or requests fail.
- Around line 27-36: Move tmpConfigDir initialization into vi.hoisted so the
mocked getUserConfigPath, getWorkspaceConfigPath, and getConfigDir functions can
safely access it during module evaluation. Add global teardown for the test
suite that recursively removes tmpConfigDir after tests complete.
- Around line 831-855: Update the GET /api/mcp-servers test to use an isolated
per-test configuration path instead of the shared tmpConfigDir/config.yaml.
Point the mocked getUserConfigPath (or equivalent test configuration) to that
unique directory or filename, and keep cleanup scoped to the isolated config so
other tests’ configuration remains unaffected.
- Around line 102-198: Update createMockState to use a typed Pick<DaemonState,
...> fixture covering the route-consumed secretStore, sessionStore,
toolRegistry, mcpClientPool, mcpServer, chat, provider/model, and
client/workspace members, with explicit types for nested mocks. Remove the broad
unknown cast and type assertions, and represent CLI clients with ClientType.CLI
rather than 'CLI' as never.
🪄 Autofix

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 Plus

Run ID: 46eca344-ebbb-485f-8d77-44d79339c063

📥 Commits

Reviewing files that changed from the base of the PR and between 201b446 and 92db074.

📒 Files selected for processing (7)
  • packages/daemon/src/daemon/web/api-schemas.test.ts
  • packages/daemon/src/daemon/web/grpc-web-control.rpc.test.ts
  • packages/daemon/src/daemon/web/grpc-web-control.test.ts
  • packages/daemon/src/daemon/web/http-security.test.ts
  • packages/daemon/src/daemon/web/openai-compat.test.ts
  • packages/daemon/src/daemon/web/server.test.ts
  • packages/daemon/src/daemon/web/validate-body.test.ts

Comment thread packages/daemon/src/daemon/web/grpc-web-control.test.ts
Comment thread packages/daemon/src/daemon/web/http-security.test.ts Outdated
Strengthen grpc-web-control, http-security, and server test assertions
from PR review: constructor args, CORS/auth chain, CSRF negatives, and
isolated config fixtures.
Lazy-init tmp config dir inside the paths mock via dynamic import so
vi.hoisted stays compatible with @typescript-eslint/no-require-imports.
@sudhirverma
sudhirverma merged commit b13b7b9 into main Aug 11, 2026
10 checks passed
@sudhirverma
sudhirverma deleted the test/daemon-web-unit-coverage branch August 11, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants