import { assert } from "chai"; import { describe, it, beforeEach, afterEach } from "node:test"; import * as m from "../index"; import * as config from "./config"; import mockModule from "./mock"; import { DronesConfigurationDefault } from "../models/department"; import { SamsaraConfigurationType, ShareIncidentRuleTypeV2, ShareIncidentRuleTypeV2Always, ShareIncidentRuleTypeV2Area, ShareIncidentRuleTypeV2AssignedUnit, ShareIncidentRuleTypeV2ComplexAND, ShareIncidentRuleTypeV2ComplexOR, ShareIncidentRuleTypeV2IncidentType, ShareIncidentRuleTypeV2ReportNumber } from "../types/department"; describe("Department", function () { let models: m.BackendModels, mongoose: m.MongooseModule; let testItem: Partial; beforeEach(async function () { const c = await m.connect(config.url); models = c.models; mongoose = c.mongoose; const mock = mockModule({ mongoose }); testItem = mock.department; await mock.beforeEach(); }); afterEach(async function () { await mongoose.disconnect(); }); it("is saved", async function () { assert.isObject(testItem); const item = new models.Department(testItem); const sut = await item.save(); await models.Department.findOne({ _id: testItem._id }).lean(); assert.isNotNull(sut._id); assert.isNotNull(sut.id); assert.equal(sut.addressDetails.city, testItem.addressDetails?.city); assert.isTrue(sut.active); assert.equal(sut.accountType, testItem.accountType); assert.equal(sut.agencyIds.length, 1); if (testItem.agencyIds?.length) { assert.equal(sut.agencyIds[0], testItem.agencyIds[0]); } else { assert.equal(testItem.agencyIds?.length, 1); } assert.equal(sut.apikey, testItem.apikey); assert.isFalse(sut.cadOneWayVehiclesEnabled); assert.isFalse(sut.cadGetLocationEnabled); assert.equal(sut.callerLocation.enabled, testItem.callerLocation?.enabled); const callerResolver = sut.callerLocation.resolvers.find(() => true); assert.isObject(callerResolver); assert.equal(callerResolver?.type, m.AccountCallerType.Dummy); if (testItem.customButtons?.length) { assert.equal(sut.customButtons[0]?.name, testItem.customButtons[0]?.name); assert.equal(sut.customButtons[0]?.url, testItem.customButtons[0]?.url); assert.equal(sut.customButtons[0]?.order, testItem.customButtons[0]?.order); assert.equal(sut.customButtons[0]?.allowExternal, testItem.customButtons[0]?.allowExternal); assert.equal(sut.customButtons[0]?.defaultExternal, testItem.customButtons[0]?.defaultExternal); assert.equal(sut.customButtons[0]?.allowFloating, testItem.customButtons[0]?.allowFloating); assert.equal(JSON.stringify(sut.customButtons[0]?.color), JSON.stringify(testItem.customButtons[0]?.color)); } else { assert.equal(testItem.customButtons?.length, 1); } assert.equal(sut.department, testItem.department); assert.equal(sut.externalNotificationsEnabled, testItem.externalNotificationsEnabled); assert.isTrue(sut.fireMapper.enabled); assert.equal(sut.fireMapper?.auth?.account, testItem.fireMapper?.auth?.account); assert.equal(sut.fireMapper?.auth?.uuid, testItem.fireMapper?.auth?.uuid); assert.equal(sut.fireMapper?.auth?.host, testItem.fireMapper?.auth?.host); assert.equal(sut.fireMapper?.auth?.apiKeyManagement?.encryptedData, testItem.fireMapper?.auth?.apiKeyManagement?.encryptedData); assert.equal(sut.fireMapper?.auth?.apiKeyManagement?.iv, testItem.fireMapper?.auth?.apiKeyManagement?.iv); assert.equal(sut.fireMapper?.auth?.apiKeyReadOnly?.encryptedData, testItem.fireMapper?.auth?.apiKeyReadOnly?.encryptedData); assert.equal(sut.fireMapper?.auth?.apiKeyReadOnly?.iv, testItem.fireMapper?.auth?.apiKeyReadOnly?.iv); assert.equal(sut.fireMapper?.auth?.licenseCode?.encryptedData, testItem.fireMapper?.auth?.licenseCode?.encryptedData); assert.equal(sut.fireMapper?.auth?.licenseCode?.iv, testItem.fireMapper?.auth?.licenseCode?.iv); assert.equal(sut.fireMapper.layerRefreshInterval, testItem.fireMapper?.layerRefreshInterval); assert.equal(sut.fireMapper?.layer?.length, 1); const fmLayer = sut.fireMapper?.layer?.find(() => true); assert.isObject(fmLayer); assert.equal(fmLayer?.name, "abc"); assert.equal(fmLayer?.pathname, "/a/b/c"); assert.equal(sut.fireMapper.proLicenseCount, testItem.fireMapper?.proLicenseCount); assert.equal(sut.fireMapper?.staticLayer?.length, 1); const stLayer = sut.fireMapper?.staticLayer?.find(() => true); assert.isObject(stLayer); assert.equal(stLayer?.uuid, "u1"); const stLayer1 = stLayer?.layer?.find(() => true); assert.isObject(stLayer1); assert.equal(stLayer1?.name, "fm - sa - 1"); assert.equal(stLayer1?.pathname, "s/a/b"); assert.equal(sut.firstArrivingEnabled, testItem.firstArrivingEnabled); assert.equal(sut.firstArriving.token, testItem.firstArriving?.token); assert.equal(sut.incidentTypes.length, 1); if (testItem.incidentTypes?.length) { assert.equal(sut.incidentTypes[0]?.name, testItem.incidentTypes[0]?.name); } else { assert.equal(testItem.incidentTypes?.length, 1); } assert.equal(JSON.stringify(sut.licensing), JSON.stringify(testItem.licensing)); assert.equal(sut.notificationEmails.length, testItem.notificationEmails?.length); assert.isTrue(sut.mowsEnabled); assert.equal(sut.orientationMarkerColor.text, testItem.orientationMarkerColor?.text); assert.equal(sut.orientationMarkerColor.background, testItem.orientationMarkerColor?.background); assert.equal(sut.partialApiKey, testItem.partialApiKey); assert.isTrue(sut.personnelStaffingEnabled); assert.equal(sut.pubNubV3.token, testItem.pubNubV3?.token); assert.equal(sut.pubNubV3.expireAt.toISOString(), testItem.pubNubV3?.expireAt.toISOString()); assert.equal(sut.pubNubV3.runAt.toISOString(), testItem.pubNubV3?.runAt.toISOString()); assert.equal(sut.pubNubV3.env, testItem.pubNubV3?.env); assert.equal(sut.restrictedComments.callTypesAllowed.length, testItem.restrictedComments?.callTypesAllowed.length); assert.equal(sut.restrictedComments.statusesAllowed.length, testItem.restrictedComments?.statusesAllowed.length); assert.equal(sut.restrictedComments.restrictedFields.length, testItem.restrictedComments?.restrictedFields.length); assert.equal(sut.restrictedComments.restrictedMessage, testItem.restrictedComments?.restrictedMessage); assert.equal(sut.restrictedComments.enabled, testItem.restrictedComments?.enabled); assert.equal(sut.reportOdometer, "status"); assert.equal(sut.reportNumberEnabled, testItem.reportNumberEnabled); assert.isTrue(sut.rtsEnabled); assert.equal(sut.rtsChannelPrefix, testItem.rtsChannelPrefix); assert.equal(sut.safetyPriorityKeywords.length, 3); assert.equal(JSON.stringify(sut.samsara), JSON.stringify(testItem.samsara)); assert.equal(sut.signupKey, testItem.signupKey); assert.isFalse(sut.shareLocationPhones); assert.isTrue(sut.shareLocationTablets); assert.isTrue(sut.shareAVL.enabled); assert.equal(sut.shareAVL.opAreaCode, testItem.shareAVL?.opAreaCode); assert.equal(sut.shareAVL.opAreaName, testItem.shareAVL?.opAreaName); assert.equal(sut.simpleSense.token, testItem.simpleSense?.token); assert.equal(sut.simpleSenseEnabled, testItem.simpleSenseEnabled); assert.equal(sut.timeZone, testItem.timeZone); assert.equal(sut.webDisclaimer.message, testItem.webDisclaimer?.message); assert.equal(sut.webDisclaimer.enabled, testItem.webDisclaimer?.enabled); assert.isFalse(sut.watchDuty.enabled); assert.isFalse(sut.watchDuty.visible); }); it("defaults drones to disabled", async function () { const item = new models.Department(testItem); const sut = await item.save(); assert.isFalse(sut.drones.enabled); }); it("defaults drones.video to no sources and an empty web url", async function () { const item = new models.Department(testItem); const sut = await item.save(); assert.deepEqual(sut.drones.video.sources, []); assert.equal(sut.drones.video.urls.web, ""); }); it("saves drones.video sources and urls, with no subdocument _id", async function () { const item = new models.Department({ ...testItem, drones: { enabled: true, video: { sources: ["Skydio"], urls: { web: "https://video.example.com/player/{uuid}?token={token}" }, }, }, }); const sut = await item.save(); assert.deepEqual(sut.drones.video.sources, ["Skydio"]); assert.equal(sut.drones.video.urls.web, "https://video.example.com/player/{uuid}?token={token}"); assert.isUndefined((sut.drones.video as unknown as { _id?: unknown })._id); assert.isUndefined((sut.drones.video.urls as unknown as { _id?: unknown })._id); }); it("loads drones.video with defaults for a document saved without drones", async function () { const legacyItem = { ...testItem }; delete legacyItem.drones; const item = new models.Department(legacyItem); const sut = await item.save(); const loaded = await models.Department.findOne({ _id: sut._id }); assert.deepEqual(loaded?.drones.video.sources, []); assert.equal(loaded?.drones.video.urls.web, ""); }); it("defaults drones.providers to an empty array", async function () { const item = new models.Department(testItem); const sut = await item.save(); assert.deepEqual(sut.drones.providers, []); assert.isFalse(sut.drones.enabled); assert.deepEqual(sut.drones.video.sources, []); assert.equal(sut.drones.video.urls.web, ""); }); for (const provider of Object.values(m.DroneProvider)) { it(`saves drones.providers with ${provider}`, async function () { const item = new models.Department({ ...testItem, drones: { providers: [provider], }, }); const sut = await item.save(); assert.deepEqual(sut.drones.providers, [provider]); }); } it("saves every drones provider", async function () { const providers = Object.values(m.DroneProvider); const item = new models.Department({ ...testItem, drones: { providers, }, }); const sut = await item.save(); assert.deepEqual(sut.drones.providers, providers); }); it("lowercases drones.providers on save", async function () { const item = new models.Department({ ...testItem, drones: { providers: ["Skydio", "FLOCK"], }, }); const sut = await item.save(); assert.deepEqual(sut.drones.providers, [m.DroneProvider.Skydio, m.DroneProvider.Flock]); }); it("keeps a drones provider this build does not know", async function () { const legacyItem = { ...testItem, drones: { ...testItem.drones, providers: ["future-vendor"] } }; await models.Department.collection.insertOne(legacyItem); const loaded = await models.Department.findOne({ _id: testItem._id }); loaded!.department = "renamed"; await loaded!.save(); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.deepEqual(raw?.drones.providers, ["future-vendor"]); assert.equal(raw?.department, "renamed"); }); it("backfills drones.providers when a department stored without it is saved", async function () { const legacyItem = { ...testItem, drones: { enabled: true, video: { sources: [], urls: { web: "" } } } }; await models.Department.collection.insertOne(legacyItem); const loaded = await models.Department.findOne({ _id: testItem._id }); await loaded!.save(); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.deepEqual(raw?.drones.providers, []); assert.isTrue(raw?.drones.enabled); }); it("hydrates drones defaults for a department stored without drones", async function () { const legacyItem = { ...testItem }; delete legacyItem.drones; await models.Department.collection.insertOne(legacyItem); const lean = await models.Department.findOne({ _id: testItem._id }).lean(); assert.isNotNull(lean); assert.notProperty(lean, "drones"); const loaded = await models.Department.findOne({ _id: testItem._id }); assert.isFalse(loaded?.drones.enabled); assert.deepEqual(loaded?.drones.providers, []); assert.deepEqual(loaded?.drones.video.sources, []); assert.equal(loaded?.drones.video.urls.web, ""); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.isNotNull(raw); assert.notProperty(raw, "drones"); }); it("keeps drones.enabled and drones.video when providers are added", async function () { const item = new models.Department({ ...testItem, drones: { enabled: true, video: { sources: ["Skydio"], urls: { web: "https://video.example.com/player/{uuid}?token={token}" }, }, }, }); const saved = await item.save(); saved.drones.providers = [m.DroneProvider.Skydio, m.DroneProvider.Flock]; await saved.save(); const sut = await models.Department.findOne({ _id: testItem._id }); assert.deepEqual(sut?.drones.providers, [m.DroneProvider.Skydio, m.DroneProvider.Flock]); assert.isTrue(sut?.drones.enabled); assert.deepEqual(sut?.drones.video.sources, ["Skydio"]); assert.equal(sut?.drones.video.urls.web, "https://video.example.com/player/{uuid}?token={token}"); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.deepEqual(raw?.drones.providers, [m.DroneProvider.Skydio, m.DroneProvider.Flock]); assert.notProperty(raw?.drones, "_id"); assert.notProperty(raw?.drones.video, "_id"); }); it("sets drones.providers on a department stored without drones and writes no sibling defaults", async function () { const legacyItem = { ...testItem }; delete legacyItem.drones; await models.Department.collection.insertOne(legacyItem); await models.Department.findOneAndUpdate( { _id: testItem._id }, { $set: { "drones.providers": [m.DroneProvider.Skydio] } } ); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.deepEqual(raw?.drones, { providers: [m.DroneProvider.Skydio] }); const loaded = await models.Department.findOne({ _id: testItem._id }); assert.deepEqual(loaded?.drones.providers, [m.DroneProvider.Skydio]); assert.isFalse(loaded?.drones.enabled); assert.deepEqual(loaded?.drones.video.sources, []); }); it("saves samsara.tagIds and defaults it to an empty array", async function () { const item = new models.Department({ ...testItem, samsara: { ...testItem.samsara, tagIds: ["8013761", "9990001"], }, }); const sut = await item.save(); assert.deepEqual(sut.samsara.tagIds, ["8013761", "9990001"]); const legacySamsara: Partial = { ...testItem.samsara }; delete legacySamsara.tagIds; const legacyItem = new models.Department({ ...testItem, samsara: legacySamsara }); assert.deepEqual(legacyItem.samsara.tagIds, []); }); it("saves SOAP forwarding connection properties", async function () { // **Feature: eso-soap-incident-forwarding, Property 6: Configuration Validation** // **Validates: Requirements 3.2, 6.5** const soapConnection: m.ForwardingConnectionType = { active: true, connectionType: "eso", fields: [{ key: "IncidentNumber", value: "incidentNumber", required: true, enabled: true, transformationRequired: false, }], callTypes: ["FIRE"], forwardAll: false, apiUrl: "https://eso.example.com/soap", authType: "basic", authHeader: "", authUser: "user", authKey: "pass", authKeySecret: "", label: "ESO Service", description: "ESO SOAP service", timeout: 30000, namespace: "http://eso.namespace", soapVersion: "1.2" as const, customHeaders: { SOAPAction: "SubmitIncident" } }; const testItemWithForwarding: Partial = { ...testItem, forwarding: { enabled: true, connections: [soapConnection] } }; const item = new models.Department(testItemWithForwarding); const sut = await item.save(); // Validate SOAP-specific properties are saved correctly const connection = sut.forwarding.connections[0]; assert.isNotNull(connection); assert.isDefined(connection); assert.equal(connection.connectionType, "eso"); assert.equal(connection.timeout, 30000); assert.equal(connection.namespace, "http://eso.namespace"); assert.equal(connection.soapVersion, "1.2"); assert.instanceOf(connection.customHeaders, Map); assert.equal(connection.customHeaders.get("SOAPAction"), "SubmitIncident"); }); it("saves shareIncident.rulesV2", async function () { const item = new models.Department(testItem); const sut = await item.save(); assert.deepEqual(sut.shareIncident.rulesV2, []); const ruleAlways: ShareIncidentRuleTypeV2Always = { type: "Always", }; const ruleAssignUnit: ShareIncidentRuleTypeV2AssignedUnit = { type: "AssignedUnit", radioNames: ["E1", "BC1"], }; const ruleIncidentType: ShareIncidentRuleTypeV2IncidentType = { type: "IncidentType", callTypeDescription: ["A", "B"], }; const ruleReportNumber: ShareIncidentRuleTypeV2ReportNumber = { type: "ReportNumber", startsWith: [ { name: "A", number: "B", } ], }; const ruleArea: ShareIncidentRuleTypeV2Area = { type: "Area", area: [ { name: "A1", objectId: 1, area: { type: "Polygon", coordinates: [ [ [-67.13734, 45.13745], [-66.96466, 44.8097], [-67.79141, 45.70258], [-67.13734, 45.13745] ] ] } }, { name: "B1", objectId: 2, area: { type: "Polygon", coordinates: [ [ [-67.137343, 45.137451], [-66.96466, 44.8097], [-67.791412, 45.702585], [-67.137343, 45.137451] ] ] } }, ], }; const ruleComplexOR: ShareIncidentRuleTypeV2ComplexOR = { type: "ComplexOR", operands: [ ruleIncidentType, ruleReportNumber, ], }; const ruleComplexAND: ShareIncidentRuleTypeV2ComplexAND = { type: "ComplexAND", operands: [ ruleAssignUnit, ruleComplexOR, ], }; const sharedToDepartmentId = "6960c647c74ecd066a0a02c8"; const rule1: ShareIncidentRuleTypeV2 = { name: "Rule 1", accounts: [ { name: "A", departmentId: sharedToDepartmentId, } ], sendNotification: true, rules: [ ruleAlways, ruleArea, ruleAssignUnit, ruleComplexAND, ruleIncidentType, ruleReportNumber, ], }; sut.shareIncident.rulesV2 = [rule1]; const sut2 = await sut.save(); const dept1cfg = sut2.shareIncident.rulesV2.find((x) => { const sharesToX = x.accounts.find((y) => y.departmentId === sharedToDepartmentId); return sharesToX !== undefined; }); assert.isObject(dept1cfg); assert.equal(dept1cfg?.accounts.length, 1); assert.equal(dept1cfg?.rules.length, 6); assert.equal(dept1cfg?.sendNotification, true); // console.log(dept1cfg); const act1 = dept1cfg?.accounts?.find((x) => x.departmentId === sharedToDepartmentId); assert.isObject(act1); assert.equal(act1?.name, "A"); // Assert using eq const ruleAU = dept1cfg?.rules.find((x) => x.type === ruleAssignUnit.type); assert.isObject(ruleAU); assert.deepEqual(ruleAU?.radioNames, ruleAssignUnit.radioNames); const ruleIT = dept1cfg?.rules.find((x) => x.type === ruleIncidentType.type); assert.isObject(ruleIT); assert.deepEqual(ruleIT?.callTypeDescription, ruleIncidentType.callTypeDescription); const ruleRN = dept1cfg?.rules.find((x) => x.type === ruleReportNumber.type); assert.isObject(ruleRN); // JSON.stringify because otherwise the test fails assert.deepEqual(JSON.stringify(ruleRN?.startsWith), JSON.stringify(ruleReportNumber.startsWith)); const ruleA1 = dept1cfg?.rules.find((x) => x.type === ruleArea.type); assert.isObject(ruleA1); assert.deepEqual(JSON.stringify(ruleA1?.area), JSON.stringify(ruleArea.area)); const ruleC1 = dept1cfg?.rules.find((x) => x.type === ruleComplexAND.type); assert.isObject(ruleC1); assert.isArray(ruleC1?.operands); const ruleC1L = ruleC1?.operands.find((x) => x.type === ruleAssignUnit.type); assert.isObject(ruleC1L); assert.deepEqual(JSON.stringify(ruleC1L?.radioNames), JSON.stringify(ruleAssignUnit.radioNames)); const ruleC1R = ruleC1?.operands.find((x) => x.type === ruleComplexOR.type); assert.isObject(ruleC1R); assert.isArray(ruleC1R?.operands); const ruleC1R1 = ruleC1R?.operands.find((x) => x.type === ruleIncidentType.type); assert.isObject(ruleC1R1); assert.deepEqual(JSON.stringify(ruleC1R1?.callTypeDescription), JSON.stringify(ruleIncidentType.callTypeDescription)); const ruleC1R2 = ruleC1R?.operands.find((x) => x.type === ruleReportNumber.type); assert.isObject(ruleC1R2); assert.deepEqual(JSON.stringify(ruleC1R2?.startsWith), JSON.stringify(ruleReportNumber.startsWith)); const ruleAls = dept1cfg?.rules.find((x) => x.type === ruleAlways.type); assert.isObject(ruleAls); }); it("casts a drones.providers string into a one-item array", async function () { const item = new models.Department({ ...testItem, drones: { providers: m.DroneProvider.Skydio, }, }); const sut = await item.save(); assert.deepEqual(sut.drones.providers, [m.DroneProvider.Skydio]); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.deepEqual(raw?.drones.providers, [m.DroneProvider.Skydio]); }); it("keeps an unknown provider added with $addToSet through a later save", async function () { await new models.Department(testItem).save(); await models.Department.findOneAndUpdate( { _id: testItem._id }, { $addToSet: { "drones.providers": "future-vendor" } } ); const loaded = await models.Department.findOne({ _id: testItem._id }); loaded!.department = "renamed"; await loaded!.save(); const raw = await models.Department.collection.findOne({ _id: testItem._id }); assert.deepEqual(raw?.drones.providers, ["future-vendor"]); assert.equal(raw?.department, "renamed"); }); it("keeps the exported drones default empty when a saved department changes providers", async function () { const sut = await new models.Department(testItem).save(); sut.drones.providers?.push(m.DroneProvider.Flock); await sut.save(); assert.deepEqual(sut.drones.providers, [m.DroneProvider.Flock]); assert.deepEqual(DronesConfigurationDefault.providers, []); await models.Department.deleteOne({ _id: testItem._id }); const next = await new models.Department(testItem).save(); assert.deepEqual(next.drones.providers, []); }); });