@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
|
@@ -584,9 +584,37 @@ describe("MicrosoftTeamsUtil.removeTeamFromBotActivity", () => {
|
|
|
584
584
|
});
|
|
585
585
|
|
|
586
586
|
expect(removeSpy).toHaveBeenCalledTimes(1);
|
|
587
|
+
/*
|
|
588
|
+
* Uninstall passes both ids: the bot is already out of the team so the
|
|
589
|
+
* Graph id cannot be looked up, and records written before it was captured
|
|
590
|
+
* are only findable by the thread id. TEAM_ID here is a thread id, matching
|
|
591
|
+
* what channelData.team.id carries.
|
|
592
|
+
*/
|
|
587
593
|
expect(removeSpy).toHaveBeenCalledWith({
|
|
588
594
|
tenantId: TENANT_ID,
|
|
589
|
-
|
|
595
|
+
teamsThreadId: TEAM_ID,
|
|
596
|
+
graphTeamId: undefined,
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("an uninstall activity that still carries aadGroupId passes it through", async () => {
|
|
601
|
+
const removeSpy: jest.SpyInstance = jest
|
|
602
|
+
.spyOn(MicrosoftTeamsUtil, "removeTeamFromProjectAuthTokens")
|
|
603
|
+
.mockResolvedValue(undefined as never);
|
|
604
|
+
|
|
605
|
+
await MicrosoftTeamsUtil.removeTeamFromBotActivity({
|
|
606
|
+
activity: teamActivity({
|
|
607
|
+
channelData: {
|
|
608
|
+
team: { id: TEAM_ID, aadGroupId: "graph-team-1" },
|
|
609
|
+
tenant: { id: TENANT_ID },
|
|
610
|
+
},
|
|
611
|
+
}),
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
expect(removeSpy).toHaveBeenCalledWith({
|
|
615
|
+
tenantId: TENANT_ID,
|
|
616
|
+
teamsThreadId: TEAM_ID,
|
|
617
|
+
graphTeamId: "graph-team-1",
|
|
590
618
|
});
|
|
591
619
|
});
|
|
592
620
|
|
|
@@ -884,7 +912,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
884
912
|
|
|
885
913
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
886
914
|
tenantId: "tenant-abc",
|
|
887
|
-
|
|
915
|
+
teamsThreadId: TEAM_ID,
|
|
888
916
|
});
|
|
889
917
|
|
|
890
918
|
expect(findBySpy).toHaveBeenCalledWith(
|
|
@@ -925,7 +953,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
925
953
|
|
|
926
954
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
927
955
|
tenantId: "tenant-abc",
|
|
928
|
-
|
|
956
|
+
teamsThreadId: teamToRemove.id,
|
|
929
957
|
});
|
|
930
958
|
|
|
931
959
|
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
@@ -950,7 +978,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
950
978
|
|
|
951
979
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
952
980
|
tenantId: "tenant-abc",
|
|
953
|
-
|
|
981
|
+
teamsThreadId: team.id,
|
|
954
982
|
});
|
|
955
983
|
|
|
956
984
|
const savedMiscData: MicrosoftTeamsMiscData = getUpdateArgs(updateSpy, 0)
|
|
@@ -976,7 +1004,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
976
1004
|
|
|
977
1005
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
978
1006
|
tenantId: "tenant-abc",
|
|
979
|
-
|
|
1007
|
+
teamsThreadId: TEAM_ID,
|
|
980
1008
|
});
|
|
981
1009
|
|
|
982
1010
|
expect(updateSpy).not.toHaveBeenCalled();
|
|
@@ -991,7 +1019,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
991
1019
|
|
|
992
1020
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
993
1021
|
tenantId: "tenant-abc",
|
|
994
|
-
|
|
1022
|
+
teamsThreadId: TEAM_ID,
|
|
995
1023
|
});
|
|
996
1024
|
|
|
997
1025
|
expect(updateSpy).not.toHaveBeenCalled();
|
|
@@ -1014,7 +1042,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
1014
1042
|
|
|
1015
1043
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
1016
1044
|
tenantId: "tenant-abc",
|
|
1017
|
-
|
|
1045
|
+
teamsThreadId: team.id,
|
|
1018
1046
|
});
|
|
1019
1047
|
|
|
1020
1048
|
expect(updateSpy).toHaveBeenCalledTimes(3);
|
|
@@ -1068,7 +1096,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
1068
1096
|
|
|
1069
1097
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
1070
1098
|
tenantId: "tenant-abc",
|
|
1071
|
-
|
|
1099
|
+
teamsThreadId: team.id,
|
|
1072
1100
|
});
|
|
1073
1101
|
|
|
1074
1102
|
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
@@ -1104,7 +1132,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
1104
1132
|
|
|
1105
1133
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
1106
1134
|
tenantId: "tenant-abc",
|
|
1107
|
-
|
|
1135
|
+
teamsThreadId: "19:team-aaa@thread.tacv2",
|
|
1108
1136
|
});
|
|
1109
1137
|
|
|
1110
1138
|
expect(updateSpy).not.toHaveBeenCalled();
|
|
@@ -1120,7 +1148,7 @@ describe("MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens", () => {
|
|
|
1120
1148
|
|
|
1121
1149
|
await MicrosoftTeamsUtil.removeTeamFromProjectAuthTokens({
|
|
1122
1150
|
tenantId: "tenant-abc",
|
|
1123
|
-
|
|
1151
|
+
teamsThreadId: team.id,
|
|
1124
1152
|
});
|
|
1125
1153
|
|
|
1126
1154
|
expect(originalMiscData.installedTeams).toEqual({ [team.id]: team });
|
|
@@ -1495,9 +1523,15 @@ describe("MicrosoftTeamsUtil.handleBotMessageActivity - team install backfill wi
|
|
|
1495
1523
|
});
|
|
1496
1524
|
|
|
1497
1525
|
expect(spies.captureTeamSpy).toHaveBeenCalledTimes(1);
|
|
1526
|
+
/*
|
|
1527
|
+
* onlyIfMissingOrStale, because this runs on every inbound channel message:
|
|
1528
|
+
* once the team is recorded it must cost one read, not a write plus a
|
|
1529
|
+
* possible Bot Framework call to resolve the group id.
|
|
1530
|
+
*/
|
|
1498
1531
|
expect(spies.captureTeamSpy).toHaveBeenCalledWith({
|
|
1499
1532
|
activity: activity,
|
|
1500
1533
|
turnContext: turnContext,
|
|
1534
|
+
onlyIfMissingOrStale: true,
|
|
1501
1535
|
});
|
|
1502
1536
|
expect(spies.captureChatSpy).not.toHaveBeenCalled();
|
|
1503
1537
|
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import Hostname from "../../../Types/API/Hostname";
|
|
2
|
+
import { describe, expect, test } from "@jest/globals";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Hostname.fromAuthority splits a URL authority into a bare host and a real
|
|
6
|
+
* Port. It exists because URL.fromString hands the WHOLE authority to the
|
|
7
|
+
* Hostname constructor, so `URL.fromString("https://example.com:8443/")`
|
|
8
|
+
* yields a hostname of "example.com:8443" and no port at all.
|
|
9
|
+
*
|
|
10
|
+
* Anything that reads the structured accessors rather than toString() then
|
|
11
|
+
* dials a host that does not exist. The SSL Certificate Monitor is one such
|
|
12
|
+
* consumer, which is why a monitor on a non-443 port could never connect —
|
|
13
|
+
* see https://github.com/OneUptime/oneuptime/issues/3225.
|
|
14
|
+
*
|
|
15
|
+
* fromString() is NOT a substitute: it splits on the FIRST colon, which
|
|
16
|
+
* mangles every IPv6 literal.
|
|
17
|
+
*/
|
|
18
|
+
describe("Hostname.fromAuthority", () => {
|
|
19
|
+
describe("host and port", () => {
|
|
20
|
+
test("splits a trailing port off the host", () => {
|
|
21
|
+
const hostname: Hostname = Hostname.fromAuthority("example.com:8443");
|
|
22
|
+
|
|
23
|
+
expect(hostname.hostname).toBe("example.com");
|
|
24
|
+
expect(hostname.port?.toNumber()).toBe(8443);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("leaves a bare host without a port", () => {
|
|
28
|
+
const hostname: Hostname = Hostname.fromAuthority("example.com");
|
|
29
|
+
|
|
30
|
+
expect(hostname.hostname).toBe("example.com");
|
|
31
|
+
expect(hostname.port).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("handles localhost with a port", () => {
|
|
35
|
+
const hostname: Hostname = Hostname.fromAuthority("localhost:5000");
|
|
36
|
+
|
|
37
|
+
expect(hostname.hostname).toBe("localhost");
|
|
38
|
+
expect(hostname.port?.toNumber()).toBe(5000);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("handles an IPv4 literal with a port", () => {
|
|
42
|
+
const hostname: Hostname = Hostname.fromAuthority("127.0.0.1:8443");
|
|
43
|
+
|
|
44
|
+
expect(hostname.hostname).toBe("127.0.0.1");
|
|
45
|
+
expect(hostname.port?.toNumber()).toBe(8443);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("round-trips back to the original authority", () => {
|
|
49
|
+
expect(Hostname.fromAuthority("example.com:8443").toString()).toBe(
|
|
50
|
+
"example.com:8443",
|
|
51
|
+
);
|
|
52
|
+
expect(Hostname.fromAuthority("example.com").toString()).toBe(
|
|
53
|
+
"example.com",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("IPv6 literals", () => {
|
|
59
|
+
test("keeps a bracketed IPv6 address intact and takes its port", () => {
|
|
60
|
+
const hostname: Hostname = Hostname.fromAuthority("[::1]:8443");
|
|
61
|
+
|
|
62
|
+
expect(hostname.hostname).toBe("[::1]");
|
|
63
|
+
expect(hostname.port?.toNumber()).toBe(8443);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("keeps a bracketed IPv6 address with no port intact", () => {
|
|
67
|
+
const hostname: Hostname = Hostname.fromAuthority("[::1]");
|
|
68
|
+
|
|
69
|
+
expect(hostname.hostname).toBe("[::1]");
|
|
70
|
+
expect(hostname.port).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("does not split an unbracketed IPv6 literal", () => {
|
|
74
|
+
/*
|
|
75
|
+
* Two or more colons cannot be a host:port, so every colon belongs to
|
|
76
|
+
* the address. fromString would have returned a host of "2001".
|
|
77
|
+
*/
|
|
78
|
+
const hostname: Hostname = Hostname.fromAuthority("2001:db8::1");
|
|
79
|
+
|
|
80
|
+
expect(hostname.hostname).toBe("2001:db8::1");
|
|
81
|
+
expect(hostname.port).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("a full bracketed IPv6 address survives with its port", () => {
|
|
85
|
+
const hostname: Hostname = Hostname.fromAuthority(
|
|
86
|
+
"[2001:db8::8a2e:370:7334]:9000",
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(hostname.hostname).toBe("[2001:db8::8a2e:370:7334]");
|
|
90
|
+
expect(hostname.port?.toNumber()).toBe(9000);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("userinfo", () => {
|
|
95
|
+
test("drops userinfo and keeps the host", () => {
|
|
96
|
+
const hostname: Hostname = Hostname.fromAuthority(
|
|
97
|
+
"user:token@hooks.example.com",
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(hostname.hostname).toBe("hooks.example.com");
|
|
101
|
+
expect(hostname.port).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("drops userinfo and keeps host and port", () => {
|
|
105
|
+
const hostname: Hostname = Hostname.fromAuthority(
|
|
106
|
+
"user:token@example.com:8443",
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(hostname.hostname).toBe("example.com");
|
|
110
|
+
expect(hostname.port?.toNumber()).toBe(8443);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("splits on the LAST @, since userinfo may contain one", () => {
|
|
114
|
+
const hostname: Hostname = Hostname.fromAuthority(
|
|
115
|
+
"user@name:token@example.com:8443",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(hostname.hostname).toBe("example.com");
|
|
119
|
+
expect(hostname.port?.toNumber()).toBe(8443);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("malformed input", () => {
|
|
124
|
+
test("an empty authority yields an empty hostname rather than throwing", () => {
|
|
125
|
+
expect(Hostname.fromAuthority("").hostname).toBe("");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a trailing colon with no digits is not treated as a port", () => {
|
|
129
|
+
const hostname: Hostname = Hostname.fromAuthority("example.com:");
|
|
130
|
+
|
|
131
|
+
expect(hostname.hostname).toBe("example.com");
|
|
132
|
+
expect(hostname.port).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("surrounding whitespace is trimmed", () => {
|
|
136
|
+
const hostname: Hostname = Hostname.fromAuthority(" example.com:443 ");
|
|
137
|
+
|
|
138
|
+
expect(hostname.hostname).toBe("example.com");
|
|
139
|
+
expect(hostname.port?.toNumber()).toBe(443);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("contrast with fromString", () => {
|
|
144
|
+
test("fromString rejects an IPv6 literal that fromAuthority handles", () => {
|
|
145
|
+
/*
|
|
146
|
+
* Pinned deliberately: it documents WHY fromAuthority exists, and
|
|
147
|
+
* guards against someone 'simplifying' one into the other.
|
|
148
|
+
*
|
|
149
|
+
* fromString splits on the FIRST colon, so "[::1]:8443" becomes a
|
|
150
|
+
* host of "[" - which the Hostname setter rejects outright.
|
|
151
|
+
*/
|
|
152
|
+
expect(Hostname.fromAuthority("[::1]:8443").hostname).toBe("[::1]");
|
|
153
|
+
expect(() => {
|
|
154
|
+
return Hostname.fromString("[::1]:8443");
|
|
155
|
+
}).toThrow();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("both agree on the simple host:port case", () => {
|
|
159
|
+
expect(Hostname.fromAuthority("example.com:8443").hostname).toBe(
|
|
160
|
+
Hostname.fromString("example.com:8443").hostname,
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import DatabaseBaseModel from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
2
|
+
import Incident from "../../../../Models/DatabaseModels/Incident";
|
|
3
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
4
|
+
import Project from "../../../../Models/DatabaseModels/Project";
|
|
5
|
+
import StatusPage from "../../../../Models/DatabaseModels/StatusPage";
|
|
6
|
+
import { ColumnAccessControl } from "../../../../Types/BaseDatabase/AccessControl";
|
|
7
|
+
import ColumnBillingAccessControl from "../../../../Types/BaseDatabase/ColumnBillingAccessControl";
|
|
8
|
+
import { getColumnAccessControlForAllColumns } from "../../../../Types/Database/AccessControl/ColumnAccessControl";
|
|
9
|
+
import { getColumnBillingAccessControlForAllColumns } from "../../../../Types/Database/AccessControl/ColumnBillingAccessControl";
|
|
10
|
+
import Dictionary from "../../../../Types/Dictionary";
|
|
11
|
+
import "reflect-metadata";
|
|
12
|
+
|
|
13
|
+
type ModelCtor = new () => DatabaseBaseModel;
|
|
14
|
+
|
|
15
|
+
const MODELS: Array<[string, ModelCtor]> = [
|
|
16
|
+
["Monitor", Monitor],
|
|
17
|
+
["Incident", Incident],
|
|
18
|
+
["Project", Project],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Monitor and Incident have no billing-decorated columns; StatusPage does.
|
|
22
|
+
const BILLING_MODELS: Array<[string, ModelCtor]> = [["StatusPage", StatusPage]];
|
|
23
|
+
|
|
24
|
+
const DEFAULT_COLUMNS: Array<string> = [
|
|
25
|
+
"_id",
|
|
26
|
+
"createdAt",
|
|
27
|
+
"deletedAt",
|
|
28
|
+
"updatedAt",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Locate the module-private metadata symbol (e.g. Symbol(ColumnAccessControl))
|
|
33
|
+
* by scanning the metadata keys of every instance property, so the tests can
|
|
34
|
+
* rebuild the dictionary from scratch exactly the way the pre-cache
|
|
35
|
+
* implementation did — without importing any private state.
|
|
36
|
+
*/
|
|
37
|
+
function findMetadataSymbol(
|
|
38
|
+
model: DatabaseBaseModel,
|
|
39
|
+
symbolName: string,
|
|
40
|
+
): symbol | null {
|
|
41
|
+
for (const key of Object.keys(model)) {
|
|
42
|
+
const metadataKeys: Array<unknown> = Reflect.getMetadataKeys(
|
|
43
|
+
model,
|
|
44
|
+
key,
|
|
45
|
+
) as Array<unknown>;
|
|
46
|
+
for (const metadataKey of metadataKeys) {
|
|
47
|
+
if (
|
|
48
|
+
typeof metadataKey === "symbol" &&
|
|
49
|
+
metadataKey.toString() === `Symbol(${symbolName})`
|
|
50
|
+
) {
|
|
51
|
+
return metadataKey;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The exact algorithm both helpers used before caching was introduced.
|
|
59
|
+
function rebuildFromReflect<T>(
|
|
60
|
+
model: DatabaseBaseModel,
|
|
61
|
+
symbolName: string,
|
|
62
|
+
): Dictionary<T> {
|
|
63
|
+
const metadataSymbol: symbol | null = findMetadataSymbol(model, symbolName);
|
|
64
|
+
const dictionary: Dictionary<T> = {};
|
|
65
|
+
|
|
66
|
+
if (!metadataSymbol) {
|
|
67
|
+
return dictionary;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const key of Object.keys(model)) {
|
|
71
|
+
const metadata: T | undefined = Reflect.getMetadata(
|
|
72
|
+
metadataSymbol,
|
|
73
|
+
model,
|
|
74
|
+
key,
|
|
75
|
+
) as T | undefined;
|
|
76
|
+
if (metadata) {
|
|
77
|
+
dictionary[key] = metadata;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return dictionary;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rebuildAccessControl(
|
|
85
|
+
model: DatabaseBaseModel,
|
|
86
|
+
): Dictionary<ColumnAccessControl> {
|
|
87
|
+
return rebuildFromReflect<ColumnAccessControl>(model, "ColumnAccessControl");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function rebuildBillingAccessControl(
|
|
91
|
+
model: DatabaseBaseModel,
|
|
92
|
+
): Dictionary<ColumnBillingAccessControl> {
|
|
93
|
+
return rebuildFromReflect<ColumnBillingAccessControl>(
|
|
94
|
+
model,
|
|
95
|
+
"ColumnBillingAccessControl",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe("getColumnAccessControlForAllColumns helper caching", () => {
|
|
100
|
+
for (const [name, ModelClass] of MODELS) {
|
|
101
|
+
test(`matches a from-scratch Reflect rebuild for ${name} — keys, order, and value identity`, () => {
|
|
102
|
+
const model: DatabaseBaseModel = new ModelClass();
|
|
103
|
+
const expected: Dictionary<ColumnAccessControl> =
|
|
104
|
+
rebuildAccessControl(model);
|
|
105
|
+
const actual: Dictionary<ColumnAccessControl> =
|
|
106
|
+
getColumnAccessControlForAllColumns(model);
|
|
107
|
+
|
|
108
|
+
// Sanity: real models have plenty of decorated columns.
|
|
109
|
+
expect(Object.keys(expected).length).toBeGreaterThan(5);
|
|
110
|
+
|
|
111
|
+
expect(Object.keys(actual)).toEqual(Object.keys(expected));
|
|
112
|
+
for (const key of Object.keys(expected)) {
|
|
113
|
+
expect(actual[key]).toBe(expected[key]);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
test("repeated calls return the same dictionary object", () => {
|
|
119
|
+
const monitor: Monitor = new Monitor();
|
|
120
|
+
const first: Dictionary<ColumnAccessControl> =
|
|
121
|
+
getColumnAccessControlForAllColumns(monitor);
|
|
122
|
+
const second: Dictionary<ColumnAccessControl> =
|
|
123
|
+
getColumnAccessControlForAllColumns(monitor);
|
|
124
|
+
|
|
125
|
+
expect(second).toBe(first);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("different instances of the same class share the cached dictionary", () => {
|
|
129
|
+
const first: Dictionary<ColumnAccessControl> =
|
|
130
|
+
getColumnAccessControlForAllColumns(new Monitor());
|
|
131
|
+
const second: Dictionary<ColumnAccessControl> =
|
|
132
|
+
getColumnAccessControlForAllColumns(new Monitor());
|
|
133
|
+
|
|
134
|
+
expect(second).toBe(first);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("second call performs zero Reflect metadata reads", () => {
|
|
138
|
+
const incident: Incident = new Incident();
|
|
139
|
+
getColumnAccessControlForAllColumns(incident); // warm the cache
|
|
140
|
+
|
|
141
|
+
const spy: jest.SpyInstance = jest.spyOn(Reflect, "getMetadata");
|
|
142
|
+
try {
|
|
143
|
+
getColumnAccessControlForAllColumns(incident);
|
|
144
|
+
expect(spy).not.toHaveBeenCalled();
|
|
145
|
+
} finally {
|
|
146
|
+
spy.mockRestore();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("different classes get distinct dictionaries", () => {
|
|
151
|
+
const monitorAccessControl: Dictionary<ColumnAccessControl> =
|
|
152
|
+
getColumnAccessControlForAllColumns(new Monitor());
|
|
153
|
+
const incidentAccessControl: Dictionary<ColumnAccessControl> =
|
|
154
|
+
getColumnAccessControlForAllColumns(new Incident());
|
|
155
|
+
const projectAccessControl: Dictionary<ColumnAccessControl> =
|
|
156
|
+
getColumnAccessControlForAllColumns(new Project());
|
|
157
|
+
|
|
158
|
+
expect(monitorAccessControl).not.toBe(incidentAccessControl);
|
|
159
|
+
expect(monitorAccessControl).not.toBe(projectAccessControl);
|
|
160
|
+
expect(incidentAccessControl).not.toBe(projectAccessControl);
|
|
161
|
+
|
|
162
|
+
expect(monitorAccessControl["monitorType"]).toBeDefined();
|
|
163
|
+
expect(incidentAccessControl["monitorType"]).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("DatabaseBaseModel.getColumnAccessControlForAllColumns override", () => {
|
|
168
|
+
test("adds default columns with record-level permissions, exactly as before", () => {
|
|
169
|
+
const monitor: Monitor = new Monitor();
|
|
170
|
+
const raw: Dictionary<ColumnAccessControl> = rebuildAccessControl(monitor);
|
|
171
|
+
const merged: Dictionary<ColumnAccessControl> =
|
|
172
|
+
monitor.getColumnAccessControlForAllColumns();
|
|
173
|
+
|
|
174
|
+
for (const key of DEFAULT_COLUMNS) {
|
|
175
|
+
expect(merged[key]).toEqual({
|
|
176
|
+
read: monitor.readRecordPermissions,
|
|
177
|
+
create: monitor.createRecordPermissions,
|
|
178
|
+
update: monitor.updateRecordPermissions,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Decorated columns flow through as the exact same Reflect singletons.
|
|
183
|
+
for (const key of Object.keys(raw)) {
|
|
184
|
+
if (!DEFAULT_COLUMNS.includes(key)) {
|
|
185
|
+
expect(merged[key]).toBe(raw[key]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
expect(Object.keys(merged).sort()).toEqual(
|
|
190
|
+
[...Object.keys(raw), ...DEFAULT_COLUMNS].sort(),
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("never pollutes the raw helper dictionary with default columns", () => {
|
|
195
|
+
const monitor: Monitor = new Monitor();
|
|
196
|
+
|
|
197
|
+
const rawKeysBefore: Array<string> = Object.keys(
|
|
198
|
+
getColumnAccessControlForAllColumns(monitor),
|
|
199
|
+
);
|
|
200
|
+
for (const key of DEFAULT_COLUMNS) {
|
|
201
|
+
expect(rawKeysBefore).not.toContain(key);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
monitor.getColumnAccessControlForAllColumns();
|
|
205
|
+
monitor.getColumnAccessControlForAllColumns();
|
|
206
|
+
|
|
207
|
+
/*
|
|
208
|
+
* AuditLogService.getRedactedFields consumes the raw helper result and
|
|
209
|
+
* must never see the record-level defaults the model override adds.
|
|
210
|
+
*/
|
|
211
|
+
const rawAfter: Dictionary<ColumnAccessControl> =
|
|
212
|
+
getColumnAccessControlForAllColumns(new Monitor());
|
|
213
|
+
expect(Object.keys(rawAfter)).toEqual(rawKeysBefore);
|
|
214
|
+
for (const key of DEFAULT_COLUMNS) {
|
|
215
|
+
expect(rawAfter[key]).toBeUndefined();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("mutating one override result does not affect the next call", () => {
|
|
220
|
+
const monitor: Monitor = new Monitor();
|
|
221
|
+
|
|
222
|
+
const first: Dictionary<ColumnAccessControl> =
|
|
223
|
+
monitor.getColumnAccessControlForAllColumns();
|
|
224
|
+
const originalName: ColumnAccessControl = first[
|
|
225
|
+
"name"
|
|
226
|
+
] as ColumnAccessControl;
|
|
227
|
+
expect(originalName).toBeDefined();
|
|
228
|
+
|
|
229
|
+
delete first["name"];
|
|
230
|
+
first["_id"] = { read: [], create: [], update: [] };
|
|
231
|
+
first["injected"] = { read: [], create: [], update: [] };
|
|
232
|
+
|
|
233
|
+
const second: Dictionary<ColumnAccessControl> =
|
|
234
|
+
monitor.getColumnAccessControlForAllColumns();
|
|
235
|
+
|
|
236
|
+
expect(second).not.toBe(first);
|
|
237
|
+
expect(second["name"]).toBe(originalName);
|
|
238
|
+
expect(second["injected"]).toBeUndefined();
|
|
239
|
+
expect(second["_id"]).toEqual({
|
|
240
|
+
read: monitor.readRecordPermissions,
|
|
241
|
+
create: monitor.createRecordPermissions,
|
|
242
|
+
update: monitor.updateRecordPermissions,
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("getColumnAccessControlFor still resolves decorated, default, and unknown columns", () => {
|
|
247
|
+
const monitor: Monitor = new Monitor();
|
|
248
|
+
const raw: Dictionary<ColumnAccessControl> = rebuildAccessControl(monitor);
|
|
249
|
+
|
|
250
|
+
expect(monitor.getColumnAccessControlFor("name")).toBe(raw["name"]);
|
|
251
|
+
expect(monitor.getColumnAccessControlFor("_id")).toEqual({
|
|
252
|
+
read: monitor.readRecordPermissions,
|
|
253
|
+
create: monitor.createRecordPermissions,
|
|
254
|
+
update: monitor.updateRecordPermissions,
|
|
255
|
+
});
|
|
256
|
+
expect(monitor.getColumnAccessControlFor("doesNotExist")).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("getColumnBillingAccessControlForAllColumns helper caching", () => {
|
|
261
|
+
for (const [name, ModelClass] of [...MODELS, ...BILLING_MODELS]) {
|
|
262
|
+
test(`matches a from-scratch Reflect rebuild for ${name} — keys, order, and value identity`, () => {
|
|
263
|
+
const model: DatabaseBaseModel = new ModelClass();
|
|
264
|
+
const expected: Dictionary<ColumnBillingAccessControl> =
|
|
265
|
+
rebuildBillingAccessControl(model);
|
|
266
|
+
const actual: Dictionary<ColumnBillingAccessControl> =
|
|
267
|
+
getColumnBillingAccessControlForAllColumns(model);
|
|
268
|
+
|
|
269
|
+
expect(Object.keys(actual)).toEqual(Object.keys(expected));
|
|
270
|
+
for (const key of Object.keys(expected)) {
|
|
271
|
+
expect(actual[key]).toBe(expected[key]);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
test("Project and StatusPage carry billing-decorated columns; Monitor's dictionary is empty", () => {
|
|
277
|
+
expect(
|
|
278
|
+
Object.keys(getColumnBillingAccessControlForAllColumns(new Project()))
|
|
279
|
+
.length,
|
|
280
|
+
).toBeGreaterThan(0);
|
|
281
|
+
expect(
|
|
282
|
+
Object.keys(getColumnBillingAccessControlForAllColumns(new StatusPage()))
|
|
283
|
+
.length,
|
|
284
|
+
).toBeGreaterThan(0);
|
|
285
|
+
expect(getColumnBillingAccessControlForAllColumns(new Monitor())).toEqual(
|
|
286
|
+
{},
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("repeated calls return the same dictionary object", () => {
|
|
291
|
+
const project: Project = new Project();
|
|
292
|
+
const first: Dictionary<ColumnBillingAccessControl> =
|
|
293
|
+
getColumnBillingAccessControlForAllColumns(project);
|
|
294
|
+
const second: Dictionary<ColumnBillingAccessControl> =
|
|
295
|
+
getColumnBillingAccessControlForAllColumns(new Project());
|
|
296
|
+
|
|
297
|
+
expect(second).toBe(first);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("second call performs zero Reflect metadata reads", () => {
|
|
301
|
+
const project: Project = new Project();
|
|
302
|
+
getColumnBillingAccessControlForAllColumns(project); // warm the cache
|
|
303
|
+
|
|
304
|
+
const spy: jest.SpyInstance = jest.spyOn(Reflect, "getMetadata");
|
|
305
|
+
try {
|
|
306
|
+
getColumnBillingAccessControlForAllColumns(project);
|
|
307
|
+
expect(spy).not.toHaveBeenCalled();
|
|
308
|
+
} finally {
|
|
309
|
+
spy.mockRestore();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("different classes get distinct dictionaries", () => {
|
|
314
|
+
const statusPageBilling: Dictionary<ColumnBillingAccessControl> =
|
|
315
|
+
getColumnBillingAccessControlForAllColumns(new StatusPage());
|
|
316
|
+
const projectBilling: Dictionary<ColumnBillingAccessControl> =
|
|
317
|
+
getColumnBillingAccessControlForAllColumns(new Project());
|
|
318
|
+
|
|
319
|
+
expect(statusPageBilling).not.toBe(projectBilling);
|
|
320
|
+
expect(Object.keys(statusPageBilling)).not.toEqual(
|
|
321
|
+
Object.keys(projectBilling),
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("model getColumnBillingAccessControl reads without mutating the cached dictionary", () => {
|
|
326
|
+
const project: Project = new Project();
|
|
327
|
+
const expected: Dictionary<ColumnBillingAccessControl> =
|
|
328
|
+
rebuildBillingAccessControl(project);
|
|
329
|
+
const keysBefore: Array<string> = Object.keys(
|
|
330
|
+
getColumnBillingAccessControlForAllColumns(project),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
for (const key of keysBefore) {
|
|
334
|
+
expect(project.getColumnBillingAccessControl(key)).toBe(expected[key]);
|
|
335
|
+
}
|
|
336
|
+
expect(project.getColumnBillingAccessControl("doesNotExist")).toBe(
|
|
337
|
+
undefined,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
expect(
|
|
341
|
+
Object.keys(getColumnBillingAccessControlForAllColumns(new Project())),
|
|
342
|
+
).toEqual(keysBefore);
|
|
343
|
+
});
|
|
344
|
+
});
|