import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { customisedWarnings, writeGeneratedFiles } from '../generated-writes.js' let outdir: string beforeEach(() => { outdir = mkdtempSync(join(tmpdir(), 'ss-gen-writes-')) }) afterEach(() => rmSync(outdir, { recursive: true, force: true })) describe('writeGeneratedFiles — the guarded write loop', () => { it('writes new and existing generated files (unconditionally — no content comparison)', () => { mkdirSync(join(outdir, 'src'), { recursive: true }) writeFileSync(join(outdir, 'src', 'A.cs'), '// @generated-by x\nold', 'utf8') const r = writeGeneratedFiles([{ path: 'src/A.cs', content: 'new' }, { path: 'src/deep/B.cs', content: 'b' }], { outdir }) expect(r.written).toHaveLength(2) expect(r.skipped).toEqual([]) expect(readFileSync(join(outdir, 'src', 'A.cs'), 'utf8')).toBe('new') expect(readFileSync(join(outdir, 'src', 'deep', 'B.cs'), 'utf8')).toBe('b') }) it('NEVER overwrites a file whose head carries @customised (both comment forms), and returns it', () => { mkdirSync(join(outdir, 'src'), { recursive: true }) writeFileSync(join(outdir, 'src', 'Svc.cs'), '// @customised — hand-owned\nmine', 'utf8') writeFileSync(join(outdir, 'src', 'Page.tsx'), '/* @customised */\nmine too', 'utf8') const r = writeGeneratedFiles([{ path: 'src/Svc.cs', content: 'gen' }, { path: 'src/Page.tsx', content: 'gen' }, { path: 'src/New.cs', content: 'gen' }], { outdir }) expect(r.skipped.map((p) => p.replace(/\\/g, '/'))).toEqual([join(outdir, 'src', 'Svc.cs'), join(outdir, 'src', 'Page.tsx')].map((p) => p.replace(/\\/g, '/'))) expect(r.written).toHaveLength(1) expect(readFileSync(join(outdir, 'src', 'Svc.cs'), 'utf8')).toContain('mine') expect(readFileSync(join(outdir, 'src', 'Page.tsx'), 'utf8')).toContain('mine too') const w = customisedWarnings(r) expect(w).toHaveLength(2) expect(w[0]).toMatch(/kept: marked @customised/) }) it('refuses a path that escapes outdir (traversal)', () => { expect(() => writeGeneratedFiles([{ path: '../escape.cs', content: 'x' }], { outdir })).toThrow() expect(existsSync(join(outdir, '..', 'escape.cs'))).toBe(false) }) it('dryRun classifies without touching the disk', () => { const r = writeGeneratedFiles([{ path: 'src/Dry.cs', content: 'x' }], { outdir, dryRun: true }) expect(r.written).toHaveLength(1) expect(existsSync(join(outdir, 'src', 'Dry.cs'))).toBe(false) }) })