Test from target (class or method)
Given a target (class or class.method), an optional source file path, and a test file path, produce or update a Mocha-style test suite (TypeScript .test.ts) aligned with project patterns. Focus on full logical branch coverage, validation, and error propagation. Source lives under src/; tests run with npm test or npm run test:all (Mocha + --require tsx/cjs).
Input parameters
Supply as ordered lines (one per line, no prefixes):
- TARGET_NAME (${targetCode}) — Fully qualified target to test: Class or Class.method. Examples:
UsersModel.getActiveUsers, AuthController.login. Use the real method name (no prefix); controllers expose private methods without .
- TESTFILEPATH (${testFile}) — Repo-relative path to the test file to create or update (must end with
.test.ts).
- (Optional) SOURCEFILEPATH (${sourceFile}) — Repo-relative path to the implementation file (e.g.
src/controllers/auth.controller.ts) for context and import resolution.
Example:
AuthController.login
test/controllers/auth.controller.test.ts
src/controllers/auth.controller.ts
Output
- Markdown-fenced TypeScript test content (or delta additions) only.
- Apply changes directly to ${testFile} in the workspace (create/update the file). Chat output must match the saved file. Do not only suggest edits in chat.
- Never modify the production file (${sourceFile}) when generating tests. Do not add factories or hooks in production to inject mocks.
Context linking
If ${sourceFile} is supplied, output (before the code block) two plain lines:
Implementation: ${sourceFile}
Test File: ${testFile}
If not supplied, skip these lines; do not guess paths. Relative imports in code are inferred from the provided source path.
Golden rules
- TypeScript/ESM:
import ... from '...'. Test files are .test.ts; source under src/ (e.g. src/controllers/, src/lib/, src/model/).
- Structure: Each class gets a top-level
describe('<ClassName>'). Each method gets a nested describe('<methodName>') — use the real method name (e.g. login, getNations), not __methodName.
- Organization: Inside each method block use optional
describe('Success') / describe('Error') when branches > 2; else individual it cases.
- Sinon: Use for stubs/spies; prefer
sinon.assert.calledWith over manual argument inspection.
- Async errors: No chai-as-promised; use try/catch for async error assertions.
- Controller private methods: Call via
(controller as any).methodName(...). Stub with sinon.stub(controller as any, 'methodName') (real name, no __).
- Secrets/config: Never import production secrets or mutate real environment configs.
- No obsolete refs: Do not reference removed or project-specific instruction files; integrate any needed logic into the test design.
- SQL: Do not assert on raw SQL strings; only assert that the query method was called with objects of the expected shape (e.g. replacements). Use
transactionClient in assertions (not transaction). Stub queryReturnFirst when the controller uses it; stub query when it expects { rows }.
- File writes: Always update ${testFile} on disk; chat output must mirror the saved content.
- Production code: Never modify ${sourceFile}. Prefer: (a) complete mock config so constructors do not throw (e.g. pubSub options, getstream, sms), (b) minimal fake env when config is missing, (c) local HTTP server or fixtures under
test/ only.
- Run tests (entire file, mandatory): After writing tests, always run the full
${testFile} suite (e.g. npm test -- ${testFile}), not just newly added tests or filtered describe/it blocks. Iterate until all tests in that file pass. Fix failures by changing test code only, not production.
- ESM stubs: Do not stub immutable ES Module default exports; Sinon cannot stub ESM bindings. Use HTTP server in test or indirection under
test/fixtures/ only if that pattern exists (do not edit production).
- Static utils: When encrypt/decrypt or similar static utilities are used, stub only those static methods (e.g.
Crypt.encryptAES); do not alter the class file.
- Unreachable branches: If a branch is impossible to cover without changing production, add
// TODO: unreachable branch without code change instead of changing production.
- PubSub/Notifications: Do not stub constructors or add production hooks. In the mock env provide config (e.g.
config.pubSubOptions with topicId and authentication: { projectId, credentials }) so new NotificationsManager(env) (and any internal PubSub client) does not throw. Add mockNotificationManager.store.resolves() and mockNotificationManager.publish.resolves() so async flow completes. For other deps (e.g. Getstream, SMS), provide the corresponding config entries so constructors do not read undefined.
- Rollback: If the controller does not wrap
rollback in try/catch, when rollback rejects, next is called with the rollback error. Assert next(rollbackError) and do not expect logger.error for rollback in that case.
- Logger: If the code under test uses
this.env.logger.warning, the mock env must provide logger.warning (not only logger.warn).
Coverage expectations
Cover: happy path, each conditional branch, invalid parameters, edge values (0, empty array, null, undefined), error propagation (next(error) in controllers, thrown errors in models/libs). Validate:
- HTTP status codes and response methods (
send, sendStatus, json, cookie, clearCookie).
- Sequence and arguments of model/DB operations.
- Side effects: cookie set/cleared, session manager calls, grants updated, data transformations.
Folder-specific directives
Controllers (test/controllers/*.test.ts)
- Mock env with: session config, getstream/sms (or equivalent) config,
pgConnection, pgModels, session (and sessionManager if used), and any other connections the controller uses (e.g. documentsConnection).
- For code that builds a notifications/pub-sub manager, provide config (e.g.
config.pubSubOptions with topicId and authentication: { projectId, credentials }) so the constructor does not throw; add store.resolves() and publish.resolves() on the mock where needed.
- Stub
env.session.checkAuthentication() to return a pass-through middleware when needed.
- Call controller methods via
(controller as any).methodName(request, response, next) — real method name.
- For methods that set cookies: assert the third argument of
response.cookie (options object); use sinon.match.object or check for expires when testing persistent sessions.
- Transactions: Stub
pgConnection.startTransaction with .resolves(t) and use the same t for commit(t) / rollback(t). In assertions use transactionClient (not transaction). For updateByKey, keys are an array (e.g. ["idus"], ['idup']); payload may include fields from request.session (e.g. idlastupdater_up).
- Error path: ensure
next(error) is called with the correct error (original or rollback error if rollback rejects and controller does not wrap it).
- Assert only externally observable behavior (responses and env calls), not implementation details.
Lib utilities (test/lib/*.test.ts)
- Use Node
assert or chai.expect for pure functions; exhaustive yet concise.
- Cover boundaries: empty arrays, null/undefined, formatting edge cases.
- For validators: valid value, invalid formats, null, undefined, empty, malformed.
- Stub only when the function uses time or randomness (e.g.
sinon.useFakeTimers()).
- Import paths: From
test/lib/ use ../../src/... (two levels to project root). From test/lib/notifications/ use ../../../src/... and ../../../config/... (three levels).
- For classes with private methods: call via
(instance as any).methodName(...); stub with sinon.stub(instance as any, 'methodName'). If the class has a single-argument constructor (e.g. options object), use that; method names are real (e.g. sendRequest), not prefixed with __.
Postgres models (test/model/postgres/.test.ts or test/pg-models/.test.ts)
- Mock connection with stubbed methods:
query, queryReturnFirst, queryPaged, insert, updateByKey, startTransaction, commit, rollback as used. Use .resolves(t) for startTransaction so the same t is used for commit/rollback.
- Stub return shapes to match the actual API:
queryPaged returns { totalRowCount, rows } or { total, rows }; query returns { rows }. Use aliased column names in mock rows when the SQL uses AS.
- For each method: assert correct
replacements and optionally sinon.match({ sql: sinon.match(/select/), replacements: [...] }). Use transactionClient when the method passes a transaction.
- Simulate DB responses:
{ rows: [...] }, { rows: [] }, null, rejection. Align stub with how the model uses the result (e.g. result?.rows ?? [] or throw when null).
- Error path must bubble the same Error instance.
- Transactional flows: assert order
startTransaction → updates/inserts → commit; error branch triggers rollback.
Notifications / domain logic (test/lib/notifications/.test.ts, test/lib/-notifications.test.ts)
- Do not change production to inject mocks. Provide mock config so the notifications/pub-sub manager constructor does not throw (e.g.
config.pubSubOptions with topicId and authentication: { projectId, credentials }).
- Add
mockNotificationManager.store.resolves() and mockNotificationManager.publish.resolves() so async flow completes.
- Call private methods via
(notifications as any).methodName(...); use describe('methodName', ...).
- Assert payload structure and invocation count on store/publish mocks. Include failure of underlying store and error propagation.
- Import paths: From
test/lib/notifications/ use ../../../src/... and ../../../config/... (three levels).
External service classes (e.g. Bridge, Getstream)
- If the class constructor takes a single options object and has a private method like
sendRequest, stub with sinon.stub(instance as any, 'sendRequest').
- If the class needs DB/config/logger, provide a complete mock (e.g.
pgConnection.queryReturnFirst, config.getstream) so constructors do not throw.
- Restore stubs in
afterEach: sinon.restore().
Export / batch utilities
- Use a minimal fake env (e.g. stubbed
pgConnection, pgModels) instead of a full real Environment when config is missing or incomplete.
- For helpers with private methods, call via
(helper as any).methodName(...).
- If file writes or streams occur, stub fs or stream constructors. Validate produced output (e.g. CSV) with a sample subset.
- Include an edge case for larger input (e.g. 1000 items) to ensure no throw and transformation called per item.
Assertion strategy
sinon.assert.calledOnce(stub), sinon.assert.calledWith(stub, expectedArgs...)
sinon.match for partial object or regex match of SQL. For pgConnection.query / updateByKey, use transactionClient in expected args (not transaction).
- For query calls, assert the shape passed by the code: e.g.
{ sql, replacements } or { sql, replacements, transactionClient }.
updateByKey: keys are an array; payload may include session-derived fields.
- For arrays and order-sensitive logic, use deep equality.
- For dates:
sinon.match.date or instanceof Date.
- Session/cookie: assert the third argument of
response.cookie (options object); use sinon.match.object or check expires for persistent sessions.
Error assertion pattern (async)
try {
await subject.method(args);
assert.fail('Expected method to throw');
} catch (err) {
assert.strictEqual(err, expectedError); // or shape assertions
}
Edge cases checklist
- Null / undefined inputs
- Empty arrays / empty objects
- Boundary numerics: 0, -1 (if invalid), large numbers
- Missing required properties
- Duplicate entries (where uniqueness applies)
- Partial updates (optional fields omitted)
- Upstream failure (e.g. token generation, notification push)
Session and auth
When controller endpoints require auth:
- Stub
env.session.checkAuthentication to return (req, res, next) => next().
- Test unauthenticated path only if the controller itself branches on session absence; otherwise rely on middleware coverage elsewhere.
Generated file behavior
- If ${testFile} exists: Merge new test blocks without duplicating existing method
describe. If the method block already exists, add only missing branches/cases. Do not remove or rename existing tests.
- If ${testFile} does not exist: Generate full skeleton (imports, top-level describe, method-level describes, coverage template). Use
// TODO for logic that cannot be inferred.
In all cases, update the real ${testFile} on disk to match the produced output.
Import paths
- From
test/ or test/controllers/ or test/lib/: ../../src/... (two levels to project root).
- From
test/lib/notifications/: ../../../src/... and ../../../config/... (three levels).
Use static imports where possible. If loading in a hook, require('../../src/lib/...') with tsx/cjs resolves correctly. Use the project’s shared types/constants package (e.g. HttpResponseStatus) when the codebase does.
Output format
Provide only the test file content inside a fenced `ts or `typescript block. When updating, show only new or changed blocks preceded by a brief comment // Added tests for <method>.
Parameter mapping
- ${targetCode}: Class or Class.method to test. Use the real method name. If only Class.method is supplied, generate tests for that method but keep the class-level and method-level
describe hierarchy.
- ${testFile}: Must end with
.test.ts. Drives import depth: two levels (../../) from test/, test/controllers/, or test/lib/; three levels (../../../) from test/lib/notifications/.
- ${sourceFile}: Optional; use to compute relative import path. Source under
src/; use .ts or no extension per project resolution. Do not use .mjs.
Quality gates (self-check)
When information is ambiguous
Insert a // TODO: comment with a concise note (max 60 chars) rather than guessing behavior that cannot be inferred from the code.
Return only the test code per the instructions above — no extra commentary beyond mandated context lines and added-block comments.
END OF SKILL.