Some checks failed
tests / Unit tests (Node 22) (push) Failing after 2m13s
Expands the unit test suite from 320 to 380 tests (+60) and adds a
Gitea Actions CI workflow. Closes all six follow-up recommendations
from the test-architecture validation report.
New tests (tests/unit/):
- ollamaClient.test.ts — Ollama SDK wrapper, options passthrough
- litellmClient.test.ts — OpenAI SDK wrapper, model fallback
- personaLoader.test.ts — Zod validation + cache invalidation
- foundryReward.test.ts — Tool plugin: lookup, errors, partial grants
- xpAwarder.test.ts — Bulk XP awards + per-player skip reasons
- redisErrorPath.test.ts — Singleton error handler does not crash
- messageRouterRunLLMTurn.test.ts — 18 cases for the runtime heart:
narrative-only path, tool dispatch, filter correction, retry loop
guard, missed-skill-check heuristic, typing indicator interval,
LLM error fallback, archive on resolve.
Coverage (line %):
- harness/litellmClient.ts 0 → 100
- harness/ollamaClient.ts 0 → 100
- harness/tools/foundryReward.ts 0 → 100
- session/xpAwarder.ts 0 → 100
- persona/loader.ts 0 → 100
- db/redis.ts 0 → 100
- bot/handlers/messageRouter.ts 0 → 39.86 (runLLMTurn now covered)
Tooling:
- package.json: + test:coverage, test:watch scripts
- devDep: @vitest/coverage-v8@^3.1.0
- tests/README.md: conventions, anti-patterns, template map
- .gitignore: exclude coverage/
- .gitea/workflows/test.yml: Node 22, npm cache, tsc --noEmit gate
Documentation (from earlier /bmad-document-project run, now committed):
- docs/index.md
- docs/project-overview.md
- docs/architecture.md
- docs/deployment-guide.md
- docs/api-contracts.md
- docs/data-models.md
- docs/source-tree-analysis.md
- docs/component-inventory.md
- docs/development-guide.md
- _bmad-output/test-artifacts/automate-validation-report.md
Co-Authored-By: Claude <noreply@anthropic.com>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
// ── capture the registered error listener so we can fire it ──────────────────
|
|
const { errorListeners } = vi.hoisted(() => ({
|
|
errorListeners: [] as Array<(err: Error) => void>,
|
|
}));
|
|
|
|
vi.mock('../../src/config.js', () => ({
|
|
config: { REDIS_URL: 'redis://localhost:6379' },
|
|
}));
|
|
|
|
vi.mock('ioredis', () => {
|
|
return {
|
|
Redis: vi.fn().mockImplementation(() => ({
|
|
on: vi.fn((event: string, listener: (err: Error) => void) => {
|
|
if (event === 'error') errorListeners.push(listener);
|
|
return undefined;
|
|
}),
|
|
})),
|
|
};
|
|
});
|
|
|
|
import { Redis } from 'ioredis';
|
|
|
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
beforeEach(() => {
|
|
errorListeners.length = 0;
|
|
consoleErrorSpy.mockClear();
|
|
// Force a re-import of redis.ts to register a fresh error listener.
|
|
vi.resetModules();
|
|
});
|
|
|
|
describe('db/redis.ts error handler', () => {
|
|
it('registers an error listener on the Redis client at module load', async () => {
|
|
await import('../../src/db/redis.js');
|
|
|
|
expect(errorListeners).toHaveLength(1);
|
|
});
|
|
|
|
it('logs the error to console.error when the Redis client emits "error"', async () => {
|
|
await import('../../src/db/redis.js');
|
|
|
|
expect(errorListeners).toHaveLength(1);
|
|
errorListeners[0](new Error('ECONNREFUSED 127.0.0.1:6379'));
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'[redis] connection error',
|
|
expect.objectContaining({ message: 'ECONNREFUSED 127.0.0.1:6379' }),
|
|
);
|
|
});
|
|
|
|
it('does not throw or crash when the error has a non-standard shape', async () => {
|
|
await import('../../src/db/redis.js');
|
|
|
|
// Some ioredis errors come wrapped or with extra props. The handler just
|
|
// forwards to console.error; it must not throw.
|
|
expect(() => {
|
|
const err = Object.assign(new Error('boom'), { code: 'ECONNRESET', syscall: 'connect' });
|
|
errorListeners[0](err);
|
|
}).not.toThrow();
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('[redis] connection error', expect.anything());
|
|
});
|
|
|
|
it('constructs the Redis client with lazyConnect and maxRetriesPerRequest: 3', async () => {
|
|
await import('../../src/db/redis.js');
|
|
|
|
expect(Redis).toHaveBeenCalledWith('redis://localhost:6379', {
|
|
lazyConnect: true,
|
|
maxRetriesPerRequest: 3,
|
|
});
|
|
});
|
|
});
|