Governs how to examine, write, fix, and run tests in the rc-unified-crm-extension monorepo. Use when writing new tests, debugging failing tests, examining test coverage, adding tests for new features or connectors, or asking about test conventions, test structure, mocking strategies, or running tests.
72
87%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
rc-unified-crm-extension/
├── packages/core/test/ # Unit tests for @app-connect/core
│ ├── setup.js # Syncs SQLite models, silences console, registers testUtils
│ ├── lib/ # Pure utility tests (jwt, oauth, callLogComposer, util…)
│ ├── handlers/ # Handler tests (auth, contact, log, admin)
│ ├── models/ # Sequelize model CRUD tests
│ ├── connector/ # Registry + proxy engine tests
│ └── mcp/tools/ # One test file per MCP tool
├── tests/ # Root integration tests
│ ├── setup.js # Syncs SQLite models before all root tests
│ ├── fixtures/connectorMocks.js # Shared mock data + factory functions for all CRMs
│ ├── platformInfo.json # CRM platform config used in parameterised tests
│ └── connectors/ # One *.int.test.js per CRM connector
└── packages/template/test/ # Smoke tests for the template package| Command | Scope |
|---|---|
npm test | All tests (root + core), combined summary |
npm run test:root | Root integration tests only |
npm test --workspace=@app-connect/core | Core unit tests only |
npm run test:coverage | All tests with coverage report |
npm run test:watch --workspace=@app-connect/core | Watch mode for core |
Environment: Tests automatically load packages/core/.env.test (for core) or .env.test at repo root (for integration tests). Never modify production .env for tests.
| Test type | File location | File name pattern | When to use |
|---|---|---|---|
| Unit | packages/core/test/<area>/ | *.test.js | Testing a single module in isolation |
| Integration | tests/connectors/ | *.int.test.js | Testing a full CRM connector end-to-end |
| MCP tool | packages/core/test/mcp/tools/ | *.test.js | Testing an MCP tool's definition + execute() |
packages/core/test/)const moduleUnderTest = require('../../../path/to/module');
// Mock ALL external dependencies at the top
jest.mock('../../../lib/jwt');
jest.mock('../../../models/callLogModel');
describe('Module Name', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('functionName', () => {
test('should <expected behaviour> when <condition>', async () => {
// Arrange
dependency.method.mockReturnValue(value);
// Act
const result = await moduleUnderTest.functionName(input);
// Assert
expect(result).toEqual(expected);
expect(dependency.method).toHaveBeenCalledWith(expectedArg);
});
});
});jest.mock() declarations go at the top of the file, before any require of the module under test.jest.clearAllMocks() in beforeEach (the jest config also sets clearMocks: true, but be explicit).// Arrange … // Act … // Assert comments for non-trivial tests.global.testUtils helpers from packages/core/test/setup.js:
createMockUser(overrides), createMockCallLog(overrides), createMockContact(overrides)resetConnectorRegistry(), createMockConnector(overrides)jest.mock('../../models/sequelize', () => {
const { Sequelize } = require('sequelize');
return { sequelize: new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) };
});await Model.sync({ force: true }) in beforeAll and await Model.destroy({ where: {} }) in afterEach.tests/connectors/)const nock = require('nock');
const connector = require('../../src/connectors/<name>');
const { createMockUser, createMockCallLog, … } = require('../fixtures/connectorMocks');
jest.mock('@app-connect/core/lib/jwt', () => ({ decodeJwt: jest.fn().mockReturnValue({ id: 'decoded-user-id' }) }));
jest.mock('@app-connect/core/models/userModel', () => ({ UserModel: { findByPk: jest.fn() } }));
describe('<Name> Connector', () => {
let mockUser;
beforeEach(() => {
nock.cleanAll();
jest.clearAllMocks();
mockUser = createMockUser({ platform: '<name>', hostname: 'test.<crm>.com' });
});
afterEach(() => { nock.cleanAll(); });
describe('getAuthType', () => {
it('should return oauth', () => {
expect(connector.getAuthType()).toBe('oauth');
});
});
describe('findContact', () => {
it('should find a contact successfully', async () => {
// Arrange — nock intercepts the real HTTP call
nock('https://test.<crm>.com')
.get('/api/contacts')
.query({ query: '+1234567890' })
.reply(200, { data: [{ id: 1, name: 'Jane Doe' }] });
// Act
const result = await connector.findContact({ user: mockUser, phoneNumber: '+1234567890' });
// Assert
expect(result.successful).toBe(true);
expect(result.contact[0].name).toBe('Jane Doe');
});
});
});nock (not jest.mock) for all external HTTP calls.nock.cleanAll() in both beforeEach and afterEach.it() in integration tests, test() in unit tests (both work — stay consistent per file).tests/fixtures/connectorMocks.js; do not inline large mock objects.getAuthType, getOauthInfo/getApiKeyInfo, getUserInfo, findContact, createCallLog, updateCallLog, createMessageLog — and their error paths.packages/core/test/mcp/tools/)Each file must test both sub-sections:
tool definition — verify definition.name, definition.description, definition.inputSchema, and required fields.execute — happy path(s) + every named error condition (missing params, invalid JWT, connector not found, DB conflict, unexpected throws).Error returns must use { success: false, error: '...' } shape; success returns { success: true, data: { … } }.
npx jest packages/core/test/mcp/tools/createCallLog.test.js --no-coverageundefined is not a function) → add/fix jest.mock().nock: No match for request → the interceptor URL/method/body doesn't match the real call.--verbose to see individual test names:
npx jest tests/connectors/pipedrive.int.test.js --verbose --no-coveragepackages/core/.env.test is present and DATABASE_URL='sqlite::memory:' is set.Before touching anything, decide which side is wrong. Getting this wrong wastes time or, worse, silences a real bug.
| Question | Points toward… |
|---|---|
| Was this test passing before a recent code change? | Code is wrong — the test caught a regression |
| Did the test always pass vacuously (e.g. mock returns anything, assertion is too loose)? | Test is wrong — it wasn't actually testing real behaviour |
| Does the new behaviour make semantic sense for the function's contract? | If yes → test needs updating; if no → code is wrong |
| Is the test asserting an implementation detail (exact internal call args) rather than observable output? | Test is wrong — over-specified, should be loosened |
| Do multiple tests fail together in the same area? | Likely code is wrong (a shared dependency broke) |
| Is only one very specific test failing while others in the same file pass? | More likely that test is wrong or its mock setup is stale |
Fix the code when:
packages/core/handlers/.{ success: false } but the tool was supposed to succeed — the tool's execute() is throwing unexpectedly.Fix the test when:
jest.mock() paths and call assertions.UserModel.findByPk now returns an extra field) — update the mock return value in the test, not the model.expect(...).toHaveBeenCalledWith(...) fails because the function signature gained a new optional param — update the assertion, not the code.git stash or a temp branch) and re-run the test. If it goes green, the code change broke it.jest.mock() return values and expect(...).toHaveBeenCalledWith(...) assertions.nock.recorder.rec() temporarily to capture the exact URL/body, then match the interceptor.createMockUser/CallLog factory and re-sync with { force: true }.test('…', async () => { … }, 60000) or check for an unresolved promise (missing await).The core package collects coverage automatically. View the HTML report after running:
npm run test:coverage --workspace=@app-connect/core
# Report at packages/core/coverage/index.htmlTarget: keep coverage stable or improving — CI runs npm run test-coverage on every push and PR.
tests/fixtures/connectorMocks.jspackages/core/test/setup.jspackages/core/jest.config.jsjest.config.js.github/workflows/tests.ymlf59d4a2
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.