-
Notifications
You must be signed in to change notification settings - Fork 2
Add comprehensive test coverage for poll, review, and moderation modules #333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3bcf157
acf8b5a
cb89e12
b453986
d8a8218
92cde49
6e6339f
41b4c7b
8da9f8c
782167e
9133d69
f83a6e2
f461217
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,145 @@ | ||||||
| # Test Coverage Analysis | ||||||
|
|
||||||
| **Date:** 2026-03-19 | ||||||
| **Test files:** 242 total (`tests/`: 196 bot tests, `web/tests/`: 46 dashboard tests) | ||||||
| **Scope of this PR:** 14 changed files focused on coverage gaps, test reliability, and Vitest warning cleanup | ||||||
|
|
||||||
| ## Current State | ||||||
|
|
||||||
| The repository currently has **242 test files** covering bot runtime code, API routes, middleware, utilities, and the Next.js dashboard. The Vitest config enforces an **85% coverage threshold** across statements, branches, functions, and lines. | ||||||
|
|
||||||
| ### Recently Resolved Test Failures | ||||||
|
|
||||||
| | Test File | Previous Failure | Root Cause | Status in This PR | | ||||||
| |-----------|---------|------------| | ||||||
|
||||||
| |-----------|---------|------------| | |
| |-----------|-------------------|-----------|-------------------| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refresh this backlog section to match the work already added here.
These sections still list DB-failure coverage and the pollHandler / reviewHandler / requireGlobalAdmin suites as follow-up work, even though this PR adds those tests. That makes the analysis stale, and the carried numbering is also what markdownlint MD029 is flagging in the recommendation lists. Move completed items into a “done in this PR” section and restart numbering under each priority heading.
Also applies to: 123-145
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/test-coverage-analysis.md` around lines 64 - 73, Update the "Database
Error Handling" backlog section in docs/test-coverage-analysis.md to reflect
work already completed in this PR: create a new "Done in this PR" subsection and
move the completed items (DB-failure coverage, tests for pollHandler,
reviewHandler, and requireGlobalAdmin suites) into it, then restart numbering
under each priority heading so lists are sequential (fixing the MD029 duplicate
numbering warning); ensure the affected modules list (ai.js, triage.js,
moderation.js, warningEngine.js, pollHandler.js, reviewHandler.js) is adjusted
to only show remaining work and update any references to the original backlog
items.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| vi.mock('../../../src/logger.js', () => ({ | ||
| info: vi.fn(), | ||
| warn: vi.fn(), | ||
| error: vi.fn(), | ||
| })); | ||
|
|
||
| vi.mock('../../../src/modules/config.js', () => ({ | ||
| getConfig: vi.fn().mockReturnValue({ botOwnerIds: ['owner1', 'owner2'] }), | ||
| })); | ||
|
|
||
| vi.mock('../../../src/utils/permissions.js', () => ({ | ||
| getBotOwnerIds: vi.fn().mockReturnValue(['owner1', 'owner2']), | ||
| })); | ||
|
|
||
| import { requireGlobalAdmin } from '../../../src/api/middleware/requireGlobalAdmin.js'; | ||
| import { warn } from '../../../src/logger.js'; | ||
|
|
||
| // ── Helpers ───────────────────────────────────────────────────────────────── | ||
|
|
||
| function makeReq(overrides = {}) { | ||
| return { authMethod: 'api-secret', user: { userId: 'owner1' }, path: '/test', ...overrides }; | ||
| } | ||
|
|
||
| function makeRes() { | ||
| const res = { | ||
| status: vi.fn().mockReturnThis(), | ||
| json: vi.fn().mockReturnThis(), | ||
| }; | ||
| return res; | ||
| } | ||
|
|
||
| // ── Tests ─────────────────────────────────────────────────────────────────── | ||
|
|
||
| describe('requireGlobalAdmin middleware', () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| // ── api-secret auth ─────────────────────────────────────────────────── | ||
|
|
||
| describe('api-secret auth', () => { | ||
| it('should call next() for api-secret auth method', () => { | ||
| const req = makeReq({ authMethod: 'api-secret' }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin(req, res, next); | ||
|
|
||
| expect(next).toHaveBeenCalled(); | ||
| expect(res.status).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| // ── oauth auth ──────────────────────────────────────────────────────── | ||
|
|
||
| describe('oauth auth', () => { | ||
| it('should call next() when user is a bot owner', () => { | ||
| const req = makeReq({ authMethod: 'oauth', user: { userId: 'owner1' } }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin(req, res, next); | ||
|
|
||
| expect(next).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should return 403 when user is not a bot owner', () => { | ||
| const req = makeReq({ authMethod: 'oauth', user: { userId: 'nobody' } }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin(req, res, next); | ||
|
|
||
| expect(res.status).toHaveBeenCalledWith(403); | ||
| expect(res.json).toHaveBeenCalledWith( | ||
| expect.objectContaining({ error: expect.stringContaining('bot owner') }), | ||
| ); | ||
| expect(next).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should handle null user gracefully', () => { | ||
| const req = makeReq({ authMethod: 'oauth', user: null }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin(req, res, next); | ||
|
|
||
| expect(res.status).toHaveBeenCalledWith(403); | ||
| expect(next).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| // ── unknown auth ────────────────────────────────────────────────────── | ||
|
|
||
| describe('unknown auth method', () => { | ||
| it('should return 401 for unknown auth method', () => { | ||
| const req = makeReq({ authMethod: 'unknown' }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin(req, res, next); | ||
|
|
||
| expect(res.status).toHaveBeenCalledWith(401); | ||
| expect(res.json).toHaveBeenCalledWith({ error: 'Unauthorized' }); | ||
| expect(warn).toHaveBeenCalledWith( | ||
| 'Unknown authMethod in global admin check', | ||
| expect.objectContaining({ authMethod: 'unknown' }), | ||
| ); | ||
| expect(next).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should return 401 for undefined auth method', () => { | ||
| const req = makeReq({ authMethod: undefined }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin(req, res, next); | ||
|
|
||
| expect(res.status).toHaveBeenCalledWith(401); | ||
| }); | ||
| }); | ||
|
|
||
| // ── 4-argument form ─────────────────────────────────────────────────── | ||
|
|
||
| describe('4-argument form (resource label)', () => { | ||
| it('should support requireGlobalAdmin(resource, req, res, next)', () => { | ||
| const req = makeReq({ authMethod: 'oauth', user: { userId: 'nobody' } }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin('Backup access', req, res, next); | ||
|
|
||
| expect(res.status).toHaveBeenCalledWith(403); | ||
| expect(res.json).toHaveBeenCalledWith( | ||
| expect.objectContaining({ error: expect.stringContaining('Backup access') }), | ||
| ); | ||
| }); | ||
|
|
||
| it('should use default label when resource is falsy', () => { | ||
| const req = makeReq({ authMethod: 'oauth', user: { userId: 'nobody' } }); | ||
| const res = makeRes(); | ||
| const next = vi.fn(); | ||
|
|
||
| requireGlobalAdmin('', req, res, next); | ||
|
|
||
| expect(res.json).toHaveBeenCalledWith( | ||
| expect.objectContaining({ error: expect.stringContaining('Global admin access') }), | ||
| ); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the coverage threshold in Line 9.
Line 9 says Vitest enforces 85% for branches, but the bot-suite policy is 82% branches; 85% across all metrics only applies to
web/tests. The current wording mixes the two suites and misstates the enforced baseline.Based on learnings: "Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead."
🤖 Prompt for AI Agents