import assert from 'node:assert/strict' import { promises as fs } from 'node:fs' import path from 'node:path' import test from 'node:test' import { scanLegacyStorageBoundaries, TEMP_ROOT, } from './check-legacy-storage-boundaries.mjs' async function withFixture(files, callback) { const root = await fs.mkdtemp(path.join(TEMP_ROOT, 'legacy-storage-boundary-')) try { for (const [relativePath, content] of Object.entries(files)) { const absolutePath = path.join(root, relativePath) await fs.mkdir(path.dirname(absolutePath), { recursive: true }) await fs.writeFile(absolutePath, content, 'utf8') } await callback(root) } finally { await fs.rm(root, { recursive: true, force: true }) } } test('拦截新 migration、Dockerfile 和 PowerShell 中的旧域名', async () => { await withFixture({ 'prisma/migrations/20260812_bad/migration.sql': "ALTER TABLE x SET DEFAULT 'https://s3.1080p.hk/x'", Dockerfile: 'ENV STORAGE=https://s3.yydd.ai', 'scripts/deploy.ps1': "$target = 'https://s3.1080p.hk'", }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.deepEqual( violations.map(item => item.relativePath).sort(), ['Dockerfile', 'prisma/migrations/20260812_bad/migration.sql', 'scripts/deploy.ps1'], ) }) }) test('旧域名匹配不区分大小写,并覆盖常见前端与容器文件', async () => { await withFixture({ 'Dockerfile.production': 'ENV STORAGE=https://S3.1080P.HK', 'Containerfile.worker': 'ENV STORAGE=https://S3.YYDD.AI', 'components/legacy.jsx': "const host='https://S3.1080P.HK'", 'styles/legacy.scss': 'body{background:url(https://S3.YYDD.AI/a.png)}', }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.deepEqual( violations.map(item => item.relativePath).sort(), [ 'Containerfile.worker', 'Dockerfile.production', 'components/legacy.jsx', 'styles/legacy.scss', ], ) }) }) test('只跳过仓库根 uploads 数据,不得漏扫源码中的 uploads 路由', async () => { await withFixture({ 'uploads/runtime-data.json': '{"legacy":"https://s3.1080p.hk/data"}', 'app/api/uploads/[...path]/route.ts': "const target='https://s3.1080p.hk/source'", 'app/uploads/[...path]/route.ts': "const target='https://s3.yydd.ai/source'", }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.deepEqual( violations.map(item => item.relativePath).sort(), ['app/api/uploads/[...path]/route.ts', 'app/uploads/[...path]/route.ts'], ) }) }) test('allowlist 文件缺标记或精确计数变化时也会失败', async () => { await withFixture({ 'lib/network-detection.ts': "// LEGACY_READ_ONLY\nconst hosts=['s3.1080p.hk']", 'lib/url-rewrite.ts': "const target='s3.1080p.hk'", }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.equal(violations.length, 2) }) }) test('精确只读兼容和唯一历史 migration 可以通过', async () => { await withFixture({ 'lib/network-detection.ts': "// LEGACY_READ_ONLY\nconst hosts=['s3.1080p.hk','s3.yydd.ai']", 'prisma/migrations/20251026015433_add_material_type/migration.sql': "DEFAULT 'https://s3.1080p.hk/logo.png'", }, async root => { assert.deepEqual(await scanLegacyStorageBoundaries(root), []) }) }) test('品牌 migration 精确清洗旧品牌值并中和三个 Logo 默认值', async () => { const migrationPath = path.join( process.cwd(), 'prisma/migrations/20260811_update_brand_logo_defaults/migration.sql', ) const content = await fs.readFile(migrationPath, 'utf8') assert.equal((content.match(/s3\\\.1080p\\\.hk/gi) || []).length, 2) assert.match(content, /LEGACY_DATA_REMEDIATION/) assert.match(content, /UPDATE "system_settings"\s+SET "headerLogoUrl" = '\/brand\/header-logo\.png'/) assert.match(content, /UPDATE "system_settings"\s+SET "loginLogoUrl" = '\/brand\/login-logo\.png'/) assert.doesNotMatch(content, /DEFAULT[^;\n]*s3\.1080p\.hk/i) assert.match(content, /"headerLogoUrl"[\s\S]*DEFAULT '\/brand\/header-logo\.png'/) assert.match(content, /"loginLogoUrl"[\s\S]*DEFAULT '\/brand\/login-logo\.png'/) assert.match(content, /ALTER COLUMN "logoUrl" SET DEFAULT '\/brand\/header-logo\.png'/) await withFixture({ 'prisma/migrations/20260811_update_brand_logo_defaults/migration.sql': content, }, async root => { assert.deepEqual(await scanLegacyStorageBoundaries(root), []) }) }) test('品牌数据修复标记不能放行默认值回写或任意新 migration', async () => { const shapedButUnsafe = `-- LEGACY_DATA_REMEDIATION UPDATE "system_settings" SET "headerLogoUrl" = '/brand/header-logo.png' WHERE "headerLogoUrl" ~* '^https?://s3\\.1080p\\.hk(?:/|$)'; UPDATE "system_settings" SET "loginLogoUrl" = '/brand/login-logo.png' WHERE "loginLogoUrl" ~* '^https?://s3\\.1080p\\.hk(?:/|$)'; ALTER TABLE "system_settings" ALTER COLUMN "logoUrl" SET DEFAULT 'https://s3.1080p.hk/logo.png';` await withFixture({ 'prisma/migrations/20260811_update_brand_logo_defaults/migration.sql': shapedButUnsafe, 'prisma/migrations/20260812_bad/migration.sql': shapedButUnsafe, }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.deepEqual( violations.map(item => item.relativePath).sort(), [ 'prisma/migrations/20260811_update_brand_logo_defaults/migration.sql', 'prisma/migrations/20260812_bad/migration.sql', ], ) }) }) test('默认 Compose 拒绝相对 uploads,并要求容器路径和禁止自动建目录', async () => { await withFixture({ 'docker-compose.yml': `services: app: environment: - UPLOAD_DIR=./uploads volumes: - ./uploads:/app/uploads `, }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.equal(violations.length, 1) assert.equal(violations[0].relativePath, 'docker-compose.yml') assert.match(violations[0].reasons.join('\n'), /relative uploads bind/) assert.match(violations[0].reasons.join('\n'), /UPLOAD_DIR=\/app\/uploads/) assert.match(violations[0].reasons.join('\n'), /GONGXIANGP2_UPLOADS_MOUNT/) assert.match(violations[0].reasons.join('\n'), /automatic host-path creation/) }) }) test('默认 Compose 只接受显式宿主机挂载变量与固定容器路径', async () => { await withFixture({ 'docker-compose.yml': `services: app: environment: UPLOAD_DIR: /app/uploads volumes: - type: bind source: \${GONGXIANGP2_UPLOADS_MOUNT:?required} target: /app/uploads bind: create_host_path: false `, }, async root => { assert.deepEqual(await scanLegacyStorageBoundaries(root), []) }) }) test('相对 uploads 只允许出现在名称明确的非生产 Compose', async () => { await withFixture({ 'docker-compose.dev.yml': `services:\n app:\n volumes:\n - ./uploads:/app/uploads\n`, 'docker-compose.production.yml': `services:\n app:\n volumes:\n - \${GONGXIANGP2_UPLOADS_MOUNT:-./uploads}:/app/uploads\n`, 'docker-compose.shadow.yml': `services:\n app:\n volumes:\n - ./uploads-shadow:/app/uploads\n`, }, async root => { const violations = await scanLegacyStorageBoundaries(root) assert.deepEqual(violations.map(item => item.relativePath), ['docker-compose.production.yml']) }) })