feat(platform): add replaceable geozone data layer

This commit is contained in:
Codex
2026-07-20 21:48:27 +03:00
parent 8a7465cf0e
commit 3446f3edc2
31 changed files with 1342 additions and 128 deletions
@@ -146,6 +146,64 @@ async function main() {
publish.batch.idempotencyKey,
contracts.buildPublishPayload(items, 'execution-42', 'workflow-7', 'node-3', 'fleet.positions.current.v2', 0).batch.idempotencyKey,
);
const zoneItems = [{
json: {
sourceId: 'gelios-zone-42',
semanticType: 'map.zone',
observedAt: '2026-07-15T12:10:00.000Z',
attributes: { display_name: 'Zone 42' },
geometry: {
type: 'Polygon',
coordinates: [[[37.60, 55.74], [37.62, 55.74], [37.62, 55.76], [37.60, 55.74]]],
},
},
}];
const replacement = contracts.buildPublishPayload(
zoneItems,
'execution-43',
'workflow-7',
'node-zones',
'map.zones.current.v1',
0,
'replace',
);
assert.equal(externalContract.validateDataProductPublish(replacement).ok, true);
assert.equal(replacement.batch.mode, 'replace');
assert.equal(replacement.batch.generationAt, zoneItems[0].json.observedAt);
const replacementBatch = contracts.buildPublishPayload(
[{ json: { generationAt: zoneItems[0].json.observedAt, facts: zoneItems.map((item) => item.json) } }],
'execution-44',
'workflow-7',
'node-zones',
'map.zones.current.v1',
0,
'replace',
);
assert.deepEqual(replacementBatch.facts, replacement.facts);
assert.equal(replacementBatch.batch.generationAt, zoneItems[0].json.observedAt);
const emptyReplacement = contracts.buildPublishPayload(
[{ json: { generationAt: '2026-07-15T13:00:00.000Z', facts: [] } }],
'execution-45',
'workflow-7',
'node-zones',
'map.zones.current.v1',
0,
'replace',
);
assert.deepEqual(emptyReplacement.facts, []);
assert.equal(externalContract.validateDataProductPublish(emptyReplacement).ok, true);
assert.throws(
() => contracts.buildPublishPayload(
[{ json: { generationAt: '2026-07-15T13:00:00.000Z', facts: zoneItems.map((item) => item.json) } }],
'execution-46',
'workflow-7',
'node-zones',
'map.zones.current.v1',
0,
'replace',
),
/replace_generationAt_observedAt_mismatch/,
);
assert.throws(
() => contracts.buildPublishPayload(
[{ json: { ...items[0].json, attributes: { accessToken: 'forbidden' } } }],
@@ -303,6 +361,29 @@ async function assertNodeHttpContracts(nodes, externalContract, constants) {
assert.equal(externalContract.validateDataProductPublish(publishCalls[0].options.body).ok, true);
assert.equal(publishResult[0][0].json.publishedFactCount, 1);
const replacementCalls = [];
const replacementContext = executionContext({
typeVersion: 2,
parameters: { dataProductId: 'map.zones.current.v1', publishMode: 'replace', sequence: 0 },
input: [{
json: {
generationAt: '2026-07-15T12:10:00.000Z',
facts: [{
sourceId: 'gelios-zone-42',
semanticType: 'map.zone',
observedAt: '2026-07-15T12:10:00.000Z',
attributes: { display_name: 'Zone 42' },
geometry: { type: 'Polygon', coordinates: [[[37.60, 55.74], [37.62, 55.74], [37.62, 55.76], [37.60, 55.74]]] },
}],
},
}],
response: { ok: true, publishedFactCount: 1, currentRemovedCount: 0 },
calls: replacementCalls,
});
await nodes.get('NdcDataProductPublish').execute.call(replacementContext);
assert.equal(replacementCalls[0].options.body.batch.mode, 'replace');
assert.equal(externalContract.validateDataProductPublish(replacementCalls[0].options.body).ok, true);
const readCalls = [];
const readContext = executionContext({
parameters: { dataProductId: 'fleet.positions.current.v1', pageSize: 1000 },
@@ -411,7 +492,7 @@ async function assertNodeHttpContracts(nodes, externalContract, constants) {
}
}
function executionContext({ parameters, input = [{ json: {} }], response, calls }) {
function executionContext({ parameters, input = [{ json: {} }], response, calls, typeVersion = 1 }) {
return {
getNodeParameter(name, _index, defaultValue) {
return Object.prototype.hasOwnProperty.call(parameters, name) ? parameters[name] : defaultValue;
@@ -419,7 +500,7 @@ function executionContext({ parameters, input = [{ json: {} }], response, calls
getInputData() { return input; },
getExecutionId() { return 'execution-42'; },
getWorkflow() { return { id: 'workflow-7' }; },
getNode() { return { id: 'node-3' }; },
getNode() { return { id: 'node-3', typeVersion }; },
helpers: {
async httpRequestWithAuthentication(credentialName, options) {
calls.push({ credentialName, options });