@herdctl/core 0.0.1 → 0.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/dist/config/__tests__/agent.test.js +31 -13
- package/dist/config/__tests__/agent.test.js.map +1 -1
- package/dist/config/__tests__/merge.test.js +9 -2
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/__tests__/schema.test.js +350 -1
- package/dist/config/__tests__/schema.test.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +828 -24
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +118 -6
- package/dist/config/schema.js.map +1 -1
- package/dist/fleet-manager/__tests__/coverage.test.js +11 -332
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/errors.test.js +1 -49
- package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/integration.test.js +109 -0
- package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
- package/dist/fleet-manager/config-reload.d.ts +164 -0
- package/dist/fleet-manager/config-reload.d.ts.map +1 -0
- package/dist/fleet-manager/config-reload.js +445 -0
- package/dist/fleet-manager/config-reload.js.map +1 -0
- package/dist/fleet-manager/context.d.ts +76 -0
- package/dist/fleet-manager/context.d.ts.map +1 -0
- package/dist/fleet-manager/context.js +11 -0
- package/dist/fleet-manager/context.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +0 -25
- package/dist/fleet-manager/errors.d.ts.map +1 -1
- package/dist/fleet-manager/errors.js +0 -38
- package/dist/fleet-manager/errors.js.map +1 -1
- package/dist/fleet-manager/event-emitters.d.ts +123 -0
- package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
- package/dist/fleet-manager/event-emitters.js +136 -0
- package/dist/fleet-manager/event-emitters.js.map +1 -0
- package/dist/fleet-manager/event-types.d.ts +0 -15
- package/dist/fleet-manager/event-types.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts +40 -653
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +95 -1720
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/index.d.ts +13 -2
- package/dist/fleet-manager/index.d.ts.map +1 -1
- package/dist/fleet-manager/index.js +19 -6
- package/dist/fleet-manager/index.js.map +1 -1
- package/dist/fleet-manager/job-control.d.ts +64 -0
- package/dist/fleet-manager/job-control.d.ts.map +1 -0
- package/dist/fleet-manager/job-control.js +296 -0
- package/dist/fleet-manager/job-control.js.map +1 -0
- package/dist/fleet-manager/log-streaming.d.ts +171 -0
- package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
- package/dist/fleet-manager/log-streaming.js +503 -0
- package/dist/fleet-manager/log-streaming.js.map +1 -0
- package/dist/fleet-manager/schedule-executor.d.ts +63 -0
- package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-executor.js +209 -0
- package/dist/fleet-manager/schedule-executor.js.map +1 -0
- package/dist/fleet-manager/schedule-management.d.ts +71 -0
- package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-management.js +171 -0
- package/dist/fleet-manager/schedule-management.js.map +1 -0
- package/dist/fleet-manager/status-queries.d.ts +105 -0
- package/dist/fleet-manager/status-queries.d.ts.map +1 -0
- package/dist/fleet-manager/status-queries.js +247 -0
- package/dist/fleet-manager/status-queries.js.map +1 -0
- package/dist/fleet-manager/types.d.ts +0 -39
- package/dist/fleet-manager/types.d.ts.map +1 -1
- package/dist/runner/__tests__/job-executor.test.js +206 -1
- package/dist/runner/__tests__/job-executor.test.js.map +1 -1
- package/dist/runner/job-executor.d.ts +9 -0
- package/dist/runner/job-executor.d.ts.map +1 -1
- package/dist/runner/job-executor.js +78 -4
- package/dist/runner/job-executor.js.map +1 -1
- package/dist/runner/types.d.ts +2 -0
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
- package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/cron.test.js +867 -0
- package/dist/scheduler/__tests__/cron.test.js.map +1 -0
- package/dist/scheduler/__tests__/scheduler.test.js +164 -5
- package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
- package/dist/scheduler/cron.d.ts +126 -0
- package/dist/scheduler/cron.d.ts.map +1 -0
- package/dist/scheduler/cron.js +390 -0
- package/dist/scheduler/cron.js.map +1 -0
- package/dist/scheduler/errors.d.ts +81 -1
- package/dist/scheduler/errors.d.ts.map +1 -1
- package/dist/scheduler/errors.js +81 -6
- package/dist/scheduler/errors.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts +2 -2
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +20 -8
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +4 -4
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +86 -20
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +1 -1
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/state/schemas/job-metadata.d.ts +2 -2
- package/package.json +33 -8
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -219
- package/.turbo/turbo-typecheck.log +0 -4
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -51
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -251
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/config/index.html +0 -191
- package/coverage/src/config/index.ts.html +0 -442
- package/coverage/src/config/interpolate.ts.html +0 -652
- package/coverage/src/config/loader.ts.html +0 -1501
- package/coverage/src/config/merge.ts.html +0 -823
- package/coverage/src/config/parser.ts.html +0 -1213
- package/coverage/src/config/schema.ts.html +0 -1123
- package/coverage/src/fleet-manager/errors.ts.html +0 -2326
- package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
- package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
- package/coverage/src/fleet-manager/index.html +0 -206
- package/coverage/src/fleet-manager/index.ts.html +0 -469
- package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
- package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
- package/coverage/src/fleet-manager/types.ts.html +0 -2602
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -181
- package/coverage/src/runner/errors.ts.html +0 -1006
- package/coverage/src/runner/index.html +0 -191
- package/coverage/src/runner/index.ts.html +0 -256
- package/coverage/src/runner/job-executor.ts.html +0 -1429
- package/coverage/src/runner/message-processor.ts.html +0 -1150
- package/coverage/src/runner/sdk-adapter.ts.html +0 -658
- package/coverage/src/runner/types.ts.html +0 -559
- package/coverage/src/scheduler/errors.ts.html +0 -388
- package/coverage/src/scheduler/index.html +0 -206
- package/coverage/src/scheduler/index.ts.html +0 -244
- package/coverage/src/scheduler/interval.ts.html +0 -652
- package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
- package/coverage/src/scheduler/schedule-state.ts.html +0 -718
- package/coverage/src/scheduler/scheduler.ts.html +0 -1795
- package/coverage/src/scheduler/types.ts.html +0 -733
- package/coverage/src/state/directory.ts.html +0 -736
- package/coverage/src/state/errors.ts.html +0 -376
- package/coverage/src/state/fleet-state.ts.html +0 -937
- package/coverage/src/state/index.html +0 -221
- package/coverage/src/state/index.ts.html +0 -322
- package/coverage/src/state/job-metadata.ts.html +0 -1420
- package/coverage/src/state/job-output.ts.html +0 -1033
- package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
- package/coverage/src/state/schemas/index.html +0 -176
- package/coverage/src/state/schemas/index.ts.html +0 -286
- package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
- package/coverage/src/state/schemas/job-output.ts.html +0 -616
- package/coverage/src/state/schemas/session-info.ts.html +0 -361
- package/coverage/src/state/session.ts.html +0 -844
- package/coverage/src/state/types.ts.html +0 -262
- package/coverage/src/state/utils/atomic.ts.html +0 -748
- package/coverage/src/state/utils/index.html +0 -146
- package/coverage/src/state/utils/index.ts.html +0 -103
- package/coverage/src/state/utils/reads.ts.html +0 -1621
- package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
- package/coverage/src/work-sources/adapters/index.html +0 -131
- package/coverage/src/work-sources/adapters/index.ts.html +0 -277
- package/coverage/src/work-sources/errors.ts.html +0 -298
- package/coverage/src/work-sources/index.html +0 -176
- package/coverage/src/work-sources/index.ts.html +0 -529
- package/coverage/src/work-sources/manager.ts.html +0 -1324
- package/coverage/src/work-sources/registry.ts.html +0 -619
- package/coverage/src/work-sources/types.ts.html +0 -568
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
- package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
- package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
- package/src/config/__tests__/agent.test.ts +0 -864
- package/src/config/__tests__/interpolate.test.ts +0 -644
- package/src/config/__tests__/loader.test.ts +0 -784
- package/src/config/__tests__/merge.test.ts +0 -751
- package/src/config/__tests__/parser.test.ts +0 -533
- package/src/config/__tests__/schema.test.ts +0 -873
- package/src/config/index.ts +0 -119
- package/src/config/interpolate.ts +0 -189
- package/src/config/loader.ts +0 -472
- package/src/config/merge.ts +0 -246
- package/src/config/parser.ts +0 -376
- package/src/config/schema.ts +0 -346
- package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
- package/src/fleet-manager/__tests__/errors.test.ts +0 -660
- package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
- package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
- package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
- package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
- package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
- package/src/fleet-manager/__tests__/reload.test.ts +0 -751
- package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
- package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
- package/src/fleet-manager/errors.ts +0 -747
- package/src/fleet-manager/event-types.ts +0 -378
- package/src/fleet-manager/fleet-manager.ts +0 -2315
- package/src/fleet-manager/index.ts +0 -128
- package/src/fleet-manager/job-manager.ts +0 -663
- package/src/fleet-manager/job-queue.ts +0 -798
- package/src/fleet-manager/types.ts +0 -839
- package/src/index.ts +0 -32
- package/src/runner/__tests__/errors.test.ts +0 -382
- package/src/runner/__tests__/job-executor.test.ts +0 -1708
- package/src/runner/__tests__/message-processor.test.ts +0 -960
- package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
- package/src/runner/errors.ts +0 -307
- package/src/runner/index.ts +0 -57
- package/src/runner/job-executor.ts +0 -448
- package/src/runner/message-processor.ts +0 -355
- package/src/runner/sdk-adapter.ts +0 -191
- package/src/runner/types.ts +0 -158
- package/src/scheduler/__tests__/errors.test.ts +0 -159
- package/src/scheduler/__tests__/interval.test.ts +0 -515
- package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
- package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
- package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
- package/src/scheduler/errors.ts +0 -101
- package/src/scheduler/index.ts +0 -53
- package/src/scheduler/interval.ts +0 -189
- package/src/scheduler/schedule-runner.ts +0 -442
- package/src/scheduler/schedule-state.ts +0 -211
- package/src/scheduler/scheduler.ts +0 -570
- package/src/scheduler/types.ts +0 -216
- package/src/state/__tests__/directory.test.ts +0 -595
- package/src/state/__tests__/fleet-state.test.ts +0 -868
- package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
- package/src/state/__tests__/job-metadata.test.ts +0 -831
- package/src/state/__tests__/job-output.test.ts +0 -856
- package/src/state/__tests__/session-schema.test.ts +0 -378
- package/src/state/__tests__/session.test.ts +0 -604
- package/src/state/directory.ts +0 -217
- package/src/state/errors.ts +0 -97
- package/src/state/fleet-state.ts +0 -284
- package/src/state/index.ts +0 -79
- package/src/state/job-metadata.ts +0 -445
- package/src/state/job-output.ts +0 -316
- package/src/state/schemas/__tests__/job-output.test.ts +0 -338
- package/src/state/schemas/fleet-state.ts +0 -120
- package/src/state/schemas/index.ts +0 -67
- package/src/state/schemas/job-metadata.ts +0 -181
- package/src/state/schemas/job-output.ts +0 -177
- package/src/state/schemas/session-info.ts +0 -92
- package/src/state/session.ts +0 -253
- package/src/state/types.ts +0 -59
- package/src/state/utils/__tests__/atomic.test.ts +0 -723
- package/src/state/utils/__tests__/reads.test.ts +0 -1071
- package/src/state/utils/atomic.ts +0 -221
- package/src/state/utils/index.ts +0 -6
- package/src/state/utils/reads.ts +0 -512
- package/src/work-sources/__tests__/github.test.ts +0 -1800
- package/src/work-sources/__tests__/manager.test.ts +0 -529
- package/src/work-sources/__tests__/registry.test.ts +0 -477
- package/src/work-sources/__tests__/types.test.ts +0 -479
- package/src/work-sources/adapters/github.ts +0 -1166
- package/src/work-sources/adapters/index.ts +0 -64
- package/src/work-sources/errors.ts +0 -71
- package/src/work-sources/index.ts +0 -148
- package/src/work-sources/manager.ts +0 -413
- package/src/work-sources/registry.ts +0 -178
- package/src/work-sources/types.ts +0 -161
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
SchedulerError,
|
|
4
|
-
IntervalParseError,
|
|
5
|
-
ScheduleTriggerError,
|
|
6
|
-
} from "../errors.js";
|
|
7
|
-
|
|
8
|
-
// =============================================================================
|
|
9
|
-
// SchedulerError (Base Class)
|
|
10
|
-
// =============================================================================
|
|
11
|
-
|
|
12
|
-
describe("SchedulerError", () => {
|
|
13
|
-
it("creates error with message", () => {
|
|
14
|
-
const error = new SchedulerError("test error message");
|
|
15
|
-
|
|
16
|
-
expect(error.message).toBe("test error message");
|
|
17
|
-
expect(error.name).toBe("SchedulerError");
|
|
18
|
-
expect(error).toBeInstanceOf(Error);
|
|
19
|
-
expect(error).toBeInstanceOf(SchedulerError);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("preserves cause when provided", () => {
|
|
23
|
-
const cause = new Error("original error");
|
|
24
|
-
const error = new SchedulerError("wrapped error", { cause });
|
|
25
|
-
|
|
26
|
-
expect(error.message).toBe("wrapped error");
|
|
27
|
-
expect(error.cause).toBe(cause);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("has undefined cause when not provided", () => {
|
|
31
|
-
const error = new SchedulerError("no cause");
|
|
32
|
-
|
|
33
|
-
expect(error.cause).toBeUndefined();
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// =============================================================================
|
|
38
|
-
// IntervalParseError
|
|
39
|
-
// =============================================================================
|
|
40
|
-
|
|
41
|
-
describe("IntervalParseError", () => {
|
|
42
|
-
it("creates error with message and input", () => {
|
|
43
|
-
const error = new IntervalParseError("invalid interval", "5x");
|
|
44
|
-
|
|
45
|
-
expect(error.message).toBe("invalid interval");
|
|
46
|
-
expect(error.name).toBe("IntervalParseError");
|
|
47
|
-
expect(error.input).toBe("5x");
|
|
48
|
-
expect(error).toBeInstanceOf(Error);
|
|
49
|
-
expect(error).toBeInstanceOf(SchedulerError);
|
|
50
|
-
expect(error).toBeInstanceOf(IntervalParseError);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("preserves input string exactly", () => {
|
|
54
|
-
const inputs = ["", " ", "5", "-5m", "1.5h", "invalid"];
|
|
55
|
-
|
|
56
|
-
for (const input of inputs) {
|
|
57
|
-
const error = new IntervalParseError("test", input);
|
|
58
|
-
expect(error.input).toBe(input);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("preserves cause when provided", () => {
|
|
63
|
-
const cause = new Error("original error");
|
|
64
|
-
const error = new IntervalParseError("wrapped error", "5x", { cause });
|
|
65
|
-
|
|
66
|
-
expect(error.cause).toBe(cause);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// =============================================================================
|
|
71
|
-
// ScheduleTriggerError
|
|
72
|
-
// =============================================================================
|
|
73
|
-
|
|
74
|
-
describe("ScheduleTriggerError", () => {
|
|
75
|
-
it("creates error with message, agentName, and scheduleName", () => {
|
|
76
|
-
const error = new ScheduleTriggerError(
|
|
77
|
-
"trigger failed",
|
|
78
|
-
"my-agent",
|
|
79
|
-
"hourly-schedule"
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
expect(error.message).toBe("trigger failed");
|
|
83
|
-
expect(error.name).toBe("ScheduleTriggerError");
|
|
84
|
-
expect(error.agentName).toBe("my-agent");
|
|
85
|
-
expect(error.scheduleName).toBe("hourly-schedule");
|
|
86
|
-
expect(error).toBeInstanceOf(Error);
|
|
87
|
-
expect(error).toBeInstanceOf(SchedulerError);
|
|
88
|
-
expect(error).toBeInstanceOf(ScheduleTriggerError);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("preserves agent and schedule names exactly", () => {
|
|
92
|
-
const error = new ScheduleTriggerError(
|
|
93
|
-
"test",
|
|
94
|
-
"agent-with-dashes",
|
|
95
|
-
"schedule_with_underscores"
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
expect(error.agentName).toBe("agent-with-dashes");
|
|
99
|
-
expect(error.scheduleName).toBe("schedule_with_underscores");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("preserves cause when provided", () => {
|
|
103
|
-
const cause = new Error("underlying failure");
|
|
104
|
-
const error = new ScheduleTriggerError(
|
|
105
|
-
"trigger failed",
|
|
106
|
-
"my-agent",
|
|
107
|
-
"my-schedule",
|
|
108
|
-
{ cause }
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
expect(error.cause).toBe(cause);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("has undefined cause when not provided", () => {
|
|
115
|
-
const error = new ScheduleTriggerError(
|
|
116
|
-
"trigger failed",
|
|
117
|
-
"my-agent",
|
|
118
|
-
"my-schedule"
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
expect(error.cause).toBeUndefined();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("can be caught as SchedulerError", () => {
|
|
125
|
-
const error = new ScheduleTriggerError(
|
|
126
|
-
"failed",
|
|
127
|
-
"agent",
|
|
128
|
-
"schedule"
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
let caught = false;
|
|
132
|
-
try {
|
|
133
|
-
throw error;
|
|
134
|
-
} catch (e) {
|
|
135
|
-
if (e instanceof SchedulerError) {
|
|
136
|
-
caught = true;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
expect(caught).toBe(true);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("can wrap another error as cause", () => {
|
|
144
|
-
const networkError = new Error("Connection refused");
|
|
145
|
-
const triggerError = new ScheduleTriggerError(
|
|
146
|
-
`Failed to trigger agent: ${networkError.message}`,
|
|
147
|
-
"remote-agent",
|
|
148
|
-
"sync-schedule",
|
|
149
|
-
{ cause: networkError }
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
expect(triggerError.message).toBe(
|
|
153
|
-
"Failed to trigger agent: Connection refused"
|
|
154
|
-
);
|
|
155
|
-
expect(triggerError.cause).toBe(networkError);
|
|
156
|
-
expect(triggerError.agentName).toBe("remote-agent");
|
|
157
|
-
expect(triggerError.scheduleName).toBe("sync-schedule");
|
|
158
|
-
});
|
|
159
|
-
});
|
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
parseInterval,
|
|
4
|
-
calculateNextTrigger,
|
|
5
|
-
isScheduleDue,
|
|
6
|
-
} from "../interval.js";
|
|
7
|
-
import { IntervalParseError } from "../errors.js";
|
|
8
|
-
|
|
9
|
-
// =============================================================================
|
|
10
|
-
// parseInterval - Valid inputs
|
|
11
|
-
// =============================================================================
|
|
12
|
-
|
|
13
|
-
describe("parseInterval", () => {
|
|
14
|
-
describe("valid intervals", () => {
|
|
15
|
-
it("parses seconds correctly", () => {
|
|
16
|
-
expect(parseInterval("5s")).toBe(5000);
|
|
17
|
-
expect(parseInterval("1s")).toBe(1000);
|
|
18
|
-
expect(parseInterval("30s")).toBe(30000);
|
|
19
|
-
expect(parseInterval("60s")).toBe(60000);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("parses minutes correctly", () => {
|
|
23
|
-
expect(parseInterval("5m")).toBe(300000);
|
|
24
|
-
expect(parseInterval("1m")).toBe(60000);
|
|
25
|
-
expect(parseInterval("30m")).toBe(1800000);
|
|
26
|
-
expect(parseInterval("60m")).toBe(3600000);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("parses hours correctly", () => {
|
|
30
|
-
expect(parseInterval("1h")).toBe(3600000);
|
|
31
|
-
expect(parseInterval("2h")).toBe(7200000);
|
|
32
|
-
expect(parseInterval("24h")).toBe(86400000);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("parses days correctly", () => {
|
|
36
|
-
expect(parseInterval("1d")).toBe(86400000);
|
|
37
|
-
expect(parseInterval("7d")).toBe(604800000);
|
|
38
|
-
expect(parseInterval("30d")).toBe(2592000000);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("handles uppercase units", () => {
|
|
42
|
-
expect(parseInterval("5S")).toBe(5000);
|
|
43
|
-
expect(parseInterval("5M")).toBe(300000);
|
|
44
|
-
expect(parseInterval("1H")).toBe(3600000);
|
|
45
|
-
expect(parseInterval("1D")).toBe(86400000);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("handles whitespace around the interval", () => {
|
|
49
|
-
expect(parseInterval(" 5m ")).toBe(300000);
|
|
50
|
-
expect(parseInterval("\t1h\t")).toBe(3600000);
|
|
51
|
-
expect(parseInterval("\n30s\n")).toBe(30000);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("handles large values", () => {
|
|
55
|
-
expect(parseInterval("1000s")).toBe(1000000);
|
|
56
|
-
expect(parseInterval("999m")).toBe(59940000);
|
|
57
|
-
expect(parseInterval("100h")).toBe(360000000);
|
|
58
|
-
expect(parseInterval("365d")).toBe(31536000000);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// =============================================================================
|
|
63
|
-
// parseInterval - Empty string
|
|
64
|
-
// =============================================================================
|
|
65
|
-
|
|
66
|
-
describe("empty string handling", () => {
|
|
67
|
-
it("throws IntervalParseError for empty string", () => {
|
|
68
|
-
expect(() => parseInterval("")).toThrow(IntervalParseError);
|
|
69
|
-
expect(() => parseInterval("")).toThrow(/cannot be empty/);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("throws IntervalParseError for whitespace-only string", () => {
|
|
73
|
-
expect(() => parseInterval(" ")).toThrow(IntervalParseError);
|
|
74
|
-
expect(() => parseInterval("\t")).toThrow(IntervalParseError);
|
|
75
|
-
expect(() => parseInterval("\n")).toThrow(IntervalParseError);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("includes the empty input in the error", () => {
|
|
79
|
-
try {
|
|
80
|
-
parseInterval("");
|
|
81
|
-
} catch (e) {
|
|
82
|
-
expect(e).toBeInstanceOf(IntervalParseError);
|
|
83
|
-
expect((e as IntervalParseError).input).toBe("");
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// =============================================================================
|
|
89
|
-
// parseInterval - Missing unit
|
|
90
|
-
// =============================================================================
|
|
91
|
-
|
|
92
|
-
describe("missing unit handling", () => {
|
|
93
|
-
it("throws IntervalParseError for number without unit", () => {
|
|
94
|
-
expect(() => parseInterval("5")).toThrow(IntervalParseError);
|
|
95
|
-
expect(() => parseInterval("5")).toThrow(/Missing time unit/);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("throws IntervalParseError for various numbers without unit", () => {
|
|
99
|
-
expect(() => parseInterval("1")).toThrow(IntervalParseError);
|
|
100
|
-
expect(() => parseInterval("100")).toThrow(IntervalParseError);
|
|
101
|
-
expect(() => parseInterval("9999")).toThrow(IntervalParseError);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("includes the input in the error", () => {
|
|
105
|
-
try {
|
|
106
|
-
parseInterval("42");
|
|
107
|
-
} catch (e) {
|
|
108
|
-
expect(e).toBeInstanceOf(IntervalParseError);
|
|
109
|
-
expect((e as IntervalParseError).input).toBe("42");
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// =============================================================================
|
|
115
|
-
// parseInterval - Invalid unit
|
|
116
|
-
// =============================================================================
|
|
117
|
-
|
|
118
|
-
describe("invalid unit handling", () => {
|
|
119
|
-
it("throws IntervalParseError for invalid unit", () => {
|
|
120
|
-
expect(() => parseInterval("5x")).toThrow(IntervalParseError);
|
|
121
|
-
expect(() => parseInterval("5x")).toThrow(/Invalid time unit "x"/);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("throws IntervalParseError for various invalid units", () => {
|
|
125
|
-
expect(() => parseInterval("5ms")).toThrow(IntervalParseError); // milliseconds not supported
|
|
126
|
-
expect(() => parseInterval("5sec")).toThrow(IntervalParseError); // "sec" not supported
|
|
127
|
-
expect(() => parseInterval("5min")).toThrow(IntervalParseError); // "min" not supported
|
|
128
|
-
expect(() => parseInterval("5hr")).toThrow(IntervalParseError); // "hr" not supported
|
|
129
|
-
expect(() => parseInterval("5w")).toThrow(IntervalParseError); // weeks not supported
|
|
130
|
-
expect(() => parseInterval("5y")).toThrow(IntervalParseError); // years not supported
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("includes valid units in the error message", () => {
|
|
134
|
-
expect(() => parseInterval("5x")).toThrow(/s \(seconds\)/);
|
|
135
|
-
expect(() => parseInterval("5x")).toThrow(/m \(minutes\)/);
|
|
136
|
-
expect(() => parseInterval("5x")).toThrow(/h \(hours\)/);
|
|
137
|
-
expect(() => parseInterval("5x")).toThrow(/d \(days\)/);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// =============================================================================
|
|
142
|
-
// parseInterval - Negative numbers
|
|
143
|
-
// =============================================================================
|
|
144
|
-
|
|
145
|
-
describe("negative number handling", () => {
|
|
146
|
-
it("throws IntervalParseError for negative numbers", () => {
|
|
147
|
-
expect(() => parseInterval("-5m")).toThrow(IntervalParseError);
|
|
148
|
-
expect(() => parseInterval("-5m")).toThrow(/Negative intervals are not allowed/);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("throws IntervalParseError for various negative values", () => {
|
|
152
|
-
expect(() => parseInterval("-1s")).toThrow(IntervalParseError);
|
|
153
|
-
expect(() => parseInterval("-100h")).toThrow(IntervalParseError);
|
|
154
|
-
expect(() => parseInterval("-1d")).toThrow(IntervalParseError);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("includes the input in the error", () => {
|
|
158
|
-
try {
|
|
159
|
-
parseInterval("-10m");
|
|
160
|
-
} catch (e) {
|
|
161
|
-
expect(e).toBeInstanceOf(IntervalParseError);
|
|
162
|
-
expect((e as IntervalParseError).input).toBe("-10m");
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// =============================================================================
|
|
168
|
-
// parseInterval - Zero value
|
|
169
|
-
// =============================================================================
|
|
170
|
-
|
|
171
|
-
describe("zero value handling", () => {
|
|
172
|
-
it("throws IntervalParseError for zero", () => {
|
|
173
|
-
expect(() => parseInterval("0s")).toThrow(IntervalParseError);
|
|
174
|
-
expect(() => parseInterval("0s")).toThrow(/Zero interval is not allowed/);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("throws IntervalParseError for zero with any unit", () => {
|
|
178
|
-
expect(() => parseInterval("0m")).toThrow(IntervalParseError);
|
|
179
|
-
expect(() => parseInterval("0h")).toThrow(IntervalParseError);
|
|
180
|
-
expect(() => parseInterval("0d")).toThrow(IntervalParseError);
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// =============================================================================
|
|
185
|
-
// parseInterval - Decimal values
|
|
186
|
-
// =============================================================================
|
|
187
|
-
|
|
188
|
-
describe("decimal value handling", () => {
|
|
189
|
-
it("throws IntervalParseError for decimal values", () => {
|
|
190
|
-
expect(() => parseInterval("1.5h")).toThrow(IntervalParseError);
|
|
191
|
-
expect(() => parseInterval("1.5h")).toThrow(/Decimal values are not supported/);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("throws IntervalParseError for various decimal values", () => {
|
|
195
|
-
expect(() => parseInterval("2.5m")).toThrow(IntervalParseError);
|
|
196
|
-
expect(() => parseInterval("0.5s")).toThrow(IntervalParseError);
|
|
197
|
-
expect(() => parseInterval("1.5d")).toThrow(IntervalParseError);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("suggests using integers in the error message", () => {
|
|
201
|
-
expect(() => parseInterval("1.5h")).toThrow(/Use integers only/);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// =============================================================================
|
|
206
|
-
// parseInterval - Invalid format
|
|
207
|
-
// =============================================================================
|
|
208
|
-
|
|
209
|
-
describe("invalid format handling", () => {
|
|
210
|
-
it("throws IntervalParseError for letter-only input", () => {
|
|
211
|
-
expect(() => parseInterval("abc")).toThrow(IntervalParseError);
|
|
212
|
-
expect(() => parseInterval("abc")).toThrow(/Missing numeric value/);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("throws IntervalParseError for random invalid formats", () => {
|
|
216
|
-
expect(() => parseInterval("m5")).toThrow(IntervalParseError);
|
|
217
|
-
expect(() => parseInterval("5 m 5")).toThrow(IntervalParseError);
|
|
218
|
-
expect(() => parseInterval("5m5s")).toThrow(IntervalParseError);
|
|
219
|
-
expect(() => parseInterval("five minutes")).toThrow(IntervalParseError);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("throws IntervalParseError for special characters", () => {
|
|
223
|
-
expect(() => parseInterval("5@m")).toThrow(IntervalParseError);
|
|
224
|
-
expect(() => parseInterval("5#h")).toThrow(IntervalParseError);
|
|
225
|
-
expect(() => parseInterval("5$d")).toThrow(IntervalParseError);
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// =============================================================================
|
|
230
|
-
// IntervalParseError properties
|
|
231
|
-
// =============================================================================
|
|
232
|
-
|
|
233
|
-
describe("IntervalParseError", () => {
|
|
234
|
-
it("has correct name property", () => {
|
|
235
|
-
try {
|
|
236
|
-
parseInterval("invalid");
|
|
237
|
-
} catch (e) {
|
|
238
|
-
expect(e).toBeInstanceOf(IntervalParseError);
|
|
239
|
-
expect((e as IntervalParseError).name).toBe("IntervalParseError");
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("preserves the input string", () => {
|
|
244
|
-
const testInputs = ["", "5", "invalid", "-5m", "1.5h"];
|
|
245
|
-
|
|
246
|
-
for (const input of testInputs) {
|
|
247
|
-
try {
|
|
248
|
-
parseInterval(input);
|
|
249
|
-
} catch (e) {
|
|
250
|
-
expect((e as IntervalParseError).input).toBe(input);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("has descriptive error messages", () => {
|
|
256
|
-
try {
|
|
257
|
-
parseInterval("5x");
|
|
258
|
-
} catch (e) {
|
|
259
|
-
expect((e as IntervalParseError).message).toContain("5x");
|
|
260
|
-
expect((e as IntervalParseError).message.length).toBeGreaterThan(20);
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// =============================================================================
|
|
267
|
-
// calculateNextTrigger
|
|
268
|
-
// =============================================================================
|
|
269
|
-
|
|
270
|
-
describe("calculateNextTrigger", () => {
|
|
271
|
-
beforeEach(() => {
|
|
272
|
-
vi.useFakeTimers();
|
|
273
|
-
vi.setSystemTime(new Date("2024-01-15T12:00:00.000Z"));
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
afterEach(() => {
|
|
277
|
-
vi.useRealTimers();
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe("first run (no lastCompletedAt)", () => {
|
|
281
|
-
it("returns now when lastCompletedAt is null", () => {
|
|
282
|
-
const result = calculateNextTrigger(null, "5m");
|
|
283
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("returns now regardless of interval when lastCompletedAt is null", () => {
|
|
287
|
-
expect(calculateNextTrigger(null, "1s").getTime()).toBe(
|
|
288
|
-
new Date("2024-01-15T12:00:00.000Z").getTime()
|
|
289
|
-
);
|
|
290
|
-
expect(calculateNextTrigger(null, "1h").getTime()).toBe(
|
|
291
|
-
new Date("2024-01-15T12:00:00.000Z").getTime()
|
|
292
|
-
);
|
|
293
|
-
expect(calculateNextTrigger(null, "1d").getTime()).toBe(
|
|
294
|
-
new Date("2024-01-15T12:00:00.000Z").getTime()
|
|
295
|
-
);
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
describe("subsequent runs", () => {
|
|
300
|
-
it("returns lastCompletedAt + interval for basic case", () => {
|
|
301
|
-
const lastCompleted = new Date("2024-01-15T11:55:00.000Z");
|
|
302
|
-
const result = calculateNextTrigger(lastCompleted, "5m");
|
|
303
|
-
// 11:55 + 5m = 12:00
|
|
304
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it("calculates next trigger with seconds interval", () => {
|
|
308
|
-
const lastCompleted = new Date("2024-01-15T11:59:30.000Z");
|
|
309
|
-
const result = calculateNextTrigger(lastCompleted, "30s");
|
|
310
|
-
// 11:59:30 + 30s = 12:00:00
|
|
311
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it("calculates next trigger with hours interval", () => {
|
|
315
|
-
const lastCompleted = new Date("2024-01-15T11:00:00.000Z");
|
|
316
|
-
const result = calculateNextTrigger(lastCompleted, "1h");
|
|
317
|
-
// 11:00 + 1h = 12:00
|
|
318
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it("calculates next trigger with days interval", () => {
|
|
322
|
-
const lastCompleted = new Date("2024-01-14T12:00:00.000Z");
|
|
323
|
-
const result = calculateNextTrigger(lastCompleted, "1d");
|
|
324
|
-
// Jan 14 12:00 + 1d = Jan 15 12:00
|
|
325
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it("returns future time when next trigger is in the future", () => {
|
|
329
|
-
const lastCompleted = new Date("2024-01-15T11:58:00.000Z");
|
|
330
|
-
const result = calculateNextTrigger(lastCompleted, "5m");
|
|
331
|
-
// 11:58 + 5m = 12:03 (in the future)
|
|
332
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:03:00.000Z").getTime());
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
describe("clock skew handling", () => {
|
|
337
|
-
it("returns now when calculated next trigger is in the past", () => {
|
|
338
|
-
// Last completed was a long time ago
|
|
339
|
-
const lastCompleted = new Date("2024-01-15T10:00:00.000Z");
|
|
340
|
-
const result = calculateNextTrigger(lastCompleted, "5m");
|
|
341
|
-
// 10:00 + 5m = 10:05, which is in the past (now is 12:00)
|
|
342
|
-
// Should return now instead
|
|
343
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("handles very old lastCompletedAt gracefully", () => {
|
|
347
|
-
const lastCompleted = new Date("2024-01-01T00:00:00.000Z");
|
|
348
|
-
const result = calculateNextTrigger(lastCompleted, "1h");
|
|
349
|
-
// Jan 1 00:00 + 1h = Jan 1 01:00, way in the past
|
|
350
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
describe("jitter", () => {
|
|
355
|
-
it("adds no jitter when jitterPercent is 0", () => {
|
|
356
|
-
const lastCompleted = new Date("2024-01-15T11:55:00.000Z");
|
|
357
|
-
const result = calculateNextTrigger(lastCompleted, "5m", 0);
|
|
358
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("adds no jitter when jitterPercent is undefined", () => {
|
|
362
|
-
const lastCompleted = new Date("2024-01-15T11:55:00.000Z");
|
|
363
|
-
const result = calculateNextTrigger(lastCompleted, "5m");
|
|
364
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it("adds jitter within expected range", () => {
|
|
368
|
-
// Mock Math.random to return 0.5 (middle of range)
|
|
369
|
-
vi.spyOn(Math, "random").mockReturnValue(0.5);
|
|
370
|
-
|
|
371
|
-
const lastCompleted = new Date("2024-01-15T11:00:00.000Z");
|
|
372
|
-
// 1h = 3600000ms, 5% jitter = 180000ms max
|
|
373
|
-
// With random = 0.5, jitter = 90000ms (1.5 minutes)
|
|
374
|
-
const result = calculateNextTrigger(lastCompleted, "1h", 5);
|
|
375
|
-
|
|
376
|
-
// 11:00 + 1h + 1.5m = 12:01:30
|
|
377
|
-
expect(result.getTime()).toBe(
|
|
378
|
-
new Date("2024-01-15T12:01:30.000Z").getTime()
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
vi.spyOn(Math, "random").mockRestore();
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it("clamps jitter to maximum 10%", () => {
|
|
385
|
-
vi.spyOn(Math, "random").mockReturnValue(1.0); // Max random
|
|
386
|
-
|
|
387
|
-
const lastCompleted = new Date("2024-01-15T11:00:00.000Z");
|
|
388
|
-
// Request 20% jitter, but should be clamped to 10%
|
|
389
|
-
// 1h = 3600000ms, 10% = 360000ms (6 minutes)
|
|
390
|
-
const result = calculateNextTrigger(lastCompleted, "1h", 20);
|
|
391
|
-
|
|
392
|
-
// 11:00 + 1h + 6m = 12:06
|
|
393
|
-
expect(result.getTime()).toBe(
|
|
394
|
-
new Date("2024-01-15T12:06:00.000Z").getTime()
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
vi.spyOn(Math, "random").mockRestore();
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it("handles negative jitter by treating as 0", () => {
|
|
401
|
-
const lastCompleted = new Date("2024-01-15T11:55:00.000Z");
|
|
402
|
-
const result = calculateNextTrigger(lastCompleted, "5m", -5);
|
|
403
|
-
// Negative jitter should be clamped to 0
|
|
404
|
-
expect(result.getTime()).toBe(new Date("2024-01-15T12:00:00.000Z").getTime());
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("jitter range is 0 to jitterPercent% of interval", () => {
|
|
408
|
-
// Test with random = 0 (minimum jitter)
|
|
409
|
-
vi.spyOn(Math, "random").mockReturnValue(0);
|
|
410
|
-
const lastCompleted = new Date("2024-01-15T11:00:00.000Z");
|
|
411
|
-
const resultMin = calculateNextTrigger(lastCompleted, "1h", 10);
|
|
412
|
-
expect(resultMin.getTime()).toBe(
|
|
413
|
-
new Date("2024-01-15T12:00:00.000Z").getTime()
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
// Test with random = 1 (maximum jitter)
|
|
417
|
-
vi.spyOn(Math, "random").mockReturnValue(1.0);
|
|
418
|
-
const resultMax = calculateNextTrigger(lastCompleted, "1h", 10);
|
|
419
|
-
// 10% of 1h = 6 minutes
|
|
420
|
-
expect(resultMax.getTime()).toBe(
|
|
421
|
-
new Date("2024-01-15T12:06:00.000Z").getTime()
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
vi.spyOn(Math, "random").mockRestore();
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
describe("error handling", () => {
|
|
429
|
-
it("throws IntervalParseError for invalid interval", () => {
|
|
430
|
-
const lastCompleted = new Date("2024-01-15T11:00:00.000Z");
|
|
431
|
-
expect(() => calculateNextTrigger(lastCompleted, "invalid")).toThrow(
|
|
432
|
-
IntervalParseError
|
|
433
|
-
);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("throws for empty interval", () => {
|
|
437
|
-
const lastCompleted = new Date("2024-01-15T11:00:00.000Z");
|
|
438
|
-
expect(() => calculateNextTrigger(lastCompleted, "")).toThrow(
|
|
439
|
-
IntervalParseError
|
|
440
|
-
);
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// =============================================================================
|
|
446
|
-
// isScheduleDue
|
|
447
|
-
// =============================================================================
|
|
448
|
-
|
|
449
|
-
describe("isScheduleDue", () => {
|
|
450
|
-
describe("with explicit now parameter", () => {
|
|
451
|
-
it("returns false when nextRunAt is in the future", () => {
|
|
452
|
-
const nextRun = new Date("2024-01-15T12:05:00.000Z");
|
|
453
|
-
const now = new Date("2024-01-15T12:00:00.000Z");
|
|
454
|
-
expect(isScheduleDue(nextRun, now)).toBe(false);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it("returns true when nextRunAt equals now", () => {
|
|
458
|
-
const nextRun = new Date("2024-01-15T12:00:00.000Z");
|
|
459
|
-
const now = new Date("2024-01-15T12:00:00.000Z");
|
|
460
|
-
expect(isScheduleDue(nextRun, now)).toBe(true);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("returns true when nextRunAt is in the past", () => {
|
|
464
|
-
const nextRun = new Date("2024-01-15T11:55:00.000Z");
|
|
465
|
-
const now = new Date("2024-01-15T12:00:00.000Z");
|
|
466
|
-
expect(isScheduleDue(nextRun, now)).toBe(true);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it("handles millisecond precision", () => {
|
|
470
|
-
const nextRun = new Date("2024-01-15T12:00:00.001Z");
|
|
471
|
-
const now = new Date("2024-01-15T12:00:00.000Z");
|
|
472
|
-
expect(isScheduleDue(nextRun, now)).toBe(false);
|
|
473
|
-
|
|
474
|
-
const nextRun2 = new Date("2024-01-15T12:00:00.000Z");
|
|
475
|
-
const now2 = new Date("2024-01-15T12:00:00.001Z");
|
|
476
|
-
expect(isScheduleDue(nextRun2, now2)).toBe(true);
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
describe("with default now", () => {
|
|
481
|
-
beforeEach(() => {
|
|
482
|
-
vi.useFakeTimers();
|
|
483
|
-
vi.setSystemTime(new Date("2024-01-15T12:00:00.000Z"));
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
afterEach(() => {
|
|
487
|
-
vi.useRealTimers();
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it("uses current time when now is not provided", () => {
|
|
491
|
-
const futureRun = new Date("2024-01-15T12:05:00.000Z");
|
|
492
|
-
expect(isScheduleDue(futureRun)).toBe(false);
|
|
493
|
-
|
|
494
|
-
const pastRun = new Date("2024-01-15T11:55:00.000Z");
|
|
495
|
-
expect(isScheduleDue(pastRun)).toBe(true);
|
|
496
|
-
|
|
497
|
-
const currentRun = new Date("2024-01-15T12:00:00.000Z");
|
|
498
|
-
expect(isScheduleDue(currentRun)).toBe(true);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
describe("edge cases", () => {
|
|
503
|
-
it("handles very old dates", () => {
|
|
504
|
-
const veryOldDate = new Date("2000-01-01T00:00:00.000Z");
|
|
505
|
-
const now = new Date("2024-01-15T12:00:00.000Z");
|
|
506
|
-
expect(isScheduleDue(veryOldDate, now)).toBe(true);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
it("handles very future dates", () => {
|
|
510
|
-
const veryFutureDate = new Date("2100-01-01T00:00:00.000Z");
|
|
511
|
-
const now = new Date("2024-01-15T12:00:00.000Z");
|
|
512
|
-
expect(isScheduleDue(veryFutureDate, now)).toBe(false);
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
});
|