@oneuptime/common 12.0.7 → 12.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/TelemetryEntity.ts +45 -0
- package/Models/DatabaseModels/TelemetryEntityRelationship.ts +26 -0
- package/Models/DatabaseModels/WorkflowLog.ts +38 -0
- package/Server/API/UserAPI.ts +16 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
- package/Server/Services/TelemetryEntityRelationshipService.ts +3 -0
- package/Server/Services/TelemetryEntityService.ts +114 -1
- package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
- package/Server/Types/Workflow/TriggerCode.ts +12 -0
- package/Server/Types/Workflow/Workflow.ts +5 -0
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
- package/Server/Utils/VM/VMAPI.ts +56 -8
- package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
- package/Tests/Server/Services/TelemetryEntityManualCreate.test.ts +248 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
- package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
- package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
- package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
- package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +379 -0
- package/Tests/Types/Workflow/Templates.test.ts +403 -0
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
- package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
- package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
- package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
- package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
- package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +821 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +287 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +185 -0
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +174 -0
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +152 -0
- package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
- package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +240 -0
- package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
- package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
- package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +31 -0
- package/Types/SerializableObjectDictionary.ts +133 -39
- package/Types/Telemetry/EntitySource.ts +40 -0
- package/Types/Telemetry/EntityType.ts +27 -0
- package/Types/Telemetry/EntityTypeGroups.ts +65 -0
- package/Types/Workflow/Component.ts +29 -0
- package/Types/Workflow/Components/API.ts +35 -0
- package/Types/Workflow/Components/BaseModel.ts +7 -3
- package/Types/Workflow/StepTrace.ts +181 -0
- package/Types/Workflow/TemplateSyntax.ts +499 -0
- package/Types/Workflow/Templates.ts +316 -0
- package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
- package/UI/Components/Dictionary/Dictionary.tsx +23 -6
- package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
- package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
- package/UI/Components/Forms/Types/Field.ts +7 -0
- package/UI/Components/Forms/Validation.ts +63 -1
- package/UI/Components/Input/Input.tsx +31 -3
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
- package/UI/Components/Modal/Modal.tsx +37 -4
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
- package/UI/Components/Workflow/ArgumentsForm.tsx +194 -7
- package/UI/Components/Workflow/Component.tsx +28 -26
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +69 -8
- package/UI/Components/Workflow/GraphLint.ts +591 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +396 -0
- package/UI/Components/Workflow/ModelSchema.ts +196 -0
- package/UI/Components/Workflow/RunForm.tsx +41 -7
- package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
- package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
- package/UI/Components/Workflow/Utils.ts +93 -1
- package/UI/Components/Workflow/Workflow.tsx +77 -4
- package/UI/Types/LayeredDismissal.ts +31 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +45 -7
- package/Utils/Telemetry/EntityKey.ts +66 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js +47 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js +27 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +16 -1
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +3 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +91 -1
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
- package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +133 -39
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +39 -0
- package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +27 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
- package/build/dist/Types/Workflow/Component.js +21 -0
- package/build/dist/Types/Workflow/Component.js.map +1 -1
- package/build/dist/Types/Workflow/Components/API.js +35 -0
- package/build/dist/Types/Workflow/Components/API.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +7 -3
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/Types/Workflow/StepTrace.js +104 -0
- package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js +326 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +218 -0
- package/build/dist/Types/Workflow/Templates.js.map +1 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/Dictionary.js +22 -6
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +44 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +12 -4
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +31 -5
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +140 -11
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +20 -19
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +31 -3
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLint.js +396 -0
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +225 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js +118 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
- package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
- package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Utils.js +73 -1
- package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +51 -6
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Types/LayeredDismissal.js +21 -0
- package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +35 -6
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityKey.js +52 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Importing an operator class FIRST is the whole point of this file.
|
|
3
|
+
*
|
|
4
|
+
* Includes, IncludesAll and IncludesNone import JSONFunctions (their fromJSON
|
|
5
|
+
* deserializes the values inside them), and JSONFunctions imports the
|
|
6
|
+
* dictionary that holds them — a cycle. While the dictionary was a plain
|
|
7
|
+
* object literal, whichever side of the cycle initialized second captured the
|
|
8
|
+
* other as `undefined`, so `{"_type":"Includes","value":[...]}` came back out
|
|
9
|
+
* of JSONFunctions.deserialize as a plain object instead of an Includes. A
|
|
10
|
+
* plain object reaching TypeORM is a silently wrong query rather than an
|
|
11
|
+
* error, which is exactly the kind of failure worth pinning down.
|
|
12
|
+
*
|
|
13
|
+
* Jest gives each test file its own module registry, so this file's import
|
|
14
|
+
* order genuinely reproduces the bad ordering.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import Includes from "../../Types/BaseDatabase/Includes";
|
|
18
|
+
import IncludesAll from "../../Types/BaseDatabase/IncludesAll";
|
|
19
|
+
import IncludesNone from "../../Types/BaseDatabase/IncludesNone";
|
|
20
|
+
import EqualTo from "../../Types/BaseDatabase/EqualTo";
|
|
21
|
+
import EqualToOrNull from "../../Types/BaseDatabase/EqualToOrNull";
|
|
22
|
+
import GreaterThan from "../../Types/BaseDatabase/GreaterThan";
|
|
23
|
+
import GreaterThanOrEqual from "../../Types/BaseDatabase/GreaterThanOrEqual";
|
|
24
|
+
import GreaterThanOrNull from "../../Types/BaseDatabase/GreaterThanOrNull";
|
|
25
|
+
import InBetween from "../../Types/BaseDatabase/InBetween";
|
|
26
|
+
import IsNull from "../../Types/BaseDatabase/IsNull";
|
|
27
|
+
import LessThan from "../../Types/BaseDatabase/LessThan";
|
|
28
|
+
import LessThanOrEqual from "../../Types/BaseDatabase/LessThanOrEqual";
|
|
29
|
+
import LessThanOrNull from "../../Types/BaseDatabase/LessThanOrNull";
|
|
30
|
+
import NotContains from "../../Types/BaseDatabase/NotContains";
|
|
31
|
+
import NotEqual from "../../Types/BaseDatabase/NotEqual";
|
|
32
|
+
import NotNull from "../../Types/BaseDatabase/NotNull";
|
|
33
|
+
import Search from "../../Types/BaseDatabase/Search";
|
|
34
|
+
import StartsWith from "../../Types/BaseDatabase/StartsWith";
|
|
35
|
+
import EndsWith from "../../Types/BaseDatabase/EndsWith";
|
|
36
|
+
import SerializableObjectDictionary from "../../Types/SerializableObjectDictionary";
|
|
37
|
+
import JSONFunctions from "../../Types/JSONFunctions";
|
|
38
|
+
import { JSONObject, ObjectType } from "../../Types/JSON";
|
|
39
|
+
import ObjectID from "../../Types/ObjectID";
|
|
40
|
+
import { describe, expect, test } from "@jest/globals";
|
|
41
|
+
|
|
42
|
+
describe("SerializableObjectDictionary registration", () => {
|
|
43
|
+
/*
|
|
44
|
+
* The regression itself. These three sit on the cycle, and this file imports
|
|
45
|
+
* them before the dictionary — the order that used to leave them undefined.
|
|
46
|
+
*/
|
|
47
|
+
test("the classes on the import cycle resolve", () => {
|
|
48
|
+
expect(SerializableObjectDictionary[ObjectType.Includes]).toBe(Includes);
|
|
49
|
+
expect(SerializableObjectDictionary[ObjectType.IncludesAll]).toBe(
|
|
50
|
+
IncludesAll,
|
|
51
|
+
);
|
|
52
|
+
expect(SerializableObjectDictionary[ObjectType.IncludesNone]).toBe(
|
|
53
|
+
IncludesNone,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("each entry resolves to the class it names", () => {
|
|
58
|
+
expect(SerializableObjectDictionary[ObjectType.EqualTo]).toBe(EqualTo);
|
|
59
|
+
expect(SerializableObjectDictionary[ObjectType.NotEqual]).toBe(NotEqual);
|
|
60
|
+
expect(SerializableObjectDictionary[ObjectType.Search]).toBe(Search);
|
|
61
|
+
expect(SerializableObjectDictionary[ObjectType.GreaterThan]).toBe(
|
|
62
|
+
GreaterThan,
|
|
63
|
+
);
|
|
64
|
+
expect(SerializableObjectDictionary[ObjectType.IsNull]).toBe(IsNull);
|
|
65
|
+
expect(SerializableObjectDictionary[ObjectType.NotNull]).toBe(NotNull);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("entries stay resolvable when read more than once", () => {
|
|
69
|
+
expect(SerializableObjectDictionary[ObjectType.Includes]).toBe(Includes);
|
|
70
|
+
expect(SerializableObjectDictionary[ObjectType.Includes]).toBe(Includes);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("an unknown type resolves to nothing rather than throwing", () => {
|
|
74
|
+
expect(SerializableObjectDictionary["NotAThing"]).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
type RoundTripFunction = (value: unknown) => unknown;
|
|
79
|
+
|
|
80
|
+
/** JSON.stringify -> JSON.parse -> deserialize, exactly as a request does. */
|
|
81
|
+
const roundTrip: RoundTripFunction = (value: unknown): unknown => {
|
|
82
|
+
const parsed: JSONObject = JSON.parse(JSON.stringify({ v: value }));
|
|
83
|
+
|
|
84
|
+
return (JSONFunctions.deserialize(parsed) as JSONObject)["v"];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
describe("query operators survive a serialize/deserialize round trip", () => {
|
|
88
|
+
test("Includes keeps its class and its values", () => {
|
|
89
|
+
const result: unknown = roundTrip(new Includes(["x", "y"]));
|
|
90
|
+
|
|
91
|
+
expect(result).toBeInstanceOf(Includes);
|
|
92
|
+
expect((result as Includes).values).toEqual(["x", "y"]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("IncludesNone keeps its class and its values", () => {
|
|
96
|
+
const result: unknown = roundTrip(new IncludesNone(["x"]));
|
|
97
|
+
|
|
98
|
+
expect(result).toBeInstanceOf(IncludesNone);
|
|
99
|
+
expect((result as IncludesNone).values).toEqual(["x"]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("IncludesAll keeps its class and its values", () => {
|
|
103
|
+
const result: unknown = roundTrip(new IncludesAll(["x", "y"]));
|
|
104
|
+
|
|
105
|
+
expect(result).toBeInstanceOf(IncludesAll);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("an empty membership list survives", () => {
|
|
109
|
+
const result: unknown = roundTrip(new Includes([]));
|
|
110
|
+
|
|
111
|
+
expect(result).toBeInstanceOf(Includes);
|
|
112
|
+
expect((result as Includes).values).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("ObjectIDs inside a membership list come back as ObjectIDs", () => {
|
|
116
|
+
const id: ObjectID = new ObjectID("11111111-1111-4111-8111-111111111111");
|
|
117
|
+
const result: unknown = roundTrip(new Includes([id]));
|
|
118
|
+
|
|
119
|
+
expect(result).toBeInstanceOf(Includes);
|
|
120
|
+
expect((result as Includes).values[0]).toBeInstanceOf(ObjectID);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("the comparison operators keep their class and value", () => {
|
|
124
|
+
expect(roundTrip(new NotEqual<string>("a"))).toBeInstanceOf(NotEqual);
|
|
125
|
+
expect(roundTrip(new EqualTo<string>("a"))).toBeInstanceOf(EqualTo);
|
|
126
|
+
expect(roundTrip(new EqualToOrNull<string>("a"))).toBeInstanceOf(
|
|
127
|
+
EqualToOrNull,
|
|
128
|
+
);
|
|
129
|
+
expect(roundTrip(new GreaterThan<number>(1))).toBeInstanceOf(GreaterThan);
|
|
130
|
+
expect(roundTrip(new GreaterThanOrEqual<number>(1))).toBeInstanceOf(
|
|
131
|
+
GreaterThanOrEqual,
|
|
132
|
+
);
|
|
133
|
+
expect(roundTrip(new LessThan<number>(1))).toBeInstanceOf(LessThan);
|
|
134
|
+
expect(roundTrip(new LessThanOrEqual<number>(1))).toBeInstanceOf(
|
|
135
|
+
LessThanOrEqual,
|
|
136
|
+
);
|
|
137
|
+
expect(roundTrip(new GreaterThanOrNull<number>(1))).toBeInstanceOf(
|
|
138
|
+
GreaterThanOrNull,
|
|
139
|
+
);
|
|
140
|
+
expect(roundTrip(new LessThanOrNull<number>(1))).toBeInstanceOf(
|
|
141
|
+
LessThanOrNull,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("the text operators keep their class", () => {
|
|
146
|
+
expect(roundTrip(new Search("a"))).toBeInstanceOf(Search);
|
|
147
|
+
expect(roundTrip(new NotContains("a"))).toBeInstanceOf(NotContains);
|
|
148
|
+
expect(roundTrip(new StartsWith("a"))).toBeInstanceOf(StartsWith);
|
|
149
|
+
expect(roundTrip(new EndsWith("a"))).toBeInstanceOf(EndsWith);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("the null checks keep their class", () => {
|
|
153
|
+
expect(roundTrip(new IsNull())).toBeInstanceOf(IsNull);
|
|
154
|
+
expect(roundTrip(new NotNull())).toBeInstanceOf(NotNull);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("InBetween keeps both of its bounds", () => {
|
|
158
|
+
const result: unknown = roundTrip(new InBetween<number>(1, 5));
|
|
159
|
+
|
|
160
|
+
expect(result).toBeInstanceOf(InBetween);
|
|
161
|
+
expect((result as InBetween<number>).startValue).toBe(1);
|
|
162
|
+
expect((result as InBetween<number>).endValue).toBe(5);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a numeric comparison keeps its number, not a string of it", () => {
|
|
166
|
+
const result: unknown = roundTrip(new GreaterThan<number>(42));
|
|
167
|
+
|
|
168
|
+
expect((result as GreaterThan<number>).value).toBe(42);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("plain values are left exactly as they are", () => {
|
|
172
|
+
expect(roundTrip("plain")).toBe("plain");
|
|
173
|
+
expect(roundTrip(7)).toBe(7);
|
|
174
|
+
expect(roundTrip(true)).toBe(true);
|
|
175
|
+
expect(roundTrip(null)).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("a whole query of mixed operators deserializes together", () => {
|
|
179
|
+
const query: JSONObject = JSON.parse(
|
|
180
|
+
JSON.stringify({
|
|
181
|
+
name: new Search("acme"),
|
|
182
|
+
tags: new Includes(["a", "b"]),
|
|
183
|
+
count: new GreaterThan<number>(3),
|
|
184
|
+
deletedAt: new IsNull(),
|
|
185
|
+
isEnabled: true,
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const result: JSONObject = JSONFunctions.deserialize(query) as JSONObject;
|
|
190
|
+
|
|
191
|
+
expect(result["name"]).toBeInstanceOf(Search);
|
|
192
|
+
expect(result["tags"]).toBeInstanceOf(Includes);
|
|
193
|
+
expect(result["count"]).toBeInstanceOf(GreaterThan);
|
|
194
|
+
expect(result["deletedAt"]).toBeInstanceOf(IsNull);
|
|
195
|
+
expect(result["isEnabled"]).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import EntityType from "../../../Types/Telemetry/EntityType";
|
|
2
|
+
import EntitySource from "../../../Types/Telemetry/EntitySource";
|
|
3
|
+
import {
|
|
4
|
+
INVENTORY_ENTITY_TYPES,
|
|
5
|
+
isNonTelemetryEntityType,
|
|
6
|
+
MANUAL_ENTITY_TYPES,
|
|
7
|
+
} from "../../../Types/Telemetry/EntityTypeGroups";
|
|
8
|
+
import { describe, expect, test } from "@jest/globals";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* These sets decide which entity types the stale-entity prune may reach and
|
|
12
|
+
* which a user may create by hand. Both questions are answered by set
|
|
13
|
+
* membership, so a type that drifts out of (or silently into) a set changes
|
|
14
|
+
* behaviour with no other code change — which is exactly what these pin.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
describe("MANUAL_ENTITY_TYPES", () => {
|
|
18
|
+
test("is non-empty", () => {
|
|
19
|
+
expect(MANUAL_ENTITY_TYPES.size).toBeGreaterThan(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("contains only real EntityType values", () => {
|
|
23
|
+
const known: Set<string> = new Set<string>(Object.values(EntityType));
|
|
24
|
+
for (const entityType of MANUAL_ENTITY_TYPES) {
|
|
25
|
+
expect(known.has(entityType)).toBe(true);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("holds exactly the types with no discovery path", () => {
|
|
30
|
+
expect(Array.from(MANUAL_ENTITY_TYPES).sort()).toEqual(
|
|
31
|
+
[
|
|
32
|
+
EntityType.Appliance,
|
|
33
|
+
EntityType.ExternalDatabase,
|
|
34
|
+
EntityType.ExternalService,
|
|
35
|
+
].sort(),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("excludes observable types", () => {
|
|
40
|
+
/*
|
|
41
|
+
* A hand-made row of an observable type would key off the manual
|
|
42
|
+
* identity attribute and so could never converge with the row ingest
|
|
43
|
+
* derives for the same thing. See MANUAL_ENTITY_TYPES.
|
|
44
|
+
*/
|
|
45
|
+
expect(MANUAL_ENTITY_TYPES.has(EntityType.Service)).toBe(false);
|
|
46
|
+
expect(MANUAL_ENTITY_TYPES.has(EntityType.Host)).toBe(false);
|
|
47
|
+
expect(MANUAL_ENTITY_TYPES.has(EntityType.KubernetesPod)).toBe(false);
|
|
48
|
+
expect(MANUAL_ENTITY_TYPES.has(EntityType.NetworkDevice)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("INVENTORY_ENTITY_TYPES", () => {
|
|
53
|
+
test("contains only real EntityType values", () => {
|
|
54
|
+
const known: Set<string> = new Set<string>(Object.values(EntityType));
|
|
55
|
+
for (const entityType of INVENTORY_ENTITY_TYPES) {
|
|
56
|
+
expect(known.has(entityType)).toBe(true);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("covers every inventory table the registry mirrors", () => {
|
|
61
|
+
expect(Array.from(INVENTORY_ENTITY_TYPES).sort()).toEqual(
|
|
62
|
+
[
|
|
63
|
+
EntityType.CloudResource,
|
|
64
|
+
EntityType.DockerHost,
|
|
65
|
+
EntityType.IoTDevice,
|
|
66
|
+
EntityType.NetworkDevice,
|
|
67
|
+
EntityType.PodmanHost,
|
|
68
|
+
EntityType.RumApplication,
|
|
69
|
+
EntityType.ServerlessFunction,
|
|
70
|
+
].sort(),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("excludes OTLP-derived types", () => {
|
|
75
|
+
expect(INVENTORY_ENTITY_TYPES.has(EntityType.Service)).toBe(false);
|
|
76
|
+
expect(INVENTORY_ENTITY_TYPES.has(EntityType.KubernetesCluster)).toBe(
|
|
77
|
+
false,
|
|
78
|
+
);
|
|
79
|
+
expect(INVENTORY_ENTITY_TYPES.has(EntityType.ProxmoxGuest)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("the two groups are disjoint", () => {
|
|
84
|
+
test("no type is both manually creatable and inventory-mirrored", () => {
|
|
85
|
+
for (const entityType of MANUAL_ENTITY_TYPES) {
|
|
86
|
+
expect(INVENTORY_ENTITY_TYPES.has(entityType)).toBe(false);
|
|
87
|
+
}
|
|
88
|
+
for (const entityType of INVENTORY_ENTITY_TYPES) {
|
|
89
|
+
expect(MANUAL_ENTITY_TYPES.has(entityType)).toBe(false);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("isNonTelemetryEntityType", () => {
|
|
95
|
+
test("is true for every manual and inventory type", () => {
|
|
96
|
+
for (const entityType of MANUAL_ENTITY_TYPES) {
|
|
97
|
+
expect(isNonTelemetryEntityType(entityType)).toBe(true);
|
|
98
|
+
}
|
|
99
|
+
for (const entityType of INVENTORY_ENTITY_TYPES) {
|
|
100
|
+
expect(isNonTelemetryEntityType(entityType)).toBe(true);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("is false for OTLP-derived types, which do have a heartbeat", () => {
|
|
105
|
+
const telemetryTypes: Array<EntityType> = Object.values(EntityType).filter(
|
|
106
|
+
(entityType: EntityType) => {
|
|
107
|
+
return (
|
|
108
|
+
!MANUAL_ENTITY_TYPES.has(entityType) &&
|
|
109
|
+
!INVENTORY_ENTITY_TYPES.has(entityType)
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(telemetryTypes.length).toBeGreaterThan(0);
|
|
115
|
+
|
|
116
|
+
for (const entityType of telemetryTypes) {
|
|
117
|
+
expect(isNonTelemetryEntityType(entityType)).toBe(false);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("every EntityType is classified one way or the other", () => {
|
|
122
|
+
for (const entityType of Object.values(EntityType)) {
|
|
123
|
+
expect(typeof isNonTelemetryEntityType(entityType)).toBe("boolean");
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("EntitySource", () => {
|
|
129
|
+
test("has exactly the three lifecycles the registry distinguishes", () => {
|
|
130
|
+
expect(Object.values(EntitySource).sort()).toEqual(
|
|
131
|
+
["discovered", "inventory", "manual"].sort(),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("values are stable strings — they are persisted and queried on", () => {
|
|
136
|
+
expect(EntitySource.Discovered).toBe("discovered");
|
|
137
|
+
expect(EntitySource.Inventory).toBe("inventory");
|
|
138
|
+
expect(EntitySource.Manual).toBe("manual");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_TRACE_STEPS,
|
|
3
|
+
MAX_TRACE_VALUE_LENGTH,
|
|
4
|
+
TRUNCATED_VALUE_SUFFIX,
|
|
5
|
+
WorkflowStepStatus,
|
|
6
|
+
WorkflowStepTrace,
|
|
7
|
+
WorkflowStepTraceEntry,
|
|
8
|
+
appendTraceStep,
|
|
9
|
+
emptyTrace,
|
|
10
|
+
parseTrace,
|
|
11
|
+
truncateTraceValue,
|
|
12
|
+
truncateTraceValues,
|
|
13
|
+
} from "../../../Types/Workflow/StepTrace";
|
|
14
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
15
|
+
import { describe, expect, test } from "@jest/globals";
|
|
16
|
+
|
|
17
|
+
type MakeEntryFunction = (
|
|
18
|
+
overrides?: Partial<WorkflowStepTraceEntry> | undefined,
|
|
19
|
+
) => WorkflowStepTraceEntry;
|
|
20
|
+
|
|
21
|
+
const makeEntry: MakeEntryFunction = (
|
|
22
|
+
overrides?: Partial<WorkflowStepTraceEntry> | undefined,
|
|
23
|
+
): WorkflowStepTraceEntry => {
|
|
24
|
+
return {
|
|
25
|
+
componentId: "api-get-1",
|
|
26
|
+
metadataId: "api-get",
|
|
27
|
+
title: "API Get",
|
|
28
|
+
status: WorkflowStepStatus.Success,
|
|
29
|
+
startedAt: "2026-01-01T00:00:00.000Z",
|
|
30
|
+
completedAt: "2026-01-01T00:00:01.000Z",
|
|
31
|
+
durationInMs: 1000,
|
|
32
|
+
argumentValues: {},
|
|
33
|
+
returnValues: {},
|
|
34
|
+
executedPort: "success",
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("truncateTraceValue", () => {
|
|
40
|
+
test("leaves a short string alone", () => {
|
|
41
|
+
expect(truncateTraceValue("hello")).toBe("hello");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("leaves a string exactly at the limit alone", () => {
|
|
45
|
+
const atLimit: string = "a".repeat(MAX_TRACE_VALUE_LENGTH);
|
|
46
|
+
|
|
47
|
+
expect(truncateTraceValue(atLimit)).toBe(atLimit);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("cuts a string past the limit and says so", () => {
|
|
51
|
+
const tooLong: string = "a".repeat(MAX_TRACE_VALUE_LENGTH + 100);
|
|
52
|
+
const result: string = truncateTraceValue(tooLong) as string;
|
|
53
|
+
|
|
54
|
+
expect(result.endsWith(TRUNCATED_VALUE_SUFFIX)).toBe(true);
|
|
55
|
+
expect(result.length).toBe(
|
|
56
|
+
MAX_TRACE_VALUE_LENGTH + TRUNCATED_VALUE_SUFFIX.length,
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("leaves small structured values as structure, not text", () => {
|
|
61
|
+
const value: JSONObject = { a: 1, b: "two" };
|
|
62
|
+
|
|
63
|
+
expect(truncateTraceValue(value)).toEqual(value);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* Half a serialized object looks like data but cannot be parsed, so a large
|
|
68
|
+
* object is replaced wholesale rather than cut in the middle.
|
|
69
|
+
*/
|
|
70
|
+
test("replaces a large object with cut text rather than half an object", () => {
|
|
71
|
+
const big: JSONObject = { body: "x".repeat(MAX_TRACE_VALUE_LENGTH + 500) };
|
|
72
|
+
const result: unknown = truncateTraceValue(big);
|
|
73
|
+
|
|
74
|
+
expect(typeof result).toBe("string");
|
|
75
|
+
expect((result as string).endsWith(TRUNCATED_VALUE_SUFFIX)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("leaves primitives of other types alone", () => {
|
|
79
|
+
expect(truncateTraceValue(42)).toBe(42);
|
|
80
|
+
expect(truncateTraceValue(true)).toBe(true);
|
|
81
|
+
expect(truncateTraceValue(null)).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("says so rather than throwing on a circular value", () => {
|
|
85
|
+
const circular: JSONObject = {};
|
|
86
|
+
(circular as unknown as { self: unknown }).self = circular;
|
|
87
|
+
|
|
88
|
+
expect(truncateTraceValue(circular)).toBe("[value could not be recorded]");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("keeps a large array as text rather than dropping it", () => {
|
|
92
|
+
const bigArray: Array<string> = new Array(2000).fill("abcdefghij");
|
|
93
|
+
const result: unknown = truncateTraceValue(
|
|
94
|
+
bigArray as unknown as JSONObject,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(typeof result).toBe("string");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("truncateTraceValues", () => {
|
|
102
|
+
test("truncates each value independently", () => {
|
|
103
|
+
const values: JSONObject = {
|
|
104
|
+
small: "ok",
|
|
105
|
+
big: "x".repeat(MAX_TRACE_VALUE_LENGTH + 10),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result: JSONObject = truncateTraceValues(values);
|
|
109
|
+
|
|
110
|
+
expect(result["small"]).toBe("ok");
|
|
111
|
+
expect((result["big"] as string).endsWith(TRUNCATED_VALUE_SUFFIX)).toBe(
|
|
112
|
+
true,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("copes with an empty object", () => {
|
|
117
|
+
expect(truncateTraceValues({})).toEqual({});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("keeps a redaction marker exactly as it is", () => {
|
|
121
|
+
expect(truncateTraceValues({ token: "[REDACTED]" })["token"]).toBe(
|
|
122
|
+
"[REDACTED]",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("appendTraceStep", () => {
|
|
128
|
+
test("adds a step to an empty trace", () => {
|
|
129
|
+
const trace: WorkflowStepTrace = appendTraceStep(emptyTrace(), makeEntry());
|
|
130
|
+
|
|
131
|
+
expect(trace.steps).toHaveLength(1);
|
|
132
|
+
expect(trace.truncated).toBeFalsy();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("keeps steps in the order they ran", () => {
|
|
136
|
+
let trace: WorkflowStepTrace = emptyTrace();
|
|
137
|
+
|
|
138
|
+
trace = appendTraceStep(trace, makeEntry({ componentId: "first" }));
|
|
139
|
+
trace = appendTraceStep(trace, makeEntry({ componentId: "second" }));
|
|
140
|
+
|
|
141
|
+
expect(
|
|
142
|
+
trace.steps.map((step: WorkflowStepTraceEntry) => {
|
|
143
|
+
return step.componentId;
|
|
144
|
+
}),
|
|
145
|
+
).toEqual(["first", "second"]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("does not mutate the trace it was given", () => {
|
|
149
|
+
const original: WorkflowStepTrace = emptyTrace();
|
|
150
|
+
|
|
151
|
+
appendTraceStep(original, makeEntry());
|
|
152
|
+
|
|
153
|
+
expect(original.steps).toHaveLength(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("holds exactly the cap without flagging truncation", () => {
|
|
157
|
+
let trace: WorkflowStepTrace = emptyTrace();
|
|
158
|
+
|
|
159
|
+
for (let i: number = 0; i < MAX_TRACE_STEPS; i++) {
|
|
160
|
+
trace = appendTraceStep(trace, makeEntry({ componentId: `step-${i}` }));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
expect(trace.steps).toHaveLength(MAX_TRACE_STEPS);
|
|
164
|
+
expect(trace.truncated).toBeFalsy();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* A failed run is read from the end — the step that broke it is the last
|
|
169
|
+
* one — so the cap drops the oldest steps, not the newest.
|
|
170
|
+
*/
|
|
171
|
+
test("drops the oldest steps past the cap and keeps the newest", () => {
|
|
172
|
+
let trace: WorkflowStepTrace = emptyTrace();
|
|
173
|
+
|
|
174
|
+
for (let i: number = 0; i < MAX_TRACE_STEPS + 5; i++) {
|
|
175
|
+
trace = appendTraceStep(trace, makeEntry({ componentId: `step-${i}` }));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
expect(trace.steps).toHaveLength(MAX_TRACE_STEPS);
|
|
179
|
+
expect(trace.truncated).toBe(true);
|
|
180
|
+
expect(trace.steps[trace.steps.length - 1]?.componentId).toBe(
|
|
181
|
+
`step-${MAX_TRACE_STEPS + 4}`,
|
|
182
|
+
);
|
|
183
|
+
expect(trace.steps[0]?.componentId).toBe("step-5");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("stays flagged as truncated once it has been", () => {
|
|
187
|
+
let trace: WorkflowStepTrace = emptyTrace();
|
|
188
|
+
|
|
189
|
+
for (let i: number = 0; i < MAX_TRACE_STEPS + 2; i++) {
|
|
190
|
+
trace = appendTraceStep(trace, makeEntry());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
trace = appendTraceStep(trace, makeEntry());
|
|
194
|
+
|
|
195
|
+
expect(trace.truncated).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("parseTrace", () => {
|
|
200
|
+
test("reads back what was written", () => {
|
|
201
|
+
const trace: WorkflowStepTrace = appendTraceStep(
|
|
202
|
+
emptyTrace(),
|
|
203
|
+
makeEntry({ componentId: "log-1" }),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const roundTripped: WorkflowStepTrace = parseTrace(
|
|
207
|
+
JSON.parse(JSON.stringify(trace)),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(roundTripped.steps).toHaveLength(1);
|
|
211
|
+
expect(roundTripped.steps[0]?.componentId).toBe("log-1");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("carries the truncated flag back", () => {
|
|
215
|
+
expect(parseTrace({ steps: [], truncated: true }).truncated).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* Rows written before the column existed, or by an older build, must read as
|
|
220
|
+
* an empty trace so the viewer falls back to the raw log instead of throwing.
|
|
221
|
+
*/
|
|
222
|
+
test("treats anything that is not a trace as empty", () => {
|
|
223
|
+
expect(parseTrace(null).steps).toEqual([]);
|
|
224
|
+
expect(parseTrace(undefined as never).steps).toEqual([]);
|
|
225
|
+
expect(parseTrace("nonsense").steps).toEqual([]);
|
|
226
|
+
expect(parseTrace(42).steps).toEqual([]);
|
|
227
|
+
expect(parseTrace([]).steps).toEqual([]);
|
|
228
|
+
expect(parseTrace({}).steps).toEqual([]);
|
|
229
|
+
expect(parseTrace({ steps: "not an array" }).steps).toEqual([]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("an empty trace is not flagged truncated", () => {
|
|
233
|
+
expect(parseTrace(null).truncated).toBeFalsy();
|
|
234
|
+
});
|
|
235
|
+
});
|