import assert from "node:assert/strict"; import test from "node:test"; import { readFile } from "node:fs/promises"; import { buildMapSearchIndex, searchMapSubjects } from "../apps/catalog/src/mapSearch.mjs"; const now = "2026-07-25T08:00:00.000Z"; const point = (sourceId, semanticType, attributes) => ({ sourceId, semanticType, observedAt: now, receivedAt: now, attributes, geometry: { type: "Point", coordinates: [37.62, 55.75] }, presentationStatus: "active", }); const profile = { id: "map.subject.v1", title: "Транспорт", semanticTypes: ["map.moving_object"], label: { mode: "attributes", fields: ["display_name"] }, }; test("search index uses stable identity, label and explicit field projection only", () => { const index = buildMapSearchIndex({ runtimeBindings: [{ bindingId: "vehicles", presentationProfileId: profile.id, facts: [point("unit-42", "map.moving_object", { display_name: "Трайк 42", tracker_id: "tracker-77", raw_secret: "must-not-be-indexed", })], }], bindingConfigs: [{ id: "vehicles", fieldProjection: ["display_name", "tracker_id"], presentationProfileId: profile.id, }], presentationProfiles: [profile], }); assert.equal(searchMapSubjects(index, "Трайк").at(0)?.sourceId, "unit-42"); assert.equal(searchMapSubjects(index, "tracker-77").at(0)?.sourceId, "unit-42"); assert.equal(searchMapSubjects(index, "unit-42").at(0)?.sourceId, "unit-42"); assert.deepEqual(searchMapSubjects(index, "must-not-be-indexed"), []); }); test("authorized joined aspect augments primary subject without creating a second entity", () => { const index = buildMapSearchIndex({ runtimeBindings: [ { bindingId: "vehicles", presentationProfileId: profile.id, facts: [point("unit-42", "map.moving_object", { display_name: "Трайк 42" })], }, { bindingId: "identity", facts: [point("unit-42", "map.moving_object", { imei: "867236078012345", provider_payload: "forbidden", })], }, ], bindingConfigs: [ { id: "vehicles", fieldProjection: ["display_name"], presentationProfileId: profile.id }, { id: "identity", joinToBindingId: "vehicles", fieldProjection: ["imei"], dataClass: "restricted", }, ], presentationProfiles: [profile], }); assert.equal(index.length, 1); assert.equal(searchMapSubjects(index, "867236078012345").at(0)?.entityId, "nodedc-runtime:vehicles:map.moving_object:unit-42"); assert.deepEqual(searchMapSubjects(index, "forbidden"), []); }); test("reference subjects remain searchable by profile labels without a provider branch", () => { const stationProfile = { id: "map.reference.station.v1", title: "Метро", semanticTypes: ["map.station"], label: { mode: "attributes", fields: ["name", "official_name"] }, }; const index = buildMapSearchIndex({ runtimeBindings: [{ bindingId: "reference.metro", presentationProfileId: stationProfile.id, facts: [point("osm.node.1", "map.station", { name: "Петроградская", provider_note: "not searchable", })], }], presentationProfiles: [stationProfile], }); assert.equal(searchMapSubjects(index, "петрог").at(0)?.title, "Петроградская"); assert.equal(searchMapSubjects(index, "osm.node.1").at(0)?.groupTitle, "Метро"); assert.deepEqual(searchMapSubjects(index, "provider_note"), []); }); test("expanded search keeps the canonical toolbar inset on every edge", async () => { const styles = await readFile(new URL("../apps/catalog/src/styles.css", import.meta.url), "utf8"); const baseToolbar = styles.match(/\.catalog-map-fixture__toolbar\s*\{(?[\s\S]*?)\n\}/)?.groups?.body ?? ""; const expandedToolbar = styles.match(/\.catalog-map-fixture__toolbar\[data-search-open\]\s*\{(?[\s\S]*?)\n\}/)?.groups?.body ?? ""; assert.match(baseToolbar, /padding: 0\.4rem/); assert.doesNotMatch(expandedToolbar, /padding-right:\s*0/); });