@herdctl/core 0.0.1 → 0.1.0

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 (284) hide show
  1. package/dist/config/__tests__/agent.test.js +61 -13
  2. package/dist/config/__tests__/agent.test.js.map +1 -1
  3. package/dist/config/__tests__/merge.test.js +10 -3
  4. package/dist/config/__tests__/merge.test.js.map +1 -1
  5. package/dist/config/__tests__/schema.test.js +350 -1
  6. package/dist/config/__tests__/schema.test.js.map +1 -1
  7. package/dist/config/index.d.ts +1 -1
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/index.js +3 -1
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/config/schema.d.ts +841 -27
  12. package/dist/config/schema.d.ts.map +1 -1
  13. package/dist/config/schema.js +129 -10
  14. package/dist/config/schema.js.map +1 -1
  15. package/dist/fleet-manager/__tests__/coverage.test.js +14 -331
  16. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
  17. package/dist/fleet-manager/__tests__/errors.test.js +1 -49
  18. package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
  19. package/dist/fleet-manager/__tests__/integration.test.js +114 -0
  20. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
  21. package/dist/fleet-manager/__tests__/job-control.test.js +13 -14
  22. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -1
  23. package/dist/fleet-manager/__tests__/reload.test.js +12 -2
  24. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
  25. package/dist/fleet-manager/__tests__/status-queries.test.js +6 -0
  26. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -1
  27. package/dist/fleet-manager/__tests__/trigger.test.js +10 -2
  28. package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -1
  29. package/dist/fleet-manager/config-reload.d.ts +164 -0
  30. package/dist/fleet-manager/config-reload.d.ts.map +1 -0
  31. package/dist/fleet-manager/config-reload.js +445 -0
  32. package/dist/fleet-manager/config-reload.js.map +1 -0
  33. package/dist/fleet-manager/context.d.ts +76 -0
  34. package/dist/fleet-manager/context.d.ts.map +1 -0
  35. package/dist/fleet-manager/context.js +11 -0
  36. package/dist/fleet-manager/context.js.map +1 -0
  37. package/dist/fleet-manager/errors.d.ts +0 -25
  38. package/dist/fleet-manager/errors.d.ts.map +1 -1
  39. package/dist/fleet-manager/errors.js +0 -38
  40. package/dist/fleet-manager/errors.js.map +1 -1
  41. package/dist/fleet-manager/event-emitters.d.ts +123 -0
  42. package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
  43. package/dist/fleet-manager/event-emitters.js +136 -0
  44. package/dist/fleet-manager/event-emitters.js.map +1 -0
  45. package/dist/fleet-manager/event-types.d.ts +0 -15
  46. package/dist/fleet-manager/event-types.d.ts.map +1 -1
  47. package/dist/fleet-manager/fleet-manager.d.ts +40 -653
  48. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
  49. package/dist/fleet-manager/fleet-manager.js +95 -1720
  50. package/dist/fleet-manager/fleet-manager.js.map +1 -1
  51. package/dist/fleet-manager/index.d.ts +13 -2
  52. package/dist/fleet-manager/index.d.ts.map +1 -1
  53. package/dist/fleet-manager/index.js +19 -6
  54. package/dist/fleet-manager/index.js.map +1 -1
  55. package/dist/fleet-manager/job-control.d.ts +67 -0
  56. package/dist/fleet-manager/job-control.d.ts.map +1 -0
  57. package/dist/fleet-manager/job-control.js +333 -0
  58. package/dist/fleet-manager/job-control.js.map +1 -0
  59. package/dist/fleet-manager/log-streaming.d.ts +171 -0
  60. package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
  61. package/dist/fleet-manager/log-streaming.js +503 -0
  62. package/dist/fleet-manager/log-streaming.js.map +1 -0
  63. package/dist/fleet-manager/schedule-executor.d.ts +63 -0
  64. package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
  65. package/dist/fleet-manager/schedule-executor.js +209 -0
  66. package/dist/fleet-manager/schedule-executor.js.map +1 -0
  67. package/dist/fleet-manager/schedule-management.d.ts +71 -0
  68. package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
  69. package/dist/fleet-manager/schedule-management.js +171 -0
  70. package/dist/fleet-manager/schedule-management.js.map +1 -0
  71. package/dist/fleet-manager/status-queries.d.ts +105 -0
  72. package/dist/fleet-manager/status-queries.d.ts.map +1 -0
  73. package/dist/fleet-manager/status-queries.js +247 -0
  74. package/dist/fleet-manager/status-queries.js.map +1 -0
  75. package/dist/fleet-manager/types.d.ts +0 -39
  76. package/dist/fleet-manager/types.d.ts.map +1 -1
  77. package/dist/runner/__tests__/job-executor.test.js +206 -1
  78. package/dist/runner/__tests__/job-executor.test.js.map +1 -1
  79. package/dist/runner/job-executor.d.ts +9 -0
  80. package/dist/runner/job-executor.d.ts.map +1 -1
  81. package/dist/runner/job-executor.js +78 -4
  82. package/dist/runner/job-executor.js.map +1 -1
  83. package/dist/runner/message-processor.d.ts.map +1 -1
  84. package/dist/runner/message-processor.js +53 -0
  85. package/dist/runner/message-processor.js.map +1 -1
  86. package/dist/runner/types.d.ts +3 -1
  87. package/dist/runner/types.d.ts.map +1 -1
  88. package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
  89. package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
  90. package/dist/scheduler/__tests__/cron.test.js +867 -0
  91. package/dist/scheduler/__tests__/cron.test.js.map +1 -0
  92. package/dist/scheduler/__tests__/scheduler.test.js +164 -5
  93. package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
  94. package/dist/scheduler/cron.d.ts +126 -0
  95. package/dist/scheduler/cron.d.ts.map +1 -0
  96. package/dist/scheduler/cron.js +390 -0
  97. package/dist/scheduler/cron.js.map +1 -0
  98. package/dist/scheduler/errors.d.ts +81 -1
  99. package/dist/scheduler/errors.d.ts.map +1 -1
  100. package/dist/scheduler/errors.js +81 -6
  101. package/dist/scheduler/errors.js.map +1 -1
  102. package/dist/scheduler/index.d.ts +1 -0
  103. package/dist/scheduler/index.d.ts.map +1 -1
  104. package/dist/scheduler/index.js +2 -0
  105. package/dist/scheduler/index.js.map +1 -1
  106. package/dist/scheduler/schedule-runner.d.ts +2 -2
  107. package/dist/scheduler/schedule-runner.d.ts.map +1 -1
  108. package/dist/scheduler/schedule-runner.js +20 -8
  109. package/dist/scheduler/schedule-runner.js.map +1 -1
  110. package/dist/scheduler/scheduler.d.ts +4 -4
  111. package/dist/scheduler/scheduler.d.ts.map +1 -1
  112. package/dist/scheduler/scheduler.js +95 -20
  113. package/dist/scheduler/scheduler.js.map +1 -1
  114. package/dist/scheduler/types.d.ts +1 -1
  115. package/dist/scheduler/types.d.ts.map +1 -1
  116. package/dist/state/schemas/job-metadata.d.ts +2 -2
  117. package/package.json +33 -8
  118. package/.turbo/turbo-build.log +0 -4
  119. package/.turbo/turbo-test.log +0 -219
  120. package/.turbo/turbo-typecheck.log +0 -4
  121. package/coverage/base.css +0 -224
  122. package/coverage/block-navigation.js +0 -87
  123. package/coverage/coverage-final.json +0 -51
  124. package/coverage/favicon.png +0 -0
  125. package/coverage/index.html +0 -251
  126. package/coverage/prettify.css +0 -1
  127. package/coverage/prettify.js +0 -2
  128. package/coverage/sort-arrow-sprite.png +0 -0
  129. package/coverage/sorter.js +0 -210
  130. package/coverage/src/config/index.html +0 -191
  131. package/coverage/src/config/index.ts.html +0 -442
  132. package/coverage/src/config/interpolate.ts.html +0 -652
  133. package/coverage/src/config/loader.ts.html +0 -1501
  134. package/coverage/src/config/merge.ts.html +0 -823
  135. package/coverage/src/config/parser.ts.html +0 -1213
  136. package/coverage/src/config/schema.ts.html +0 -1123
  137. package/coverage/src/fleet-manager/errors.ts.html +0 -2326
  138. package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
  139. package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
  140. package/coverage/src/fleet-manager/index.html +0 -206
  141. package/coverage/src/fleet-manager/index.ts.html +0 -469
  142. package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
  143. package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
  144. package/coverage/src/fleet-manager/types.ts.html +0 -2602
  145. package/coverage/src/index.html +0 -116
  146. package/coverage/src/index.ts.html +0 -181
  147. package/coverage/src/runner/errors.ts.html +0 -1006
  148. package/coverage/src/runner/index.html +0 -191
  149. package/coverage/src/runner/index.ts.html +0 -256
  150. package/coverage/src/runner/job-executor.ts.html +0 -1429
  151. package/coverage/src/runner/message-processor.ts.html +0 -1150
  152. package/coverage/src/runner/sdk-adapter.ts.html +0 -658
  153. package/coverage/src/runner/types.ts.html +0 -559
  154. package/coverage/src/scheduler/errors.ts.html +0 -388
  155. package/coverage/src/scheduler/index.html +0 -206
  156. package/coverage/src/scheduler/index.ts.html +0 -244
  157. package/coverage/src/scheduler/interval.ts.html +0 -652
  158. package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
  159. package/coverage/src/scheduler/schedule-state.ts.html +0 -718
  160. package/coverage/src/scheduler/scheduler.ts.html +0 -1795
  161. package/coverage/src/scheduler/types.ts.html +0 -733
  162. package/coverage/src/state/directory.ts.html +0 -736
  163. package/coverage/src/state/errors.ts.html +0 -376
  164. package/coverage/src/state/fleet-state.ts.html +0 -937
  165. package/coverage/src/state/index.html +0 -221
  166. package/coverage/src/state/index.ts.html +0 -322
  167. package/coverage/src/state/job-metadata.ts.html +0 -1420
  168. package/coverage/src/state/job-output.ts.html +0 -1033
  169. package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
  170. package/coverage/src/state/schemas/index.html +0 -176
  171. package/coverage/src/state/schemas/index.ts.html +0 -286
  172. package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
  173. package/coverage/src/state/schemas/job-output.ts.html +0 -616
  174. package/coverage/src/state/schemas/session-info.ts.html +0 -361
  175. package/coverage/src/state/session.ts.html +0 -844
  176. package/coverage/src/state/types.ts.html +0 -262
  177. package/coverage/src/state/utils/atomic.ts.html +0 -748
  178. package/coverage/src/state/utils/index.html +0 -146
  179. package/coverage/src/state/utils/index.ts.html +0 -103
  180. package/coverage/src/state/utils/reads.ts.html +0 -1621
  181. package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
  182. package/coverage/src/work-sources/adapters/index.html +0 -131
  183. package/coverage/src/work-sources/adapters/index.ts.html +0 -277
  184. package/coverage/src/work-sources/errors.ts.html +0 -298
  185. package/coverage/src/work-sources/index.html +0 -176
  186. package/coverage/src/work-sources/index.ts.html +0 -529
  187. package/coverage/src/work-sources/manager.ts.html +0 -1324
  188. package/coverage/src/work-sources/registry.ts.html +0 -619
  189. package/coverage/src/work-sources/types.ts.html +0 -568
  190. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
  191. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
  192. package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
  193. package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
  194. package/src/config/__tests__/agent.test.ts +0 -864
  195. package/src/config/__tests__/interpolate.test.ts +0 -644
  196. package/src/config/__tests__/loader.test.ts +0 -784
  197. package/src/config/__tests__/merge.test.ts +0 -751
  198. package/src/config/__tests__/parser.test.ts +0 -533
  199. package/src/config/__tests__/schema.test.ts +0 -873
  200. package/src/config/index.ts +0 -119
  201. package/src/config/interpolate.ts +0 -189
  202. package/src/config/loader.ts +0 -472
  203. package/src/config/merge.ts +0 -246
  204. package/src/config/parser.ts +0 -376
  205. package/src/config/schema.ts +0 -346
  206. package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
  207. package/src/fleet-manager/__tests__/errors.test.ts +0 -660
  208. package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
  209. package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
  210. package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
  211. package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
  212. package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
  213. package/src/fleet-manager/__tests__/reload.test.ts +0 -751
  214. package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
  215. package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
  216. package/src/fleet-manager/errors.ts +0 -747
  217. package/src/fleet-manager/event-types.ts +0 -378
  218. package/src/fleet-manager/fleet-manager.ts +0 -2315
  219. package/src/fleet-manager/index.ts +0 -128
  220. package/src/fleet-manager/job-manager.ts +0 -663
  221. package/src/fleet-manager/job-queue.ts +0 -798
  222. package/src/fleet-manager/types.ts +0 -839
  223. package/src/index.ts +0 -32
  224. package/src/runner/__tests__/errors.test.ts +0 -382
  225. package/src/runner/__tests__/job-executor.test.ts +0 -1708
  226. package/src/runner/__tests__/message-processor.test.ts +0 -960
  227. package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
  228. package/src/runner/errors.ts +0 -307
  229. package/src/runner/index.ts +0 -57
  230. package/src/runner/job-executor.ts +0 -448
  231. package/src/runner/message-processor.ts +0 -355
  232. package/src/runner/sdk-adapter.ts +0 -191
  233. package/src/runner/types.ts +0 -158
  234. package/src/scheduler/__tests__/errors.test.ts +0 -159
  235. package/src/scheduler/__tests__/interval.test.ts +0 -515
  236. package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
  237. package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
  238. package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
  239. package/src/scheduler/errors.ts +0 -101
  240. package/src/scheduler/index.ts +0 -53
  241. package/src/scheduler/interval.ts +0 -189
  242. package/src/scheduler/schedule-runner.ts +0 -442
  243. package/src/scheduler/schedule-state.ts +0 -211
  244. package/src/scheduler/scheduler.ts +0 -570
  245. package/src/scheduler/types.ts +0 -216
  246. package/src/state/__tests__/directory.test.ts +0 -595
  247. package/src/state/__tests__/fleet-state.test.ts +0 -868
  248. package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
  249. package/src/state/__tests__/job-metadata.test.ts +0 -831
  250. package/src/state/__tests__/job-output.test.ts +0 -856
  251. package/src/state/__tests__/session-schema.test.ts +0 -378
  252. package/src/state/__tests__/session.test.ts +0 -604
  253. package/src/state/directory.ts +0 -217
  254. package/src/state/errors.ts +0 -97
  255. package/src/state/fleet-state.ts +0 -284
  256. package/src/state/index.ts +0 -79
  257. package/src/state/job-metadata.ts +0 -445
  258. package/src/state/job-output.ts +0 -316
  259. package/src/state/schemas/__tests__/job-output.test.ts +0 -338
  260. package/src/state/schemas/fleet-state.ts +0 -120
  261. package/src/state/schemas/index.ts +0 -67
  262. package/src/state/schemas/job-metadata.ts +0 -181
  263. package/src/state/schemas/job-output.ts +0 -177
  264. package/src/state/schemas/session-info.ts +0 -92
  265. package/src/state/session.ts +0 -253
  266. package/src/state/types.ts +0 -59
  267. package/src/state/utils/__tests__/atomic.test.ts +0 -723
  268. package/src/state/utils/__tests__/reads.test.ts +0 -1071
  269. package/src/state/utils/atomic.ts +0 -221
  270. package/src/state/utils/index.ts +0 -6
  271. package/src/state/utils/reads.ts +0 -512
  272. package/src/work-sources/__tests__/github.test.ts +0 -1800
  273. package/src/work-sources/__tests__/manager.test.ts +0 -529
  274. package/src/work-sources/__tests__/registry.test.ts +0 -477
  275. package/src/work-sources/__tests__/types.test.ts +0 -479
  276. package/src/work-sources/adapters/github.ts +0 -1166
  277. package/src/work-sources/adapters/index.ts +0 -64
  278. package/src/work-sources/errors.ts +0 -71
  279. package/src/work-sources/index.ts +0 -148
  280. package/src/work-sources/manager.ts +0 -413
  281. package/src/work-sources/registry.ts +0 -178
  282. package/src/work-sources/types.ts +0 -161
  283. package/tsconfig.json +0 -9
  284. package/vitest.config.ts +0 -19
@@ -1,477 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import type { WorkSourceAdapter, WorkSourceConfig, WorkSourceFactory } from "../index.js";
3
- import {
4
- registerWorkSource,
5
- getWorkSource,
6
- getRegisteredTypes,
7
- isWorkSourceRegistered,
8
- unregisterWorkSource,
9
- clearWorkSourceRegistry,
10
- UnknownWorkSourceError,
11
- DuplicateWorkSourceError,
12
- } from "../index.js";
13
-
14
- // =============================================================================
15
- // Test Fixtures
16
- // =============================================================================
17
-
18
- /**
19
- * Create a mock work source adapter for testing
20
- */
21
- function createMockAdapter(type: string): WorkSourceAdapter {
22
- return {
23
- type,
24
- async fetchAvailableWork() {
25
- return { items: [] };
26
- },
27
- async claimWork() {
28
- return { success: true };
29
- },
30
- async completeWork() {},
31
- async releaseWork() {
32
- return { success: true };
33
- },
34
- async getWork() {
35
- return undefined;
36
- },
37
- };
38
- }
39
-
40
- /**
41
- * Create a mock factory that returns a configured adapter
42
- */
43
- function createMockFactory(type: string): WorkSourceFactory {
44
- return (_config: WorkSourceConfig) => createMockAdapter(type);
45
- }
46
-
47
- // =============================================================================
48
- // Test Setup/Teardown
49
- // =============================================================================
50
-
51
- describe("Work Source Registry", () => {
52
- // Store originally registered types to restore after tests
53
- let originalTypes: string[] = [];
54
-
55
- beforeEach(() => {
56
- // Capture what was registered before the test
57
- originalTypes = getRegisteredTypes();
58
- // Clear for isolated testing
59
- clearWorkSourceRegistry();
60
- });
61
-
62
- afterEach(() => {
63
- // Restore original registrations after each test
64
- clearWorkSourceRegistry();
65
- // Re-import to trigger auto-registration of built-in adapters
66
- // Note: In real tests, the module is already loaded so we manually
67
- // restore the github adapter
68
- if (originalTypes.includes("github")) {
69
- registerWorkSource("github", createMockFactory("github"));
70
- }
71
- });
72
-
73
- // ===========================================================================
74
- // registerWorkSource tests
75
- // ===========================================================================
76
-
77
- describe("registerWorkSource", () => {
78
- it("registers a new work source factory", () => {
79
- const factory = createMockFactory("test");
80
- registerWorkSource("test", factory);
81
-
82
- expect(isWorkSourceRegistered("test")).toBe(true);
83
- expect(getRegisteredTypes()).toContain("test");
84
- });
85
-
86
- it("allows registering multiple different types", () => {
87
- registerWorkSource("type-a", createMockFactory("type-a"));
88
- registerWorkSource("type-b", createMockFactory("type-b"));
89
- registerWorkSource("type-c", createMockFactory("type-c"));
90
-
91
- const types = getRegisteredTypes();
92
- expect(types).toContain("type-a");
93
- expect(types).toContain("type-b");
94
- expect(types).toContain("type-c");
95
- expect(types).toHaveLength(3);
96
- });
97
-
98
- it("throws DuplicateWorkSourceError when registering same type twice", () => {
99
- registerWorkSource("duplicate", createMockFactory("duplicate"));
100
-
101
- expect(() => {
102
- registerWorkSource("duplicate", createMockFactory("duplicate"));
103
- }).toThrow(DuplicateWorkSourceError);
104
- });
105
-
106
- it("DuplicateWorkSourceError contains the source type", () => {
107
- registerWorkSource("my-source", createMockFactory("my-source"));
108
-
109
- try {
110
- registerWorkSource("my-source", createMockFactory("my-source"));
111
- expect.fail("Should have thrown DuplicateWorkSourceError");
112
- } catch (error) {
113
- expect(error).toBeInstanceOf(DuplicateWorkSourceError);
114
- const dupError = error as DuplicateWorkSourceError;
115
- expect(dupError.sourceType).toBe("my-source");
116
- expect(dupError.message).toContain("my-source");
117
- expect(dupError.message).toContain("already registered");
118
- }
119
- });
120
- });
121
-
122
- // ===========================================================================
123
- // getWorkSource tests
124
- // ===========================================================================
125
-
126
- describe("getWorkSource", () => {
127
- it("returns a configured adapter instance", () => {
128
- const factory = createMockFactory("test");
129
- registerWorkSource("test", factory);
130
-
131
- const adapter = getWorkSource({ type: "test" });
132
-
133
- expect(adapter).toBeDefined();
134
- expect(adapter.type).toBe("test");
135
- });
136
-
137
- it("calls factory with the provided config", () => {
138
- let capturedConfig: WorkSourceConfig | undefined;
139
- const factory: WorkSourceFactory = (config) => {
140
- capturedConfig = config;
141
- return createMockAdapter("custom");
142
- };
143
- registerWorkSource("custom", factory);
144
-
145
- const config: WorkSourceConfig = {
146
- type: "custom",
147
- labels: { ready: "ready-label", in_progress: "wip-label" },
148
- customOption: "custom-value",
149
- };
150
- getWorkSource(config);
151
-
152
- expect(capturedConfig).toEqual(config);
153
- expect(capturedConfig?.customOption).toBe("custom-value");
154
- });
155
-
156
- it("throws UnknownWorkSourceError for unregistered type", () => {
157
- expect(() => {
158
- getWorkSource({ type: "unknown" });
159
- }).toThrow(UnknownWorkSourceError);
160
- });
161
-
162
- it("UnknownWorkSourceError contains type and available types", () => {
163
- registerWorkSource("available-a", createMockFactory("available-a"));
164
- registerWorkSource("available-b", createMockFactory("available-b"));
165
-
166
- try {
167
- getWorkSource({ type: "missing" });
168
- expect.fail("Should have thrown UnknownWorkSourceError");
169
- } catch (error) {
170
- expect(error).toBeInstanceOf(UnknownWorkSourceError);
171
- const unknownError = error as UnknownWorkSourceError;
172
- expect(unknownError.sourceType).toBe("missing");
173
- expect(unknownError.availableTypes).toContain("available-a");
174
- expect(unknownError.availableTypes).toContain("available-b");
175
- expect(unknownError.message).toContain("missing");
176
- expect(unknownError.message).toContain("available-a");
177
- }
178
- });
179
-
180
- it("UnknownWorkSourceError shows 'none' when no types registered", () => {
181
- try {
182
- getWorkSource({ type: "any" });
183
- expect.fail("Should have thrown UnknownWorkSourceError");
184
- } catch (error) {
185
- expect(error).toBeInstanceOf(UnknownWorkSourceError);
186
- const unknownError = error as UnknownWorkSourceError;
187
- expect(unknownError.availableTypes).toEqual([]);
188
- expect(unknownError.message).toContain("none");
189
- }
190
- });
191
-
192
- it("returns different instances for different configs", () => {
193
- let callCount = 0;
194
- const factory: WorkSourceFactory = (config) => {
195
- callCount++;
196
- return createMockAdapter("instance");
197
- };
198
- registerWorkSource("instance", factory);
199
-
200
- const adapter1 = getWorkSource({ type: "instance" });
201
- const adapter2 = getWorkSource({ type: "instance" });
202
-
203
- // Factory should be called each time
204
- expect(callCount).toBe(2);
205
- // Each call creates a new instance
206
- expect(adapter1).not.toBe(adapter2);
207
- });
208
- });
209
-
210
- // ===========================================================================
211
- // getRegisteredTypes tests
212
- // ===========================================================================
213
-
214
- describe("getRegisteredTypes", () => {
215
- it("returns empty array when no types registered", () => {
216
- expect(getRegisteredTypes()).toEqual([]);
217
- });
218
-
219
- it("returns all registered types", () => {
220
- registerWorkSource("alpha", createMockFactory("alpha"));
221
- registerWorkSource("beta", createMockFactory("beta"));
222
-
223
- const types = getRegisteredTypes();
224
- expect(types).toHaveLength(2);
225
- expect(types).toContain("alpha");
226
- expect(types).toContain("beta");
227
- });
228
-
229
- it("returns a copy, not the internal state", () => {
230
- registerWorkSource("test", createMockFactory("test"));
231
-
232
- const types1 = getRegisteredTypes();
233
- const types2 = getRegisteredTypes();
234
-
235
- expect(types1).not.toBe(types2);
236
- expect(types1).toEqual(types2);
237
- });
238
- });
239
-
240
- // ===========================================================================
241
- // isWorkSourceRegistered tests
242
- // ===========================================================================
243
-
244
- describe("isWorkSourceRegistered", () => {
245
- it("returns false for unregistered type", () => {
246
- expect(isWorkSourceRegistered("not-registered")).toBe(false);
247
- });
248
-
249
- it("returns true for registered type", () => {
250
- registerWorkSource("registered", createMockFactory("registered"));
251
- expect(isWorkSourceRegistered("registered")).toBe(true);
252
- });
253
-
254
- it("returns false after type is unregistered", () => {
255
- registerWorkSource("temp", createMockFactory("temp"));
256
- expect(isWorkSourceRegistered("temp")).toBe(true);
257
-
258
- unregisterWorkSource("temp");
259
- expect(isWorkSourceRegistered("temp")).toBe(false);
260
- });
261
- });
262
-
263
- // ===========================================================================
264
- // unregisterWorkSource tests
265
- // ===========================================================================
266
-
267
- describe("unregisterWorkSource", () => {
268
- it("removes a registered type", () => {
269
- registerWorkSource("removable", createMockFactory("removable"));
270
- expect(isWorkSourceRegistered("removable")).toBe(true);
271
-
272
- const result = unregisterWorkSource("removable");
273
-
274
- expect(result).toBe(true);
275
- expect(isWorkSourceRegistered("removable")).toBe(false);
276
- });
277
-
278
- it("returns false for unregistered type", () => {
279
- const result = unregisterWorkSource("never-existed");
280
- expect(result).toBe(false);
281
- });
282
-
283
- it("allows re-registering after unregister", () => {
284
- registerWorkSource("reuse", createMockFactory("reuse"));
285
- unregisterWorkSource("reuse");
286
-
287
- // Should not throw
288
- registerWorkSource("reuse", createMockFactory("reuse"));
289
- expect(isWorkSourceRegistered("reuse")).toBe(true);
290
- });
291
- });
292
-
293
- // ===========================================================================
294
- // clearWorkSourceRegistry tests
295
- // ===========================================================================
296
-
297
- describe("clearWorkSourceRegistry", () => {
298
- it("removes all registered types", () => {
299
- registerWorkSource("one", createMockFactory("one"));
300
- registerWorkSource("two", createMockFactory("two"));
301
- registerWorkSource("three", createMockFactory("three"));
302
-
303
- clearWorkSourceRegistry();
304
-
305
- expect(getRegisteredTypes()).toEqual([]);
306
- expect(isWorkSourceRegistered("one")).toBe(false);
307
- expect(isWorkSourceRegistered("two")).toBe(false);
308
- expect(isWorkSourceRegistered("three")).toBe(false);
309
- });
310
-
311
- it("is idempotent", () => {
312
- registerWorkSource("test", createMockFactory("test"));
313
-
314
- clearWorkSourceRegistry();
315
- clearWorkSourceRegistry();
316
- clearWorkSourceRegistry();
317
-
318
- expect(getRegisteredTypes()).toEqual([]);
319
- });
320
- });
321
-
322
- // ===========================================================================
323
- // Factory pattern tests
324
- // ===========================================================================
325
-
326
- describe("Factory Pattern", () => {
327
- it("factory receives full config for customization", () => {
328
- interface CustomConfig extends WorkSourceConfig {
329
- apiToken?: string;
330
- baseUrl?: string;
331
- }
332
-
333
- let receivedToken: string | undefined;
334
- let receivedUrl: string | undefined;
335
-
336
- const factory: WorkSourceFactory = (config) => {
337
- const customConfig = config as CustomConfig;
338
- receivedToken = customConfig.apiToken;
339
- receivedUrl = customConfig.baseUrl;
340
- return createMockAdapter("custom");
341
- };
342
-
343
- registerWorkSource("custom", factory);
344
-
345
- getWorkSource({
346
- type: "custom",
347
- apiToken: "secret-token",
348
- baseUrl: "https://api.example.com",
349
- });
350
-
351
- expect(receivedToken).toBe("secret-token");
352
- expect(receivedUrl).toBe("https://api.example.com");
353
- });
354
-
355
- it("factory can create different adapters based on config", () => {
356
- const factory: WorkSourceFactory = (config) => {
357
- const adapter = createMockAdapter("conditional");
358
- // Override fetchAvailableWork based on config
359
- if (config.labels?.ready === "special") {
360
- adapter.fetchAvailableWork = async () => ({
361
- items: [],
362
- totalCount: 999,
363
- });
364
- }
365
- return adapter;
366
- };
367
-
368
- registerWorkSource("conditional", factory);
369
-
370
- const normalAdapter = getWorkSource({ type: "conditional" });
371
- const specialAdapter = getWorkSource({
372
- type: "conditional",
373
- labels: { ready: "special" },
374
- });
375
-
376
- // Both are valid adapters with different behavior
377
- expect(normalAdapter.type).toBe("conditional");
378
- expect(specialAdapter.type).toBe("conditional");
379
- });
380
- });
381
-
382
- // ===========================================================================
383
- // Module singleton behavior tests
384
- // ===========================================================================
385
-
386
- describe("Singleton Behavior", () => {
387
- it("registry state persists across function calls", () => {
388
- registerWorkSource("persistent", createMockFactory("persistent"));
389
-
390
- // Multiple calls to check registration all see the same state
391
- expect(isWorkSourceRegistered("persistent")).toBe(true);
392
- expect(getRegisteredTypes()).toContain("persistent");
393
- expect(() => getWorkSource({ type: "persistent" })).not.toThrow();
394
- });
395
-
396
- it("modifications are visible to subsequent operations", () => {
397
- // Register
398
- registerWorkSource("visible", createMockFactory("visible"));
399
- expect(getRegisteredTypes()).toContain("visible");
400
-
401
- // Unregister
402
- unregisterWorkSource("visible");
403
- expect(getRegisteredTypes()).not.toContain("visible");
404
-
405
- // Re-register
406
- registerWorkSource("visible", createMockFactory("visible"));
407
- expect(getRegisteredTypes()).toContain("visible");
408
- });
409
- });
410
- });
411
-
412
- // =============================================================================
413
- // Built-in Adapter Registration Tests
414
- // =============================================================================
415
-
416
- describe("Built-in Adapters", () => {
417
- it("github adapter is pre-registered at module load", async () => {
418
- // The adapters/index.js module auto-registers when imported.
419
- // Since ESM modules are cached, we need to manually register
420
- // after clearing for tests.
421
- clearWorkSourceRegistry();
422
-
423
- // Import the factory directly and register it
424
- const { createGitHubAdapter } = await import("../adapters/github.js");
425
- registerWorkSource("github", createGitHubAdapter);
426
-
427
- expect(isWorkSourceRegistered("github")).toBe(true);
428
- expect(getRegisteredTypes()).toContain("github");
429
- });
430
-
431
- it("github adapter can be retrieved via getWorkSource", async () => {
432
- clearWorkSourceRegistry();
433
- const { createGitHubAdapter } = await import("../adapters/github.js");
434
- registerWorkSource("github", createGitHubAdapter);
435
-
436
- const adapter = getWorkSource({ type: "github" });
437
-
438
- expect(adapter).toBeDefined();
439
- expect(adapter.type).toBe("github");
440
- });
441
-
442
- it("github adapter respects config labels", async () => {
443
- clearWorkSourceRegistry();
444
- const { createGitHubAdapter } = await import("../adapters/github.js");
445
- registerWorkSource("github", createGitHubAdapter);
446
-
447
- // Should not throw - config is passed to factory
448
- const adapter = getWorkSource({
449
- type: "github",
450
- labels: {
451
- ready: "custom-ready",
452
- in_progress: "custom-wip",
453
- },
454
- });
455
-
456
- expect(adapter.type).toBe("github");
457
- });
458
-
459
- it("GitHubWorkSourceAdapter implements WorkSourceAdapter interface", async () => {
460
- const { GitHubWorkSourceAdapter } = await import("../adapters/github.js");
461
-
462
- const adapter = new GitHubWorkSourceAdapter({
463
- type: "github",
464
- owner: "testowner",
465
- repo: "testrepo",
466
- labels: { ready: "ready", in_progress: "wip" },
467
- });
468
-
469
- // Verify the interface
470
- expect(adapter.type).toBe("github");
471
- expect(typeof adapter.fetchAvailableWork).toBe("function");
472
- expect(typeof adapter.claimWork).toBe("function");
473
- expect(typeof adapter.completeWork).toBe("function");
474
- expect(typeof adapter.releaseWork).toBe("function");
475
- expect(typeof adapter.getWork).toBe("function");
476
- });
477
- });