65 lines
4.6 KiB
JavaScript
65 lines
4.6 KiB
JavaScript
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)
|
||
})
|