import test from 'node:test' import assert from 'node:assert/strict' import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, relative, resolve } from 'node:path' import { spawnSync } from 'node:child_process' const root = resolve('.') for (const script of ['migrate-production-db.sh', 'backfill-post-thumbnails-production.sh', 'backfill-post-body-thumbnails-production.sh', 'backfill-page-cover-thumbnails-production.sh']) { test(`운영 ${script}는 명시한 Compose 파일·환경 파일만 사용한다`, () => { const directory = mkdtempSync(join(tmpdir(), 'sori-compose-test-')) try { const logPath = join(directory, 'docker.log') const envPath = join(directory, 'production.env') writeFileSync(envPath, 'COMPOSE_PROJECT_NAME=sori-studio\n') writeFileSync(join(directory, 'docker'), '#!/bin/sh\nprintf "<%s>" "$@" >> "$SORI_TEST_DOCKER_LOG"\nprintf "\\n" >> "$SORI_TEST_DOCKER_LOG"\n', { mode: 0o755 }) const result = spawnSync('sh', [`scripts/${script}`, 'status'], { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: `${directory}:${process.env.PATH}`, ENV_FILE: relative(root, envPath), SORI_TEST_DOCKER_LOG: logPath } }) assert.equal(result.status, 0, result.stderr) const calls = readFileSync(logPath, 'utf8').trim().split('\n') assert.ok(calls.length > 0) for (const call of calls) { assert.ok(call.startsWith(`<-f><${join(root, 'docker-compose.yaml')}><--env-file><${relative(root, envPath)}>`)) } } finally { rmSync(directory, { recursive: true, force: true }) } }) } for (const exitCode of [0, 19]) { test(`운영 이미지 빌드는 컨테이너 변경 없이 build만 실행하고 종료 코드 ${exitCode}를 전달한다`, () => { const directory = mkdtempSync(join(tmpdir(), 'sori-image-build-test-')) try { const logPath = join(directory, 'docker.log') const envPath = join(directory, 'production.env') writeFileSync(envPath, 'COMPOSE_PROJECT_NAME=sori-studio\n') writeFileSync(join(directory, 'docker'), `#!/bin/sh\nprintf "<%s>" "$@" > "$SORI_TEST_DOCKER_LOG"\nexit ${exitCode}\n`, { mode: 0o755 }) const result = spawnSync('sh', ['scripts/build-production-image.sh'], { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: `${directory}:${process.env.PATH}`, ENV_FILE: envPath, SORI_TEST_DOCKER_LOG: logPath, APP_SERVICE: 'sori-studio' } }) assert.equal(result.status, exitCode, result.stderr) assert.equal(readFileSync(logPath, 'utf8'), `<-f><${join(root, 'docker-compose.build.yml')}><--env-file><${envPath}>`) } finally { rmSync(directory, { recursive: true, force: true }) } }) } test('운영 이미지 빌드는 읽을 수 없는 환경 파일을 거부한다', () => { const directory = mkdtempSync(join(tmpdir(), 'sori-image-build-test-')) try { const result = spawnSync('sh', ['scripts/build-production-image.sh'], { cwd: root, encoding: 'utf8', env: { ...process.env, ENV_FILE: join(directory, 'missing.env') } }) assert.equal(result.status, 1) assert.match(result.stderr, /환경 파일을 읽을 수 없습니다/) } finally { rmSync(directory, { recursive: true, force: true }) } }) test('운영 마이그레이션은 SQL과 이력을 같은 트랜잭션으로 실행하고 실패하면 중단한다', () => { const directory = mkdtempSync(join(tmpdir(), 'sori-migration-test-')) try { const logPath = join(directory, 'docker.log') const envPath = join(directory, 'production.env') writeFileSync(envPath, 'COMPOSE_PROJECT_NAME=sori-studio\n') writeFileSync(join(directory, 'docker'), `#!/bin/sh printf "<%s>" "$@" >> "$SORI_TEST_DOCKER_LOG" printf "\\n" >> "$SORI_TEST_DOCKER_LOG" for argument in "$@"; do case "$argument" in *"to_regclass('public.schema_migrations')"*) printf 't\\n' ;; --single-transaction) exit 19 ;; esac done `, { mode: 0o755 }) const result = spawnSync('sh', ['scripts/migrate-production-db.sh', 'migrate'], { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: `${directory}:${process.env.PATH}`, ENV_FILE: envPath, SORI_TEST_DOCKER_LOG: logPath } }) assert.equal(result.status, 19, result.stderr) const calls = readFileSync(logPath, 'utf8').trim().split('\n') const transactions = calls.filter(call => call.includes('<--single-transaction>')) assert.equal(transactions.length, 1) assert.ok(transactions[0].startsWith(`<-f><${join(root, 'docker-compose.yaml')}><--env-file><${envPath}>`)) assert.match(transactions[0], /<-f><\/docker-entrypoint-initdb.d\/001_[^>]+\.sql>/) assert.match(transactions[0], /<-c>