@gencow/core 0.1.27 → 0.1.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.
Files changed (83) hide show
  1. package/dist/document-types.d.ts +65 -0
  2. package/dist/document-types.js +15 -0
  3. package/dist/grounded-answer-types.d.ts +62 -0
  4. package/dist/grounded-answer-types.js +6 -0
  5. package/dist/index.d.ts +10 -1
  6. package/dist/index.js +4 -0
  7. package/dist/rag-ingest-types.d.ts +39 -0
  8. package/dist/rag-ingest-types.js +1 -0
  9. package/dist/rag-operations-types.d.ts +81 -0
  10. package/dist/rag-operations-types.js +1 -0
  11. package/dist/rag-schema.d.ts +1557 -0
  12. package/dist/rag-schema.js +87 -0
  13. package/dist/reactive.d.ts +13 -0
  14. package/dist/rls-db.d.ts +9 -2
  15. package/dist/runtime-env-policy.d.ts +5 -0
  16. package/dist/runtime-env-policy.js +56 -0
  17. package/dist/search-types.d.ts +83 -0
  18. package/dist/search-types.js +1 -0
  19. package/dist/server.d.ts +1 -2
  20. package/dist/server.js +0 -1
  21. package/dist/storage-shared.d.ts +36 -0
  22. package/dist/storage-shared.js +39 -0
  23. package/dist/storage.d.ts +2 -26
  24. package/dist/storage.js +19 -15
  25. package/dist/workflow-types.d.ts +3 -1
  26. package/package.json +8 -7
  27. package/src/document-types.ts +95 -0
  28. package/src/grounded-answer-types.ts +78 -0
  29. package/src/index.ts +66 -1
  30. package/src/rag-ingest-types.ts +52 -0
  31. package/src/rag-operations-types.ts +90 -0
  32. package/src/rag-schema.ts +94 -0
  33. package/src/reactive.ts +13 -0
  34. package/src/rls-db.ts +9 -4
  35. package/src/runtime-env-policy.ts +66 -0
  36. package/src/search-types.ts +91 -0
  37. package/src/server.ts +1 -2
  38. package/src/storage-shared.ts +74 -0
  39. package/src/storage.ts +29 -46
  40. package/src/workflow-types.ts +3 -1
  41. package/src/__tests__/auth.test.ts +0 -118
  42. package/src/__tests__/crons.test.ts +0 -83
  43. package/src/__tests__/crud-codegen-integration.test.ts +0 -246
  44. package/src/__tests__/crud-owner-rls.test.ts +0 -387
  45. package/src/__tests__/crud.test.ts +0 -930
  46. package/src/__tests__/dist-exports.test.ts +0 -176
  47. package/src/__tests__/fixtures/basic/auth.ts +0 -32
  48. package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
  49. package/src/__tests__/fixtures/basic/index.ts +0 -6
  50. package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
  51. package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
  52. package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
  53. package/src/__tests__/fixtures/basic/schema.ts +0 -51
  54. package/src/__tests__/fixtures/basic/tasks.ts +0 -15
  55. package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
  56. package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
  57. package/src/__tests__/helpers/pglite-migrations.ts +0 -32
  58. package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
  59. package/src/__tests__/helpers/seed-like-fill.ts +0 -202
  60. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
  61. package/src/__tests__/httpaction.test.ts +0 -122
  62. package/src/__tests__/image-optimization.test.ts +0 -648
  63. package/src/__tests__/load.test.ts +0 -389
  64. package/src/__tests__/network-sim.test.ts +0 -319
  65. package/src/__tests__/reactive.test.ts +0 -479
  66. package/src/__tests__/retry.test.ts +0 -113
  67. package/src/__tests__/rls-crud-basic.test.ts +0 -317
  68. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
  69. package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
  70. package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
  71. package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
  72. package/src/__tests__/rls-session-and-policies.test.ts +0 -228
  73. package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
  74. package/src/__tests__/scheduler-durable.test.ts +0 -173
  75. package/src/__tests__/scheduler-exec.test.ts +0 -328
  76. package/src/__tests__/scheduler.test.ts +0 -187
  77. package/src/__tests__/storage.test.ts +0 -334
  78. package/src/__tests__/tsconfig.json +0 -8
  79. package/src/__tests__/validator.test.ts +0 -323
  80. package/src/__tests__/workflow.test.ts +0 -606
  81. package/src/__tests__/ws-integration.test.ts +0 -309
  82. package/src/__tests__/ws-scale.test.ts +0 -241
  83. package/src/auth.ts +0 -155
@@ -1,328 +0,0 @@
1
- /**
2
- * packages/core/src/__tests__/scheduler-exec.test.ts
3
- *
4
- * Scheduler 실행 테스트 — 순수 유닛 레벨에서 createScheduler()의
5
- * runAfter, registerAction, executeAction, cancel, cron 동작 검증.
6
- *
7
- * Run: bun test packages/core/src/__tests__/scheduler-exec.test.ts
8
- */
9
-
10
- import { describe, it, expect, beforeEach } from "bun:test";
11
- import { createScheduler, getSchedulerInfo } from "../scheduler";
12
-
13
- describe("Scheduler 실행 — runAfter", () => {
14
- it("runAfter → 지정 시간 후 action 실행", async () => {
15
- const scheduler = createScheduler();
16
- let executed = false;
17
-
18
- scheduler.registerAction("test.delayed", async () => {
19
- executed = true;
20
- });
21
-
22
- scheduler.runAfter(100, "test.delayed");
23
-
24
- // 100ms 전에는 실행 안 됨
25
- expect(executed).toBe(false);
26
-
27
- // 200ms 후 실행 확인
28
- await new Promise((r) => setTimeout(r, 200));
29
- expect(executed).toBe(true);
30
- });
31
-
32
- it("runAfter → args가 action에 전달된다", async () => {
33
- const scheduler = createScheduler();
34
- let receivedArgs: any = null;
35
-
36
- scheduler.registerAction("test.withArgs", async (args) => {
37
- receivedArgs = args;
38
- });
39
-
40
- scheduler.runAfter(50, "test.withArgs", { taskId: 42 });
41
- await new Promise((r) => setTimeout(r, 150));
42
-
43
- expect(receivedArgs).toEqual({ taskId: 42 });
44
- });
45
-
46
- it("runAfter → 반환된 jobId가 유효하다", () => {
47
- const scheduler = createScheduler();
48
- scheduler.registerAction("noop", async () => {});
49
- const id = scheduler.runAfter(10000, "noop");
50
- expect(typeof id).toBe("string");
51
- expect(id).toMatch(/^job_/);
52
-
53
- // cleanup
54
- scheduler.cancel(id);
55
- });
56
-
57
- it("runAfter → pendingJobs에 등록된다", () => {
58
- const scheduler = createScheduler();
59
- scheduler.registerAction("noop", async () => {});
60
- const id = scheduler.runAfter(10000, "noop");
61
-
62
- const info = getSchedulerInfo();
63
- const job = info.pendingJobs.find((j: any) => j.id === id);
64
- expect(job).toBeDefined();
65
- expect(job!.action).toBe("noop");
66
-
67
- // cleanup
68
- scheduler.cancel(id);
69
- });
70
-
71
- it("runAfter → 실행 후 pendingJobs에서 제거된다", async () => {
72
- const scheduler = createScheduler();
73
- let actionDone = false;
74
- scheduler.registerAction("fast-track", async () => {
75
- actionDone = true;
76
- });
77
- const id = scheduler.runAfter(50, "fast-track");
78
-
79
- // 실행 전: 등록 확인
80
- const infoBefore = getSchedulerInfo();
81
- expect(infoBefore.pendingJobs.some((j: any) => j.id === id)).toBe(true);
82
-
83
- // 실행 완료 대기 (setTimeout + await executeAction + splice)
84
- // setTimeout 50ms + executeAction async + splice → 최소 300ms 필요
85
- await new Promise((r) => setTimeout(r, 500));
86
- expect(actionDone).toBe(true);
87
-
88
- // 실행 후: 해당 ID가 제거됨 (splice는 executeAction 후 동기적으로 실행)
89
- const infoAfter = getSchedulerInfo();
90
- const stillPending = infoAfter.pendingJobs.some((j: any) => j.id === id);
91
-
92
- // 만약 아직 남아있다면 내부 구현의 비동기 splice 이슈이므로 경고만 출력
93
- if (stillPending) {
94
- console.warn("[test] pendingJobs splice is async — skipping strict check");
95
- }
96
- // action이 실행된 것만 확인 (핵심 기능 검증)
97
- expect(actionDone).toBe(true);
98
- });
99
- });
100
-
101
- describe("Scheduler 실행 — cancel", () => {
102
- it("cancel → runAfter를 취소할 수 있다", async () => {
103
- const scheduler = createScheduler();
104
- let executed = false;
105
-
106
- scheduler.registerAction("cancel.test", async () => {
107
- executed = true;
108
- });
109
-
110
- const id = scheduler.runAfter(100, "cancel.test");
111
- const cancelled = scheduler.cancel(id);
112
- expect(cancelled).toBe(true);
113
-
114
- await new Promise((r) => setTimeout(r, 200));
115
- expect(executed).toBe(false); // 취소되어 실행 안 됨
116
- });
117
-
118
- it("cancel → 이미 취소된 job → false 반환", () => {
119
- const scheduler = createScheduler();
120
- scheduler.registerAction("noop", async () => {});
121
- const id = scheduler.runAfter(10000, "noop");
122
- scheduler.cancel(id);
123
- expect(scheduler.cancel(id)).toBe(false); // 이미 취소됨
124
- });
125
-
126
- it("cancel → 존재하지 않는 ID → false 반환", () => {
127
- const scheduler = createScheduler();
128
- expect(scheduler.cancel("nonexistent_job")).toBe(false);
129
- });
130
- });
131
-
132
- describe("Scheduler 실행 — registerAction + executeAction", () => {
133
- it("registerAction 후 executeAction으로 즉시 실행", async () => {
134
- const scheduler = createScheduler();
135
- let result = "";
136
-
137
- scheduler.registerAction("greet", async (args) => {
138
- result = `Hello ${args.name}`;
139
- });
140
-
141
- await scheduler.executeAction("greet", { name: "World" });
142
- expect(result).toBe("Hello World");
143
- });
144
-
145
- it("미등록 action executeAction → throw 안 함, console.error 출력", async () => {
146
- const scheduler = createScheduler();
147
- // 공개 API는 에러를 삼기고 console.error만 출력
148
- const errors: string[] = [];
149
- const origError = console.error;
150
- console.error = (...args: any[]) => {
151
- errors.push(args.map(String).join(" "));
152
- };
153
-
154
- await scheduler.executeAction("nonexistent"); // throw하지 않음
155
-
156
- console.error = origError;
157
- expect(errors.some((e) => e.includes("nonexistent"))).toBe(true);
158
- });
159
-
160
- it("action 에러 시 다른 action에 영향 없음", async () => {
161
- const scheduler = createScheduler();
162
- let secondRan = false;
163
-
164
- scheduler.registerAction("failing", async () => {
165
- throw new Error("Intentional failure");
166
- });
167
- scheduler.registerAction("healthy", async () => {
168
- secondRan = true;
169
- });
170
-
171
- // 실패하는 action — 공개 API는 throw하지 않음 (에러를 삼김)
172
- const origError = console.error;
173
- console.error = () => {}; // suppress expected error log
174
- await scheduler.executeAction("failing"); // throw하지 않음
175
- console.error = origError;
176
-
177
- // 정상 action은 여전히 동작
178
- await scheduler.executeAction("healthy");
179
- expect(secondRan).toBe(true);
180
- });
181
-
182
- it("executeActionStrict는 미등록 action에서 throw한다", async () => {
183
- const scheduler = createScheduler();
184
- await expect(scheduler.executeActionStrict("nonexistent")).rejects.toThrow(
185
- 'Action "nonexistent" not registered',
186
- );
187
- });
188
- });
189
-
190
- describe("Scheduler 실행 — runAt", () => {
191
- it("runAt(과거 시점) → 즉시 실행", async () => {
192
- const scheduler = createScheduler();
193
- let executed = false;
194
-
195
- scheduler.registerAction("past", async () => {
196
- executed = true;
197
- });
198
-
199
- // 이미 지난 시점
200
- scheduler.runAt(Date.now() - 1000, "past");
201
- await new Promise((r) => setTimeout(r, 100));
202
- expect(executed).toBe(true);
203
- });
204
-
205
- it("runAt(Date 객체) → 지원됨", async () => {
206
- const scheduler = createScheduler();
207
- let executed = false;
208
-
209
- scheduler.registerAction("dateObj", async () => {
210
- executed = true;
211
- });
212
-
213
- scheduler.runAt(new Date(Date.now() + 50), "dateObj");
214
- await new Promise((r) => setTimeout(r, 150));
215
- expect(executed).toBe(true);
216
- });
217
- });
218
-
219
- describe("Scheduler 실행 — cron", () => {
220
- it("cron 등록 → cronInfo에 기록된다", () => {
221
- const scheduler = createScheduler();
222
- scheduler.cron("test-cron", "*/5 * * * *", async () => {});
223
-
224
- const info = getSchedulerInfo();
225
- const cronEntry = info.crons.find((c: any) => c.name === "test-cron");
226
- expect(cronEntry).toBeDefined();
227
- expect(cronEntry!.pattern).toBe("*/5 * * * *");
228
- });
229
-
230
- it("cron 등록 시 name과 pattern이 올바르게 저장된다", () => {
231
- const scheduler = createScheduler();
232
- scheduler.cron("hourly-cleanup", "0 * * * *", async () => {});
233
-
234
- const info = getSchedulerInfo();
235
- const entry = info.crons.find((c: any) => c.name === "hourly-cleanup");
236
- expect(entry).toBeDefined();
237
- expect(entry!.name).toBe("hourly-cleanup");
238
- expect(entry!.registeredAt).toBeDefined();
239
- });
240
-
241
- it("동일 이름 cron 재등록 → 이전 것 중지 후 교체", () => {
242
- const scheduler = createScheduler();
243
- scheduler.cron("dup", "*/5 * * * *", async () => {});
244
- scheduler.cron("dup", "*/10 * * * *", async () => {});
245
-
246
- // cronInfo에 2개 다 기록되어 있지만,
247
- // 내부적으로 첫 번째 task는 stop()됨
248
- const info = getSchedulerInfo();
249
- const dups = info.crons.filter((c: any) => c.name === "dup");
250
- expect(dups.length).toBeGreaterThanOrEqual(1);
251
- });
252
-
253
- it("초 단위 cron — 1초 간격 실행 확인 (6자리 패턴)", async () => {
254
- const scheduler = createScheduler();
255
- let count = 0;
256
-
257
- // 매 초 실행 (6자리 cron)
258
- scheduler.cron("every-second", "* * * * * *", async () => {
259
- count++;
260
- });
261
-
262
- // 2.5초 대기 → 최소 2회 실행 기대
263
- await new Promise((r) => setTimeout(r, 2500));
264
- expect(count).toBeGreaterThanOrEqual(2);
265
- });
266
- });
267
-
268
- describe("Scheduler 실행 — onError dead-letter", () => {
269
- it("runAfter 실패 시 onError action이 호출된다", async () => {
270
- const scheduler = createScheduler();
271
- let errorHandlerArgs: any = null;
272
-
273
- scheduler.registerAction("failing.step", async () => {
274
- throw new Error("Step failed!");
275
- });
276
-
277
- scheduler.registerAction("pipeline.onError", async (args) => {
278
- errorHandlerArgs = args;
279
- });
280
-
281
- scheduler.runAfter(
282
- 50,
283
- "failing.step",
284
- { input: "test" },
285
- {
286
- onError: "pipeline.onError",
287
- },
288
- );
289
-
290
- await new Promise((r) => setTimeout(r, 300));
291
-
292
- expect(errorHandlerArgs).not.toBeNull();
293
- expect(errorHandlerArgs.failedAction).toBe("failing.step");
294
- expect(errorHandlerArgs.error).toBe("Step failed!");
295
- expect(errorHandlerArgs.originalArgs).toEqual({ input: "test" });
296
- });
297
-
298
- it("실패한 작업이 failedJobs에 기록된다", async () => {
299
- const scheduler = createScheduler();
300
-
301
- scheduler.registerAction("record.fail", async () => {
302
- throw new Error("Recorded failure");
303
- });
304
-
305
- scheduler.runAfter(50, "record.fail", { id: 1 });
306
-
307
- await new Promise((r) => setTimeout(r, 300));
308
-
309
- const info = getSchedulerInfo();
310
- const failed = info.failedJobs.find((j: any) => j.action === "record.fail");
311
- expect(failed).toBeDefined();
312
- expect(failed!.error).toBe("Recorded failure");
313
- });
314
-
315
- it("pendingJobs에 status 필드가 포함된다", () => {
316
- const scheduler = createScheduler();
317
- scheduler.registerAction("noop", async () => {});
318
- const id = scheduler.runAfter(10000, "noop");
319
-
320
- const info = getSchedulerInfo();
321
- const job = info.pendingJobs.find((j: any) => j.id === id);
322
- expect(job).toBeDefined();
323
- expect(job!.status).toBe("pending");
324
-
325
- // cleanup
326
- scheduler.cancel(id);
327
- });
328
- });
@@ -1,187 +0,0 @@
1
- /**
2
- * packages/core/src/__tests__/scheduler.test.ts
3
- *
4
- * Tests for createScheduler() — scheduled tasks + action registry.
5
- *
6
- * Run: bun test packages/core/src/__tests__/scheduler.test.ts
7
- */
8
-
9
- import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test";
10
- import { createScheduler, getSchedulerInfo } from "../scheduler";
11
-
12
- describe("createScheduler()", () => {
13
- let scheduler: ReturnType<typeof createScheduler>;
14
-
15
- beforeEach(() => {
16
- scheduler = createScheduler();
17
- });
18
-
19
- // ─── registerAction + executeAction ──────────────────
20
-
21
- describe("registerAction / executeAction", () => {
22
- it("등록된 액션 실행", async () => {
23
- const fn = mock(async (args: any) => {});
24
- scheduler.registerAction("test.action", fn);
25
-
26
- await scheduler.executeAction("test.action", { foo: 1 });
27
-
28
- expect(fn).toHaveBeenCalledTimes(1);
29
- expect(fn.mock.calls[0][0]).toEqual({ foo: 1 });
30
- });
31
-
32
- it("미등록 액션 실행 시 console.error만 출력 (throw 안 함)", async () => {
33
- const errorSpy = mock(() => {});
34
- const orig = console.error;
35
- console.error = errorSpy;
36
-
37
- await scheduler.executeAction("nonexistent.action");
38
-
39
- expect(errorSpy).toHaveBeenCalled();
40
- expect(String(errorSpy.mock.calls[0][0])).toContain("nonexistent.action");
41
-
42
- console.error = orig;
43
- });
44
-
45
- it("액션 핸들러 에러 시 console.error 출력 (throw 안 함)", async () => {
46
- scheduler.registerAction("failing.action", async () => {
47
- throw new Error("action failed");
48
- });
49
-
50
- const errorSpy = mock((..._args: any[]) => {});
51
- const orig = console.error;
52
- console.error = errorSpy;
53
-
54
- await scheduler.executeAction("failing.action");
55
-
56
- expect(errorSpy).toHaveBeenCalled();
57
-
58
- console.error = orig;
59
- });
60
-
61
- it("executeActionStrict는 등록된 액션의 args를 그대로 전달한다", async () => {
62
- const fn = mock(async (_args: any) => {});
63
- scheduler.registerAction("strict.action", fn);
64
-
65
- await scheduler.executeActionStrict("strict.action", { workflowId: "wf-1" });
66
-
67
- expect(fn).toHaveBeenCalledTimes(1);
68
- expect(fn.mock.calls[0][0]).toEqual({ workflowId: "wf-1" });
69
- });
70
-
71
- it("executeActionStrict는 액션 에러를 호출자에게 다시 던진다", async () => {
72
- scheduler.registerAction("strict.fail", async () => {
73
- throw new Error("strict failed");
74
- });
75
-
76
- await expect(scheduler.executeActionStrict("strict.fail")).rejects.toThrow("strict failed");
77
- });
78
- });
79
-
80
- // ─── runAfter ───────────────────────────────────────
81
-
82
- describe("runAfter()", () => {
83
- it("jobId를 반환한다", () => {
84
- const jobId = scheduler.runAfter(10000, "test.action");
85
- expect(typeof jobId).toBe("string");
86
- expect(jobId).toContain("job_");
87
-
88
- // cleanup
89
- scheduler.cancel(jobId);
90
- });
91
-
92
- it("지정 시간 후 액션을 실행한다", async () => {
93
- const fn = mock(async () => {});
94
- scheduler.registerAction("delayed.action", fn);
95
-
96
- scheduler.runAfter(50, "delayed.action", { key: "val" });
97
-
98
- // 액션은 아직 실행되지 않아야 함
99
- expect(fn).not.toHaveBeenCalled();
100
-
101
- // 100ms 대기 후 실행됨
102
- await new Promise((r) => setTimeout(r, 100));
103
- expect(fn).toHaveBeenCalledTimes(1);
104
- expect(fn.mock.calls[0][0]).toEqual({ key: "val" });
105
- });
106
-
107
- it("pendingJobs에 등록된다", () => {
108
- const jobId = scheduler.runAfter(60000, "test.long");
109
- const info = getSchedulerInfo();
110
- const found = info.pendingJobs.find((j) => j.id === jobId);
111
- expect(found).toBeDefined();
112
- expect(found!.action).toBe("test.long");
113
-
114
- // cleanup
115
- scheduler.cancel(jobId);
116
- });
117
- });
118
-
119
- // ─── cancel ─────────────────────────────────────────
120
-
121
- describe("cancel()", () => {
122
- it("예약된 작업을 취소한다", () => {
123
- const fn = mock(async () => {});
124
- scheduler.registerAction("cancel.test", fn);
125
-
126
- const jobId = scheduler.runAfter(50, "cancel.test");
127
- const cancelled = scheduler.cancel(jobId);
128
-
129
- expect(cancelled).toBe(true);
130
- });
131
-
132
- it("존재하지 않는 jobId → false 반환", () => {
133
- expect(scheduler.cancel("nonexistent_id")).toBe(false);
134
- });
135
-
136
- it("취소된 작업은 실행되지 않는다", async () => {
137
- const fn = mock(async () => {});
138
- scheduler.registerAction("cancel.verify", fn);
139
-
140
- const jobId = scheduler.runAfter(30, "cancel.verify");
141
- scheduler.cancel(jobId);
142
-
143
- await new Promise((r) => setTimeout(r, 100));
144
- expect(fn).not.toHaveBeenCalled();
145
- });
146
- });
147
-
148
- // ─── runAt ──────────────────────────────────────────
149
-
150
- describe("runAt()", () => {
151
- it("Date 객체로 예약 가능", () => {
152
- const future = new Date(Date.now() + 60000);
153
- const jobId = scheduler.runAt(future, "test.runat");
154
- expect(typeof jobId).toBe("string");
155
- scheduler.cancel(jobId);
156
- });
157
-
158
- it("timestamp(ms)로 예약 가능", () => {
159
- const ts = Date.now() + 60000;
160
- const jobId = scheduler.runAt(ts, "test.runat.ts");
161
- expect(typeof jobId).toBe("string");
162
- scheduler.cancel(jobId);
163
- });
164
-
165
- it("과거 시각 → 즉시 실행 (ms=0)", async () => {
166
- const fn = mock(async () => {});
167
- scheduler.registerAction("past.action", fn);
168
-
169
- scheduler.runAt(Date.now() - 10000, "past.action");
170
-
171
- await new Promise((r) => setTimeout(r, 50));
172
- expect(fn).toHaveBeenCalledTimes(1);
173
- });
174
- });
175
-
176
- // ─── getSchedulerInfo ───────────────────────────────
177
-
178
- describe("getSchedulerInfo()", () => {
179
- it("crons와 pendingJobs를 반환한다", () => {
180
- const info = getSchedulerInfo();
181
- expect(info).toHaveProperty("crons");
182
- expect(info).toHaveProperty("pendingJobs");
183
- expect(Array.isArray(info.crons)).toBe(true);
184
- expect(Array.isArray(info.pendingJobs)).toBe(true);
185
- });
186
- });
187
- });