From 2c2444b5390cd2460fbaf9a56fb350ca56ffefdf Mon Sep 17 00:00:00 2001 From: Barry Dwyer Date: Thu, 18 Dec 2025 11:26:00 +0200 Subject: [PATCH 1/2] Add support for Update action Fixed only reverse engineering corrections with an approval --- v1-to-v2-data-migration/helpers/transform.ts | 109 ++-- .../tests/unit/postProcess.test.ts | 587 ++++++++++++++++++ 2 files changed, 626 insertions(+), 70 deletions(-) diff --git a/v1-to-v2-data-migration/helpers/transform.ts b/v1-to-v2-data-migration/helpers/transform.ts index 34ba9c2..211a1d3 100644 --- a/v1-to-v2-data-migration/helpers/transform.ts +++ b/v1-to-v2-data-migration/helpers/transform.ts @@ -232,9 +232,11 @@ function legacyHistoryItemToV2ActionType( }, } case 'DECLARATION_UPDATED': //TODO - check if this is correct + const update = transformCorrection(historyItem, eventType, declaration) return { type: 'DECLARE' as ActionType, - declaration: {}, + declaration: update.output, + annotation: update.input, } default: break @@ -260,7 +262,7 @@ function legacyHistoryItemToV2ActionType( status: 'Accepted', type: 'REQUEST_CORRECTION' as ActionType, declaration: correction.output, - annotation: { ...declaration, ...annotation, ...correction.input }, + annotation: { ...annotation, ...correction.input }, requestId: historyItem.id, } case 'APPROVED_CORRECTION': @@ -471,7 +473,7 @@ export function transform( ], } - return postProcess(documents) + return postProcess(documents, declaration) } /** @@ -505,84 +507,51 @@ function deepMerge(target: any, source: any): any { return result } -function postProcess(documents: TransformedDocument): TransformedDocument { - const correctionResolverKeys = Object.keys(correctionResolver) - - // Step 1: Build a map of current declaration state at each action - // We start from the final declaration and work backwards - let currentDeclaration: Record = {} - - // Find the final declaration by merging all DECLARE/REGISTER/VALIDATE declarations - for (const action of documents.actions) { - if ( - (action.type === 'DECLARE' || - action.type === 'REGISTER' || - action.type === 'VALIDATE') && - action.declaration && - Object.keys(action.declaration).length > 0 - ) { - currentDeclaration = deepMerge(currentDeclaration, action.declaration) - } - } - - // Step 2: Process corrections in reverse order to reverse-engineer the original state - const corrections: Array<{ index: number; action: Action }> = [] +function postProcess( + document: TransformedDocument, + currDeclaration: Record +): TransformedDocument { + const resolverKeys = Object.keys({ + ...correctionResolver, + ...declareResolver, + }) + const hasKeys = (declaration: {} | null | undefined) => + Object.keys(declaration || {}).length > 0 + let previousDeclaration = currDeclaration - for (let i = 0; i < documents.actions.length; i++) { - if (documents.actions[i].type === 'REQUEST_CORRECTION') { - corrections.push({ index: i, action: documents.actions[i] }) - } - } + const approvedCorrections = [] - // Process corrections from newest to oldest to reverse engineer - for (let i = corrections.length - 1; i >= 0; i--) { - const { action } = corrections[i] + const rev = document.actions.slice().reverse() - if (!action.annotation || !action.declaration) { - continue + for (const action of rev) { + if (action.type === 'APPROVE_CORRECTION') { + approvedCorrections.push(action.requestId) } - // Filter out correctionResolver metadata fields from annotation - const filteredAnnotation = Object.fromEntries( - Object.entries(action.annotation).filter( - ([key]) => !correctionResolverKeys.includes(key) + const declaration = action.declaration || {} + const annotation = Object.fromEntries( + Object.entries(action.annotation || {}).filter( + ([key]) => !resolverKeys.includes(key) ) ) - // First, reverse the correction: for each field in the correction's declaration, - // replace the current state with the input value (from filteredAnnotation) - for (const key of Object.keys(action.declaration)) { - if (filteredAnnotation.hasOwnProperty(key)) { - currentDeclaration[key] = filteredAnnotation[key] + if (hasKeys(declaration)) { + if (action.type === 'REQUEST_CORRECTION') { + if (approvedCorrections.includes(action.requestId)) { + previousDeclaration = deepMerge(previousDeclaration, annotation) + action.annotation = previousDeclaration + } + continue + } + action.declaration = previousDeclaration + if (hasKeys(annotation)) { + previousDeclaration = deepMerge(previousDeclaration, annotation) + action.annotation = deepMerge(previousDeclaration, annotation) } } - - // Now set the annotation to the reversed state (which is the state BEFORE this correction) - const newAnnotation = { - ...currentDeclaration, - ...Object.fromEntries( - Object.entries(action.annotation).filter(([key]) => - correctionResolverKeys.includes(key) - ) - ), - } - - action.annotation = newAnnotation } - // Step 3: Update the base DECLARE/REGISTER/VALIDATE actions with the reverse-engineered state - // Find the first action with a non-empty declaration - for (const action of documents.actions) { - if ( - (action.type === 'DECLARE' || - action.type === 'REGISTER' || - action.type === 'VALIDATE') && - action.declaration && - Object.keys(action.declaration).length > 0 - ) { - action.declaration = { ...currentDeclaration } - } - } + document.actions = rev.reverse() - return documents + return document } diff --git a/v1-to-v2-data-migration/tests/unit/postProcess.test.ts b/v1-to-v2-data-migration/tests/unit/postProcess.test.ts index 20fd9df..8ed233c 100644 --- a/v1-to-v2-data-migration/tests/unit/postProcess.test.ts +++ b/v1-to-v2-data-migration/tests/unit/postProcess.test.ts @@ -29,6 +29,7 @@ Deno.test('PostProcess - Single Correction', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -58,10 +59,18 @@ Deno.test('PostProcess - Single Correction', async (t) => { }, ], }, + { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, ], }) const result = transform(registration, birthResolver, 'birth') + console.log(JSON.stringify(result, null, 2)) // Find the actions const registerAction = result.actions.find( @@ -106,6 +115,7 @@ Deno.test('PostProcess - Single Correction', async (t) => { office: { id: 'office1' }, }, { + id: 'correction2', date: '2024-01-02T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user2', role: { id: 'REGISTRATION_AGENT' } }, @@ -125,6 +135,13 @@ Deno.test('PostProcess - Single Correction', async (t) => { }, ], }, + { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction2', + }, ], }) @@ -164,6 +181,7 @@ Deno.test('PostProcess - Single Correction', async (t) => { office: { id: 'office1' }, }, { + id: 'correction3', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -193,6 +211,13 @@ Deno.test('PostProcess - Single Correction', async (t) => { }, ], }, + { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction3', + }, ], }) @@ -245,6 +270,7 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -265,6 +291,14 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { ], }, { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, + { + id: 'correction2', date: '2024-01-04T16:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, @@ -294,6 +328,13 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { }, ], }, + { + date: '2024-01-05T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction2', + }, ], }) @@ -352,6 +393,7 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -372,6 +414,14 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { ], }, { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, + { + id: 'correction2', date: '2024-01-04T16:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, @@ -391,6 +441,13 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { }, ], }, + { + date: '2024-01-05T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction2', + }, ], }) @@ -453,6 +510,7 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-05T15:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user5', role: { id: 'REGISTRATION_AGENT' } }, @@ -472,6 +530,13 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { }, ], }, + { + date: '2024-01-06T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, ], }) @@ -519,6 +584,7 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -538,6 +604,13 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { }, ], }, + { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, { date: '2024-01-04T15:00:00Z', action: 'VIEWED', @@ -551,6 +624,7 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { office: { id: 'office1' }, }, { + id: 'correction2', date: '2024-01-06T17:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user6', role: { id: 'REGISTRATION_AGENT' } }, @@ -580,6 +654,13 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { }, ], }, + { + date: '2024-01-07T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction2', + }, ], }) @@ -643,6 +724,7 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-04T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, @@ -662,6 +744,13 @@ Deno.test('PostProcess - Corrections With Actions In Between', async (t) => { }, ], }, + { + date: '2024-01-05T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, ], }) @@ -713,6 +802,7 @@ Deno.test('PostProcess - Death Event Corrections', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -732,6 +822,13 @@ Deno.test('PostProcess - Death Event Corrections', async (t) => { }, ], }, + { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, ], }) @@ -774,6 +871,7 @@ Deno.test('PostProcess - Death Event Corrections', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -794,6 +892,14 @@ Deno.test('PostProcess - Death Event Corrections', async (t) => { ], }, { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, + { + id: 'correction2', date: '2024-01-04T16:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, @@ -813,6 +919,13 @@ Deno.test('PostProcess - Death Event Corrections', async (t) => { }, ], }, + { + date: '2024-01-06T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction2', + }, ], }) @@ -878,6 +991,7 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { office: { id: 'office1' }, }, { + id: 'correction1', date: '2024-01-03T14:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, @@ -898,6 +1012,14 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { ], }, { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, + { + id: 'correction2', date: '2024-01-04T16:00:00Z', action: 'REQUESTED_CORRECTION', user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, @@ -917,6 +1039,13 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { }, ], }, + { + date: '2024-01-05T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction2', + }, ], }) @@ -952,3 +1081,461 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { } ) }) + +Deno.test('PostProcess - Updates', async (t) => { + const birthResolver = buildBirthResolver() + + await t.step( + 'should set annotation to previous declaration and reverse enngineer the original declaration', + () => { + const registration = buildBirthEventRegistration({ + child: { + gender: 'female', + }, + mother: { + nationality: ['GBR'], + }, + history: [ + { + date: '2024-01-01T10:00:00Z', + regStatus: 'DECLARED', + user: { id: 'user1', role: { id: 'FIELD_AGENT' } }, + office: { id: 'office1' }, + }, + { + date: '2024-01-02T12:00:00Z', + regStatus: 'REGISTERED', + user: { id: 'user2', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + }, + { + date: '2024-01-03T14:00:00Z', + regStatus: 'DECLARATION_UPDATED', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + input: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'male', + }, + { + valueCode: 'mother', + valueId: 'nationality', + value: 'USA', + }, + ], + output: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'female', + }, + { + valueCode: 'mother', + valueId: 'nationality', + value: 'GBR', + }, + ], + }, + ], + }) + + const result = transform(registration, birthResolver, 'birth') + + const registerAction = result.actions.find( + (a) => a.type === 'REGISTER' + ) as Action + const declareActions = result.actions.filter((a) => a.type === 'DECLARE') + const originalDeclareAction = declareActions[0] + const updatedDeclareAction = declareActions[1] + + // First correction (child.gender) + assertEquals( + updatedDeclareAction?.declaration?.['child.gender'], + 'female' + ) + assertEquals( + updatedDeclareAction?.declaration?.['mother.nationality'], + 'GBR' + ) + + // REGISTER and DECLARE should have BOTH reverse engineered values + assertEquals(registerAction?.declaration?.['child.gender'], 'male') + assertEquals(originalDeclareAction?.declaration?.['child.gender'], 'male') + assertEquals(registerAction?.declaration?.['mother.nationality'], 'USA') + assertEquals( + originalDeclareAction?.declaration?.['mother.nationality'], + 'USA' + ) + } + ) +}) + +Deno.test('PostProcess - Mix of Updates and Corrections', async (t) => { + const birthResolver = buildBirthResolver() + + await t.step( + 'should handle both DECLARATION_UPDATED and corrections in the same history', + () => { + const registration = buildBirthEventRegistration({ + child: { + gender: 'male', + }, + mother: { + nationality: ['GBR'], + }, + registration: { + trackingId: 'B123456', + registrationNumber: '2024B123456', + contactPhoneNumber: '0711111111', + }, + history: [ + { + date: '2024-01-01T10:00:00Z', + regStatus: 'DECLARED', + user: { id: 'user1', role: { id: 'FIELD_AGENT' } }, + office: { id: 'office1' }, + }, + { + date: '2024-01-02T12:00:00Z', + regStatus: 'DECLARATION_UPDATED', + user: { id: 'user2', role: { id: 'FIELD_AGENT' } }, + office: { id: 'office1' }, + input: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'female', + }, + ], + output: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'male', + }, + ], + }, + { + date: '2024-01-03T12:00:00Z', + regStatus: 'REGISTERED', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + }, + { + id: 'correction1', + date: '2024-01-04T14:00:00Z', + action: 'REQUESTED_CORRECTION', + user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + input: [ + { + valueCode: 'mother', + valueId: 'nationality', + value: 'USA', + }, + { + valueCode: 'informant', + valueId: 'registrationPhone', + value: '0799999999', + }, + ], + output: [ + { + valueCode: 'mother', + valueId: 'nationality', + value: 'GBR', + }, + { + valueCode: 'informant', + valueId: 'registrationPhone', + value: '0711111111', + }, + ], + }, + { + date: '2024-01-05T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user5', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, + ], + }) + + const result = transform(registration, birthResolver, 'birth') + + // Find all actions + const declareActions = result.actions.filter((a) => a.type === 'DECLARE') + const originalDeclareAction = declareActions[0] + const updatedDeclareAction = declareActions[1] + const registerAction = result.actions.find( + (a) => a.type === 'REGISTER' + ) as Action + const correctionAction = result.actions.find( + (a) => a.type === 'REQUEST_CORRECTION' + ) as Action + + // The updated DECLARE action should have the value after the update + assertEquals(updatedDeclareAction?.declaration?.['child.gender'], 'male') + + // The original DECLARE action should be reverse-engineered to show the value before the update + assertEquals( + originalDeclareAction?.declaration?.['child.gender'], + 'female' + ) + + // The correction should have new values in declaration + assertEquals(correctionAction?.declaration?.['mother.nationality'], 'GBR') + assertEquals( + correctionAction?.declaration?.['informant.phoneNo'], + '0711111111' + ) + + // The correction should have old values in annotation + assertEquals(correctionAction?.annotation?.['mother.nationality'], 'USA') + assertEquals( + correctionAction?.annotation?.['informant.phoneNo'], + '0799999999' + ) + + // The REGISTER action should be reverse-engineered to show values before the correction + assertEquals(registerAction?.declaration?.['mother.nationality'], 'USA') + assertEquals( + registerAction?.declaration?.['informant.phoneNo'], + '0799999999' + ) + + // The REGISTER action should also have the updated child gender (from update) + assertEquals(registerAction?.declaration?.['child.gender'], 'male') + } + ) +}) + +Deno.test('PostProcess - Correction Without Approval', async (t) => { + const birthResolver = buildBirthResolver() + + await t.step( + 'should not reverse engineer declaration or annotation for correction without approval', + () => { + const registration = buildBirthEventRegistration({ + child: { + gender: 'female', + }, + mother: { + nationality: ['GBR'], + }, + history: [ + { + date: '2024-01-01T10:00:00Z', + regStatus: 'DECLARED', + user: { id: 'user1', role: { id: 'FIELD_AGENT' } }, + office: { id: 'office1' }, + }, + { + date: '2024-01-02T12:00:00Z', + regStatus: 'REGISTERED', + user: { id: 'user2', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + }, + { + id: 'correction1', + date: '2024-01-03T14:00:00Z', + action: 'REQUESTED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + input: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'male', + }, + { + valueCode: 'mother', + valueId: 'nationality', + value: 'USA', + }, + ], + output: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'female', + }, + { + valueCode: 'mother', + valueId: 'nationality', + value: 'GBR', + }, + ], + }, + // Note: No APPROVED_CORRECTION action here + ], + }) + + const result = transform(registration, birthResolver, 'birth') + + const registerAction = result.actions.find( + (a) => a.type === 'REGISTER' + ) as Action + const declareAction = result.actions.find( + (a) => a.type === 'DECLARE' + ) as Action + const correctionAction = result.actions.find( + (a) => a.type === 'REQUEST_CORRECTION' + ) as Action + + // The correction action should still have declaration set from the output + assertEquals(correctionAction?.declaration?.['child.gender'], 'female') + assertEquals(correctionAction?.declaration?.['mother.nationality'], 'GBR') + + // The correction action should have annotation with input values (unchanged by post-processing without approval) + assertEquals(correctionAction?.annotation?.['child.gender'], 'male') + assertEquals(correctionAction?.annotation?.['mother.nationality'], 'USA') + + // The REGISTER action should NOT be modified - it should still have the final state values + // Because without approval, no reverse engineering should happen + assertEquals(registerAction?.declaration?.['child.gender'], 'female') + assertEquals(registerAction?.declaration?.['mother.nationality'], 'GBR') + + // The DECLARE action should also NOT be modified + assertEquals(declareAction?.declaration?.['child.gender'], 'female') + assertEquals(declareAction?.declaration?.['mother.nationality'], 'GBR') + } + ) + + await t.step( + 'should not reverse engineer for multiple corrections where only some are approved', + () => { + const registration = buildBirthEventRegistration({ + child: { + gender: 'unknown', + }, + mother: { + nationality: ['FRA'], + }, + registration: { + trackingId: 'B123456', + registrationNumber: '2024B123456', + contactPhoneNumber: '0733333333', + }, + history: [ + { + date: '2024-01-01T10:00:00Z', + regStatus: 'DECLARED', + user: { id: 'user1', role: { id: 'FIELD_AGENT' } }, + office: { id: 'office1' }, + }, + { + date: '2024-01-02T12:00:00Z', + regStatus: 'REGISTERED', + user: { id: 'user2', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + }, + { + id: 'correction1', + date: '2024-01-03T14:00:00Z', + action: 'REQUESTED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + input: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'male', + }, + ], + output: [ + { + valueCode: 'child', + valueId: 'gender', + value: 'female', + }, + ], + }, + { + date: '2024-01-04T14:00:00Z', + action: 'APPROVED_CORRECTION', + user: { id: 'user3', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + requestId: 'correction1', + }, + { + id: 'correction2', + date: '2024-01-05T14:00:00Z', + action: 'REQUESTED_CORRECTION', + user: { id: 'user4', role: { id: 'REGISTRATION_AGENT' } }, + office: { id: 'office1' }, + input: [ + { + valueCode: 'mother', + valueId: 'nationality', + value: 'USA', + }, + { + valueCode: 'informant', + valueId: 'registrationPhone', + value: '0799999999', + }, + ], + output: [ + { + valueCode: 'mother', + valueId: 'nationality', + value: 'FRA', + }, + { + valueCode: 'informant', + valueId: 'registrationPhone', + value: '0733333333', + }, + ], + }, + // Note: correction2 is NOT approved + ], + }) + + const result = transform(registration, birthResolver, 'birth') + + const registerAction = result.actions.find( + (a) => a.type === 'REGISTER' + ) as Action + const corrections = result.actions.filter( + (a) => a.type === 'REQUEST_CORRECTION' + ) as Action[] + + assertEquals(corrections.length, 2) + + const firstCorrection = corrections[0] + const secondCorrection = corrections[1] + + // First correction (approved) should have annotation and reverse engineering + assertEquals(firstCorrection?.declaration?.['child.gender'], 'female') + assertEquals(firstCorrection?.annotation?.['child.gender'], 'male') + + // REGISTER should be reverse-engineered for the first correction + assertEquals(registerAction?.declaration?.['child.gender'], 'male') + + // Second correction (not approved) should have annotation with input values (unchanged by post-processing) + assertEquals(secondCorrection?.declaration?.['mother.nationality'], 'FRA') + assertEquals( + secondCorrection?.declaration?.['informant.phoneNo'], + '0733333333' + ) + assertEquals(secondCorrection?.annotation?.['mother.nationality'], 'USA') + assertEquals( + secondCorrection?.annotation?.['informant.phoneNo'], + '0799999999' + ) + + // For the second correction fields, REGISTER should NOT be reverse-engineered + // It should keep the final state values + assertEquals(registerAction?.declaration?.['mother.nationality'], 'FRA') + assertEquals( + registerAction?.declaration?.['informant.phoneNo'], + '0733333333' + ) + } + ) +}) From 13e173bb45cf0e43716128ad28805432306974c6 Mon Sep 17 00:00:00 2001 From: Barry Dwyer Date: Thu, 18 Dec 2025 14:16:07 +0200 Subject: [PATCH 2/2] Review feedback --- v1-to-v2-data-migration/helpers/transform.ts | 2 +- v1-to-v2-data-migration/tests/unit/postProcess.test.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/v1-to-v2-data-migration/helpers/transform.ts b/v1-to-v2-data-migration/helpers/transform.ts index 211a1d3..d82b61c 100644 --- a/v1-to-v2-data-migration/helpers/transform.ts +++ b/v1-to-v2-data-migration/helpers/transform.ts @@ -546,7 +546,7 @@ function postProcess( action.declaration = previousDeclaration if (hasKeys(annotation)) { previousDeclaration = deepMerge(previousDeclaration, annotation) - action.annotation = deepMerge(previousDeclaration, annotation) + action.annotation = previousDeclaration } } } diff --git a/v1-to-v2-data-migration/tests/unit/postProcess.test.ts b/v1-to-v2-data-migration/tests/unit/postProcess.test.ts index 8ed233c..947c0c4 100644 --- a/v1-to-v2-data-migration/tests/unit/postProcess.test.ts +++ b/v1-to-v2-data-migration/tests/unit/postProcess.test.ts @@ -70,7 +70,6 @@ Deno.test('PostProcess - Single Correction', async (t) => { }) const result = transform(registration, birthResolver, 'birth') - console.log(JSON.stringify(result, null, 2)) // Find the actions const registerAction = result.actions.find( @@ -968,7 +967,7 @@ Deno.test('PostProcess - Multiple Corrections', async (t) => { const birthResolver = buildBirthResolver() await t.step( - 'should set annotation to previous declaration and reverse enngineer the original declaration', + 'should set annotation to previous declaration and reverse engineer the original declaration', () => { const registration = buildBirthEventRegistration({ child: {