Skip to content

test(backend): add comprehensive unit test suite (179 cases) - #45

Open
parthdude07 wants to merge 1 commit into
genesis-kb:userauthfrom
parthdude07:test/unit-backend
Open

test(backend): add comprehensive unit test suite (179 cases)#45
parthdude07 wants to merge 1 commit into
genesis-kb:userauthfrom
parthdude07:test/unit-backend

Conversation

@parthdude07

@parthdude07 parthdude07 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

This PR adds a full suite of 179 passing unit tests for the backend, covering utilities, middleware, services, and controllers. It also adds ESM testing support with Jest.


Summary by cubic

Adds a comprehensive backend unit test suite (179 cases) and enables native ESM testing with Jest. Also updates asyncHandler to return its Promise to improve error propagation in middleware.

  • New backend/jest.config.js enables ESM tests; npm test sets NODE_OPTIONS="--experimental-vm-modules" for Node’s ESM.
  • Adds dev dependency supertest; production dependencies are unchanged.
  • Collects coverage from src/** excluding src/server.js and src/config/logger.js.
  • Changes asyncHandler to return the Promise (old: no return; new: returns). This improves chaining/testability without altering Express behavior.
  • Requires Node >=18 (unchanged). Tests run without extra env; JWT-specific assertions skip if JWT_SECRET is not set.

Written for commit 9ec5066. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a604ddf-cbf5-4dee-bc8d-3ef68b2e72a5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@cubic-dev-ai cubic-dev-ai 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.

11 issues found across 19 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/tests/controllers/aiController.test.js">

<violation number="1" location="backend/tests/controllers/aiController.test.js:36">
P3: The tests only cover happy paths and never assert the HTTP status code, even though createMockRes already captures it. They also never exercise the controller's error handling (Gemini service rejection, Supabase failure) or the `transcriptId`-absent branch where `getCachedAIContent`/`cacheAIContent` must not be called. A controller returning the wrong status or failing to propagate an AI/DB error would pass this suite. Add tests that reject the service mocks and assert `res.statusCode`, and one test per handler without `transcriptId` asserting the cache helpers are not invoked.</violation>
</file>

<file name="backend/tests/middleware/rateLimiter.test.js">

<violation number="1" location="backend/tests/middleware/rateLimiter.test.js:4">
P3: The docstring says this file "Tests configuration values and the skip logic for rate limiters," but all five tests only assert `typeof limiter === 'function'`. `rateLimit()` always returns a middleware function, so these assertions cannot fail and give no actual coverage of windowMs/max, the health-endpoint skip in generalLimiter, or the keyGenerator in aiLimiter. Remove the misleading docstring or add assertions for the configuration and skip behavior.</violation>
</file>

<file name="backend/tests/controllers/transcriptController.test.js">

<violation number="1" location="backend/tests/controllers/transcriptController.test.js:75">
P3: The second `mockQuery.mockResolvedValueOnce({ rows: [] })` in the 'returns 200 when found' test is never consumed. `getTranscriptById` calls `supabaseService.fetchTranscriptById`, which issues exactly one query; there is no chunks query, so the mock queued for it is dead and the `// chunks (empty for now)` comment is misleading. Remove the extra mock or the comment so the test reflects the actual query path.</violation>
</file>

<file name="backend/package.json">

<violation number="1" location="backend/package.json:12">
P2: The `NODE_OPTIONS='--experimental-vm-modules' jest` inline-env syntax only works on POSIX shells (sh/bash). On Windows cmd/PowerShell the script fails because `VAR=value` at the start of a command is not valid syntax, so the npm test script breaks for Windows developers and CI runners. Use a cross-platform approach, e.g. `cross-env NODE_OPTIONS=--experimental-vm-modules jest`, or set the flag in the Jest config with `--experimental-vm-modules` passed to jest itself.</violation>

<violation number="2" location="backend/package.json:42">
P3: supertest is added as a devDependency but never imported anywhere in the test suite — `rg supertest tests/` returns no matches across the 15 test files, which mock controllers with `jest.unstable_mockModule` and plain mock `res` objects. Remove the dependency (and any lockfile update) unless a future integration test is intended here.</violation>
</file>

<file name="backend/tests/middleware/validation.test.js">

<violation number="1" location="backend/tests/middleware/validation.test.js:65">
P3: This describe block is labeled "register" but exercises `validationRules.authRegister`, not `validationRules.register`. The naming will mislead readers about which rule set is actually covered, and the real `register` rules remain untested. Align the description with the rules under test, or add coverage for the actual `register` set.</violation>
</file>

<file name="backend/tests/services/authService.test.js">

<violation number="1" location="backend/tests/services/authService.test.js:5">
P3: The file header states "Mocks: query (from supabaseService), bcrypt, jwt, config", but bcrypt and jwt are not mocked — the tests generate real bcrypt hashes via `await import('bcryptjs')` and exercise the real jsonwebtoken `jsonwebtoken.sign` in `generateToken`, and jsonwebtoken isn't imported anywhere in this file. Correct the comment so it matches reality (only query, config, and logger are mocked) and won't mislead a maintainer into thinking bcrypt/jwt isolation exists.</violation>
</file>

<file name="backend/tests/middleware/auth.test.js">

<violation number="1" location="backend/tests/middleware/auth.test.js:79">
P3: This test is named "throws 401 TOKEN_EXPIRED for expired JWT" but never exercises the TOKEN_EXPIRED branch. The token is signed with the hardcoded secret `test-secret-for-unit-tests-min32chars!!`, while the middleware verifies with `config.auth.jwtSecret` (a different secret from env). `jwt.verify` checks the signature before the expiry claim, so a mismatched secret always throws 'invalid signature' -> `INVALID_TOKEN`, and the permissive assertion (line 79) lets the test pass without ever hitting the `TOKEN_EXPIRED` mapping in `verifyToken`. The test gives false coverage confidence for the error-handling path it claims to cover. Sign the expired token with the real config secret (or mock `jwt.verify`) and assert `TOKEN_EXPIRED` specifically.</violation>

<violation number="2" location="backend/tests/middleware/auth.test.js:86">
P3: The `if (!secret) return` skip guards (lines 86, 104, 141) are dead code, so the valid-token tests either never run (silently passing without assertions) or couple the suite to a real environment secret rather than being isolated unit tests. `auth.test.js` imports `auth.js`, which imports `config/index.js`, and that module runs `validateEnvVars(['JWT_SECRET'])` at import time and throws if `JWT_SECRET` is missing. So when the secret is absent the whole test file fails to load before any guard runs; when it is present the guards never trigger. This contradicts the file header's claim that jwt.verify is mocked. The suite needs `JWT_SECRET` set and depends on the real config secret, making it non-deterministic outside CI secrets and masking the untested valid-token paths.</violation>
</file>

<file name="backend/tests/controllers/authController.test.js">

<violation number="1" location="backend/tests/controllers/authController.test.js:22">
P2: This test mocks only `authService` and `logger`, but importing the controller pulls in the real `../middleware/errorHandler.js`, which imports `../config/index.js`. That config module runs `validateEnvVars(['JWT_SECRET'])` at import time (and loads `dotenv`) and throws if `JWT_SECRET` is unset, so this unit test fails to load in any environment without that secret/dotenv. The sibling healthController.test.js avoids this by mocking `../../src/config/index.js`. Mock the config (and errorHandler) dependency here too so the controller test stays isolated.</violation>
</file>

<file name="backend/tests/controllers/audiobookController.test.js">

<violation number="1" location="backend/tests/controllers/audiobookController.test.js:4">
P3: The file header claims this suite covers `buildRoadmapChapters`, but that function is private in `audiobookController.js` (not exported) and there is no test exercising it. Likewise, `fetchAllAudiobooks`, `fetchAudiobookRoadmap`, and `fetchUserProgress` are declared in the mock but never used — `getAllAudiobooks` and `getAudiobookRoadmap` (which exercise them) are not even imported. Either the header/mocks should reflect actual coverage or these routes should be tested.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread backend/package.json
"build": "npm install --production",
"lint": "eslint src/",
"test": "jest"
"test": "NODE_OPTIONS='--experimental-vm-modules' jest"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The NODE_OPTIONS='--experimental-vm-modules' jest inline-env syntax only works on POSIX shells (sh/bash). On Windows cmd/PowerShell the script fails because VAR=value at the start of a command is not valid syntax, so the npm test script breaks for Windows developers and CI runners. Use a cross-platform approach, e.g. cross-env NODE_OPTIONS=--experimental-vm-modules jest, or set the flag in the Jest config with --experimental-vm-modules passed to jest itself.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/package.json, line 12:

<comment>The `NODE_OPTIONS='--experimental-vm-modules' jest` inline-env syntax only works on POSIX shells (sh/bash). On Windows cmd/PowerShell the script fails because `VAR=value` at the start of a command is not valid syntax, so the npm test script breaks for Windows developers and CI runners. Use a cross-platform approach, e.g. `cross-env NODE_OPTIONS=--experimental-vm-modules jest`, or set the flag in the Jest config with `--experimental-vm-modules` passed to jest itself.</comment>

<file context>
@@ -9,7 +9,7 @@
     "build": "npm install --production",
     "lint": "eslint src/",
-    "test": "jest"
+    "test": "NODE_OPTIONS='--experimental-vm-modules' jest"
   },
   "keywords": [
</file context>

default: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
}));

const { register, login, me } = await import(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This test mocks only authService and logger, but importing the controller pulls in the real ../middleware/errorHandler.js, which imports ../config/index.js. That config module runs validateEnvVars(['JWT_SECRET']) at import time (and loads dotenv) and throws if JWT_SECRET is unset, so this unit test fails to load in any environment without that secret/dotenv. The sibling healthController.test.js avoids this by mocking ../../src/config/index.js. Mock the config (and errorHandler) dependency here too so the controller test stays isolated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/controllers/authController.test.js, line 22:

<comment>This test mocks only `authService` and `logger`, but importing the controller pulls in the real `../middleware/errorHandler.js`, which imports `../config/index.js`. That config module runs `validateEnvVars(['JWT_SECRET'])` at import time (and loads `dotenv`) and throws if `JWT_SECRET` is unset, so this unit test fails to load in any environment without that secret/dotenv. The sibling healthController.test.js avoids this by mocking `../../src/config/index.js`. Mock the config (and errorHandler) dependency here too so the controller test stays isolated.</comment>

<file context>
@@ -0,0 +1,138 @@
+  default: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
+}));
+
+const { register, login, me } = await import(
+  '../../src/controllers/authController.js'
+);
</file context>

extractEntities,
} = await import('../../src/controllers/aiController.js');

function createMockRes() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The tests only cover happy paths and never assert the HTTP status code, even though createMockRes already captures it. They also never exercise the controller's error handling (Gemini service rejection, Supabase failure) or the transcriptId-absent branch where getCachedAIContent/cacheAIContent must not be called. A controller returning the wrong status or failing to propagate an AI/DB error would pass this suite. Add tests that reject the service mocks and assert res.statusCode, and one test per handler without transcriptId asserting the cache helpers are not invoked.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/controllers/aiController.test.js, line 36:

<comment>The tests only cover happy paths and never assert the HTTP status code, even though createMockRes already captures it. They also never exercise the controller's error handling (Gemini service rejection, Supabase failure) or the `transcriptId`-absent branch where `getCachedAIContent`/`cacheAIContent` must not be called. A controller returning the wrong status or failing to propagate an AI/DB error would pass this suite. Add tests that reject the service mocks and assert `res.statusCode`, and one test per handler without `transcriptId` asserting the cache helpers are not invoked.</comment>

<file context>
@@ -0,0 +1,162 @@
+  extractEntities,
+} = await import('../../src/controllers/aiController.js');
+
+function createMockRes() {
+  const res = {
+    statusCode: null,
</file context>

@@ -0,0 +1,43 @@
/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The docstring says this file "Tests configuration values and the skip logic for rate limiters," but all five tests only assert typeof limiter === 'function'. rateLimit() always returns a middleware function, so these assertions cannot fail and give no actual coverage of windowMs/max, the health-endpoint skip in generalLimiter, or the keyGenerator in aiLimiter. Remove the misleading docstring or add assertions for the configuration and skip behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/middleware/rateLimiter.test.js, line 4:

<comment>The docstring says this file "Tests configuration values and the skip logic for rate limiters," but all five tests only assert `typeof limiter === 'function'`. `rateLimit()` always returns a middleware function, so these assertions cannot fail and give no actual coverage of windowMs/max, the health-endpoint skip in generalLimiter, or the keyGenerator in aiLimiter. Remove the misleading docstring or add assertions for the configuration and skip behavior.</comment>

<file context>
@@ -0,0 +1,43 @@
+/**
+ * Unit Tests — rateLimiter.js middleware
+ *
+ * Tests configuration values and the skip logic for rate limiters.
+ */
+
</file context>

it('returns 200 with transcript data when found', async () => {
const row = { id: 't1', title: 'Talk' };
mockQuery.mockResolvedValueOnce({ rows: [row] }); // transcript
mockQuery.mockResolvedValueOnce({ rows: [] }); // chunks (empty for now)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The second mockQuery.mockResolvedValueOnce({ rows: [] }) in the 'returns 200 when found' test is never consumed. getTranscriptById calls supabaseService.fetchTranscriptById, which issues exactly one query; there is no chunks query, so the mock queued for it is dead and the // chunks (empty for now) comment is misleading. Remove the extra mock or the comment so the test reflects the actual query path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/controllers/transcriptController.test.js, line 75:

<comment>The second `mockQuery.mockResolvedValueOnce({ rows: [] })` in the 'returns 200 when found' test is never consumed. `getTranscriptById` calls `supabaseService.fetchTranscriptById`, which issues exactly one query; there is no chunks query, so the mock queued for it is dead and the `// chunks (empty for now)` comment is misleading. Remove the extra mock or the comment so the test reflects the actual query path.</comment>

<file context>
@@ -0,0 +1,135 @@
+  it('returns 200 with transcript data when found', async () => {
+    const row = { id: 't1', title: 'Talk' };
+    mockQuery.mockResolvedValueOnce({ rows: [row] }); // transcript
+    mockQuery.mockResolvedValueOnce({ rows: [] });    // chunks (empty for now)
+
+    const req = { params: { id: 't1' } };
</file context>


// ─── validationRules.register ───────────────────────────────────────────────

describe('validationRules.register (authRegister)', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This describe block is labeled "register" but exercises validationRules.authRegister, not validationRules.register. The naming will mislead readers about which rule set is actually covered, and the real register rules remain untested. Align the description with the rules under test, or add coverage for the actual register set.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/middleware/validation.test.js, line 65:

<comment>This describe block is labeled "register" but exercises `validationRules.authRegister`, not `validationRules.register`. The naming will mislead readers about which rule set is actually covered, and the real `register` rules remain untested. Align the description with the rules under test, or add coverage for the actual `register` set.</comment>

<file context>
@@ -0,0 +1,178 @@
+
+// ─── validationRules.register ───────────────────────────────────────────────
+
+describe('validationRules.register (authRegister)', () => {
+  const rules = validationRules.authRegister;
+
</file context>

* Unit Tests — authService.js
*
* Tests registerUser, loginUser, getUserById.
* Mocks: query (from supabaseService), bcrypt, jwt, config.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The file header states "Mocks: query (from supabaseService), bcrypt, jwt, config", but bcrypt and jwt are not mocked — the tests generate real bcrypt hashes via await import('bcryptjs') and exercise the real jsonwebtoken jsonwebtoken.sign in generateToken, and jsonwebtoken isn't imported anywhere in this file. Correct the comment so it matches reality (only query, config, and logger are mocked) and won't mislead a maintainer into thinking bcrypt/jwt isolation exists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/services/authService.test.js, line 5:

<comment>The file header states "Mocks: query (from supabaseService), bcrypt, jwt, config", but bcrypt and jwt are not mocked — the tests generate real bcrypt hashes via `await import('bcryptjs')` and exercise the real jsonwebtoken `jsonwebtoken.sign` in `generateToken`, and jsonwebtoken isn't imported anywhere in this file. Correct the comment so it matches reality (only query, config, and logger are mocked) and won't mislead a maintainer into thinking bcrypt/jwt isolation exists.</comment>

<file context>
@@ -0,0 +1,202 @@
+ * Unit Tests — authService.js
+ *
+ * Tests registerUser, loginUser, getUserById.
+ * Mocks: query (from supabaseService), bcrypt, jwt, config.
+ */
+
</file context>
Suggested change
* Mocks: query (from supabaseService), bcrypt, jwt, config.
* Mocks: query (from supabaseService), config, logger.

it('sets req.user and calls next() for a valid JWT', () => {
// Use the actual JWT_SECRET from the environment (set in .env)
const secret = process.env.JWT_SECRET;
if (!secret) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The if (!secret) return skip guards (lines 86, 104, 141) are dead code, so the valid-token tests either never run (silently passing without assertions) or couple the suite to a real environment secret rather than being isolated unit tests. auth.test.js imports auth.js, which imports config/index.js, and that module runs validateEnvVars(['JWT_SECRET']) at import time and throws if JWT_SECRET is missing. So when the secret is absent the whole test file fails to load before any guard runs; when it is present the guards never trigger. This contradicts the file header's claim that jwt.verify is mocked. The suite needs JWT_SECRET set and depends on the real config secret, making it non-deterministic outside CI secrets and masking the untested valid-token paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/middleware/auth.test.js, line 86:

<comment>The `if (!secret) return` skip guards (lines 86, 104, 141) are dead code, so the valid-token tests either never run (silently passing without assertions) or couple the suite to a real environment secret rather than being isolated unit tests. `auth.test.js` imports `auth.js`, which imports `config/index.js`, and that module runs `validateEnvVars(['JWT_SECRET'])` at import time and throws if `JWT_SECRET` is missing. So when the secret is absent the whole test file fails to load before any guard runs; when it is present the guards never trigger. This contradicts the file header's claim that jwt.verify is mocked. The suite needs `JWT_SECRET` set and depends on the real config secret, making it non-deterministic outside CI secrets and masking the untested valid-token paths.</comment>

<file context>
@@ -0,0 +1,155 @@
+  it('sets req.user and calls next() for a valid JWT', () => {
+    // Use the actual JWT_SECRET from the environment (set in .env)
+    const secret = process.env.JWT_SECRET;
+    if (!secret) {
+      // Skip in CI where JWT_SECRET may not be set
+      console.warn('Skipping valid JWT test: JWT_SECRET not set');
</file context>

} catch (err) {
expect(err.statusCode).toBe(401);
// Either TOKEN_EXPIRED or INVALID_TOKEN depending on secret match
expect(['TOKEN_EXPIRED', 'INVALID_TOKEN']).toContain(err.code);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This test is named "throws 401 TOKEN_EXPIRED for expired JWT" but never exercises the TOKEN_EXPIRED branch. The token is signed with the hardcoded secret test-secret-for-unit-tests-min32chars!!, while the middleware verifies with config.auth.jwtSecret (a different secret from env). jwt.verify checks the signature before the expiry claim, so a mismatched secret always throws 'invalid signature' -> INVALID_TOKEN, and the permissive assertion (line 79) lets the test pass without ever hitting the TOKEN_EXPIRED mapping in verifyToken. The test gives false coverage confidence for the error-handling path it claims to cover. Sign the expired token with the real config secret (or mock jwt.verify) and assert TOKEN_EXPIRED specifically.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/middleware/auth.test.js, line 79:

<comment>This test is named "throws 401 TOKEN_EXPIRED for expired JWT" but never exercises the TOKEN_EXPIRED branch. The token is signed with the hardcoded secret `test-secret-for-unit-tests-min32chars!!`, while the middleware verifies with `config.auth.jwtSecret` (a different secret from env). `jwt.verify` checks the signature before the expiry claim, so a mismatched secret always throws 'invalid signature' -> `INVALID_TOKEN`, and the permissive assertion (line 79) lets the test pass without ever hitting the `TOKEN_EXPIRED` mapping in `verifyToken`. The test gives false coverage confidence for the error-handling path it claims to cover. Sign the expired token with the real config secret (or mock `jwt.verify`) and assert `TOKEN_EXPIRED` specifically.</comment>

<file context>
@@ -0,0 +1,155 @@
+    } catch (err) {
+      expect(err.statusCode).toBe(401);
+      // Either TOKEN_EXPIRED or INVALID_TOKEN depending on secret match
+      expect(['TOKEN_EXPIRED', 'INVALID_TOKEN']).toContain(err.code);
+    }
+  });
</file context>

/**
* Unit Tests — audiobookController.js
*
* Tests input validation, status enums, progress capping, and buildRoadmapChapters.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The file header claims this suite covers buildRoadmapChapters, but that function is private in audiobookController.js (not exported) and there is no test exercising it. Likewise, fetchAllAudiobooks, fetchAudiobookRoadmap, and fetchUserProgress are declared in the mock but never used — getAllAudiobooks and getAudiobookRoadmap (which exercise them) are not even imported. Either the header/mocks should reflect actual coverage or these routes should be tested.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/controllers/audiobookController.test.js, line 4:

<comment>The file header claims this suite covers `buildRoadmapChapters`, but that function is private in `audiobookController.js` (not exported) and there is no test exercising it. Likewise, `fetchAllAudiobooks`, `fetchAudiobookRoadmap`, and `fetchUserProgress` are declared in the mock but never used — `getAllAudiobooks` and `getAudiobookRoadmap` (which exercise them) are not even imported. Either the header/mocks should reflect actual coverage or these routes should be tested.</comment>

<file context>
@@ -0,0 +1,250 @@
+/**
+ * Unit Tests — audiobookController.js
+ *
+ * Tests input validation, status enums, progress capping, and buildRoadmapChapters.
+ * Mocks: audiobookService.
+ */
</file context>

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