@oneuptime/common 11.3.26 → 11.3.28
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/IncidentEpisodeRoleMember.ts +1 -1
- package/Models/DatabaseModels/IncidentMember.ts +1 -1
- package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/MigrationFailure.ts +170 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
- package/Server/API/AlertAPI.ts +1 -1
- package/Server/API/IncidentAPI.ts +2 -2
- package/Server/API/IncidentEpisodeAPI.ts +1 -1
- package/Server/API/LlmProviderAPI.ts +1 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/PostgresDatabase.ts +11 -0
- package/Server/Services/AIService.ts +74 -2
- package/Server/Services/AnalyticsDatabaseService.ts +10 -1
- package/Server/Services/DatabaseService.ts +10 -1
- package/Server/Services/IncidentService.ts +1 -1
- package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
- package/Server/Services/IncomingCallPolicyService.ts +42 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MigrationFailureService.ts +9 -0
- package/Server/Services/MonitorProbeService.ts +33 -7
- package/Server/Services/MonitorService.ts +19 -7
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
- package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
- package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
- package/Server/Services/TeamMemberService.ts +6 -0
- package/Server/Services/UserNotificationRuleService.ts +13 -42
- package/Server/Services/UserOnCallLogService.ts +90 -0
- package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
- package/Server/Types/Database/QueryUtil.ts +57 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
- package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
- package/Server/Utils/AI/Toolbox/Index.ts +4 -1
- package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
- package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
- package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
- package/Server/Utils/LLM/LLMService.ts +110 -11
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
- package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
- package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
- package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
- package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
- package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
- package/Tests/Types/Events/Recurring.test.ts +22 -9
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
- package/Types/Date.ts +200 -15
- package/Types/OnCallDutyPolicy/Layer.ts +790 -260
- package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
- package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
- package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
- package/build/dist/Server/API/AlertAPI.js +1 -1
- package/build/dist/Server/API/IncidentAPI.js +2 -2
- package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +1 -0
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.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/Infrastructure/PostgresDatabase.js +9 -0
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +53 -2
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MigrationFailureService.js +9 -0
- package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
- package/build/dist/Server/Services/MonitorProbeService.js +33 -7
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +12 -5
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +6 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +4 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Types/Date.js +153 -15
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
- package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- package/package.json +1 -1
|
@@ -49,6 +49,13 @@ export interface LLMUsage {
|
|
|
49
49
|
promptTokens: number;
|
|
50
50
|
completionTokens: number;
|
|
51
51
|
totalTokens: number;
|
|
52
|
+
/*
|
|
53
|
+
* Prompt-caching breakdown, when the provider reports it. cachedInputTokens
|
|
54
|
+
* are input tokens served from cache (billed at a large discount);
|
|
55
|
+
* cacheCreationTokens are input tokens written to the cache on this call.
|
|
56
|
+
*/
|
|
57
|
+
cachedInputTokens?: number | undefined;
|
|
58
|
+
cacheCreationTokens?: number | undefined;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
export interface LLMCompletionResponse {
|
|
@@ -237,6 +244,17 @@ export default class LLMService {
|
|
|
237
244
|
const toolCalls: Array<LLMToolCall> | undefined =
|
|
238
245
|
this.parseOpenAIToolCalls(message);
|
|
239
246
|
|
|
247
|
+
/*
|
|
248
|
+
* OpenAI (and Azure OpenAI) automatically cache a stable prompt prefix
|
|
249
|
+
* once it is long enough and report the cache hit under
|
|
250
|
+
* prompt_tokens_details.cached_tokens — surface it for cost visibility.
|
|
251
|
+
*/
|
|
252
|
+
const cachedTokens: number | undefined = usage
|
|
253
|
+
? ((usage["prompt_tokens_details"] as JSONObject | undefined)?.[
|
|
254
|
+
"cached_tokens"
|
|
255
|
+
] as number | undefined)
|
|
256
|
+
: undefined;
|
|
257
|
+
|
|
240
258
|
return {
|
|
241
259
|
content: (message["content"] as string) || "",
|
|
242
260
|
toolCalls: toolCalls,
|
|
@@ -246,6 +264,7 @@ export default class LLMService {
|
|
|
246
264
|
promptTokens: usage["prompt_tokens"] as number,
|
|
247
265
|
completionTokens: usage["completion_tokens"] as number,
|
|
248
266
|
totalTokens: usage["total_tokens"] as number,
|
|
267
|
+
cachedInputTokens: cachedTokens || undefined,
|
|
249
268
|
}
|
|
250
269
|
: undefined,
|
|
251
270
|
};
|
|
@@ -565,6 +584,32 @@ export default class LLMService {
|
|
|
565
584
|
continue;
|
|
566
585
|
}
|
|
567
586
|
|
|
587
|
+
/*
|
|
588
|
+
* Anthropic requires strictly alternating user/assistant turns and
|
|
589
|
+
* returns a 400 on two consecutive same-role messages. The agent loop
|
|
590
|
+
* can legitimately emit back-to-back user turns (e.g. a tool_result
|
|
591
|
+
* user message immediately followed by the "budget exhausted, answer
|
|
592
|
+
* now" nudge), so coalesce a run of same-role messages into one instead
|
|
593
|
+
* of failing the whole request.
|
|
594
|
+
*/
|
|
595
|
+
const previousMessage: JSONObject | undefined =
|
|
596
|
+
anthropicMessages[anthropicMessages.length - 1];
|
|
597
|
+
|
|
598
|
+
if (previousMessage && previousMessage["role"] === msg.role) {
|
|
599
|
+
if (typeof previousMessage["content"] === "string") {
|
|
600
|
+
previousMessage["content"] =
|
|
601
|
+
`${previousMessage["content"] as string}\n\n${msg.content}`;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (Array.isArray(previousMessage["content"])) {
|
|
605
|
+
(previousMessage["content"] as Array<JSONObject>).push({
|
|
606
|
+
type: "text",
|
|
607
|
+
text: msg.content,
|
|
608
|
+
});
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
568
613
|
anthropicMessages.push({
|
|
569
614
|
role: msg.role,
|
|
570
615
|
content: msg.content,
|
|
@@ -602,18 +647,43 @@ export default class LLMService {
|
|
|
602
647
|
max_tokens: request.maxTokens || LLMService.ANTHROPIC_DEFAULT_MAX_TOKENS,
|
|
603
648
|
};
|
|
604
649
|
|
|
650
|
+
/*
|
|
651
|
+
* Prompt caching. The system prompt and tool definitions are the large,
|
|
652
|
+
* stable prefix re-sent on every turn of the agent loop, so caching them is
|
|
653
|
+
* the biggest single cost + latency lever (cached input is billed at ~10%).
|
|
654
|
+
* An ephemeral cache_control breakpoint on the system block and on the LAST
|
|
655
|
+
* tool caches the whole system + tools prefix. cache_control is GA under
|
|
656
|
+
* anthropic-version 2023-06-01, so no beta header is required.
|
|
657
|
+
*/
|
|
605
658
|
if (systemMessage) {
|
|
606
|
-
requestData["system"] =
|
|
659
|
+
requestData["system"] = [
|
|
660
|
+
{
|
|
661
|
+
type: "text",
|
|
662
|
+
text: systemMessage,
|
|
663
|
+
cache_control: { type: "ephemeral" },
|
|
664
|
+
},
|
|
665
|
+
];
|
|
607
666
|
}
|
|
608
667
|
|
|
609
668
|
if (request.tools && request.tools.length > 0) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
669
|
+
const anthropicTools: Array<JSONObject> = request.tools.map(
|
|
670
|
+
(tool: LLMToolDefinition) => {
|
|
671
|
+
return {
|
|
672
|
+
name: tool.name,
|
|
673
|
+
description: tool.description,
|
|
674
|
+
input_schema: tool.inputSchema,
|
|
675
|
+
};
|
|
676
|
+
},
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
// A breakpoint on the last tool caches the entire tools block before it.
|
|
680
|
+
const lastTool: JSONObject | undefined =
|
|
681
|
+
anthropicTools[anthropicTools.length - 1];
|
|
682
|
+
if (lastTool) {
|
|
683
|
+
lastTool["cache_control"] = { type: "ephemeral" };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
requestData["tools"] = anthropicTools;
|
|
617
687
|
}
|
|
618
688
|
|
|
619
689
|
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
@@ -685,10 +755,21 @@ export default class LLMService {
|
|
|
685
755
|
stopReason: jsonData["stop_reason"] === "tool_use" ? "tool_use" : "stop",
|
|
686
756
|
usage: usage
|
|
687
757
|
? {
|
|
688
|
-
promptTokens: usage["input_tokens"] as number,
|
|
689
|
-
completionTokens: usage["output_tokens"] as number,
|
|
758
|
+
promptTokens: (usage["input_tokens"] as number) || 0,
|
|
759
|
+
completionTokens: (usage["output_tokens"] as number) || 0,
|
|
760
|
+
/*
|
|
761
|
+
* Anthropic reports cache-read and cache-creation input tokens
|
|
762
|
+
* separately from input_tokens. Surface them and fold them into
|
|
763
|
+
* totalTokens so the logged total reflects the full billable input.
|
|
764
|
+
*/
|
|
765
|
+
cachedInputTokens:
|
|
766
|
+
(usage["cache_read_input_tokens"] as number) || undefined,
|
|
767
|
+
cacheCreationTokens:
|
|
768
|
+
(usage["cache_creation_input_tokens"] as number) || undefined,
|
|
690
769
|
totalTokens:
|
|
691
770
|
((usage["input_tokens"] as number) || 0) +
|
|
771
|
+
((usage["cache_read_input_tokens"] as number) || 0) +
|
|
772
|
+
((usage["cache_creation_input_tokens"] as number) || 0) +
|
|
692
773
|
((usage["output_tokens"] as number) || 0),
|
|
693
774
|
}
|
|
694
775
|
: undefined,
|
|
@@ -812,11 +893,29 @@ export default class LLMService {
|
|
|
812
893
|
});
|
|
813
894
|
}
|
|
814
895
|
|
|
896
|
+
/*
|
|
897
|
+
* Ollama reports token counts on the final /api/chat response as
|
|
898
|
+
* prompt_eval_count (input) and eval_count (output). Populate usage from
|
|
899
|
+
* them so LlmLog, the AI dashboards and (costed self-hosted Ollama)
|
|
900
|
+
* billing are not silently blind to token spend.
|
|
901
|
+
*/
|
|
902
|
+
const ollamaPromptTokens: number =
|
|
903
|
+
(jsonData["prompt_eval_count"] as number) || 0;
|
|
904
|
+
const ollamaCompletionTokens: number =
|
|
905
|
+
(jsonData["eval_count"] as number) || 0;
|
|
906
|
+
|
|
815
907
|
return {
|
|
816
908
|
content: (message["content"] as string) || "",
|
|
817
909
|
toolCalls: toolCalls,
|
|
818
910
|
stopReason: toolCalls && toolCalls.length > 0 ? "tool_use" : "stop",
|
|
819
|
-
usage:
|
|
911
|
+
usage:
|
|
912
|
+
ollamaPromptTokens || ollamaCompletionTokens
|
|
913
|
+
? {
|
|
914
|
+
promptTokens: ollamaPromptTokens,
|
|
915
|
+
completionTokens: ollamaCompletionTokens,
|
|
916
|
+
totalTokens: ollamaPromptTokens + ollamaCompletionTokens,
|
|
917
|
+
}
|
|
918
|
+
: undefined,
|
|
820
919
|
};
|
|
821
920
|
}
|
|
822
921
|
}
|
|
@@ -141,31 +141,34 @@ function parseUnixNanoToDate(
|
|
|
141
141
|
value: string | number | undefined,
|
|
142
142
|
context: string,
|
|
143
143
|
): Date {
|
|
144
|
-
let numericValue: number = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
145
|
-
|
|
146
144
|
if (value !== undefined && value !== null) {
|
|
147
145
|
try {
|
|
148
146
|
if (typeof value === "string") {
|
|
149
|
-
const
|
|
150
|
-
if (isNaN(
|
|
147
|
+
const trimmed: string = value.trim();
|
|
148
|
+
if (trimmed === "" || isNaN(Number(trimmed))) {
|
|
151
149
|
throw new Error(`Invalid timestamp string: ${value}`);
|
|
152
150
|
}
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
/*
|
|
152
|
+
* Hand the raw nanosecond string to fromUnixNano so it can divide
|
|
153
|
+
* with BigInt — nanosecond epochs exceed Number.MAX_SAFE_INTEGER, so
|
|
154
|
+
* parsing to a float here would drop up to a full millisecond.
|
|
155
|
+
*/
|
|
156
|
+
return OneUptimeDate.fromUnixNano(trimmed);
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === "number") {
|
|
155
159
|
if (!Number.isFinite(value)) {
|
|
156
160
|
throw new Error(`Invalid timestamp number: ${value}`);
|
|
157
161
|
}
|
|
158
|
-
|
|
162
|
+
return OneUptimeDate.fromUnixNano(value);
|
|
159
163
|
}
|
|
160
164
|
} catch (error) {
|
|
161
165
|
logger.warn(
|
|
162
166
|
`Error processing ${context}: ${error instanceof Error ? error.message : String(error)}, using current time`,
|
|
163
167
|
);
|
|
164
|
-
numericValue = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
return OneUptimeDate.
|
|
171
|
+
return OneUptimeDate.getCurrentDate();
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
// Same trim-or-null read contract as OtelIngestBaseService.getStringAttribute.
|
|
@@ -242,31 +242,34 @@ function parseUnixNanoToDate(
|
|
|
242
242
|
value: string | number | undefined,
|
|
243
243
|
context: string,
|
|
244
244
|
): Date {
|
|
245
|
-
let numericValue: number = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
246
|
-
|
|
247
245
|
if (value !== undefined && value !== null) {
|
|
248
246
|
try {
|
|
249
247
|
if (typeof value === "string") {
|
|
250
|
-
const
|
|
251
|
-
if (isNaN(
|
|
248
|
+
const trimmed: string = value.trim();
|
|
249
|
+
if (trimmed === "" || isNaN(Number(trimmed))) {
|
|
252
250
|
throw new Error(`Invalid timestamp string: ${value}`);
|
|
253
251
|
}
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
/*
|
|
253
|
+
* Hand the raw nanosecond string to fromUnixNano so it can divide
|
|
254
|
+
* with BigInt — nanosecond epochs exceed Number.MAX_SAFE_INTEGER, so
|
|
255
|
+
* parsing to a float here would drop up to a full millisecond.
|
|
256
|
+
*/
|
|
257
|
+
return OneUptimeDate.fromUnixNano(trimmed);
|
|
258
|
+
}
|
|
259
|
+
if (typeof value === "number") {
|
|
256
260
|
if (!Number.isFinite(value)) {
|
|
257
261
|
throw new Error(`Invalid timestamp number: ${value}`);
|
|
258
262
|
}
|
|
259
|
-
|
|
263
|
+
return OneUptimeDate.fromUnixNano(value);
|
|
260
264
|
}
|
|
261
265
|
} catch (error) {
|
|
262
266
|
logger.warn(
|
|
263
267
|
`Error processing ${context}: ${error instanceof Error ? error.message : String(error)}, using current time`,
|
|
264
268
|
);
|
|
265
|
-
numericValue = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
266
269
|
}
|
|
267
270
|
}
|
|
268
271
|
|
|
269
|
-
return OneUptimeDate.
|
|
272
|
+
return OneUptimeDate.getCurrentDate();
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
// Same trim-or-null read contract as OtelIngestBaseService.getStringAttribute.
|
|
@@ -234,14 +234,6 @@ describe("AnalyticsDatabaseService", () => {
|
|
|
234
234
|
jest.spyOn(logger, "error").mockImplementation(() => {
|
|
235
235
|
return undefined!;
|
|
236
236
|
});
|
|
237
|
-
|
|
238
|
-
/*
|
|
239
|
-
* The base getException method is async and throws without await in the
|
|
240
|
-
* catch block, creating unhandled rejections. Override to throw synchronously.
|
|
241
|
-
*/
|
|
242
|
-
(service as any).getException = (error: Error): never => {
|
|
243
|
-
throw error;
|
|
244
|
-
};
|
|
245
237
|
});
|
|
246
238
|
|
|
247
239
|
afterEach(() => {
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import OnCallDutyPolicyScheduleLayerUserService from "../../../Server/Services/OnCallDutyPolicyScheduleLayerUserService";
|
|
2
|
+
import Model from "../../../Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser";
|
|
3
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
4
|
+
import logger from "../../../Server/Utils/Logger";
|
|
5
|
+
import { describe, expect, test, afterEach } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* OnCallDutyPolicyScheduleLayerUserService.onBeforeUpdate re-sequences the
|
|
9
|
+
* `order` of the other rows in a schedule layer when a user is dragged to a new
|
|
10
|
+
* position. Audit finding L3: the "moving down" branch (newOrder > currentOrder)
|
|
11
|
+
* decremented EVERY row with `order <= newOrder`, including rows ABOVE the moved
|
|
12
|
+
* user. Dragging a user downward therefore drove the top row's order to 0 (and
|
|
13
|
+
* negative after repeated down-drags) and opened a gap at 1, breaking the
|
|
14
|
+
* 1-based contiguous invariant that create-default (count+1) and delete
|
|
15
|
+
* re-sequencing depend on.
|
|
16
|
+
*
|
|
17
|
+
* The fix adds the missing lower bound `resource.order > currentOrder`, so only
|
|
18
|
+
* rows strictly between the old and new position shift — mirroring the
|
|
19
|
+
* double-bounded "moving up" branch (`order >= newOrder && order < currentOrder`).
|
|
20
|
+
*
|
|
21
|
+
* These tests stub the three persistence helpers onBeforeUpdate calls
|
|
22
|
+
* (findOneBy -> moved row's current order + layer id; findBy -> every row in the
|
|
23
|
+
* layer; updateOneBy -> the per-row shift) and CAPTURE each updateOneBy so we can
|
|
24
|
+
* assert exactly which rows moved and to what order. No Postgres involved.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const service: any = OnCallDutyPolicyScheduleLayerUserService as any;
|
|
28
|
+
|
|
29
|
+
// A schedule layer id shared by every row in these single-layer scenarios.
|
|
30
|
+
const LAYER_ID: ObjectID = new ObjectID("layer1");
|
|
31
|
+
|
|
32
|
+
const USER_A: ObjectID = new ObjectID("A");
|
|
33
|
+
const USER_B: ObjectID = new ObjectID("B");
|
|
34
|
+
const USER_C: ObjectID = new ObjectID("C");
|
|
35
|
+
|
|
36
|
+
// A captured updateOneBy shift: which row and what order it was set to.
|
|
37
|
+
interface CapturedShift {
|
|
38
|
+
id: string;
|
|
39
|
+
order: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Build a fake persisted row for the layer.
|
|
43
|
+
function row(id: ObjectID, order: number): Model {
|
|
44
|
+
return {
|
|
45
|
+
_id: id,
|
|
46
|
+
order,
|
|
47
|
+
onCallDutyPolicyScheduleLayerId: LAYER_ID,
|
|
48
|
+
} as unknown as Model;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Build the UpdateBy passed to onBeforeUpdate for moving `movedId` to `newOrder`.
|
|
52
|
+
function updateBy(movedId: ObjectID, newOrder: number): any {
|
|
53
|
+
return {
|
|
54
|
+
data: { order: newOrder },
|
|
55
|
+
query: { _id: movedId },
|
|
56
|
+
props: {},
|
|
57
|
+
} as any;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Stub findOneBy (moved row lookup), findBy (all rows in the layer) and
|
|
62
|
+
* updateOneBy (record the shift). Returns the array captured shifts are pushed
|
|
63
|
+
* into so each test can inspect exactly what moved.
|
|
64
|
+
*/
|
|
65
|
+
function stubService(
|
|
66
|
+
movedRow: Model,
|
|
67
|
+
layerRows: Array<Model>,
|
|
68
|
+
): Array<CapturedShift> {
|
|
69
|
+
const captured: Array<CapturedShift> = [];
|
|
70
|
+
|
|
71
|
+
jest.spyOn(service, "findOneBy").mockResolvedValue(movedRow as never);
|
|
72
|
+
|
|
73
|
+
jest.spyOn(service, "findBy").mockResolvedValue(layerRows as never);
|
|
74
|
+
|
|
75
|
+
jest
|
|
76
|
+
.spyOn(service, "updateOneBy")
|
|
77
|
+
.mockImplementation((...args: Array<unknown>): Promise<void> => {
|
|
78
|
+
const updateArg: {
|
|
79
|
+
query: { _id: ObjectID };
|
|
80
|
+
data: { order: number };
|
|
81
|
+
} = args[0] as {
|
|
82
|
+
query: { _id: ObjectID };
|
|
83
|
+
data: { order: number };
|
|
84
|
+
};
|
|
85
|
+
captured.push({
|
|
86
|
+
id: updateArg.query._id.toString(),
|
|
87
|
+
order: updateArg.data.order,
|
|
88
|
+
});
|
|
89
|
+
return Promise.resolve();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return captured;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe("OnCallDutyPolicyScheduleLayerUserService.onBeforeUpdate reorder", () => {
|
|
96
|
+
afterEach(() => {
|
|
97
|
+
jest.restoreAllMocks();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* ----------------------------------------------------------------------- *
|
|
102
|
+
* 1. MOVE DOWN B: order 2 -> 3. Only C (3 -> 2) shifts; A is untouched.
|
|
103
|
+
* -----------------------------------------------------------------------
|
|
104
|
+
*/
|
|
105
|
+
test("moving B down (2 -> 3) decrements ONLY C and never touches A", async () => {
|
|
106
|
+
const captured: Array<CapturedShift> = stubService(row(USER_B, 2), [
|
|
107
|
+
row(USER_A, 1),
|
|
108
|
+
row(USER_B, 2),
|
|
109
|
+
row(USER_C, 3),
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
await service.onBeforeUpdate(updateBy(USER_B, 3));
|
|
113
|
+
|
|
114
|
+
// Exactly one row shifted, and it was C going from 3 down to 2.
|
|
115
|
+
expect(captured).toHaveLength(1);
|
|
116
|
+
expect(captured[0]).toEqual({ id: USER_C.toString(), order: 2 });
|
|
117
|
+
|
|
118
|
+
// A (the top row) must never be decremented — the regression.
|
|
119
|
+
const aShifts: Array<CapturedShift> = captured.filter(
|
|
120
|
+
(c: CapturedShift): boolean => {
|
|
121
|
+
return c.id === USER_A.toString();
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
expect(aShifts).toHaveLength(0);
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* After the shift, with the outer update setting B = 3, the resulting layer
|
|
128
|
+
* is contiguous {A:1, C:2, B:3}.
|
|
129
|
+
*/
|
|
130
|
+
const finalOrders: Record<string, number> = {
|
|
131
|
+
[USER_A.toString()]: 1, // untouched
|
|
132
|
+
[USER_C.toString()]: 2, // shifted
|
|
133
|
+
[USER_B.toString()]: 3, // set by the outer update
|
|
134
|
+
};
|
|
135
|
+
expect(finalOrders[USER_A.toString()]).toBe(1);
|
|
136
|
+
expect(finalOrders[USER_C.toString()]).toBe(2);
|
|
137
|
+
expect(finalOrders[USER_B.toString()]).toBe(3);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* ----------------------------------------------------------------------- *
|
|
142
|
+
* 2. MOVE UP C: order 3 -> 1. A (1 -> 2) and B (2 -> 3) increment; C untouched.
|
|
143
|
+
* -----------------------------------------------------------------------
|
|
144
|
+
*/
|
|
145
|
+
test("moving C up (3 -> 1) increments ONLY A and B", async () => {
|
|
146
|
+
const captured: Array<CapturedShift> = stubService(row(USER_C, 3), [
|
|
147
|
+
row(USER_A, 1),
|
|
148
|
+
row(USER_B, 2),
|
|
149
|
+
row(USER_C, 3),
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
await service.onBeforeUpdate(updateBy(USER_C, 1));
|
|
153
|
+
|
|
154
|
+
expect(captured).toHaveLength(2);
|
|
155
|
+
|
|
156
|
+
const byId: Record<string, number> = {};
|
|
157
|
+
for (const shift of captured) {
|
|
158
|
+
byId[shift.id] = shift.order;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
expect(byId[USER_A.toString()]).toBe(2);
|
|
162
|
+
expect(byId[USER_B.toString()]).toBe(3);
|
|
163
|
+
// C itself is not part of the shift loop; the outer update sets it to 1.
|
|
164
|
+
expect(byId[USER_C.toString()]).toBeUndefined();
|
|
165
|
+
|
|
166
|
+
// Contiguous {C:1, A:2, B:3}.
|
|
167
|
+
const finalOrders: Record<string, number> = {
|
|
168
|
+
[USER_C.toString()]: 1,
|
|
169
|
+
[USER_A.toString()]: 2,
|
|
170
|
+
[USER_B.toString()]: 3,
|
|
171
|
+
};
|
|
172
|
+
expect(Object.values(finalOrders).sort()).toEqual([1, 2, 3]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* ----------------------------------------------------------------------- *
|
|
177
|
+
* 3. MOVE DOWN A: order 1 -> 3 (dragging the TOP row all the way down).
|
|
178
|
+
* Only B (2 -> 1) and C (3 -> 2) decrement; A is not touched by the loop.
|
|
179
|
+
* -----------------------------------------------------------------------
|
|
180
|
+
*/
|
|
181
|
+
test("moving the top row A down (1 -> 3) decrements ONLY B and C, no row hits 0", async () => {
|
|
182
|
+
const captured: Array<CapturedShift> = stubService(row(USER_A, 1), [
|
|
183
|
+
row(USER_A, 1),
|
|
184
|
+
row(USER_B, 2),
|
|
185
|
+
row(USER_C, 3),
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
await service.onBeforeUpdate(updateBy(USER_A, 3));
|
|
189
|
+
|
|
190
|
+
expect(captured).toHaveLength(2);
|
|
191
|
+
|
|
192
|
+
const byId: Record<string, number> = {};
|
|
193
|
+
for (const shift of captured) {
|
|
194
|
+
byId[shift.id] = shift.order;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
expect(byId[USER_B.toString()]).toBe(1);
|
|
198
|
+
expect(byId[USER_C.toString()]).toBe(2);
|
|
199
|
+
// A is the moved row itself — the shift loop must not decrement it.
|
|
200
|
+
expect(byId[USER_A.toString()]).toBeUndefined();
|
|
201
|
+
|
|
202
|
+
// Nothing collapsed to 0 or negative.
|
|
203
|
+
for (const shift of captured) {
|
|
204
|
+
expect(shift.order).toBeGreaterThan(0);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/*
|
|
209
|
+
* ----------------------------------------------------------------------- *
|
|
210
|
+
* 4. Regression: in a move-down, NO captured shift may set an order <= 0.
|
|
211
|
+
* Before the fix, dragging B down drove A's order to 0. This asserts the
|
|
212
|
+
* invariant across the whole move-down code path.
|
|
213
|
+
* -----------------------------------------------------------------------
|
|
214
|
+
*/
|
|
215
|
+
test("regression: no move-down shift ever sets order <= 0", async () => {
|
|
216
|
+
const captured: Array<CapturedShift> = stubService(row(USER_B, 2), [
|
|
217
|
+
row(USER_A, 1),
|
|
218
|
+
row(USER_B, 2),
|
|
219
|
+
row(USER_C, 3),
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
await service.onBeforeUpdate(updateBy(USER_B, 3));
|
|
223
|
+
|
|
224
|
+
for (const shift of captured) {
|
|
225
|
+
expect(shift.order).toBeGreaterThan(0);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Keep any incidental logger.error noise out of the test output.
|
|
231
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
232
|
+
return undefined;
|
|
233
|
+
});
|