/* * ★ **연결 이관의 문 셋** — 몸통은 순수 함수이고, 이 검사는 **문이 그 규율을 지키는지** 본다. * * ── 무엇을 옮기나 (사용자와 맞춘 개념, 2026-09-05) ──────────────────────── * 옮기는 것은 「붙는 법」이지 「붙은 결과」가 아니다. 마스터·사실·잰 값·비밀값은 안 옮긴다 — * 앞의 셋은 다시 얻을 수 있고, 비밀값은 그 환경의 것이지 셋업이 아니다. * * ── 이 검사가 지키는 것 ──────────────────────────────────────────────────── * 문이 몸통의 판단을 **뒤집지 않는지**다. 몸통 시험 19개가 이미 「비밀값이 안 나간다」를 지키는데, * 문이 그것을 되살려 쓰면 그 19개가 아무것도 못 막는다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { exportConnections, planImport, SECRET_KEYS } from '../server/service/reference/connection-portability.js' const row = (over: any = {}) => ({ source: 'mes-miratek', system: 'operato-plant', siteName: '미라텍', adapterType: 'operato-plant', connectionConfig: { baseUrl: 'https://plant.example', siteId: 'mes-miratek', token: 'SECRET-VALUE', hookSecret: 'ALSO-SECRET' }, ...over }) test('★ 비밀값이 패키지에 안 나간다 — 이름만 나간다', () => { const b = exportConnections([row()], { at: '2026-09-05T00:00:00.000Z' }) const text = JSON.stringify(b) assert.ok(!text.includes('SECRET-VALUE'), '토큰이 패키지에 실렸다') assert.ok(!text.includes('ALSO-SECRET'), '훅 비밀값이 패키지에 실렸다') assert.deepEqual(b.connections[0].secretsRequired.sort(), ['hookSecret', 'token'], '무엇을 채워야 하는지 말하지 않는다') }) test('★ 선언은 나간다 — 그것이 옮기려는 것이다', () => { const b = exportConnections([row()], { at: '2026-09-05T00:00:00.000Z' }) const c = b.connections[0] assert.equal(c.adapterType, 'operato-plant') assert.equal(c.config.baseUrl, 'https://plant.example') assert.equal(c.config.siteId, 'mes-miratek') }) test('★ 새 비밀 칸도 기본으로 안 나간다 — 이름 규칙으로 거른다', () => { /* * 비밀 칸이 하나 늘 때 이 목록을 안 고치면 그 값이 파일로 나간다. 그래서 규칙이 이름이고, * 그 규칙이 지금 무엇을 아는지 값으로 못 박는다. */ assert.ok(SECRET_KEYS.includes('token' as any)) assert.ok(SECRET_KEYS.includes('hookSecret' as any)) assert.ok(SECRET_KEYS.includes('intakeSecret' as any)) assert.ok(SECRET_KEYS.length >= 4, `비밀 칸 이름을 ${SECRET_KEYS.length}개만 안다`) }) test('★ 없는 비밀값을 지어내지 않는다 — 이름을 대고 사람에게 넘긴다', () => { const b = exportConnections([row()], { at: '2026-09-05T00:00:00.000Z' }) /* 이 환경에는 그 비밀값이 없다. */ const plan = planImport(b, [], () => false) const needs = plan.needsHuman.find(n => n.source === 'mes-miratek') assert.ok(needs, '사람이 채울 것을 말하지 않는다') assert.deepEqual(needs!.keys.sort(), ['hookSecret', 'token']) }) test('★ 이미 있는 연결의 비밀값을 패키지가 안 지운다', () => { const b = exportConnections([row()], { at: '2026-09-05T00:00:00.000Z' }) const existing = [{ source: 'mes-miratek', connectionConfig: { token: 'LIVE-TOKEN' } }] const plan = planImport(b, existing, () => false) const a = plan.actions.find(x => (x as any).connection?.source === 'mes-miratek') as any assert.equal(a?.action, 'update', `갱신이 아니라 ${a?.action} 이다`) assert.ok(a.keptSecrets?.includes('token'), '이미 있는 토큰을 지킨다고 말하지 않는다') }) test('★ 패키지가 아닌 것을 받으면 아무것도 하지 않는다', () => { for (const bad of [null, {}, { kind: 'something-else' }, 'text']) { const plan = planImport(bad, [], () => false) assert.equal(plan.actions.length, 0, `${JSON.stringify(bad)} 로 무언가를 하려 했다`) assert.ok(plan.errors.length, '왜 안 되는지 말하지 않는다') } }) test('★ 이 검사가 실제로 무언가를 잰다 — 견준 것이 0이면 빨강', () => { const b = exportConnections([row()], { at: '2026-09-05T00:00:00.000Z' }) assert.equal(b.connections.length, 1, '패키지가 비었다 — 이 검사가 아무것도 안 잰다') assert.ok(planImport(b, [], () => true).actions.length > 0, '계획이 비었다') })