import { afterEach, describe, expect, test } from "bun:test"; import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { InvalidUtf8Error, readText } from "../src/lib/fs"; import { disableReadOnlyGuard } from "../src/lib/guard"; import { preflightInstall } from "../src/commands/preflight-install"; const directories: string[] = []; afterEach(async () => { await Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))); }); async function fixture(bytes: Uint8Array): Promise { const directory = await mkdtemp(join(tmpdir(), "hy2xs-utf8-")); directories.push(directory); const path = join(directory, "config.env"); await writeFile(path, bytes); return path; } describe("строгое чтение текстовых файлов", () => { for (const [name, bytes] of [ ["байт FF", new Uint8Array([0x61, 0xff, 0x62])], ["оборванная последовательность C3", new Uint8Array([0x61, 0xc3])], ["UTF-8-кодирование суррогата", new Uint8Array([0x61, 0xed, 0xa0, 0x80])] ] as const) { test(`отвергает ${name} без U+FFFD`, async () => { const path = await fixture(bytes); await expect(readText(path)).rejects.toBeInstanceOf(InvalidUtf8Error); }); } test("сохраняет начальный BOM как U+FEFF для транспортной проверки", async () => { const path = await fixture(new Uint8Array([0xef, 0xbb, 0xbf, 0x41, 0x3d, 0x31, 0x0a])); expect(await readText(path)).toBe("\uFEFFA=1\n"); }); test("не путает настоящий U+FFFD с ошибкой декодирования", async () => { const text = "A=abcde�\n"; const path = await fixture(new TextEncoder().encode(text)); expect(await readText(path)).toBe(text); }); test("byte-level отказ preflight не изменяет каталог пакета", async () => { for (const bytes of [ new Uint8Array([0xff]), new Uint8Array([0xc3]), new Uint8Array([0xed, 0xa0, 0x80]) ]) { const path = await fixture(bytes); const packageDir = join(path, ".."); const before = await readdir(packageDir); try { await expect( preflightInstall({ packageDir, sourceConfigPath: path, runtimeConfigPath: path, nonInteractive: true, skipFirewall: false, skipServiceStart: false, skipSmoke: false }) ).rejects.toBeInstanceOf(InvalidUtf8Error); } finally { disableReadOnlyGuard(); } expect(await readdir(packageDir)).toEqual(before); } }); });