o2o-site-AEO/solution/site/src/publish-version.test.ts
Mina Choi f2087aad5e feat(solution): 발행 워커·버전 관리와 예약·미리보기 정리
상시 프리렌더와 중복 예약 안내를 없애고, 검수된 발행 버전을 보존한다. 미리보기는 실제 렌더 완료까지 스피너를 표시한다.

사이트 81건, 발행·롤백·서치콘솔 45건, 프로세스 수명 3건 통과. 빌더·사이트 빌드 및 compose 설정 검증 통과.
2026-09-15 16:12:16 +09:00

67 lines
3.2 KiB
TypeScript

import {afterEach, expect, it} from 'vitest';
import {spawnSync} from 'node:child_process';
import {existsSync, mkdtempSync, mkdirSync, readFileSync, readlinkSync, rmSync, writeFileSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join, resolve} from 'node:path';
import {MOONLIGHT_STAY_PAYLOAD} from './fixtures/moonlight-stay';
const scratch: string[] = [];
afterEach(() => scratch.splice(0).forEach((dir) => rmSync(dir, {recursive: true, force: true})));
it('검수 전 비공개·버전 전환·롤백·실패 격리·목업 보존', () => {
const root = mkdtempSync(join(tmpdir(), 'web4ai-version-test-'));
scratch.push(root);
const out = join(root, 'out');
const payloadFile = join(root, 'test-pension.json');
const run = (...args: string[]) => spawnSync(process.execPath, [
resolve('dist/prerender/prerender.js'), `--out=${out}`, ...args,
], {encoding: 'utf8', timeout: 20000});
const payload = structuredClone(MOONLIGHT_STAY_PAYLOAD);
payload.site.slug = 'test-pension';
payload.site.basePath = '/s/test-pension';
payload.site.publish = true;
payload.site.version = 1;
payload.media.forEach((media) => {media.url = `/test/${media.mediaId}.jpg`;});
const save = () => writeFileSync(payloadFile, JSON.stringify(payload));
const stage = () => run(`--payload=${payloadFile}`, '--stage-only');
mkdirSync(join(out, 's/stay'), {recursive: true});
mkdirSync(join(out, 'assets'), {recursive: true});
writeFileSync(join(out, 's/stay/index.html'), '<link href="/assets/mock.css">protected');
writeFileSync(join(out, 'assets/mock.css'), 'mock css');
save();
let result = stage();
expect(result.status, result.stderr).toBe(0);
expect(existsSync(join(out, 's/test-pension'))).toBe(false);
result = run('--activate=test-pension:1');
expect(result.status, result.stderr).toBe(0);
const v1 = readFileSync(join(out, 's/test-pension/index.html'), 'utf8');
expect(readlinkSync(join(out, 's/test-pension'))).toBe('../versions/test-pension/1');
expect(readFileSync(join(out, 'sitemap.xml'), 'utf8')).toContain('/s/test-pension');
payload.site.version = 2;
payload.place.name = '새 이름 테스트 펜션';
save();
result = stage();
expect(result.status, result.stderr).toBe(0);
expect(readFileSync(join(out, 's/test-pension/index.html'), 'utf8')).toBe(v1);
expect(run('--activate=test-pension:2').status).toBe(0);
expect(readFileSync(join(out, 's/test-pension/index.html'), 'utf8')).toContain('새 이름 테스트 펜션');
payload.site.version = 1;
save();
expect(stage().status).toBe(0);
expect(run('--activate=test-pension:1').status).toBe(0);
expect(readFileSync(join(out, 's/test-pension/index.html'), 'utf8')).toBe(v1);
payload.site.version = 3;
payload.schemaVersion = 99 as 1;
save();
expect(stage().status).not.toBe(0);
expect(run('--activate=test-pension:3').status).not.toBe(0);
expect(readFileSync(join(out, 's/test-pension/index.html'), 'utf8')).toBe(v1);
expect(run('--activate=stay:1').status).not.toBe(0);
expect(run('--seed-assets').status).toBe(0);
expect(readFileSync(join(out, 's/stay/index.html'), 'utf8')).toBe('<link href="/assets/mock.css">protected');
expect(readFileSync(join(out, 'assets/mock.css'), 'utf8')).toBe('mock css');
}, 60000);