@oneuptime/common 12.0.0 → 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/OnCallDutyPolicyScheduleLayer.ts +12 -2
- 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/LocalMigrationGenerationDataSource.ts +12 -2
- 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/LlmProviderService.ts +69 -9
- 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/RunnerService.ts +1 -7
- 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/Server/Utils/Monitor/Criteria/CompareCriteria.ts +12 -0
- package/Tests/App/Dashboard/RunnerInstallInstructions.test.tsx +119 -0
- package/Tests/App/Dashboard/RunnerStatus.test.tsx +274 -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/LlmProviderUsableByProject.test.ts +293 -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/AI/ToolArgsExtractors.test.ts +141 -0
- package/Tests/Server/Utils/AI/Toolbox/WidgetBuilder.test.ts +205 -0
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +897 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Runner/RunnerLiveStatus.test.ts +320 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +285 -0
- package/Tests/UI/Components/Detail/EntityFields.test.tsx +166 -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/Runbook/RunbookStep.ts +15 -0
- package/Types/Runner/RunnerLiveStatus.ts +114 -0
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/Detail/Detail.tsx +155 -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/OnCallDutyPolicyScheduleLayer.js +16 -4
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.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/LocalMigrationGenerationDataSource.js +11 -1
- package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.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/LlmProviderService.js +61 -8
- package/build/dist/Server/Services/LlmProviderService.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/RunnerService.js +1 -6
- package/build/dist/Server/Services/RunnerService.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/Server/Utils/Monitor/Criteria/CompareCriteria.js +11 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- 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/Runbook/RunbookStep.js.map +1 -1
- package/build/dist/Types/Runner/RunnerLiveStatus.js +69 -0
- package/build/dist/Types/Runner/RunnerLiveStatus.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +97 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- 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,293 @@
|
|
|
1
|
+
import LlmProviderService from "../../../Server/Services/LlmProviderService";
|
|
2
|
+
import LlmProvider from "../../../Models/DatabaseModels/LlmProvider";
|
|
3
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
4
|
+
import Select from "../../../Server/Types/Database/Select";
|
|
5
|
+
import { describe, expect, test, afterEach } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* isProviderUsableByProject answers "may this project run against this
|
|
9
|
+
* provider id?" for callers that hold a caller-supplied id — today the
|
|
10
|
+
* runbook AI step's optional provider pin, whose id comes out of an
|
|
11
|
+
* unvalidated JSON column.
|
|
12
|
+
*
|
|
13
|
+
* The invariant: a project may use a global provider or one it owns, and
|
|
14
|
+
* nothing else. A provider carries an apiKey, so a yes here for another
|
|
15
|
+
* project's row would hand project A the use of project B's key.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
19
|
+
const otherProjectId: ObjectID = ObjectID.generate();
|
|
20
|
+
const providerId: ObjectID = ObjectID.generate();
|
|
21
|
+
|
|
22
|
+
function fakeProvider(overrides: Partial<LlmProvider> = {}): LlmProvider {
|
|
23
|
+
return {
|
|
24
|
+
id: providerId,
|
|
25
|
+
...overrides,
|
|
26
|
+
} as unknown as LlmProvider;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("LlmProviderService.isProviderUsableByProject", () => {
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
jest.restoreAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("accepts a provider the project owns", async () => {
|
|
35
|
+
jest
|
|
36
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
37
|
+
.mockResolvedValue(fakeProvider({ projectId, isGlobalLlm: false }));
|
|
38
|
+
|
|
39
|
+
await expect(
|
|
40
|
+
LlmProviderService.isProviderUsableByProject({
|
|
41
|
+
projectId,
|
|
42
|
+
llmProviderId: providerId,
|
|
43
|
+
}),
|
|
44
|
+
).resolves.toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("accepts a global provider, which is shared with every project", async () => {
|
|
48
|
+
jest
|
|
49
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
50
|
+
.mockResolvedValue(fakeProvider({ isGlobalLlm: true }));
|
|
51
|
+
|
|
52
|
+
await expect(
|
|
53
|
+
LlmProviderService.isProviderUsableByProject({
|
|
54
|
+
projectId,
|
|
55
|
+
llmProviderId: providerId,
|
|
56
|
+
}),
|
|
57
|
+
).resolves.toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("rejects a provider owned by a different project", async () => {
|
|
61
|
+
jest.spyOn(LlmProviderService, "findOneBy").mockResolvedValue(
|
|
62
|
+
fakeProvider({
|
|
63
|
+
projectId: otherProjectId,
|
|
64
|
+
isGlobalLlm: false,
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
await expect(
|
|
69
|
+
LlmProviderService.isProviderUsableByProject({
|
|
70
|
+
projectId,
|
|
71
|
+
llmProviderId: providerId,
|
|
72
|
+
}),
|
|
73
|
+
).resolves.toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("rejects a provider that no longer exists", async () => {
|
|
77
|
+
jest.spyOn(LlmProviderService, "findOneBy").mockResolvedValue(null);
|
|
78
|
+
|
|
79
|
+
await expect(
|
|
80
|
+
LlmProviderService.isProviderUsableByProject({
|
|
81
|
+
projectId,
|
|
82
|
+
llmProviderId: providerId,
|
|
83
|
+
}),
|
|
84
|
+
).resolves.toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("rejects a provider with no owner and no global flag", async () => {
|
|
88
|
+
/*
|
|
89
|
+
* A row that is neither global nor owned matches nobody. Treating an
|
|
90
|
+
* absent projectId as a match would make an orphaned row usable by every
|
|
91
|
+
* project at once.
|
|
92
|
+
*/
|
|
93
|
+
jest
|
|
94
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
95
|
+
.mockResolvedValue(fakeProvider({ isGlobalLlm: false }));
|
|
96
|
+
|
|
97
|
+
await expect(
|
|
98
|
+
LlmProviderService.isProviderUsableByProject({
|
|
99
|
+
projectId,
|
|
100
|
+
llmProviderId: providerId,
|
|
101
|
+
}),
|
|
102
|
+
).resolves.toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("rejects a non-uuid id without ever touching the database", async () => {
|
|
106
|
+
/*
|
|
107
|
+
* _id is a uuid column: querying it with garbage is a Postgres cast
|
|
108
|
+
* error, not an empty result. The id arrives from an unvalidated JSON
|
|
109
|
+
* config, so the shape check has to happen before the query — otherwise
|
|
110
|
+
* a typo in a runbook surfaces as a driver error during an incident.
|
|
111
|
+
*/
|
|
112
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
113
|
+
jest.spyOn(LlmProviderService, "findOneBy");
|
|
114
|
+
|
|
115
|
+
await expect(
|
|
116
|
+
LlmProviderService.isProviderUsableByProject({
|
|
117
|
+
projectId,
|
|
118
|
+
llmProviderId: new ObjectID("not-a-uuid"),
|
|
119
|
+
}),
|
|
120
|
+
).resolves.toBe(false);
|
|
121
|
+
|
|
122
|
+
expect(findSpy).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("rejects an empty id without touching the database", async () => {
|
|
126
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
127
|
+
jest.spyOn(LlmProviderService, "findOneBy");
|
|
128
|
+
|
|
129
|
+
await expect(
|
|
130
|
+
LlmProviderService.isProviderUsableByProject({
|
|
131
|
+
projectId,
|
|
132
|
+
llmProviderId: new ObjectID(""),
|
|
133
|
+
}),
|
|
134
|
+
).resolves.toBe(false);
|
|
135
|
+
|
|
136
|
+
expect(findSpy).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("never selects the apiKey — a yes/no must not load secrets", async () => {
|
|
140
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
141
|
+
jest
|
|
142
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
143
|
+
.mockResolvedValue(fakeProvider({ projectId }));
|
|
144
|
+
|
|
145
|
+
await LlmProviderService.isProviderUsableByProject({
|
|
146
|
+
projectId,
|
|
147
|
+
llmProviderId: providerId,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const select: Select<LlmProvider> = findSpy.mock.calls[0]![0]!
|
|
151
|
+
.select as Select<LlmProvider>;
|
|
152
|
+
expect(select["apiKey"]).toBeUndefined();
|
|
153
|
+
expect(select["projectId"]).toBe(true);
|
|
154
|
+
expect(select["isGlobalLlm"]).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("looks the provider up by id alone, so a global row is reachable", async () => {
|
|
158
|
+
/*
|
|
159
|
+
* Scoping the query by projectId would silently exclude global
|
|
160
|
+
* providers (their projectId is NULL) — the tenant decision belongs in
|
|
161
|
+
* the check on the loaded row, not in the query.
|
|
162
|
+
*/
|
|
163
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
164
|
+
jest
|
|
165
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
166
|
+
.mockResolvedValue(fakeProvider({ isGlobalLlm: true }));
|
|
167
|
+
|
|
168
|
+
await LlmProviderService.isProviderUsableByProject({
|
|
169
|
+
projectId,
|
|
170
|
+
llmProviderId: providerId,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const query: Record<string, unknown> = findSpy.mock.calls[0]![0]!
|
|
174
|
+
.query as Record<string, unknown>;
|
|
175
|
+
expect(query["_id"]).toBe(providerId.toString());
|
|
176
|
+
expect(query["projectId"]).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* getProviderForChat shares the same usability predicate. Its contract is the
|
|
182
|
+
* opposite of the runbook step's on failure — a human is watching a chat, so
|
|
183
|
+
* an unusable id falls back to the default instead of erroring — and these
|
|
184
|
+
* tests pin that difference down so a future refactor of the shared predicate
|
|
185
|
+
* cannot quietly swap one behaviour for the other.
|
|
186
|
+
*/
|
|
187
|
+
describe("LlmProviderService.getProviderForChat", () => {
|
|
188
|
+
afterEach(() => {
|
|
189
|
+
jest.restoreAllMocks();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("uses an explicitly chosen provider the project owns", async () => {
|
|
193
|
+
const chosen: LlmProvider = fakeProvider({ projectId });
|
|
194
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
195
|
+
jest.spyOn(LlmProviderService, "findOneBy").mockResolvedValue(chosen);
|
|
196
|
+
|
|
197
|
+
const result: LlmProvider | null =
|
|
198
|
+
await LlmProviderService.getProviderForChat({
|
|
199
|
+
projectId,
|
|
200
|
+
llmProviderId: providerId,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result).toBe(chosen);
|
|
204
|
+
expect(findSpy).toHaveBeenCalledTimes(1);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("uses an explicitly chosen global provider", async () => {
|
|
208
|
+
const chosen: LlmProvider = fakeProvider({ isGlobalLlm: true });
|
|
209
|
+
jest.spyOn(LlmProviderService, "findOneBy").mockResolvedValue(chosen);
|
|
210
|
+
|
|
211
|
+
await expect(
|
|
212
|
+
LlmProviderService.getProviderForChat({
|
|
213
|
+
projectId,
|
|
214
|
+
llmProviderId: providerId,
|
|
215
|
+
}),
|
|
216
|
+
).resolves.toBe(chosen);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("falls back to the project default when the chosen provider belongs to another project", async () => {
|
|
220
|
+
const foreign: LlmProvider = fakeProvider({ projectId: otherProjectId });
|
|
221
|
+
const projectDefault: LlmProvider = fakeProvider({ projectId });
|
|
222
|
+
jest
|
|
223
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
224
|
+
.mockResolvedValueOnce(foreign)
|
|
225
|
+
.mockResolvedValueOnce(projectDefault);
|
|
226
|
+
|
|
227
|
+
const result: LlmProvider | null =
|
|
228
|
+
await LlmProviderService.getProviderForChat({
|
|
229
|
+
projectId,
|
|
230
|
+
llmProviderId: providerId,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(result).toBe(projectDefault);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("falls back when the chosen provider no longer exists", async () => {
|
|
237
|
+
const projectDefault: LlmProvider = fakeProvider({ projectId });
|
|
238
|
+
jest
|
|
239
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
240
|
+
.mockResolvedValueOnce(null)
|
|
241
|
+
.mockResolvedValueOnce(projectDefault);
|
|
242
|
+
|
|
243
|
+
await expect(
|
|
244
|
+
LlmProviderService.getProviderForChat({
|
|
245
|
+
projectId,
|
|
246
|
+
llmProviderId: providerId,
|
|
247
|
+
}),
|
|
248
|
+
).resolves.toBe(projectDefault);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("a non-uuid choice falls back instead of hitting the database with it", async () => {
|
|
252
|
+
/*
|
|
253
|
+
* Without the shape check this query is a Postgres cast error rather
|
|
254
|
+
* than a miss, so a stale or hand-edited id would break a whole
|
|
255
|
+
* conversation instead of quietly resolving to the default.
|
|
256
|
+
*/
|
|
257
|
+
const projectDefault: LlmProvider = fakeProvider({ projectId });
|
|
258
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
259
|
+
jest
|
|
260
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
261
|
+
.mockResolvedValue(projectDefault);
|
|
262
|
+
|
|
263
|
+
const result: LlmProvider | null =
|
|
264
|
+
await LlmProviderService.getProviderForChat({
|
|
265
|
+
projectId,
|
|
266
|
+
llmProviderId: new ObjectID("not-a-uuid"),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(result).toBe(projectDefault);
|
|
270
|
+
// Only the default-resolution query ran — never one keyed on the garbage id.
|
|
271
|
+
for (const call of findSpy.mock.calls) {
|
|
272
|
+
expect(
|
|
273
|
+
(call[0]!.query as Record<string, unknown>)["_id"],
|
|
274
|
+
).toBeUndefined();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("with no choice at all it goes straight to default resolution", async () => {
|
|
279
|
+
const projectDefault: LlmProvider = fakeProvider({ projectId });
|
|
280
|
+
const findSpy: jest.SpiedFunction<typeof LlmProviderService.findOneBy> =
|
|
281
|
+
jest
|
|
282
|
+
.spyOn(LlmProviderService, "findOneBy")
|
|
283
|
+
.mockResolvedValue(projectDefault);
|
|
284
|
+
|
|
285
|
+
await expect(
|
|
286
|
+
LlmProviderService.getProviderForChat({ projectId }),
|
|
287
|
+
).resolves.toBe(projectDefault);
|
|
288
|
+
|
|
289
|
+
const query: Record<string, unknown> = findSpy.mock.calls[0]![0]!
|
|
290
|
+
.query as Record<string, unknown>;
|
|
291
|
+
expect(query["isDefault"]).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
});
|