A test earns its value by detecting a specific regression, rather than adding a green tick. Conversation knowledge stores cross parsing, SQL, vector indexes, HTTP, and model calls. Organize fixtures around failure boundaries. This article checks ChatCrystal’s actual test entry points, then practices functions, dependency isolation, rollback, and route checks in one independent teaching file.
Current testing has four entry paths
The root scripts run server, client, electron, and audit-policy sequentially for pnpm test. That is not one src/**/*.test.ts glob. Server tests run node:test through tsx; client and Electron have their own test directories. Audit-policy uses a Node command with file isolation disabled.
| Path | Current command or mechanism | Observable scope |
|---|---|---|
| server | tsx --test src/**/*.test.ts |
Service, database, parser, and route contracts |
| client | tsx --test client/test/*.test.ts |
API wrappers, static source, language, and interface conventions |
| electron | tsx --test electron/test/*.test.ts |
Desktop state, navigation, updates, and wiring |
| audit-policy | node --test --test-isolation=none |
Policy decisions over audit results |
node:test and assert are built in, but the test chain also depends on tsx, sql.js, Fastify, and other packages. The entire suite is not dependency-free. Client source assertions are not real browser interaction tests either. The project’s engine requirement is Node 24.
A testing pyramid helps consider cost, but no evidence establishes a mandatory unit-test percentage for this project. Prioritize destructive data failures, state transitions, and external dependency failures, then add checks across boundaries based on regression evidence.
Find counterexamples worth preserving in existing tests
embedding.test.ts checks structured text fields and malformed JSON, as well as Vectra staged and committed state. “Does not throw” alone is insufficient: assert what survives and what stays out of index text. The chunking value 500 is a soft threshold; a long paragraph can exceed it. See embedding service design.
transaction.test.ts checks commits, rollbacks, and nested savepoints separately. A transaction function’s name does not establish that failures leave no partial data. Route tests use Fastify inject to register routes and inspect responses without listening on a port. This does not cover real networks, reverse proxies, or Electron lifecycles.
Some service functions accept replaceable dependencies to avoid models and real indexes. The project also directly replaces console.log or globalThis.fetch and restores them in finally, as in registry.test.ts and client API tests. Thus “all dependencies are injected, with no global replacement” is not current behavior. Isolation and restoration matter more than absolute claims about mock styles.
Write a complete offline teaching test
Install pinned dependencies in a new directory, save lesson.test.mjs, and run with Node 24+. These four tests use teaching functions, a teaching table, and a teaching route, rather than actual ChatCrystal services. They access no real model, personal database, imported file, or listening port.
npm init -y
npm install --ignore-scripts --no-audit --no-fund sql.js@1.14.2 fastify@5.12.3
node --test lesson.test.mjs
import assert from 'node:assert/strict';
import test from 'node:test';
import Fastify from 'fastify';
import initSqlJs from 'sql.js';
const normalize = (text) => text.trim().replace(/\s+/g, ' ');
async function summarizeIfAccepted(accepted, summarize) {
if (!accepted) return null;
return summarize();
}
test('normalization preserves content and handles whitespace', () => {
assert.equal(normalize(' first\nsecond '), 'first second');
assert.equal(normalize(''), '');
});
test('rejected input never reaches the model dependency', async () => {
const result = await summarizeIfAccepted(false, async () => {
throw new Error('unexpected model call');
});
assert.equal(result, null);
assert.equal(await summarizeIfAccepted(true, async () => 'fixture'), 'fixture');
});
test('a failed SQL transaction leaves no inserted row', async () => {
const SQL = await initSqlJs();
const db = new SQL.Database();
try {
db.run('CREATE TABLE items (value TEXT)');
assert.throws(() => {
db.run('BEGIN');
try {
db.run("INSERT INTO items VALUES ('temporary')");
throw new Error('fixture failure');
} catch (error) {
db.run('ROLLBACK');
throw error;
}
}, /fixture failure/);
assert.equal(db.exec('SELECT COUNT(*) FROM items')[0].values[0][0], 0);
} finally {
db.close();
}
});
test('inject checks one route without listening on a port', async () => {
const app = Fastify();
app.get('/lesson', async () => ({ success: true, data: ['fixture'] }));
try {
const response = await app.inject({ method: 'GET', url: '/lesson' });
assert.equal(response.statusCode, 200);
assert.deepEqual(response.json().data, ['fixture']);
assert.equal((await app.inject({ method: 'GET', url: '/missing' })).statusCode, 404);
} finally {
await app.close();
}
});
The file should produce four passing tests. Remove ROLLBACK and the failed transaction leaves temporary behind, making the zero-row assertion fail. Restore it and the tests pass again. Alternatively, make the rejected branch call summarize and verify that the unexpected dependency call fails the test. Exercising real failure paths establishes whether assertions check the intended behavior.
This minimal SQL test does not implement ChatCrystal’s nested transactions or verify disk persistence. Test actual withTransaction using existing project tests and additional failure fixtures. A rollback in one database also cannot establish consistency across vectors, files, and SQL.
Isolate environments and fixtures, then clean up
Some modules read DATA_DIR when imported. Actual import service tests set a temporary directory before dynamic imports. Tags route tests clean up connections and the temporary directory. Using a default path and deleting tables crosses the isolation boundary.
Fixtures should specify IDs, times, and roles, avoiding dependence on execution order. Restore registry, environment, global fetch, and timer changes. File isolation differs from shared state inside a file. WASM lookup also needs to match the dependency layout: the project’s sql.test-helper.ts uses require.resolve rather than assuming a fixed relative node_modules path.
State what CI success proves
The release workflow runs pnpm test before building. Passing existing tests establishes only that covered fixtures did not fail. It cannot rule out unknown regressions or replace packaged startup and interaction checks. Model quality requires separate reproducible samples; a successful mock does not establish model quality.
This article runs only the independent four-test lesson and its counterexamples. It does not run the entire application suite or produce timing benchmarks. For an actual change, execute project checks around affected boundaries and record remaining network, file, model, and desktop coverage limits.