@oneuptime/common 12.0.1 → 12.0.2
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/Host.ts +12 -4
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +47 -5
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +47 -5
- package/Server/Services/DatabaseService.ts +87 -6
- package/Server/Services/DockerHostService.ts +47 -5
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +48 -5
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +48 -5
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +47 -5
- package/Server/Services/KubernetesClusterService.ts +48 -5
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +106 -1
- package/Server/Services/MonitorService.ts +57 -0
- package/Server/Services/PodmanHostService.ts +47 -5
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +47 -5
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +48 -5
- package/Server/Services/ServerlessFunctionService.ts +48 -5
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Date.ts +112 -0
- package/Types/Events/Recurring.ts +99 -1
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +42 -5
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +42 -5
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +69 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +42 -5
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +43 -5
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +42 -5
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +92 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +47 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +42 -5
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +43 -5
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import CephClusterService from "../../../Server/Services/CephClusterService";
|
|
2
|
+
import CloudResourceService from "../../../Server/Services/CloudResourceService";
|
|
3
|
+
import DockerHostService from "../../../Server/Services/DockerHostService";
|
|
4
|
+
import DockerSwarmClusterService from "../../../Server/Services/DockerSwarmClusterService";
|
|
5
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
6
|
+
import HostService from "../../../Server/Services/HostService";
|
|
7
|
+
import IoTFleetService from "../../../Server/Services/IoTFleetService";
|
|
8
|
+
import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
|
|
9
|
+
import PodmanHostService from "../../../Server/Services/PodmanHostService";
|
|
10
|
+
import ProxmoxClusterService from "../../../Server/Services/ProxmoxClusterService";
|
|
11
|
+
import RumApplicationService from "../../../Server/Services/RumApplicationService";
|
|
12
|
+
import ServerlessFunctionService from "../../../Server/Services/ServerlessFunctionService";
|
|
13
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
14
|
+
import {
|
|
15
|
+
afterEach,
|
|
16
|
+
beforeEach,
|
|
17
|
+
describe,
|
|
18
|
+
expect,
|
|
19
|
+
jest,
|
|
20
|
+
test,
|
|
21
|
+
} from "@jest/globals";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Issue #3006 was reported against Host, but HostService.updateLastSeen was
|
|
25
|
+
* never the only service shaped that way. Ten siblings write the same two
|
|
26
|
+
* liveness columns (lastSeenAt + otelCollectorStatus) in the SAME hook-free
|
|
27
|
+
* UPDATE as a handful of optional columns harvested from OpenTelemetry
|
|
28
|
+
* resource attributes — and each has its own markDisconnected* job that
|
|
29
|
+
* flips the resource to "disconnected" 15 minutes after lastSeenAt stops
|
|
30
|
+
* advancing.
|
|
31
|
+
*
|
|
32
|
+
* The clamp that stops a Postgres rejection now lives inside
|
|
33
|
+
* updateColumnsByIdWithoutHooks (see HookFreeWriteLengthClamp.test.ts), so
|
|
34
|
+
* the statement should not fail in the first place. This suite pins the
|
|
35
|
+
* SECOND, independent guard, per service: if the enriched write fails
|
|
36
|
+
* ANYWAY — for any reason at all, a constraint nobody predicted, a column
|
|
37
|
+
* type nobody clamped — the liveness columns are retried on their own.
|
|
38
|
+
* Enrichment is what gets dropped, never the heartbeat.
|
|
39
|
+
*
|
|
40
|
+
* Also pinned per service: the throttle that keeps the steady state cheap
|
|
41
|
+
* must not swallow the retry, and a Redis outage must fail OPEN. Marking a
|
|
42
|
+
* live resource disconnected because the cache went down would be the same
|
|
43
|
+
* bug wearing a different hat.
|
|
44
|
+
*
|
|
45
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
type WriteCall = { id: ObjectID; data: Record<string, unknown> };
|
|
49
|
+
|
|
50
|
+
type ServiceCase = {
|
|
51
|
+
/** Name of the service, used as the describe() title. */
|
|
52
|
+
name: string;
|
|
53
|
+
service: {
|
|
54
|
+
updateLastSeen(id: ObjectID, extra?: never): Promise<void>;
|
|
55
|
+
updateColumnsByIdWithoutHooks(input: {
|
|
56
|
+
id: ObjectID;
|
|
57
|
+
data: unknown;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
};
|
|
60
|
+
/** A metadata payload this service accepts, and a different one. */
|
|
61
|
+
extra: Record<string, unknown>;
|
|
62
|
+
otherExtra: Record<string, unknown>;
|
|
63
|
+
/** A string column whose value the collector supplies. */
|
|
64
|
+
oversizedExtra: Record<string, unknown>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Longer than any bounded column on any of these models. */
|
|
68
|
+
const OVERSIZED: string = "x".repeat(5000);
|
|
69
|
+
|
|
70
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
71
|
+
const SERVICE_CASES: Array<ServiceCase> = [
|
|
72
|
+
{
|
|
73
|
+
name: "DockerHostService",
|
|
74
|
+
service: DockerHostService as any,
|
|
75
|
+
extra: { osType: "linux" },
|
|
76
|
+
otherExtra: { osType: "windows" },
|
|
77
|
+
oversizedExtra: { osVersion: OVERSIZED },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "PodmanHostService",
|
|
81
|
+
service: PodmanHostService as any,
|
|
82
|
+
extra: { osType: "linux" },
|
|
83
|
+
otherExtra: { osType: "windows" },
|
|
84
|
+
oversizedExtra: { osVersion: OVERSIZED },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "KubernetesClusterService",
|
|
88
|
+
service: KubernetesClusterService as any,
|
|
89
|
+
extra: { agentVersion: "1.2.3" },
|
|
90
|
+
otherExtra: { agentVersion: "1.2.4" },
|
|
91
|
+
oversizedExtra: { agentVersion: OVERSIZED },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "ProxmoxClusterService",
|
|
95
|
+
service: ProxmoxClusterService as any,
|
|
96
|
+
extra: { pveVersion: "8.1.4" },
|
|
97
|
+
otherExtra: { pveVersion: "8.2.0" },
|
|
98
|
+
oversizedExtra: { pveVersion: OVERSIZED },
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "IoTFleetService",
|
|
102
|
+
service: IoTFleetService as any,
|
|
103
|
+
extra: { agentVersion: "1.2.3" },
|
|
104
|
+
otherExtra: { agentVersion: "1.2.4" },
|
|
105
|
+
oversizedExtra: { agentVersion: OVERSIZED },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "DockerSwarmClusterService",
|
|
109
|
+
service: DockerSwarmClusterService as any,
|
|
110
|
+
extra: { dockerVersion: "25.0.3" },
|
|
111
|
+
otherExtra: { dockerVersion: "26.0.0" },
|
|
112
|
+
oversizedExtra: { swarmId: OVERSIZED },
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "CephClusterService",
|
|
116
|
+
service: CephClusterService as any,
|
|
117
|
+
extra: { cephVersion: "18.2.2" },
|
|
118
|
+
otherExtra: { cephVersion: "19.0.0" },
|
|
119
|
+
oversizedExtra: { fsid: OVERSIZED },
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "ServerlessFunctionService",
|
|
123
|
+
service: ServerlessFunctionService as any,
|
|
124
|
+
extra: { runtimeName: "nodejs" },
|
|
125
|
+
otherExtra: { runtimeName: "python" },
|
|
126
|
+
oversizedExtra: { functionVersion: OVERSIZED },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "CloudResourceService",
|
|
130
|
+
service: CloudResourceService as any,
|
|
131
|
+
extra: { cloudProvider: "aws" },
|
|
132
|
+
otherExtra: { cloudProvider: "gcp" },
|
|
133
|
+
oversizedExtra: { cloudRegion: OVERSIZED },
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "RumApplicationService",
|
|
137
|
+
service: RumApplicationService as any,
|
|
138
|
+
extra: { sdkLanguage: "webjs" },
|
|
139
|
+
otherExtra: { sdkLanguage: "swift" },
|
|
140
|
+
oversizedExtra: { clientType: OVERSIZED },
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
/*
|
|
144
|
+
* Host rides the same table so the eleven paths stay pinned together —
|
|
145
|
+
* its own richer regression coverage lives in
|
|
146
|
+
* HostServiceUpdateLastSeen.test.ts.
|
|
147
|
+
*/
|
|
148
|
+
name: "HostService",
|
|
149
|
+
service: HostService as any,
|
|
150
|
+
extra: { osType: "linux" },
|
|
151
|
+
otherExtra: { osType: "windows" },
|
|
152
|
+
oversizedExtra: { hostId: OVERSIZED },
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
156
|
+
|
|
157
|
+
describe.each(SERVICE_CASES)(
|
|
158
|
+
"$name.updateLastSeen",
|
|
159
|
+
({ service, extra, otherExtra, oversizedExtra }: ServiceCase) => {
|
|
160
|
+
let writes: Array<WriteCall>;
|
|
161
|
+
let cache: Map<string, string>;
|
|
162
|
+
let deletedCacheKeys: Array<string>;
|
|
163
|
+
|
|
164
|
+
const RESOURCE_ID: ObjectID = ObjectID.generate();
|
|
165
|
+
|
|
166
|
+
/** Records every hook-free UPDATE instead of issuing it. */
|
|
167
|
+
function mockWritesSucceeding(): void {
|
|
168
|
+
jest
|
|
169
|
+
.spyOn(service, "updateColumnsByIdWithoutHooks")
|
|
170
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
171
|
+
writes.push({
|
|
172
|
+
id: input.id,
|
|
173
|
+
data: { ...(input.data as Record<string, unknown>) },
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Fails any write carrying more than the two liveness columns — the
|
|
180
|
+
* shape of a Postgres rejection caused by one bad optional column.
|
|
181
|
+
*/
|
|
182
|
+
function mockEnrichedWriteFailing(): void {
|
|
183
|
+
jest
|
|
184
|
+
.spyOn(service, "updateColumnsByIdWithoutHooks")
|
|
185
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
186
|
+
const data: Record<string, unknown> = {
|
|
187
|
+
...(input.data as Record<string, unknown>),
|
|
188
|
+
};
|
|
189
|
+
writes.push({ id: input.id, data: data });
|
|
190
|
+
if (Object.keys(data).length > 2) {
|
|
191
|
+
throw new Error("value too long for type character varying(100)");
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function lastWrite(): Record<string, unknown> {
|
|
197
|
+
return writes[writes.length - 1]!.data;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
writes = [];
|
|
202
|
+
cache = new Map<string, string>();
|
|
203
|
+
deletedCacheKeys = [];
|
|
204
|
+
|
|
205
|
+
jest
|
|
206
|
+
.spyOn(GlobalCache, "getString")
|
|
207
|
+
.mockImplementation(async (namespace: string, key: string) => {
|
|
208
|
+
return cache.get(`${namespace}:${key}`) ?? null;
|
|
209
|
+
});
|
|
210
|
+
jest
|
|
211
|
+
.spyOn(GlobalCache, "setString")
|
|
212
|
+
.mockImplementation(
|
|
213
|
+
async (namespace: string, key: string, value: string) => {
|
|
214
|
+
cache.set(`${namespace}:${key}`, value);
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
jest
|
|
218
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
219
|
+
.mockImplementation(async (namespace: string, key: string) => {
|
|
220
|
+
deletedCacheKeys.push(`${namespace}:${key}`);
|
|
221
|
+
cache.delete(`${namespace}:${key}`);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
afterEach(() => {
|
|
226
|
+
jest.restoreAllMocks();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("liveness columns", () => {
|
|
230
|
+
beforeEach(() => {
|
|
231
|
+
mockWritesSucceeding();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("always writes lastSeenAt and marks the collector connected", async () => {
|
|
235
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
236
|
+
|
|
237
|
+
expect(writes).toHaveLength(1);
|
|
238
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
239
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("writes to the resource it was given", async () => {
|
|
243
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
244
|
+
|
|
245
|
+
expect(writes[0]!.id.toString()).toBe(RESOURCE_ID.toString());
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("skips the write when the same metadata was written recently", async () => {
|
|
249
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
250
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
251
|
+
|
|
252
|
+
expect(writes).toHaveLength(1);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("writes again as soon as any metadata changes", async () => {
|
|
256
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
257
|
+
await service.updateLastSeen(RESOURCE_ID, otherExtra as never);
|
|
258
|
+
|
|
259
|
+
expect(writes).toHaveLength(2);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("passes collector metadata through to the write path", async () => {
|
|
263
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
264
|
+
|
|
265
|
+
for (const [column, value] of Object.entries(extra)) {
|
|
266
|
+
expect(lastWrite()[column]).toBe(value);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("fallback when the enriched write fails", () => {
|
|
272
|
+
beforeEach(() => {
|
|
273
|
+
mockEnrichedWriteFailing();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("does not throw — ingest keeps going", async () => {
|
|
277
|
+
await expect(
|
|
278
|
+
service.updateLastSeen(RESOURCE_ID, oversizedExtra as never),
|
|
279
|
+
).resolves.toBeUndefined();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("retries with liveness only, so lastSeenAt still advances", async () => {
|
|
283
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
284
|
+
|
|
285
|
+
expect(writes).toHaveLength(2);
|
|
286
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
287
|
+
"lastSeenAt",
|
|
288
|
+
"otelCollectorStatus",
|
|
289
|
+
]);
|
|
290
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
291
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("the retry targets the same resource", async () => {
|
|
295
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
296
|
+
|
|
297
|
+
expect(writes[1]!.id.toString()).toBe(RESOURCE_ID.toString());
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("busts the throttle cache so the next batch is not skipped", async () => {
|
|
301
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
302
|
+
|
|
303
|
+
expect(deletedCacheKeys).toHaveLength(1);
|
|
304
|
+
expect(deletedCacheKeys[0]).toContain(RESOURCE_ID.toString());
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("the next batch retries rather than short-circuiting on a cache hit", async () => {
|
|
308
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
309
|
+
writes = [];
|
|
310
|
+
|
|
311
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
312
|
+
|
|
313
|
+
// Enriched attempt + liveness fallback, not a silent no-op.
|
|
314
|
+
expect(writes).toHaveLength(2);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("a failing cache delete does not stop the liveness retry", async () => {
|
|
318
|
+
jest
|
|
319
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
320
|
+
.mockRejectedValue(new Error("redis down"));
|
|
321
|
+
|
|
322
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
323
|
+
|
|
324
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
325
|
+
"lastSeenAt",
|
|
326
|
+
"otelCollectorStatus",
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("surfaces a genuinely broken database instead of hiding it", async () => {
|
|
331
|
+
// Both attempts fail — the caller must find out.
|
|
332
|
+
jest
|
|
333
|
+
.spyOn(service, "updateColumnsByIdWithoutHooks")
|
|
334
|
+
.mockRejectedValue(new Error("connection terminated"));
|
|
335
|
+
|
|
336
|
+
await expect(
|
|
337
|
+
service.updateLastSeen(RESOURCE_ID, extra as never),
|
|
338
|
+
).rejects.toThrow("connection terminated");
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("cache outages fail open", () => {
|
|
343
|
+
beforeEach(() => {
|
|
344
|
+
mockWritesSucceeding();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("a read failure still writes — never mark a live resource disconnected", async () => {
|
|
348
|
+
jest
|
|
349
|
+
.spyOn(GlobalCache, "getString")
|
|
350
|
+
.mockRejectedValue(new Error("redis down"));
|
|
351
|
+
|
|
352
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
353
|
+
|
|
354
|
+
expect(writes).toHaveLength(1);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("a write failure still writes", async () => {
|
|
358
|
+
jest
|
|
359
|
+
.spyOn(GlobalCache, "setString")
|
|
360
|
+
.mockRejectedValue(new Error("redis down"));
|
|
361
|
+
|
|
362
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
363
|
+
|
|
364
|
+
expect(writes).toHaveLength(1);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
describe("liveness fallback coverage", () => {
|
|
371
|
+
test("covers every service that writes liveness on the hook-free path", () => {
|
|
372
|
+
/*
|
|
373
|
+
* If a twelfth resource type grows an updateLastSeen, it belongs in
|
|
374
|
+
* SERVICE_CASES — that is the whole point of this suite. Bumping this
|
|
375
|
+
* number without adding the case defeats it.
|
|
376
|
+
*/
|
|
377
|
+
expect(SERVICE_CASES).toHaveLength(11);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("no service is listed twice", () => {
|
|
381
|
+
const names: Array<string> = SERVICE_CASES.map((c: ServiceCase) => {
|
|
382
|
+
return c.name;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
expect(new Set(names).size).toBe(names.length);
|
|
386
|
+
});
|
|
387
|
+
});
|