/* * ★ **오더 식별자와 거래 식별자는 다른 칸에 든다** — 한 칸에 두 낱말이 들어가던 자리. * * ── 실측 (2026-09-05) ────────────────────────────────────────────────────── * 승격 칸 `order_id` 하나가 두 낱말을 담고 있었다. * * order.status order-1 내부 오더 id * epcis.TransformationEvent urn:epc:id:gdti:9521321.403.1 GS1 거래 식별자 * * 한 오더의 이력이 **색인 안에서 두 쪽으로 갈라진다.** 어느 이름으로 물어도 절반만 나오고, * 오류는 나지 않는다. 어휘가 섞인 행 10,004개. * * 그 갈라짐이 막은 것은 「이 개체를 어느 레시피로 만들었나」다 — 답은 오더 사건에 이미 있는데 * (`recipeKey`, 실측 328,021/328,021), 변환 사건에서 그 오더로 건너갈 색인이 없었다. * * ── 왜 이 검사인가 ──────────────────────────────────────────────────────── * 「합치면 편하다」는 힘이 계속 작용한다 — 값을 채우는 관점에서는 한 칸이 간단해 보이고, 실제로 * 그 이유로 합쳐져 있었다(옛 주석이 「오더를 스스로 말하는 사건은 그 말을 그대로 쓴다」고 적고 * 있었다). 그러니 갈라 둔 것을 **값으로** 못 박는다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { bizTransactionOf, orderOf, twinEventKeys } from '../server/service/twin-event/twin-event-keys.js' import { SUBJECT_KINDS, subjectsOf } from '../server/service/twin-subject/event-subjects.js' const GDTI = 'urn:epc:id:gdti:9521321.403.1' /** 실측한 변환 사건의 모양 — GDTI 만 있고 오더 id 는 없다. */ const transformation = { eventType: 'epcis.TransformationEvent', data: { type: 'TransformationEvent', inputEPCList: ['urn:epc:id:sgtin:9521321.011111.1'], outputEPCList: ['urn:epc:id:sgtin:9521321.033333.1'], bizTransactionList: [{ type: 'urn:epcglobal:cbv:btt:prodorder', bizTransaction: GDTI }] } } /** 실측한 오더 사건의 모양 — 둘 다 든다. 이것이 다리다. */ const orderStatus = { eventType: 'order.status', data: { orderId: 'order-1', status: 'created', recipeKey: 'trailer', bizTransaction: GDTI } } /* ── 갈라져 있나 ─────────────────────────────────────────────────────────── */ test('★ 변환 사건의 GDTI 는 거래 칸에 든다 — 오더 칸에 앉지 않는다', () => { assert.equal(bizTransactionOf(transformation), GDTI) assert.equal(orderOf(transformation), undefined, 'GDTI 가 오더 식별자 자리에 앉았다') }) test('★ 오더 사건은 두 칸을 다 든다 — 이것이 EPCIS 에서 오더로 건너가는 다리다', () => { const keys = twinEventKeys(orderStatus) assert.equal(keys.orderId, 'order-1') assert.equal(keys.bizTransaction, GDTI) }) test('★ 두 사건이 같은 거래 값으로 이어진다 — 문자열까지 같아야 색인이 잇는다', () => { /* * 이 검사가 이 변경의 목적이다. 값이 한 글자라도 다르면 색인 조회가 0을 내고, 그 0은 * 「그런 오더가 없다」와 구별되지 않는다. */ assert.equal(twinEventKeys(transformation).bizTransaction, twinEventKeys(orderStatus).bizTransaction) }) test('★ 오더 사건에서 레시피를 읽을 수 있다 — 짐작하지 않아도 되는 근거', () => { /* 자재로 되짚어 첫 레시피를 고르던 자리가 이 값으로 대체된다. */ assert.equal((orderStatus.data as any).recipeKey, 'trailer') }) /* ── 합쳐지지 않았나 ──────────────────────────────────────────────────────── */ test('★ 오더 칸이 거래로 되돌아가지 않는다 — 옛 되돌림(fallback)이 살아나면 빨강', () => { /* * 옛 코드: `d.orderId ?? d.bizTransactionList?.[0]?.bizTransaction`. * 그 한 줄이 돌아오면 아래가 GDTI 를 내고, 두 낱말이 다시 한 칸에 든다. */ const onlyTx = { eventType: 'epcis.ObjectEvent', data: { bizTransactionList: [{ bizTransaction: GDTI }] } } assert.equal(orderOf(onlyTx), undefined, '오더 칸이 거래 값으로 되돌아간다') assert.equal(bizTransactionOf(onlyTx), GDTI) }) test('★ 주체 표도 갈라 적는다 — 거래가 오더 이름으로 앉지 않는다', () => { const subjects = subjectsOf(transformation) const kinds = (id: string) => subjects.filter(s => s.id === id).map(s => s.kind) assert.deepEqual(kinds(GDTI), ['transaction'], `GDTI 가 붙은 종류가 다르다: ${JSON.stringify(subjects)}`) assert.ok(SUBJECT_KINDS.includes('transaction' as any), '거래 종류가 목록에 없다') }) test('★ 오더 사건은 두 주체를 다 낸다', () => { const subjects = subjectsOf(orderStatus) const has = (kind: string, id: string) => subjects.some(s => s.kind === kind && s.id === id) assert.ok(has('order', 'order-1'), `오더 주체가 없다: ${JSON.stringify(subjects)}`) assert.ok(has('transaction', GDTI), `거래 주체가 없다: ${JSON.stringify(subjects)}`) }) /* ── 검사가 실제로 무언가를 재나 ─────────────────────────────────────────── */ test('★ 이 검사가 실제로 무언가를 잰다 — 견준 것이 0이면 빨강', () => { const keys = twinEventKeys(orderStatus) assert.ok(Object.values(keys).filter(v => v !== undefined).length >= 3, `승격 키를 ${JSON.stringify(keys)} 만 뽑았다`) assert.ok(subjectsOf(orderStatus).length >= 2, '주체가 둘도 안 나왔다') assert.ok(subjectsOf(transformation).length >= 1, '변환 사건에서 주체가 하나도 안 나왔다') })