import { ParsedNode, parseNode, parseNodesJson } from "./monitoringService"; import { Domain, MAC, OnlineState, Site, UnixTimestampSeconds } from "../types"; import Logger from "../logger"; import type { MockLogger } from "../__mocks__/logger"; import { now, parseTimestamp } from "../utils/time"; const mockedLogger = Logger as MockLogger; jest.mock("../logger"); jest.mock("../db/database"); const NODES_JSON_INVALID_VERSION = 1; const NODES_JSON_VALID_VERSION = 2; const TIMESTAMP_INVALID_STRING = "2020-01-02T42:99:23.000Z"; const TIMESTAMP_VALID_STRING = "2020-01-02T12:34:56.000Z"; const PARSED_TIMESTAMP_VALID = parseTimestamp(TIMESTAMP_VALID_STRING); if (PARSED_TIMESTAMP_VALID === null) { fail("Should not happen: Parsed valid timestamp as invalid."); } const TIMESTAMP_VALID: UnixTimestampSeconds = PARSED_TIMESTAMP_VALID; beforeEach(() => { mockedLogger.reset(); }); test("parseNode() should fail parsing node for empty node data", () => { // given const importTimestamp = now(); const nodeData = {}; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for empty node info", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: {}, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for non-string node id", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: 42, }, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for empty node id", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "", }, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for empty network info", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: {}, }, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for invalid mac", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "xxx", }, }, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for missing flags", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, }, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for empty flags", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, }, flags: {}, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for missing last seen timestamp", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, }, flags: { online: true, }, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should fail parsing node for invalid last seen timestamp", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, }, flags: { online: true, }, lastseen: 42, }; // then expect(() => parseNode(importTimestamp, nodeData)).toThrowError(); }); test("parseNode() should succeed parsing node without site and domain", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, }, flags: { online: true, }, lastseen: TIMESTAMP_VALID_STRING, }; // then const expectedParsedNode: ParsedNode = { mac: "12:34:56:78:90:AB" as MAC, importTimestamp: importTimestamp, state: OnlineState.ONLINE, lastSeen: TIMESTAMP_VALID, site: undefined, domain: undefined, }; expect(parseNode(importTimestamp, nodeData)).toEqual(expectedParsedNode); }); test("parseNode() should succeed parsing node with site and domain", () => { // given const importTimestamp = now(); const nodeData = { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, system: { site_code: "test-site", domain_code: "test-domain", }, }, flags: { online: true, }, lastseen: TIMESTAMP_VALID_STRING, }; // then const expectedParsedNode: ParsedNode = { mac: "12:34:56:78:90:AB" as MAC, importTimestamp: importTimestamp, state: OnlineState.ONLINE, lastSeen: TIMESTAMP_VALID, site: "test-site" as Site, domain: "test-domain" as Domain, }; expect(parseNode(importTimestamp, nodeData)).toEqual(expectedParsedNode); }); test("parseNodesJson() should fail parsing empty string", () => { // given const json = ""; // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing malformed JSON", () => { // given const json = '{"version": 2]'; // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing JSON null", () => { // given const json = JSON.stringify(null); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing JSON string", () => { // given const json = JSON.stringify("foo"); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing JSON number", () => { // given const json = JSON.stringify(42); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing empty JSON object", () => { // given const json = JSON.stringify({}); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing for mismatching version", () => { // given const json = JSON.stringify({ version: NODES_JSON_INVALID_VERSION, }); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing for missing timestamp", () => { // given const json = JSON.stringify({ version: NODES_JSON_VALID_VERSION, nodes: [], }); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing for invalid timestamp", () => { // given const json = JSON.stringify({ version: NODES_JSON_VALID_VERSION, timestamp: TIMESTAMP_INVALID_STRING, nodes: [], }); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should fail parsing for nodes object instead of array", () => { // given const json = JSON.stringify({ version: NODES_JSON_VALID_VERSION, timestamp: TIMESTAMP_VALID_STRING, nodes: {}, }); // then expect(() => parseNodesJson(json)).toThrowError(); }); test("parseNodesJson() should succeed parsing no nodes", () => { // given const json = JSON.stringify({ version: NODES_JSON_VALID_VERSION, timestamp: TIMESTAMP_VALID_STRING, nodes: [], }); // when const result = parseNodesJson(json); // then expect(result.nodes).toEqual([]); expect(result.failedNodesCount).toEqual(0); expect(result.totalNodesCount).toEqual(0); }); test("parseNodesJson() should skip parsing invalid nodes", () => { // given const json = JSON.stringify({ version: NODES_JSON_VALID_VERSION, timestamp: TIMESTAMP_VALID_STRING, nodes: [ {}, { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, system: { site_code: "test-site", domain_code: "test-domain", }, }, flags: { online: true, }, lastseen: TIMESTAMP_INVALID_STRING, }, ], }); // when const result = parseNodesJson(json); // then expect(result.nodes).toEqual([]); expect(result.failedNodesCount).toEqual(2); expect(result.totalNodesCount).toEqual(2); expect( mockedLogger.getMessages("error", "monitoring", "parsing-nodes-json") .length ).toEqual(2); }); test("parseNodesJson() should parse valid nodes", () => { // given const json = JSON.stringify({ version: NODES_JSON_VALID_VERSION, timestamp: TIMESTAMP_VALID_STRING, nodes: [ {}, // keep an invalid one for good measure { nodeinfo: { node_id: "1234567890ab", network: { mac: "12:34:56:78:90:ab", }, system: { site_code: "test-site", domain_code: "test-domain", }, }, flags: { online: true, }, lastseen: TIMESTAMP_VALID_STRING, }, ], }); // when const result = parseNodesJson(json); // then const expectedParsedNode: ParsedNode = { mac: "12:34:56:78:90:AB" as MAC, importTimestamp: TIMESTAMP_VALID, state: OnlineState.ONLINE, lastSeen: TIMESTAMP_VALID, site: "test-site" as Site, domain: "test-domain" as Domain, }; expect(result.nodes).toEqual([expectedParsedNode]); expect(result.failedNodesCount).toEqual(1); expect(result.totalNodesCount).toEqual(2); expect( mockedLogger.getMessages("error", "monitoring", "parsing-nodes-json") .length ).toEqual(1); });