@oneuptime/common 12.0.12 → 12.0.13
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/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
- package/Models/DatabaseModels/NetworkDevice.ts +64 -0
- package/Models/DatabaseModels/StatusPage.ts +59 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
- package/Server/API/AIChatAPI.ts +55 -56
- package/Server/API/CommonAPI.ts +89 -0
- package/Server/API/MicrosoftTeamsAPI.ts +20 -9
- package/Server/API/SlackAPI.ts +13 -10
- package/Server/API/StatusPageAPI.ts +2197 -2128
- package/Server/API/TelemetryAPI.ts +72 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/UserAuthorization.ts +11 -2
- package/Server/Services/ApiKeyPermissionService.ts +116 -2
- package/Server/Services/DatabaseService.ts +17 -6
- package/Server/Services/LogAggregationService.ts +34 -0
- package/Server/Services/LogService.ts +21 -0
- package/Server/Services/NetworkSiteService.ts +12 -0
- package/Server/Services/SpanService.ts +21 -0
- package/Server/Services/TraceAggregationService.ts +15 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
- package/Server/Utils/APIKey/AccessPermission.ts +16 -40
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
- package/Server/Utils/LogRedaction.ts +576 -0
- package/Server/Utils/Logger.ts +123 -46
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
- package/Server/Utils/Monitor/MonitorResource.ts +29 -9
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
- package/Server/Utils/StartServer.ts +30 -9
- package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
- package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
- package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
- package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
- package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
- package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
- package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
- package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
- package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
- package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
- package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
- package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
- package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
- package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
- package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
- package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
- package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
- package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
- package/Tests/UI/Components/MasterPage.test.tsx +36 -1
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
- package/Types/API/Hostname.ts +79 -0
- package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
- package/Types/Database/DatabaseProperty.ts +38 -0
- package/Types/Database/TableColumn.ts +34 -7
- package/Types/Events/Recurring.ts +16 -1
- package/Types/Monitor/MonitorStep.ts +13 -1
- package/Types/Monitor/MonitorType.ts +345 -40
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
- package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
- package/UI/Components/CardSelect/CardSelect.tsx +714 -105
- package/UI/Components/EmptyState/EmptyState.tsx +8 -1
- package/UI/Components/Footer/Footer.tsx +32 -6
- package/UI/Components/Forms/Fields/FormField.tsx +3 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Loader/PageLoader.tsx +29 -8
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
- package/UI/Components/MasterPage/MasterPage.tsx +24 -11
- package/Utils/Monitor/MonitorMetricType.ts +16 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
- package/Utils/Telemetry/CrossSignalScope.ts +75 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +48 -35
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +59 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +13 -10
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +745 -673
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +34 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.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/UserAuthorization.js +6 -2
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +15 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogService.js +16 -0
- package/build/dist/Server/Services/LogService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +12 -0
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +16 -0
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +449 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Logger.js +98 -45
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -7
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +63 -0
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +38 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js +24 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +13 -1
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +9 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +330 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +7 -4
- package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
- package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The policy cache is keyed by projectId plus a CLIENT-supplied appIdentifier,
|
|
5
|
+
* so anyone holding an ingestion key (which ships in public browser JS) can
|
|
6
|
+
* mint unlimited distinct keys. These tests pin that the store is bounded -
|
|
7
|
+
* the regression they guard is the unbounded module-level Map that grew until
|
|
8
|
+
* the ingest pod was OOM-killed - while preserving the store's contracts:
|
|
9
|
+
* negative (null-policy) caching, per-project prefix invalidation, and the
|
|
10
|
+
* kill marker that must always land even at capacity.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
jest.mock("../../../../Server/Infrastructure/Redis", () => {
|
|
14
|
+
return {
|
|
15
|
+
__esModule: true,
|
|
16
|
+
default: {
|
|
17
|
+
getClient: jest.fn().mockReturnValue(null),
|
|
18
|
+
isConnected: jest.fn().mockReturnValue(false),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
jest.mock("../../../../Server/Utils/Logger", () => {
|
|
24
|
+
return {
|
|
25
|
+
__esModule: true,
|
|
26
|
+
default: {
|
|
27
|
+
debug: jest.fn(),
|
|
28
|
+
info: jest.fn(),
|
|
29
|
+
warn: jest.fn(),
|
|
30
|
+
error: jest.fn(),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import SessionReplayGateCacheStore, {
|
|
36
|
+
MAX_KILL_KEY_CACHE_ENTRIES,
|
|
37
|
+
MAX_POLICY_CACHE_ENTRIES,
|
|
38
|
+
PolicyCacheEntry,
|
|
39
|
+
} from "../../../../Server/Utils/SessionReplay/SessionReplayGateCacheStore";
|
|
40
|
+
|
|
41
|
+
type TestPolicy = { name: string };
|
|
42
|
+
|
|
43
|
+
function policyKey(prefix: string, index: number): string {
|
|
44
|
+
return `${prefix}:app-${index}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fillPolicyCache(prefix: string, count: number): void {
|
|
48
|
+
for (let i: number = 0; i < count; i++) {
|
|
49
|
+
SessionReplayGateCacheStore.setPolicyEntry<TestPolicy>(
|
|
50
|
+
policyKey(prefix, i),
|
|
51
|
+
{ name: `policy-${i}` },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("SessionReplayGateCacheStore bounded policy cache", (): void => {
|
|
57
|
+
beforeEach((): void => {
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
/* Module-level Maps persist across tests. */
|
|
60
|
+
SessionReplayGateCacheStore.clearCache();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("evicts the oldest entry instead of growing past the cap under a unique-key flood", (): void => {
|
|
64
|
+
fillPolicyCache("proj", MAX_POLICY_CACHE_ENTRIES + 1);
|
|
65
|
+
|
|
66
|
+
expect(
|
|
67
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
68
|
+
policyKey("proj", 0),
|
|
69
|
+
),
|
|
70
|
+
).toBeUndefined();
|
|
71
|
+
|
|
72
|
+
const last: PolicyCacheEntry<TestPolicy> | undefined =
|
|
73
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
74
|
+
policyKey("proj", MAX_POLICY_CACHE_ENTRIES),
|
|
75
|
+
);
|
|
76
|
+
expect(last).toBeDefined();
|
|
77
|
+
expect(last?.policy).toEqual({
|
|
78
|
+
name: `policy-${MAX_POLICY_CACHE_ENTRIES}`,
|
|
79
|
+
});
|
|
80
|
+
}, 15000);
|
|
81
|
+
|
|
82
|
+
it("rewriting an existing key at capacity evicts nothing", (): void => {
|
|
83
|
+
fillPolicyCache("proj", MAX_POLICY_CACHE_ENTRIES);
|
|
84
|
+
|
|
85
|
+
SessionReplayGateCacheStore.setPolicyEntry<TestPolicy>(
|
|
86
|
+
policyKey("proj", 5),
|
|
87
|
+
{ name: "rewritten" },
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (let i: number = 0; i < MAX_POLICY_CACHE_ENTRIES; i++) {
|
|
91
|
+
expect(
|
|
92
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
93
|
+
policyKey("proj", i),
|
|
94
|
+
),
|
|
95
|
+
).toBeDefined();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
expect(
|
|
99
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
100
|
+
policyKey("proj", 5),
|
|
101
|
+
)?.policy,
|
|
102
|
+
).toEqual({ name: "rewritten" });
|
|
103
|
+
}, 15000);
|
|
104
|
+
|
|
105
|
+
it("a rewrite refreshes recency, so the next eviction takes the stale key", (): void => {
|
|
106
|
+
fillPolicyCache("proj", MAX_POLICY_CACHE_ENTRIES);
|
|
107
|
+
|
|
108
|
+
SessionReplayGateCacheStore.setPolicyEntry<TestPolicy>(
|
|
109
|
+
policyKey("proj", 0),
|
|
110
|
+
{ name: "refreshed" },
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
SessionReplayGateCacheStore.setPolicyEntry<TestPolicy>("proj:app-new", {
|
|
114
|
+
name: "new",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(
|
|
118
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
119
|
+
policyKey("proj", 1),
|
|
120
|
+
),
|
|
121
|
+
).toBeUndefined();
|
|
122
|
+
expect(
|
|
123
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
124
|
+
policyKey("proj", 0),
|
|
125
|
+
)?.policy,
|
|
126
|
+
).toEqual({ name: "refreshed" });
|
|
127
|
+
expect(
|
|
128
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>("proj:app-new"),
|
|
129
|
+
).toBeDefined();
|
|
130
|
+
}, 15000);
|
|
131
|
+
|
|
132
|
+
it("still caches null policies - the hot-path refusal must stay cached", (): void => {
|
|
133
|
+
SessionReplayGateCacheStore.setPolicyEntry<TestPolicy>(
|
|
134
|
+
"proj:app-none",
|
|
135
|
+
null,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const entry: PolicyCacheEntry<TestPolicy> | undefined =
|
|
139
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>("proj:app-none");
|
|
140
|
+
|
|
141
|
+
expect(entry).toBeDefined();
|
|
142
|
+
expect(entry?.policy).toBeNull();
|
|
143
|
+
expect(typeof entry?.loadedAt).toBe("number");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("clearCache(projectId) deletes exactly that project's prefixed entries after evictions", (): void => {
|
|
147
|
+
const projectA: ObjectID = ObjectID.generate();
|
|
148
|
+
const projectB: ObjectID = ObjectID.generate();
|
|
149
|
+
|
|
150
|
+
fillPolicyCache(projectA.toString(), MAX_POLICY_CACHE_ENTRIES);
|
|
151
|
+
/* These writes evict projectA's oldest entries. */
|
|
152
|
+
fillPolicyCache(projectB.toString(), 5);
|
|
153
|
+
|
|
154
|
+
SessionReplayGateCacheStore.clearCache(projectA);
|
|
155
|
+
|
|
156
|
+
for (let i: number = 0; i < MAX_POLICY_CACHE_ENTRIES; i++) {
|
|
157
|
+
expect(
|
|
158
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
159
|
+
policyKey(projectA.toString(), i),
|
|
160
|
+
),
|
|
161
|
+
).toBeUndefined();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (let i: number = 0; i < 5; i++) {
|
|
165
|
+
expect(
|
|
166
|
+
SessionReplayGateCacheStore.getPolicyEntry<TestPolicy>(
|
|
167
|
+
policyKey(projectB.toString(), i),
|
|
168
|
+
),
|
|
169
|
+
).toBeDefined();
|
|
170
|
+
}
|
|
171
|
+
}, 15000);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("SessionReplayGateCacheStore bounded kill-key cache", (): void => {
|
|
175
|
+
beforeEach((): void => {
|
|
176
|
+
jest.clearAllMocks();
|
|
177
|
+
SessionReplayGateCacheStore.clearCache();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("the local kill marker always lands, even at capacity", async (): Promise<void> => {
|
|
181
|
+
/* Redis is absent, so the fail-open path caches isDisabled:false. */
|
|
182
|
+
for (let i: number = 0; i < MAX_KILL_KEY_CACHE_ENTRIES; i++) {
|
|
183
|
+
await SessionReplayGateCacheStore.isProjectKilled(ObjectID.generate());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const killedProject: ObjectID = ObjectID.generate();
|
|
187
|
+
await SessionReplayGateCacheStore.markProjectDisabled(killedProject);
|
|
188
|
+
|
|
189
|
+
expect(
|
|
190
|
+
await SessionReplayGateCacheStore.isProjectKilled(killedProject),
|
|
191
|
+
).toBe(true);
|
|
192
|
+
}, 30000);
|
|
193
|
+
|
|
194
|
+
it("an evicted kill entry re-resolves through the normal path", async (): Promise<void> => {
|
|
195
|
+
const firstProject: ObjectID = ObjectID.generate();
|
|
196
|
+
await SessionReplayGateCacheStore.isProjectKilled(firstProject);
|
|
197
|
+
|
|
198
|
+
/* Push the first project's entry out of the bounded map. */
|
|
199
|
+
for (let i: number = 0; i < MAX_KILL_KEY_CACHE_ENTRIES; i++) {
|
|
200
|
+
await SessionReplayGateCacheStore.isProjectKilled(ObjectID.generate());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await expect(
|
|
204
|
+
SessionReplayGateCacheStore.isProjectKilled(firstProject),
|
|
205
|
+
).resolves.toBe(false);
|
|
206
|
+
}, 30000);
|
|
207
|
+
});
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import {
|
|
2
|
+
jsonBodyParserOptions,
|
|
3
|
+
urlEncodedBodyParserOptions,
|
|
4
|
+
} from "../../../Server/Utils/StartServer";
|
|
5
|
+
import {
|
|
6
|
+
ExpressJson,
|
|
7
|
+
ExpressRequest,
|
|
8
|
+
ExpressResponse,
|
|
9
|
+
ExpressUrlEncoded,
|
|
10
|
+
OneUptimeRequest,
|
|
11
|
+
RequestHandler,
|
|
12
|
+
} from "../../../Server/Utils/Express";
|
|
13
|
+
import logger from "../../../Server/Utils/Logger";
|
|
14
|
+
import {
|
|
15
|
+
afterEach,
|
|
16
|
+
beforeEach,
|
|
17
|
+
describe,
|
|
18
|
+
expect,
|
|
19
|
+
jest,
|
|
20
|
+
test,
|
|
21
|
+
} from "@jest/globals";
|
|
22
|
+
import { Readable } from "stream";
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import path from "path";
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* The body-parser verify callbacks capture the raw request body for webhook
|
|
28
|
+
* signature verification (Slack, WhatsApp, GitHub, billing). Every consumer
|
|
29
|
+
* does a read-only string comparison against the captured value, so the
|
|
30
|
+
* urlencoded path must alias ONE decoded string to both rawBody and
|
|
31
|
+
* rawFormUrlEncodedBody instead of decoding the buffer twice, and neither
|
|
32
|
+
* path may embed the body (which carries signing material) in its debug
|
|
33
|
+
* message.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
type SpyLike = {
|
|
37
|
+
mock: { calls: Array<Array<unknown>> };
|
|
38
|
+
mockRestore: () => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type MockFn = ReturnType<typeof jest.fn>;
|
|
42
|
+
|
|
43
|
+
const JSON_DEBUG_MESSAGE: string =
|
|
44
|
+
"Raw JSON Body for signature verification captured";
|
|
45
|
+
const URL_ENCODED_DEBUG_MESSAGE: string =
|
|
46
|
+
"Raw Form Url Encoded Body for signature verification captured";
|
|
47
|
+
|
|
48
|
+
let debugSpy: SpyLike;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
debugSpy = jest
|
|
52
|
+
.spyOn(logger, "debug")
|
|
53
|
+
.mockImplementation((): void => {}) as unknown as SpyLike;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
debugSpy.mockRestore();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
type VerifyInvoker = (req: OneUptimeRequest, buf: Buffer) => void;
|
|
61
|
+
|
|
62
|
+
const invokeUrlEncodedVerify: VerifyInvoker = (
|
|
63
|
+
req: OneUptimeRequest,
|
|
64
|
+
buf: Buffer,
|
|
65
|
+
): void => {
|
|
66
|
+
urlEncodedBodyParserOptions.verify(
|
|
67
|
+
req as ExpressRequest,
|
|
68
|
+
{} as ExpressResponse,
|
|
69
|
+
buf,
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const invokeJsonVerify: VerifyInvoker = (
|
|
74
|
+
req: OneUptimeRequest,
|
|
75
|
+
buf: Buffer,
|
|
76
|
+
): void => {
|
|
77
|
+
jsonBodyParserOptions.verify(
|
|
78
|
+
req as ExpressRequest,
|
|
79
|
+
{} as ExpressResponse,
|
|
80
|
+
buf,
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
describe("urlencoded verify: single decode aliased to both fields", () => {
|
|
85
|
+
test("decodes the buffer exactly once", () => {
|
|
86
|
+
const toString: MockFn = jest.fn().mockReturnValue("a=1&b=2");
|
|
87
|
+
const req: OneUptimeRequest = {} as OneUptimeRequest;
|
|
88
|
+
|
|
89
|
+
invokeUrlEncodedVerify(req, { toString } as unknown as Buffer);
|
|
90
|
+
|
|
91
|
+
expect(toString).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("both fields come from the SAME decode, not two independent copies", () => {
|
|
95
|
+
/*
|
|
96
|
+
* A buffer whose toString returns a different value on every call: with
|
|
97
|
+
* one decode both fields hold the first value; the old two-decode code
|
|
98
|
+
* would leave the second value on rawBody.
|
|
99
|
+
*/
|
|
100
|
+
const toString: MockFn = jest
|
|
101
|
+
.fn()
|
|
102
|
+
.mockReturnValueOnce("first-decode")
|
|
103
|
+
.mockReturnValue("second-decode");
|
|
104
|
+
const req: OneUptimeRequest = {} as OneUptimeRequest;
|
|
105
|
+
|
|
106
|
+
invokeUrlEncodedVerify(req, { toString } as unknown as Buffer);
|
|
107
|
+
|
|
108
|
+
expect(req.rawFormUrlEncodedBody).toBe("first-decode");
|
|
109
|
+
expect(req.rawBody).toBe("first-decode");
|
|
110
|
+
expect(req.rawBody).toBe(req.rawFormUrlEncodedBody);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("captures an ASCII body byte-identical to buf.toString()", () => {
|
|
114
|
+
const body: string = "a=1&b=2&msg=hello+world&sig=abc123";
|
|
115
|
+
const buf: Buffer = Buffer.from(body);
|
|
116
|
+
const req: OneUptimeRequest = {} as OneUptimeRequest;
|
|
117
|
+
|
|
118
|
+
invokeUrlEncodedVerify(req, buf);
|
|
119
|
+
|
|
120
|
+
expect(req.rawFormUrlEncodedBody).toBe(buf.toString());
|
|
121
|
+
expect(req.rawBody).toBe(buf.toString());
|
|
122
|
+
expect(req.rawBody).toBe(body);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("captures a multi-byte UTF-8 body byte-identical to buf.toString()", () => {
|
|
126
|
+
const body: string = "name=Grüße&emoji=🚀🔥&city=東京&mixed=a™b";
|
|
127
|
+
const buf: Buffer = Buffer.from(body, "utf8");
|
|
128
|
+
const req: OneUptimeRequest = {} as OneUptimeRequest;
|
|
129
|
+
|
|
130
|
+
invokeUrlEncodedVerify(req, buf);
|
|
131
|
+
|
|
132
|
+
expect(req.rawFormUrlEncodedBody).toBe(buf.toString());
|
|
133
|
+
expect(req.rawBody).toBe(buf.toString());
|
|
134
|
+
expect(req.rawBody).toBe(body);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("urlencoded verify: debug log is constant and body-free", () => {
|
|
139
|
+
test("logs the constant message with request attributes, never the body", () => {
|
|
140
|
+
const secret: string = "SlackSigningSecretMarker123";
|
|
141
|
+
const req: OneUptimeRequest = {
|
|
142
|
+
requestId: "req-123",
|
|
143
|
+
} as OneUptimeRequest;
|
|
144
|
+
|
|
145
|
+
invokeUrlEncodedVerify(req, Buffer.from(`token=${secret}&user=alice`));
|
|
146
|
+
|
|
147
|
+
expect(debugSpy.mock.calls).toHaveLength(1);
|
|
148
|
+
|
|
149
|
+
const message: unknown = debugSpy.mock.calls[0]![0];
|
|
150
|
+
const attributes: unknown = debugSpy.mock.calls[0]![1];
|
|
151
|
+
|
|
152
|
+
expect(message).toBe(URL_ENCODED_DEBUG_MESSAGE);
|
|
153
|
+
expect(message as string).not.toContain(secret);
|
|
154
|
+
expect(message as string).not.toContain("alice");
|
|
155
|
+
expect(attributes).toEqual({ requestId: "req-123" });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("json verify: regression guard", () => {
|
|
160
|
+
test("sets rawBody to the exact body and leaves rawFormUrlEncodedBody unset", () => {
|
|
161
|
+
const body: string = '{"alert":"CPU high","city":"東京","emoji":"🚀"}';
|
|
162
|
+
const req: OneUptimeRequest = {} as OneUptimeRequest;
|
|
163
|
+
|
|
164
|
+
invokeJsonVerify(req, Buffer.from(body, "utf8"));
|
|
165
|
+
|
|
166
|
+
expect(req.rawBody).toBe(body);
|
|
167
|
+
expect(req.rawFormUrlEncodedBody).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("decodes the buffer exactly once", () => {
|
|
171
|
+
const toString: MockFn = jest.fn().mockReturnValue("{}");
|
|
172
|
+
const req: OneUptimeRequest = {} as OneUptimeRequest;
|
|
173
|
+
|
|
174
|
+
invokeJsonVerify(req, { toString } as unknown as Buffer);
|
|
175
|
+
|
|
176
|
+
expect(toString).toHaveBeenCalledTimes(1);
|
|
177
|
+
expect(req.rawBody).toBe("{}");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("logs the constant message with request attributes, never the body", () => {
|
|
181
|
+
const secret: string = "GitHubWebhookSecretMarker456";
|
|
182
|
+
const req: OneUptimeRequest = {
|
|
183
|
+
requestId: "req-456",
|
|
184
|
+
} as OneUptimeRequest;
|
|
185
|
+
|
|
186
|
+
invokeJsonVerify(req, Buffer.from(`{"secret":"${secret}"}`));
|
|
187
|
+
|
|
188
|
+
expect(debugSpy.mock.calls).toHaveLength(1);
|
|
189
|
+
|
|
190
|
+
const message: unknown = debugSpy.mock.calls[0]![0];
|
|
191
|
+
const attributes: unknown = debugSpy.mock.calls[0]![1];
|
|
192
|
+
|
|
193
|
+
expect(message).toBe(JSON_DEBUG_MESSAGE);
|
|
194
|
+
expect(message as string).not.toContain(secret);
|
|
195
|
+
expect(attributes).toEqual({ requestId: "req-456" });
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
* The exported options through the REAL express parsers, so the verify
|
|
201
|
+
* contract (arguments, timing, the parsed body still coming out the other
|
|
202
|
+
* end) is pinned against body-parser itself and not just against how these
|
|
203
|
+
* tests call the callbacks.
|
|
204
|
+
*/
|
|
205
|
+
describe("options through the real express parsers", () => {
|
|
206
|
+
type StreamRequestFactory = (
|
|
207
|
+
body: string,
|
|
208
|
+
contentType: string,
|
|
209
|
+
) => OneUptimeRequest;
|
|
210
|
+
|
|
211
|
+
const createStreamRequest: StreamRequestFactory = (
|
|
212
|
+
body: string,
|
|
213
|
+
contentType: string,
|
|
214
|
+
): OneUptimeRequest => {
|
|
215
|
+
const readable: Readable = new Readable({
|
|
216
|
+
read(): void {},
|
|
217
|
+
});
|
|
218
|
+
readable.push(Buffer.from(body, "utf8"));
|
|
219
|
+
readable.push(null);
|
|
220
|
+
|
|
221
|
+
const req: OneUptimeRequest = readable as unknown as OneUptimeRequest;
|
|
222
|
+
req.headers = {
|
|
223
|
+
"content-type": contentType,
|
|
224
|
+
"content-length": String(Buffer.byteLength(body, "utf8")),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return req;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type MiddlewareRunner = (
|
|
231
|
+
middleware: RequestHandler,
|
|
232
|
+
req: OneUptimeRequest,
|
|
233
|
+
) => Promise<void>;
|
|
234
|
+
|
|
235
|
+
const runMiddleware: MiddlewareRunner = (
|
|
236
|
+
middleware: RequestHandler,
|
|
237
|
+
req: OneUptimeRequest,
|
|
238
|
+
): Promise<void> => {
|
|
239
|
+
return new Promise<void>(
|
|
240
|
+
(resolve: () => void, reject: (reason: unknown) => void) => {
|
|
241
|
+
middleware(req, {} as ExpressResponse, (err?: unknown): void => {
|
|
242
|
+
if (err) {
|
|
243
|
+
reject(err);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
resolve();
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
test("urlencoded: parses the body AND captures one raw copy on both fields", async () => {
|
|
253
|
+
const body: string = "a=1&b=2&name=Gr%C3%BC%C3%9Fe";
|
|
254
|
+
const req: OneUptimeRequest = createStreamRequest(
|
|
255
|
+
body,
|
|
256
|
+
"application/x-www-form-urlencoded",
|
|
257
|
+
);
|
|
258
|
+
const middleware: RequestHandler = ExpressUrlEncoded(
|
|
259
|
+
urlEncodedBodyParserOptions,
|
|
260
|
+
) as RequestHandler;
|
|
261
|
+
|
|
262
|
+
await runMiddleware(middleware, req);
|
|
263
|
+
|
|
264
|
+
expect(req.body).toEqual({ a: "1", b: "2", name: "Grüße" });
|
|
265
|
+
expect(req.rawBody).toBe(body);
|
|
266
|
+
expect(req.rawFormUrlEncodedBody).toBe(body);
|
|
267
|
+
expect(debugSpy.mock.calls).toHaveLength(1);
|
|
268
|
+
expect(debugSpy.mock.calls[0]![0]).toBe(URL_ENCODED_DEBUG_MESSAGE);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("json: parses the body AND captures the raw copy on rawBody", async () => {
|
|
272
|
+
const body: string = '{"a":1,"city":"東京"}';
|
|
273
|
+
const req: OneUptimeRequest = createStreamRequest(body, "application/json");
|
|
274
|
+
const middleware: RequestHandler = ExpressJson(
|
|
275
|
+
jsonBodyParserOptions,
|
|
276
|
+
) as RequestHandler;
|
|
277
|
+
|
|
278
|
+
await runMiddleware(middleware, req);
|
|
279
|
+
|
|
280
|
+
expect(req.body).toEqual({ a: 1, city: "東京" });
|
|
281
|
+
expect(req.rawBody).toBe(body);
|
|
282
|
+
expect(req.rawFormUrlEncodedBody).toBeUndefined();
|
|
283
|
+
expect(debugSpy.mock.calls).toHaveLength(1);
|
|
284
|
+
expect(debugSpy.mock.calls[0]![0]).toBe(JSON_DEBUG_MESSAGE);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/*
|
|
289
|
+
* Read rather than executed, following VendorAssets.test.ts: what matters is
|
|
290
|
+
* only that StartServer builds its middlewares from these exact exported
|
|
291
|
+
* option objects, so the behavioral tests above are testing what production
|
|
292
|
+
* actually runs.
|
|
293
|
+
*/
|
|
294
|
+
describe("StartServer wires the exported options into its middlewares", () => {
|
|
295
|
+
const startServerSource: string = fs.readFileSync(
|
|
296
|
+
path.resolve(
|
|
297
|
+
__dirname,
|
|
298
|
+
"..",
|
|
299
|
+
"..",
|
|
300
|
+
"..",
|
|
301
|
+
"Server",
|
|
302
|
+
"Utils",
|
|
303
|
+
"StartServer.ts",
|
|
304
|
+
),
|
|
305
|
+
"utf8",
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
test("json middleware is built from jsonBodyParserOptions", () => {
|
|
309
|
+
expect(startServerSource).toMatch(
|
|
310
|
+
/ExpressJson\(\s*jsonBodyParserOptions,?\s*\)/,
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("urlencoded middleware is built from urlEncodedBodyParserOptions", () => {
|
|
315
|
+
expect(startServerSource).toMatch(
|
|
316
|
+
/ExpressUrlEncoded\(\s*urlEncodedBodyParserOptions,?\s*\)/,
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
/*
|
|
321
|
+
* Large probe payloads depend on the 50mb limit; the express default is
|
|
322
|
+
* 100kb, so a transcription slip in these exported objects would reject
|
|
323
|
+
* real traffic while every tiny-body test here still passes.
|
|
324
|
+
*/
|
|
325
|
+
test("both option objects keep the 50mb limit and extended parsing", () => {
|
|
326
|
+
for (const options of [
|
|
327
|
+
jsonBodyParserOptions,
|
|
328
|
+
urlEncodedBodyParserOptions,
|
|
329
|
+
]) {
|
|
330
|
+
expect(options.limit).toBe("50mb");
|
|
331
|
+
expect(options.extended).toBe(true);
|
|
332
|
+
expect(typeof options.verify).toBe("function");
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The X-Robots-Tag half of the status page indexing opt-out.
|
|
3
|
+
*
|
|
4
|
+
* The meta tag in index.ejs only covers the HTML. This header is what carries
|
|
5
|
+
* the same instruction on the responses that have no <head> to put a tag in -
|
|
6
|
+
* the RSS feed and llms.txt - so it is tested on its own.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import applyStatusPageRobotsHeader from "../../../Server/Utils/StatusPageSearchEngineIndexing";
|
|
10
|
+
import { ExpressResponse } from "../../../Server/Utils/Express";
|
|
11
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
12
|
+
|
|
13
|
+
type FakeResponse = {
|
|
14
|
+
set: ReturnType<typeof jest.fn>;
|
|
15
|
+
headersSent: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function fakeResponse(headersSent: boolean = false): FakeResponse {
|
|
19
|
+
return {
|
|
20
|
+
set: jest.fn(),
|
|
21
|
+
headersSent,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("applyStatusPageRobotsHeader", () => {
|
|
26
|
+
test("sends noindex, nofollow when the owner turned indexing off", () => {
|
|
27
|
+
const res: FakeResponse = fakeResponse();
|
|
28
|
+
|
|
29
|
+
applyStatusPageRobotsHeader(res as unknown as ExpressResponse, false);
|
|
30
|
+
|
|
31
|
+
expect(res.set).toHaveBeenCalledTimes(1);
|
|
32
|
+
expect(res.set).toHaveBeenCalledWith("X-Robots-Tag", "noindex, nofollow");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("sends no header at all when indexing is on", () => {
|
|
36
|
+
/*
|
|
37
|
+
* Not "sends index, follow". Emitting the default explicitly would
|
|
38
|
+
* overwrite a noindex that an operator set in front of OneUptime - a CDN
|
|
39
|
+
* rule or an ingress annotation - because the last writer of a header
|
|
40
|
+
* wins. Silence leaves those in place.
|
|
41
|
+
*/
|
|
42
|
+
const res: FakeResponse = fakeResponse();
|
|
43
|
+
|
|
44
|
+
applyStatusPageRobotsHeader(res as unknown as ExpressResponse, true);
|
|
45
|
+
|
|
46
|
+
expect(res.set).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("does not touch a response that has already been flushed", () => {
|
|
50
|
+
/*
|
|
51
|
+
* Node drops headers set after the response has gone out, and logs
|
|
52
|
+
* nothing. Guarding here keeps a late caller from thinking it succeeded.
|
|
53
|
+
*/
|
|
54
|
+
const res: FakeResponse = fakeResponse(true);
|
|
55
|
+
|
|
56
|
+
applyStatusPageRobotsHeader(res as unknown as ExpressResponse, false);
|
|
57
|
+
|
|
58
|
+
expect(res.set).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
});
|