From 11a2a0664332616a0b99457e41c23e896e93b7e0 Mon Sep 17 00:00:00 2001 From: eleith Date: Sun, 25 Oct 2026 19:27:36 -0700 Subject: [PATCH] Focus configuration tests on application behavior --- .changeset/focused-config-tests.md | 6 + .github/workflows/config.yml | 2 +- .../config/scripts/generate-schema.test.ts | 184 ------------------ packages/config/src/index.test.ts | 47 ----- packages/config/src/load.test.ts | 4 +- packages/config/src/schema.test.ts | 15 -- packages/config/vitest.config.ts | 2 +- 7 files changed, 11 insertions(+), 249 deletions(-) create mode 100644 .changeset/focused-config-tests.md delete mode 100644 packages/config/scripts/generate-schema.test.ts delete mode 100644 packages/config/src/index.test.ts diff --git a/.changeset/focused-config-tests.md b/.changeset/focused-config-tests.md new file mode 100644 index 0000000..c82f7cd --- /dev/null +++ b/.changeset/focused-config-tests.md @@ -0,0 +1,6 @@ +--- +'@directory/web': patch +'@directory/cli': patch +--- + +Remove config build tests and check defaults through the loader. diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 8eb4597..bed9067 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -49,5 +49,5 @@ jobs: run: pnpm --filter @directory/config check - name: Lint repository run: pnpm lint - - name: Unit and artifact tests + - name: Unit tests run: pnpm --filter @directory/config test:coverage diff --git a/packages/config/scripts/generate-schema.test.ts b/packages/config/scripts/generate-schema.test.ts deleted file mode 100644 index 0ced5b4..0000000 --- a/packages/config/scripts/generate-schema.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import { - cp, - mkdir, - mkdtemp, - readFile, - readdir, - rm, - stat, - symlink, - writeFile -} from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; - -const packageDirectory = fileURLToPath(new URL('..', import.meta.url)); -const workspaceDirectory = fileURLToPath(new URL('../../..', import.meta.url)); - -let fixtureRoot: string; -let fixturePackage: string; -let artifact: string; - -function run(...args: string[]) { - const result = spawnSync( - process.execPath, - [join(fixturePackage, 'scripts/generate-schema.ts'), ...args], - { cwd: fixtureRoot, encoding: 'utf8', timeout: 30_000, maxBuffer: 2 * 1024 * 1024 } - ); - expect(result.error).toBeUndefined(); - return result; -} - -async function expectCleanCache(): Promise { - const entries = await readdir(join(fixturePackage, 'node_modules/.cache')); - expect(entries.filter((name) => name.startsWith('directory-schema-'))).toEqual([]); -} - -beforeEach(async () => { - fixtureRoot = await mkdtemp(join(tmpdir(), 'directory-schema-test-')); - fixturePackage = join(fixtureRoot, 'packages/config'); - artifact = join(fixturePackage, 'src/config.schema.json'); - await mkdir(join(fixturePackage, 'scripts'), { recursive: true }); - await cp(join(workspaceDirectory, 'tsconfig.base.json'), join(fixtureRoot, 'tsconfig.base.json')); - for (const name of ['package.json', 'tsconfig.json', 'src']) { - await cp(join(packageDirectory, name), join(fixturePackage, name), { recursive: true }); - } - await cp( - join(packageDirectory, 'scripts/generate-schema.ts'), - join(fixturePackage, 'scripts/generate-schema.ts') - ); - await rm(artifact, { force: true }); - await symlink(join(workspaceDirectory, 'node_modules'), join(fixtureRoot, 'node_modules'), 'dir'); - // Link dependencies individually so the generator's cache stays disposable, too. - await mkdir(join(fixturePackage, 'node_modules')); - for (const name of await readdir(join(packageDirectory, 'node_modules'))) { - if (!name.startsWith('.')) { - await symlink( - join(packageDirectory, 'node_modules', name), - join(fixturePackage, 'node_modules', name), - 'dir' - ); - } - } -}); - -afterEach(async () => { - if (fixtureRoot) { - await rm(fixtureRoot, { recursive: true, force: true }); - } -}); - -describe('schema generator CLI from clean source', () => { - it('generates before a build, formats deterministically, and checks without rewriting', async () => { - expect(run().status).toBe(0); - const first = await readFile(artifact, 'utf8'); - expect(JSON.parse(first)).toMatchObject({ - $schema: 'https://json-schema.org/draft/2020-12/schema' - }); - expect(first.endsWith('\n')).toBe(true); - expect(first).toContain('\n\t'); - expect(run().status).toBe(0); - expect(await readFile(artifact, 'utf8')).toBe(first); - const beforeCheck = await stat(artifact, { bigint: true }); - const checked = run('--check'); - expect(checked.status, checked.stderr).toBe(0); - expect(checked.stdout).toContain('up to date'); - expect(await readFile(artifact, 'utf8')).toBe(first); - const afterCheck = await stat(artifact, { bigint: true }); - expect(afterCheck.mtimeNs).toBe(beforeCheck.mtimeNs); - expect(afterCheck.ctimeNs).toBe(beforeCheck.ctimeNs); - await expectCleanCache(); - }, 30_000); - - it('fails on byte drift without changing the artifact', async () => { - expect(run().status).toBe(0); - const drifted = `${await readFile(artifact, 'utf8')}\n`; - await writeFile(artifact, drifted); - const checked = run('--check'); - expect(checked.status).toBe(1); - expect(checked.stderr).toContain('out of date'); - expect(checked.stderr).toContain('pnpm schema:generate'); - expect(await readFile(artifact, 'utf8')).toBe(drifted); - await expectCleanCache(); - }, 30_000); - - it('reports a missing artifact without generating it', async () => { - const checked = run('--check'); - expect(checked.status).toBe(1); - expect(checked.stderr).toContain('src/config.schema.json is missing'); - await expect(readFile(artifact)).rejects.toMatchObject({ code: 'ENOENT' }); - await expectCleanCache(); - }, 30_000); - - it('uses edited canonical source even when dist contains stale output', async () => { - const dist = join(fixturePackage, 'dist'); - await mkdir(dist); - const staleModule = 'throw new Error("Stale dist must not be imported");\n'; - for (const name of ['index.js', 'schema.js', 'editor-schema.js']) { - await writeFile(join(dist, name), staleModule); - } - expect(run().status).toBe(0); - const original = await readFile(artifact, 'utf8'); - const schemaSource = join(fixturePackage, 'src/schema.ts'); - await writeFile( - schemaSource, - `${await readFile(schemaSource, 'utf8')}\nDirectoryConfigurationSchema.description = 'Fresh canonical source fixture';\n` - ); - const checked = run('--check'); - expect(checked.status, checked.stderr).toBe(1); - expect(checked.stderr).toContain('out of date'); - expect(await readFile(artifact, 'utf8')).toBe(original); - const generated = run(); - expect(generated.status, generated.stderr).toBe(0); - const updated = await readFile(artifact, 'utf8'); - expect(updated).not.toBe(original); - expect(updated).toContain('Fresh canonical source fixture'); - for (const name of ['index.js', 'schema.js', 'editor-schema.js']) { - expect(await readFile(join(dist, name), 'utf8')).toBe(staleModule); - } - await expectCleanCache(); - }, 30_000); - - it('rejects unknown and repeated arguments without generating an artifact', async () => { - for (const args of [['--unknown'], ['--check', '--check'], ['--check', 'extra'], ['--']]) { - const result = run(...args); - expect(result.status).toBe(1); - expect(result.stderr).toContain('Usage: node scripts/generate-schema.ts [--check]'); - } - await expect(readFile(artifact)).rejects.toMatchObject({ code: 'ENOENT' }); - await expect(readdir(join(fixturePackage, 'node_modules/.cache'))).rejects.toMatchObject({ - code: 'ENOENT' - }); - }, 30_000); - - it('reports compilation errors, preserves the artifact, and removes temporary output', async () => { - await writeFile(artifact, 'artifact sentinel\n'); - const schemaSource = join(fixturePackage, 'src/schema.ts'); - await writeFile( - schemaSource, - `${await readFile(schemaSource, 'utf8')}\nconst compilationFailure: string = 123;\n` - ); - const result = run(); - expect(result.status).toBe(1); - expect(result.stderr).toContain('Schema generation failed:'); - expect(result.stderr).toContain('TS2322'); - expect(await readFile(artifact, 'utf8')).toBe('artifact sentinel\n'); - await expectCleanCache(); - }, 30_000); - - it('cleans temporary modules after an import failure without writing an artifact', async () => { - const editorSource = join(fixturePackage, 'src/editor-schema.ts'); - await writeFile( - editorSource, - `${await readFile(editorSource, 'utf8')}\nthrow new Error('Fixture import failed');\n` - ); - const result = run(); - expect(result.status).toBe(1); - expect(result.stderr).toContain('Fixture import failed'); - await expect(readFile(artifact)).rejects.toMatchObject({ code: 'ENOENT' }); - await expectCleanCache(); - }, 30_000); -}); diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts deleted file mode 100644 index 9b9662c..0000000 --- a/packages/config/src/index.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { configurationVersion, DirectoryConfigurationSchema } from '@directory/config'; -import type { DirectoryConfiguration, DirectorySite } from '@directory/config/types'; -import { describe, expect, expectTypeOf, it } from 'vitest'; - -describe('@directory/config compiled public module', () => { - it('exports the supported configuration format as a literal', () => { - expect(configurationVersion).toBe(1); - expectTypeOf().toEqualTypeOf<1>(); - expect(import.meta.resolve('@directory/config')).toBe( - new URL('../dist/index.js', import.meta.url).href - ); - }); - - it('exports the canonical schema and editor JSON at their compiled/artifact paths', () => { - expect(DirectoryConfigurationSchema.properties.version.const).toBe(configurationVersion); - expect(import.meta.resolve('@directory/config/schema.json')).toBe( - new URL('./config.schema.json', import.meta.url).href - ); - expectTypeOf().toEqualTypeOf<1>(); - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); - }); - - it('keeps the types entry free of runtime schema, format registration and loader imports', () => { - const typesUrl = import.meta.resolve('@directory/config/types'); - expect(typesUrl).toBe(new URL('../dist/types.js', import.meta.url).href); - expect(readFileSync(new URL(typesUrl), 'utf8').trim()).toBe('export {};'); - const result = spawnSync( - process.execPath, - [ - '--input-type=module', - '--eval', - ` - const types = await import(${JSON.stringify(typesUrl)}); - const { FormatRegistry } = await import(${JSON.stringify(import.meta.resolve('@sinclair/typebox'))}); - console.log(JSON.stringify({ exports: Object.keys(types), registered: FormatRegistry.Has('directory-http-url') })); - ` - ], - { encoding: 'utf8' } - ); - expect(result.status, result.stderr).toBe(0); - expect(JSON.parse(result.stdout)).toEqual({ exports: [], registered: false }); - }); -}); diff --git a/packages/config/src/load.test.ts b/packages/config/src/load.test.ts index b6323ef..0687719 100644 --- a/packages/config/src/load.test.ts +++ b/packages/config/src/load.test.ts @@ -51,7 +51,7 @@ function expectRedacted(error: unknown): void { } describe('default, interpolate, then validate', () => { - it('loads defaults while retaining authored order, tag lists, group and advisory flags', () => { + it('loads independent defaults while preserving authored values and order', () => { const raw = { links: { zebra: { @@ -96,6 +96,8 @@ describe('default, interpolate, then validate', () => { }); expect(config).not.toHaveProperty('auth'); expect(config.links.alpha).not.toHaveProperty('group'); + config.links.alpha!.tags.push('changed'); + expect(validateConfig(raw, { env: {} }).links.alpha!.tags).toEqual([]); expect(raw).toEqual(before); }); diff --git a/packages/config/src/schema.test.ts b/packages/config/src/schema.test.ts index 6e52073..a752b25 100644 --- a/packages/config/src/schema.test.ts +++ b/packages/config/src/schema.test.ts @@ -82,21 +82,6 @@ describe('configuration defaults and inferred types', () => { expect(value).not.toHaveProperty('links.router.group'); }); - it('does not mutate authored input or share mutable defaults between calls', () => { - const first = defaultFixture(raw); - if (!Value.Check(DirectoryConfigurationSchema, configurationReferences, first)) - throw new Error('Invalid fixture'); - first.site.title = 'Changed'; - first.site.logo_path = '/public/changed.svg'; - expect(defaultFixture(raw)).toHaveProperty('site.logo_path', '/favicon.svg'); - first.links['photos']!.requires_auth = true; - first.links['photos']!.tags.push('changed'); - expect(defaultFixture(raw)).toHaveProperty('links.photos.tags', []); - expect(defaultFixture(raw)).toHaveProperty('site.title', 'Directory'); - expect(defaultFixture(raw)).toHaveProperty('links.photos.requires_auth', false); - expect(raw).toEqual({ links: { photos: link } }); - }); - it('keeps loaded defaults required and genuinely optional metadata optional in TypeScript', () => { expectTypeOf().toEqualTypeOf<1>(); expectTypeOf().toEqualTypeOf(); diff --git a/packages/config/vitest.config.ts b/packages/config/vitest.config.ts index a0c4958..0c668a7 100644 --- a/packages/config/vitest.config.ts +++ b/packages/config/vitest.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - include: ['src/**/*.test.ts', 'scripts/**/*.test.ts'], + include: ['src/**/*.test.ts'], coverage: { provider: 'v8', include: ['src/**/*.ts'], -- 2.54.0