import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { L2_CONNECTION_INSTANCE_SCHEMA_VERSION, instantiateL2Connection, validateDataProductPublish, validateProviderPackage, } from "../src/index.mjs"; import { GELIOS_POSITIONS_DATA_PRODUCT_ID, GELIOS_POSITIONS_DATA_PRODUCT_VERSION, GELIOS_POSITIONS_ONTOLOGY_REVISION, GELIOS_UNIT_SOURCE_ID_PREFIX, geliosProviderPackageV1, geliosUnitsCurrentFixtureV1, } from "../providers/gelios/v1/index.mjs"; import { geliosProviderPackageV2 } from "../providers/gelios/v2/index.mjs"; import { normalizeDataProductDefinition } from "../../../services/external-data-plane/src/data-product-policy.mjs"; const expectedFields = [ "course_degrees", "display_name", "elevation_meters", "geometry", "hdop", "horizontal_accuracy_meters", "object_kind", "operational_status", "position_source", "position_valid", "quality_flags", "satellite_count", "speed_kph", ]; assert.deepEqual(validateProviderPackage(geliosProviderPackageV1), { ok: true, errors: [] }); assert.deepEqual(validateProviderPackage(geliosProviderPackageV2), { ok: true, errors: [] }); const geliosSdkAuth = geliosProviderPackageV2.authModes[0]; const geliosSdkUnitsRead = geliosProviderPackageV2.capabilities[0]; assert.equal(geliosProviderPackageV2.id, "gelios.provider.v2"); assert.equal(geliosProviderPackageV2.version, "2.0.0"); assert.equal(geliosSdkAuth.id, "gelios.sdk-query-token.v2"); assert.deepEqual(geliosSdkAuth.transport, { placement: "query", name: "token" }); assert.deepEqual(geliosSdkAuth.tokenLifecycle, { artifacts: ["access"], requestArtifact: "access", refreshMode: "not_applicable", }); assert.equal(geliosSdkUnitsRead.request.baseUrl, "https://admin.geliospro.com"); assert.equal(geliosSdkUnitsRead.request.path, "/sdk/"); assert.deepEqual(geliosSdkUnitsRead.request.query, { svc: "get_units", params: "{}" }); assert.equal(geliosProviderPackageV2.dataProducts[0].id, GELIOS_POSITIONS_DATA_PRODUCT_ID); const geliosAuth = geliosProviderPackageV1.authModes[0]; const geliosUnitsRead = geliosProviderPackageV1.capabilities[0]; assert.equal(geliosAuth.id, "gelios.rest-bearer.v1"); assert.deepEqual(geliosAuth.transport, { placement: "header", name: "Authorization" }); assert.deepEqual(geliosAuth.tokenLifecycle, { artifacts: ["access", "refresh"], requestArtifact: "access", refreshMode: "operator_managed", }); assert.equal(geliosAuth.tokenLifecycle.artifacts.includes("writer"), false); assert.equal(geliosUnitsRead.request.baseUrl, "https://api.geliospro.com"); assert.equal(geliosUnitsRead.request.path, "/api/v1/units"); assert.deepEqual(geliosUnitsRead.request.query, {}); const product = geliosProviderPackageV1.dataProducts[0]; const registeredProduct = JSON.parse(await readFile(new URL( "../../../services/external-data-plane/definitions/fleet.positions.current.v1.json", import.meta.url, ), "utf8")); assert.equal(product.id, GELIOS_POSITIONS_DATA_PRODUCT_ID); assert.equal(product.version, GELIOS_POSITIONS_DATA_PRODUCT_VERSION); assert.equal(product.ontologyRevision, GELIOS_POSITIONS_ONTOLOGY_REVISION); assert.equal(product.id, "fleet.positions.current.v1"); assert.equal(product.version, "1.0.0"); assert.equal(product.ontologyRevision, "ontology.map.moving_object.v1"); assert.deepEqual(product.fields, expectedFields); assert.deepEqual(product, registeredProduct); assert.doesNotThrow(() => normalizeDataProductDefinition(product)); assert.equal(product.fields.every((field) => /^[a-z][a-z0-9_]*$/.test(field)), true); const mapping = geliosProviderPackageV1.mappingContracts[0]; assert.equal(mapping.target.dataProductId, product.id); assert.equal(mapping.target.version, product.version); assert.equal(mapping.target.ontologyRevision, product.ontologyRevision); assert.deepEqual( [...Object.keys(mapping.fact.attributes), "geometry"].sort(), [...product.fields].sort(), ); assert.equal(mapping.derivations.operational_status.parameters.staleAfterMs, 60000); assert.deepEqual(mapping.derivations.quality_flags.default, []); assert.equal(mapping.fact.observedAt.fallback, "collection_received_at"); assert.equal(GELIOS_UNIT_SOURCE_ID_PREFIX, "gelios-unit-"); assert.equal(mapping.fact.sourceId.prefix, GELIOS_UNIT_SOURCE_ID_PREFIX); assert.equal(mapping.fact.sourceId.coerce, "string"); assert.equal(mapping.fact.geometry.longitude.paths.includes("lastMsg.lon"), true); assert.equal(mapping.fact.geometry.latitude.paths.includes("lastMsg.lat"), true); assert.equal(mapping.fact.attributes.speed_kph.paths.includes("lastMsg.speed"), true); assert.equal(validateDataProductPublish(geliosUnitsCurrentFixtureV1.expectedPublish).ok, true); assert.equal( geliosUnitsCurrentFixtureV1.sourceResponse.units.length, geliosUnitsCurrentFixtureV1.expectedPublish.facts.length, "every entity returned for the bound credential remains in the publish fixture", ); assert.deepEqual( geliosUnitsCurrentFixtureV1.expectedPublish.facts.map((fact) => fact.sourceId), geliosUnitsCurrentFixtureV1.sourceResponse.units.map( (unit) => `${GELIOS_UNIT_SOURCE_ID_PREFIX}${String(unit.id)}`, ), ); assert.equal( geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].sourceId, "gelios-unit-001002", "provider ID strings must remain byte-for-byte stable after the canonical prefix", ); assert.equal(geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].geometry, undefined); assert.equal(geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].attributes.operational_status, "no_position"); assert.equal(geliosUnitsCurrentFixtureV1.sourceResponse.units[1].lastMsg, undefined); assert.equal( geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].observedAt, geliosUnitsCurrentFixtureV1.collectionContext.receivedAt, ); const realtimeProfileId = "gelios.positions.current.realtime.v1"; assert.equal( geliosProviderPackageV1.collectionProfiles[0].cardinality.onExceed, "require_partitioned_data_product", ); const accountA = instantiateL2Connection(geliosProviderPackageV1, { tenantId: "tenant-alpha", connectionId: "gelios-account-alpha", collectionProfileId: realtimeProfileId, providerCredentialRef: "ndc-credref:provider-alpha-0001", }); const accountB = instantiateL2Connection(geliosProviderPackageV1, { tenantId: "tenant-beta", connectionId: "gelios-account-beta", collectionProfileId: realtimeProfileId, providerCredentialRef: "ndc-credref:provider-beta-0001", }); assert.equal(accountA.schemaVersion, L2_CONNECTION_INSTANCE_SCHEMA_VERSION); assert.equal(accountA.package.id, accountB.package.id); assert.equal(accountA.package.version, accountB.package.version); assert.equal(accountA.l2TemplateId, accountB.l2TemplateId); assert.notEqual(accountA.connectionId, accountB.connectionId); assert.notEqual(accountA.credentialRefs.provider.reference, accountB.credentialRefs.provider.reference); assert.equal(accountA.credentialRefs.provider.owner, "ndc_l2_credentials"); assert.deepEqual(Object.keys(accountA.credentialRefs), ["provider"]); assert.deepEqual(accountA.systemBindings.publisher, { role: "publisher", owner: "ndc_l2_credentials", management: "control_plane_managed", nodeType: "n8n-nodes-ndc.ndcDataProductPublish", desiredState: "bound", status: "unresolved", }); assert.equal(accountA.scope.mode, "all_visible_to_credential"); assert.equal(accountA.scope.refresh, "each_collection_run"); assert.equal(Object.isFrozen(accountA), true); assert.equal(Object.isFrozen(accountA.credentialRefs), true); assert.equal(Object.isFrozen(accountA.systemBindings), true); const withEntityAllowlist = structuredClone(geliosProviderPackageV1); withEntityAllowlist.capabilities[0].entityScope.unitIds = ["gelios-unit-1001"]; assert.equal( validateProviderPackage(withEntityAllowlist).errors.includes("capabilities[0].entityScope.unitIds_not_allowed"), true, ); const withGroupFilter = structuredClone(geliosProviderPackageV1); withGroupFilter.collectionProfiles[0].entityScope.businessEntityFilter = "provider_group"; assert.equal( validateProviderPackage(withGroupFilter).errors.includes("collectionProfiles[0].entityScope.businessEntityFilter_must_be_forbidden"), true, ); const withWrongRevision = structuredClone(geliosProviderPackageV1); withWrongRevision.mappingContracts[0].target.ontologyRevision = "gelios.positions.v1"; assert.equal( validateProviderPackage(withWrongRevision).errors.includes("mappingContract.gelios.units.to.fleet.positions.current.v1.target.ontologyRevision_must_match_data_product"), true, ); const withMissingDerivation = structuredClone(geliosProviderPackageV1); delete withMissingDerivation.mappingContracts[0].derivations.position_valid; assert.equal( validateProviderPackage(withMissingDerivation).errors.includes("mappingContracts[0].derive_position_valid_missing_definition"), true, ); const withWrongFactSemanticType = structuredClone(geliosProviderPackageV1); withWrongFactSemanticType.mappingContracts[0].fact.semanticType.constant = "map.other"; assert.equal( validateProviderPackage(withWrongFactSemanticType).errors.includes( "mappingContract.gelios.units.to.fleet.positions.current.v1.fact.semanticType_must_equal_target_constant", ), true, ); const nonGeospatialPackage = structuredClone(geliosProviderPackageV1); nonGeospatialPackage.dataProducts[0].fields = ["display_name"]; nonGeospatialPackage.fieldPolicies[0].targetFields = ["display_name"]; delete nonGeospatialPackage.mappingContracts[0].fact.geometry; nonGeospatialPackage.mappingContracts[0].fact.attributes = { display_name: { strategy: "first_non_empty", paths: ["name"], coerce: "string" }, }; delete nonGeospatialPackage.mappingContracts[0].derivations; assert.deepEqual(validateProviderPackage(nonGeospatialPackage), { ok: true, errors: [] }); for (const restrictedPath of ["phone", "phone.number", "lmsg"]) { const withRestrictedMappingPath = structuredClone(geliosProviderPackageV1); withRestrictedMappingPath.mappingContracts[0].fact.attributes.display_name.paths = [restrictedPath]; assert.equal( validateProviderPackage(withRestrictedMappingPath).errors.includes( "mappingContract.gelios.units.to.fleet.positions.current.v1.fact_source_path_restricted", ), true, ); } const withFieldPolicyProductMismatch = structuredClone(geliosProviderPackageV1); const secondProduct = structuredClone(withFieldPolicyProductMismatch.dataProducts[0]); secondProduct.id = "fleet.positions.alternate.v1"; withFieldPolicyProductMismatch.dataProducts.push(secondProduct); withFieldPolicyProductMismatch.manifest.dataProductIds.push(secondProduct.id); withFieldPolicyProductMismatch.fieldPolicies[0].dataProductId = secondProduct.id; assert.equal( validateProviderPackage(withFieldPolicyProductMismatch).errors.includes( "mappingContract.gelios.units.to.fleet.positions.current.v1.field_policy_data_product_mismatch", ), true, ); const withCamelCaseField = structuredClone(geliosProviderPackageV1); withCamelCaseField.dataProducts[0].fields[12] = "speedKph"; assert.equal(validateProviderPackage(withCamelCaseField).ok, false); const withSecretField = structuredClone(geliosProviderPackageV1); withSecretField.dataProducts[0].fields[12] = "api_token"; withSecretField.fieldPolicies[0].targetFields[12] = "api_token"; withSecretField.mappingContracts[0].fact.attributes.api_token = { constant: "metadata-only" }; delete withSecretField.mappingContracts[0].fact.attributes.speed_kph; const secretFieldValidation = validateProviderPackage(withSecretField); assert.equal(secretFieldValidation.errors.includes("dataProducts[0].fields_must_not_contain_secret_fields"), true); assert.equal(secretFieldValidation.errors.includes("fieldPolicies[0].targetFields_must_not_contain_secret_fields"), true); assert.equal(secretFieldValidation.errors.includes("mappingContracts[0].fact.attributes.api_token_secret_field_not_allowed"), true); const withRuntimeAccountState = structuredClone(geliosProviderPackageV1); withRuntimeAccountState.tenantId = "must-not-live-in-package"; assert.equal( validateProviderPackage(withRuntimeAccountState).errors.includes("providerPackage_must_not_contain_connection_instance_state"), true, ); const withPrimitiveCapability = structuredClone(geliosProviderPackageV1); withPrimitiveCapability.capabilities.push(null); assert.doesNotThrow(() => validateProviderPackage(withPrimitiveCapability)); assert.equal(validateProviderPackage(withPrimitiveCapability).ok, false); const withMalformedNestedArrays = structuredClone(geliosProviderPackageV1); withMalformedNestedArrays.collectionProfiles[0].capabilityIds = {}; withMalformedNestedArrays.l2Templates[0].credentialBindings = {}; withMalformedNestedArrays.l2Templates[0].steps = {}; assert.doesNotThrow(() => validateProviderPackage(withMalformedNestedArrays)); assert.equal(validateProviderPackage(withMalformedNestedArrays).ok, false); const withCredentialInStaticQuery = structuredClone(geliosProviderPackageV1); withCredentialInStaticQuery.authModes[0].transport = { placement: "query", name: "token" }; withCredentialInStaticQuery.capabilities[0].request.query.token = "plain-provider-key"; assert.equal( validateProviderPackage(withCredentialInStaticQuery).errors.includes("capabilities[0].request.query.token_credential_parameter_not_allowed"), true, ); assert.equal( validateProviderPackage(withCredentialInStaticQuery).errors.includes("capability.gelios.units.current.read.request.query_must_not_embed_credential_parameter"), true, ); const withCredentialInBaseUrl = structuredClone(geliosProviderPackageV1); withCredentialInBaseUrl.capabilities[0].request.baseUrl = "https://user:pass@api.geliospro.com/?token=forbidden"; assert.equal(validateProviderPackage(withCredentialInBaseUrl).ok, false); for (const unsafePath of ["/ok\nX-Evil: yes", "/back\\slash", "/query?x=1"]) { const withUnsafeRequestPath = structuredClone(geliosProviderPackageV1); withUnsafeRequestPath.capabilities[0].request.path = unsafePath; assert.equal(validateProviderPackage(withUnsafeRequestPath).ok, false); } for (const unsafeCollectionPath of ["__proto__.polluted", "data.items[0]", "constructor.prototype"]) { const withUnsafeCollectionPath = structuredClone(geliosProviderPackageV1); withUnsafeCollectionPath.capabilities[0].request.response.collectionPaths = [unsafeCollectionPath]; assert.equal(validateProviderPackage(withUnsafeCollectionPath).ok, false); } const withUnsafeMappingPath = structuredClone(geliosProviderPackageV1); withUnsafeMappingPath.mappingContracts[0].fact.attributes.display_name.paths = ["__proto__.polluted"]; assert.equal(validateProviderPackage(withUnsafeMappingPath).ok, false); const withUnsafeCollectionCapability = structuredClone(geliosProviderPackageV1); withUnsafeCollectionCapability.capabilities[0].classification = "write"; assert.equal( validateProviderPackage(withUnsafeCollectionCapability).errors.includes("collectionProfile.gelios.positions.current.realtime.v1.capabilityIds_must_reference_implemented_safe_read"), true, ); const withUnexecutedSecondCapability = structuredClone(geliosProviderPackageV1); const secondCapability = structuredClone(withUnexecutedSecondCapability.capabilities[0]); secondCapability.id = "gelios.units.metadata.read"; withUnexecutedSecondCapability.capabilities.push(secondCapability); withUnexecutedSecondCapability.manifest.capabilityIds.push(secondCapability.id); for (const profile of withUnexecutedSecondCapability.collectionProfiles) { profile.capabilityIds.push(secondCapability.id); } assert.equal( validateProviderPackage(withUnexecutedSecondCapability).errors.includes( "collectionProfile.gelios.positions.current.realtime.v1.capabilityIds_must_select_one_v1_read", ), true, ); const withReorderedSteps = structuredClone(geliosProviderPackageV1); [withReorderedSteps.l2Templates[0].steps[0], withReorderedSteps.l2Templates[0].steps[1]] = [ withReorderedSteps.l2Templates[0].steps[1], withReorderedSteps.l2Templates[0].steps[0], ]; assert.equal( validateProviderPackage(withReorderedSteps).errors.includes("l2Templates[0].steps_sequence_invalid"), true, ); const withUnknownConnectionParameters = structuredClone(geliosProviderPackageV1); withUnknownConnectionParameters.l2Templates[0].connectionParameters = ["foo"]; assert.equal( validateProviderPackage(withUnknownConnectionParameters).errors.includes( "l2Templates[0].connectionParameters_must_match_v1_contract", ), true, ); const withCredentialAuthMismatch = structuredClone(geliosProviderPackageV1); const secondAuthMode = structuredClone(withCredentialAuthMismatch.authModes[0]); secondAuthMode.id = "gelios.rest-bearer.alternate.v1"; withCredentialAuthMismatch.authModes.push(secondAuthMode); withCredentialAuthMismatch.manifest.authModeIds.push(secondAuthMode.id); withCredentialAuthMismatch.l2Templates[0].credentialBindings[0].authModeId = secondAuthMode.id; assert.equal( validateProviderPackage(withCredentialAuthMismatch).errors.includes( "l2Template.gelios.positions.current.l2.v1.provider_credential_auth_mode_mismatch", ), true, ); const withCallerManagedPublisher = structuredClone(geliosProviderPackageV1); withCallerManagedPublisher.l2Templates[0].credentialBindings[1].management = "connection_input"; assert.equal( validateProviderPackage(withCallerManagedPublisher).errors.includes( "l2Templates[0].credentialBindings[1].publisher_binding_shape_invalid", ), true, ); const withUnknownTokenArtifact = structuredClone(geliosProviderPackageV1); withUnknownTokenArtifact.authModes[0].tokenLifecycle.artifacts.push("writer"); assert.equal( validateProviderPackage(withUnknownTokenArtifact).errors.includes( "authModes[0].tokenLifecycle.artifacts_invalid", ), true, ); const withRefreshUsedForRequest = structuredClone(geliosProviderPackageV1); withRefreshUsedForRequest.authModes[0].tokenLifecycle.requestArtifact = "refresh"; assert.equal( validateProviderPackage(withRefreshUsedForRequest).errors.includes( "authModes[0].tokenLifecycle.requestArtifact_must_be_access", ), true, ); const withUnmanagedRefreshArtifact = structuredClone(geliosProviderPackageV1); withUnmanagedRefreshArtifact.authModes[0].tokenLifecycle.refreshMode = "not_applicable"; assert.equal( validateProviderPackage(withUnmanagedRefreshArtifact).errors.includes( "authModes[0].tokenLifecycle.refreshMode_required_for_refresh_artifact", ), true, ); for (const history of [{ mode: "none", intervalMs: 60000, strategy: "latest-per-entity-per-bucket", retentionDays: 90, }, { mode: "sampled", intervalMs: 24 * 60 * 60 * 1000 + 1, strategy: "latest-per-entity-per-bucket", retentionDays: 90, }, { mode: "all", retentionDays: 3651, }]) { const withInvalidHistory = structuredClone(geliosProviderPackageV1); withInvalidHistory.dataProducts[0].history = history; assert.equal(validateProviderPackage(withInvalidHistory).ok, false); assert.throws(() => normalizeDataProductDefinition(withInvalidHistory.dataProducts[0])); } const withBrokenArtifactChain = structuredClone(geliosProviderPackageV1); withBrokenArtifactChain.l2Templates[0].steps[4].dataProductId = "unrelated.product.v1"; assert.equal( validateProviderPackage(withBrokenArtifactChain).errors.includes("collectionProfile.gelios.positions.current.realtime.v1.template_data_product_mismatch"), true, ); const withSecretNamedDerivationParameter = structuredClone(geliosProviderPackageV1); withSecretNamedDerivationParameter.mappingContracts[0].derivations.position_valid.parameters = { accessToken: "opaque-provider-value", }; assert.equal( validateProviderPackage(withSecretNamedDerivationParameter).errors.includes( "mappingContracts[0].derivations.position_valid.parameters.accessToken_secret_field_not_allowed", ), true, ); const withObjectConstant = structuredClone(geliosProviderPackageV1); withObjectConstant.mappingContracts[0].fact.attributes.object_kind.constant = { accessToken: "opaque-provider-value", }; assert.equal( validateProviderPackage(withObjectConstant).errors.includes( "mappingContracts[0].fact.attributes.object_kind.constant_must_be_scalar_or_scalar_array", ), true, ); assert.throws(() => instantiateL2Connection(geliosProviderPackageV1, { tenantId: "tenant-alpha", connectionId: "gelios-account-alpha", collectionProfileId: realtimeProfileId, providerCredentialRef: "Bearer plaintext-must-not-cross-boundary", }), /l2_connection_invalid/); assert.throws(() => instantiateL2Connection(geliosProviderPackageV1, { tenantId: "tenant-alpha", connectionId: "gelios-account-alpha", collectionProfileId: realtimeProfileId, providerCredentialRef: "ndc-credref:provider-alpha-0001", writerCredentialRef: "ndc-credref:caller-writer-forbidden", }), /options\.writerCredentialRef_not_allowed/); const serializedPackage = JSON.stringify(geliosProviderPackageV1); assert.equal(serializedPackage.includes('"prefix":"unit-"'), false); assert.equal(serializedPackage.includes("unitIds"), false); assert.equal(serializedPackage.includes("tenant-alpha"), false); assert.equal(serializedPackage.includes("gelios-gateway"), false); assert.equal(serializedPackage.includes("writer_credential_ref"), false); assert.equal(serializedPackage.includes("writerCapabilityByReferenceOnly"), false); assert.equal(serializedPackage.includes("publisherBindingControlPlaneManaged\":true"), true); assert.equal(serializedPackage.includes("providerSpecificServiceForbidden\":true"), true); assert.deepEqual( geliosProviderPackageV1.l2Templates[0].steps.map((step) => step.kind), [ "collection_trigger", "provider_request", "extract_items", "semantic_mapping", "data_product_publish", ], ); assert.equal( geliosProviderPackageV1.l2Templates[0].steps.at(-1).nodeType, "n8n-nodes-ndc.ndcDataProductPublish", ); console.log("external-provider-contract provider-package: ok");