diff --git a/.agents/PROJECT.md b/.agents/PROJECT.md index 05a7dcfa..00f841b6 100644 --- a/.agents/PROJECT.md +++ b/.agents/PROJECT.md @@ -226,6 +226,7 @@ The package README documents the `kb.yaml` configuration schema and merge semant - `nmr test` - Run tests - `nmr test:watch` - Run tests in watch mode - `nmr test:coverage` - Run tests with coverage +- `nmr test:integration` - Run deliberate-only integration tests (`*.int.test.ts`), excluded from the unit suite and CI - `nmr lint` - Fix lint - `nmr lint:check` - Check for lint - `nmr typecheck` - TypeScript check @@ -275,6 +276,7 @@ The package README documents the `kb.yaml` configuration schema and merge semant - Base config in `config/vitest.config.ts` - Coverage reporting with v8 provider - Package-specific configurations for different test types +- Real-library / full-install tests (e.g. real installs) are deliberate-only `*.int.test.ts` tests: excluded from the default unit suite and CI, run on demand via `nmr test:integration`. A package opts in by providing `vitest.integration.config.ts` and `vitest.standalone.config.ts` (see `packages/agents/`). Land new real-library/full-install tests here, not in the unit suite. ### Code quality diff --git a/packages/agents/src/commands/__tests__/install-smoke.test.ts b/packages/agents/src/commands/__tests__/install.int.test.ts similarity index 94% rename from packages/agents/src/commands/__tests__/install-smoke.test.ts rename to packages/agents/src/commands/__tests__/install.int.test.ts index 3c2f526d..e41ff396 100644 --- a/packages/agents/src/commands/__tests__/install-smoke.test.ts +++ b/packages/agents/src/commands/__tests__/install.int.test.ts @@ -10,13 +10,13 @@ import { isEnoent } from '../../lib/type-guards.ts'; import type { InstallOptions } from '../../lib/types.ts'; import { installCommand } from '../install.ts'; -// The only tests that install the real library; they assert real-content invariants a synthetic fixture cannot. -// Adding more real installs here raises the suite's parallel-load cost, so keep them to the minimum below. -describe('install smoke (real library)', () => { +// Installs the real content library, not a fixture, to catch failures that only show up with real +// content, such as an unreplaced `{...}` token or a link that wasn't rewritten. +describe('install (real library, full catalog)', () => { let tempDir: string; beforeEach(async () => { - tempDir = path.join(tmpdir(), `agents-test-smoke-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(tmpdir(), `agents-test-install-int-${Date.now()}-${Math.random().toString(36).slice(2)}`); await mkdir(path.join(tempDir, '.claude', 'skills'), { recursive: true }); await mkdir(path.join(tempDir, '.claude', 'agents'), { recursive: true }); await mkdir(path.join(tempDir, '.rovodev', 'skills'), { recursive: true }); diff --git a/packages/agents/src/commands/__tests__/sync-global-smoke.test.ts b/packages/agents/src/commands/__tests__/sync-global.int.test.ts similarity index 82% rename from packages/agents/src/commands/__tests__/sync-global-smoke.test.ts rename to packages/agents/src/commands/__tests__/sync-global.int.test.ts index a606ced0..65a1d96c 100644 --- a/packages/agents/src/commands/__tests__/sync-global-smoke.test.ts +++ b/packages/agents/src/commands/__tests__/sync-global.int.test.ts @@ -9,14 +9,13 @@ import type { InstallOptions } from '../../lib/types.ts'; import { initGlobalCommand } from '../init.ts'; import { syncGlobalCommand } from '../sync.ts'; -// Exercises the real content library end-to-end: `init --global` seeds the `all` collection, then `sync --global` -// resolves it and deploys the whole catalog into an isolated temp home. Heavier than the fixture-based sync tests -// (it deploys the real catalog), so kept to a single case. -describe('sync --global smoke (real library, all collection)', () => { +// Runs `init --global` then `sync --global` against the real content library to catch failures that +// only show up with real content. +describe('sync --global (real library, all collection)', () => { let homeDir: string; beforeEach(async () => { - homeDir = path.join(tmpdir(), `agents-test-sync-global-smoke-${Date.now()}-${Math.random().toString(36).slice(2)}`); + homeDir = path.join(tmpdir(), `agents-test-sync-global-int-${Date.now()}-${Math.random().toString(36).slice(2)}`); await mkdir(homeDir, { recursive: true }); }); diff --git a/packages/agents/vitest.integration.config.ts b/packages/agents/vitest.integration.config.ts new file mode 100644 index 00000000..cd7ea419 --- /dev/null +++ b/packages/agents/vitest.integration.config.ts @@ -0,0 +1,19 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; + +import { baseConfig } from '../../config/vitest.config.js'; +import { integrationTestPatterns } from '../../config/vitest.integration.config.js'; + +// Deliberate-only run for `*.int.test.ts` (real-library / full-install tests). Built from the root base +// config — which carries no `include` — so the integration `include` selects only `*.int.test.ts` rather +// than concatenating with the package's default `include` (vitest `mergeConfig` concatenates arrays). +// The timeout is wide because these install the real catalog end-to-end and run off the CI path. +const config = defineConfig({ + test: { + coverage: { include: ['src/**/*.ts'] }, + environment: 'node', + include: integrationTestPatterns, + testTimeout: 120_000, + }, +}); + +export default mergeConfig(baseConfig, config); diff --git a/packages/agents/vitest.standalone.config.ts b/packages/agents/vitest.standalone.config.ts new file mode 100644 index 00000000..d46644df --- /dev/null +++ b/packages/agents/vitest.standalone.config.ts @@ -0,0 +1,13 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; + +import { integrationTestPatterns } from '../../config/vitest.integration.config.js'; +import baseConfig from './vitest.config.js'; + +// Default test run: everything the base config selects, minus the deliberate-only `*.int.test.ts` tests. +const config = defineConfig({ + test: { + exclude: integrationTestPatterns, + }, +}); + +export default mergeConfig(baseConfig, config);