gongxiangp2/scripts/backfill-art-workflow-planner-owner.test.mjs

65 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from 'node:test'
import assert from 'node:assert/strict'
import { createRequire } from 'node:module'
import { ART_CODE, buildOwnerPatches, applyOwnerPatches, digest } from './backfill-art-workflow-planner-owner.mjs'
const { normalizeArtPlannerOwner, selectArtPlannerOwner } = createRequire(import.meta.url)('../lib/art-workflow-owner.cjs')
const source = { processInstanceId: 'w', name: '策划案负责人', value: '测试甲', extValue: JSON.stringify([{ emplId: 'ding-1', name: '测试甲', avatar: 'private-avatar' }]) }
const snapshot = { processInstanceId: 'w', businessId: 'code', formFields: { brand: '测试', planningTaskStats: 'unchanged' } }
test('共享提取器保留稳定ID、去掉无关联系人资料、支持姓名和明确空值', () => {
assert.equal(normalizeArtPlannerOwner(source.value, source.extValue), '[{"emplId":"ding-1","name":"测试甲"}]')
assert.equal(normalizeArtPlannerOwner(' 测试甲 ', 'invalid'), '测试甲')
assert.equal(normalizeArtPlannerOwner('', null), null)
assert.equal(selectArtPlannerOwner([{ ...source, sourceRowId: 1 }, { ...source, sourceRowId: 2, value: '', extValue: '' }]), null)
assert.equal(selectArtPlannerOwner([{ ...source, sourceRowId: '9007199254740993' }, { ...source, sourceRowId: '9007199254740992', value: '旧人', extValue: '' }]), normalizeArtPlannerOwner(source.value, source.extValue))
})
test('只计划缺失字段;已有空值不覆盖;上游缺失流程阻断,字段不存在保存明确空值', () => {
assert.equal(buildOwnerPatches([{ ...snapshot, formFields: { plannerOwner: null } }], []).length, 0)
assert.throws(() => buildOwnerPatches([snapshot], []), /SOURCE_WORKFLOW_NOT_FOUND/)
assert.equal(buildOwnerPatches([snapshot], [{ processInstanceId: 'w', name: null, value: null }])[0].owner, null)
const [patch] = buildOwnerPatches([snapshot], [source])
assert.deepEqual(patch.before, snapshot.formFields)
assert.equal(patch.beforeHash, digest({ planningTaskStats: 'unchanged', brand: '测试' }))
})
function database(initial, owner = 'lease', expires = new Date(Date.now() + 60000)) {
let state = structuredClone(initial), writes = 0
const tx = {
$queryRawUnsafe: async () => [{ lease_owner: owner, lease_expires_at: expires }],
dingWorkflowSnapshot: { findUnique: async () => ({ formFields: state, processCode: ART_CODE }) },
$executeRawUnsafe: async (_sql, next, _id, code, previous) => {
assert.equal(code, ART_CODE); assert.deepEqual(JSON.parse(previous), state)
state = JSON.parse(next); writes++; return 1
},
}
return { $transaction: async work => { const before = structuredClone(state); try { return await work(tx) } catch (e) { state = before; throw e } },
state: () => state, writes: () => writes }
}
test('apply仅补字段、幂等、精确restore,保留原字段', async () => {
const patches = buildOwnerPatches([snapshot], [source]), db = database(snapshot.formFields)
assert.deepEqual(await applyOwnerPatches(db, patches, 'lease'), { changed: 1, skipped: 0 })
assert.deepEqual(db.state(), { ...snapshot.formFields, plannerOwner: patches[0].owner })
assert.deepEqual(await applyOwnerPatches(db, patches, 'lease'), { changed: 0, skipped: 1 })
assert.deepEqual(await applyOwnerPatches(db, patches, 'lease', true), { changed: 1, skipped: 0 })
assert.deepEqual(db.state(), snapshot.formFields)
assert.deepEqual(await applyOwnerPatches(db, patches, 'lease', true), { changed: 0, skipped: 1 })
})
test('并发更改要求重新审计;其他同步已补齐字段时不覆盖;租约失效不写入', async () => {
const patches = buildOwnerPatches([snapshot], [source])
const changed = database({ ...snapshot.formFields, brand: 'changed' })
await assert.rejects(applyOwnerPatches(changed, patches, 'lease'), /SNAPSHOT_CHANGED_REAUDIT/)
assert.equal(changed.writes(), 0)
const synced = database({ ...snapshot.formFields, plannerOwner: 'authoritative-new-owner' })
assert.deepEqual(await applyOwnerPatches(synced, patches, 'lease'), { changed: 0, skipped: 1 })
for (const db of [database(snapshot.formFields, 'other'), database(snapshot.formFields, 'lease', new Date(0))]) {
await assert.rejects(applyOwnerPatches(db, patches, 'lease'), /SYNC_LEASE_LOST/)
assert.equal(db.writes(), 0)
}
})
test('恢复时不覆盖已发生的后续同步或其他字段变更', async () => {
const patches = buildOwnerPatches([snapshot], [source])
const db = database({ ...snapshot.formFields, plannerOwner: patches[0].owner, brand: 'later' })
await assert.rejects(applyOwnerPatches(db, patches, 'lease', true), /RESTORE_CONFLICT_REVIEW_MANUALLY/)
assert.equal(db.writes(), 0)
})