@skillsmith/core 0.4.17 → 0.5.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 (202) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/api/client.d.ts +19 -21
  4. package/dist/src/api/client.d.ts.map +1 -1
  5. package/dist/src/api/client.events.d.ts +39 -0
  6. package/dist/src/api/client.events.d.ts.map +1 -0
  7. package/dist/src/api/client.events.js +77 -0
  8. package/dist/src/api/client.events.js.map +1 -0
  9. package/dist/src/api/client.js +39 -33
  10. package/dist/src/api/client.js.map +1 -1
  11. package/dist/src/api/event-batcher.d.ts +81 -0
  12. package/dist/src/api/event-batcher.d.ts.map +1 -0
  13. package/dist/src/api/event-batcher.js +191 -0
  14. package/dist/src/api/event-batcher.js.map +1 -0
  15. package/dist/src/api/index.d.ts +1 -0
  16. package/dist/src/api/index.d.ts.map +1 -1
  17. package/dist/src/api/index.js +2 -0
  18. package/dist/src/api/index.js.map +1 -1
  19. package/dist/src/api/schemas.d.ts +58 -0
  20. package/dist/src/api/schemas.d.ts.map +1 -1
  21. package/dist/src/api/schemas.js +45 -0
  22. package/dist/src/api/schemas.js.map +1 -1
  23. package/dist/src/db/migration-runner.d.ts +44 -0
  24. package/dist/src/db/migration-runner.d.ts.map +1 -0
  25. package/dist/src/db/migration-runner.js +175 -0
  26. package/dist/src/db/migration-runner.js.map +1 -0
  27. package/dist/src/db/migration.d.ts.map +1 -1
  28. package/dist/src/db/migration.js +2 -1
  29. package/dist/src/db/migration.js.map +1 -1
  30. package/dist/src/db/migrations/v12-risk-score-history.d.ts +10 -0
  31. package/dist/src/db/migrations/v12-risk-score-history.d.ts.map +1 -0
  32. package/dist/src/db/migrations/v12-risk-score-history.js +25 -0
  33. package/dist/src/db/migrations/v12-risk-score-history.js.map +1 -0
  34. package/dist/src/db/migrations/v13-team-tables.d.ts +11 -0
  35. package/dist/src/db/migrations/v13-team-tables.d.ts.map +1 -0
  36. package/dist/src/db/migrations/v13-team-tables.js +14 -0
  37. package/dist/src/db/migrations/v13-team-tables.js.map +1 -0
  38. package/dist/src/db/schema-sql.d.ts +16 -0
  39. package/dist/src/db/schema-sql.d.ts.map +1 -0
  40. package/dist/src/db/schema-sql.js +161 -0
  41. package/dist/src/db/schema-sql.js.map +1 -0
  42. package/dist/src/db/schema.d.ts +7 -32
  43. package/dist/src/db/schema.d.ts.map +1 -1
  44. package/dist/src/db/schema.js +13 -303
  45. package/dist/src/db/schema.js.map +1 -1
  46. package/dist/src/exports/repositories.d.ts +1 -0
  47. package/dist/src/exports/repositories.d.ts.map +1 -1
  48. package/dist/src/exports/repositories.js +4 -0
  49. package/dist/src/exports/repositories.js.map +1 -1
  50. package/dist/src/exports/services.d.ts +2 -1
  51. package/dist/src/exports/services.d.ts.map +1 -1
  52. package/dist/src/exports/services.js +1 -0
  53. package/dist/src/exports/services.js.map +1 -1
  54. package/dist/src/index.d.ts +1 -1
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +1 -1
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/repositories/RiskScoreHistoryRepository.d.ts +37 -0
  59. package/dist/src/repositories/RiskScoreHistoryRepository.d.ts.map +1 -0
  60. package/dist/src/repositories/RiskScoreHistoryRepository.js +66 -0
  61. package/dist/src/repositories/RiskScoreHistoryRepository.js.map +1 -0
  62. package/dist/src/scoring/index.d.ts +1 -0
  63. package/dist/src/scoring/index.d.ts.map +1 -1
  64. package/dist/src/scoring/index.js +1 -0
  65. package/dist/src/scoring/index.js.map +1 -1
  66. package/dist/src/scoring/quality-score.d.ts +49 -0
  67. package/dist/src/scoring/quality-score.d.ts.map +1 -0
  68. package/dist/src/scoring/quality-score.js +73 -0
  69. package/dist/src/scoring/quality-score.js.map +1 -0
  70. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
  71. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  72. package/dist/src/security/index.d.ts +2 -0
  73. package/dist/src/security/index.d.ts.map +1 -1
  74. package/dist/src/security/index.js +2 -0
  75. package/dist/src/security/index.js.map +1 -1
  76. package/dist/src/security/risk-trend.d.ts +21 -0
  77. package/dist/src/security/risk-trend.d.ts.map +1 -0
  78. package/dist/src/security/risk-trend.js +81 -0
  79. package/dist/src/security/risk-trend.js.map +1 -0
  80. package/dist/src/security/scanner/SecurityScanner.d.ts +2 -0
  81. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  82. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  83. package/dist/src/security/scanner/SecurityScanner.helpers.js +14 -8
  84. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  85. package/dist/src/security/scanner/SecurityScanner.js +55 -1
  86. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  87. package/dist/src/security/scanner/index.d.ts +1 -1
  88. package/dist/src/security/scanner/index.d.ts.map +1 -1
  89. package/dist/src/security/scanner/index.js +1 -1
  90. package/dist/src/security/scanner/index.js.map +1 -1
  91. package/dist/src/security/scanner/patterns.d.ts +6 -0
  92. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  93. package/dist/src/security/scanner/patterns.js +25 -0
  94. package/dist/src/security/scanner/patterns.js.map +1 -1
  95. package/dist/src/security/scanner/types.d.ts +2 -1
  96. package/dist/src/security/scanner/types.d.ts.map +1 -1
  97. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  98. package/dist/src/security/scanner/weights.js +1 -0
  99. package/dist/src/security/scanner/weights.js.map +1 -1
  100. package/dist/src/services/skill-config-schema.d.ts +36 -0
  101. package/dist/src/services/skill-config-schema.d.ts.map +1 -0
  102. package/dist/src/services/skill-config-schema.js +76 -0
  103. package/dist/src/services/skill-config-schema.js.map +1 -0
  104. package/dist/src/services/skill-installation.feedback.d.ts +24 -0
  105. package/dist/src/services/skill-installation.feedback.d.ts.map +1 -0
  106. package/dist/src/services/skill-installation.feedback.js +37 -0
  107. package/dist/src/services/skill-installation.feedback.js.map +1 -0
  108. package/dist/src/services/skill-installation.helpers.d.ts +33 -7
  109. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  110. package/dist/src/services/skill-installation.helpers.js +74 -32
  111. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  112. package/dist/src/services/skill-installation.service.d.ts +8 -16
  113. package/dist/src/services/skill-installation.service.d.ts.map +1 -1
  114. package/dist/src/services/skill-installation.service.js +86 -37
  115. package/dist/src/services/skill-installation.service.js.map +1 -1
  116. package/dist/src/services/skill-installation.types.d.ts +22 -0
  117. package/dist/src/services/skill-installation.types.d.ts.map +1 -1
  118. package/dist/src/services/skill-installation.types.js.map +1 -1
  119. package/dist/src/types.d.ts +2 -0
  120. package/dist/src/types.d.ts.map +1 -1
  121. package/dist/tests/SecurityScanner.ai-defence.test.d.ts +6 -0
  122. package/dist/tests/SecurityScanner.ai-defence.test.d.ts.map +1 -0
  123. package/dist/tests/SecurityScanner.ai-defence.test.js +221 -0
  124. package/dist/tests/SecurityScanner.ai-defence.test.js.map +1 -0
  125. package/dist/tests/SecurityScanner.performance.test.d.ts +6 -0
  126. package/dist/tests/SecurityScanner.performance.test.d.ts.map +1 -0
  127. package/dist/tests/SecurityScanner.performance.test.js +132 -0
  128. package/dist/tests/SecurityScanner.performance.test.js.map +1 -0
  129. package/dist/tests/SecurityScanner.scoring.test.d.ts +6 -0
  130. package/dist/tests/SecurityScanner.scoring.test.d.ts.map +1 -0
  131. package/dist/tests/SecurityScanner.scoring.test.js +197 -0
  132. package/dist/tests/SecurityScanner.scoring.test.js.map +1 -0
  133. package/dist/tests/SecurityScanner.test.d.ts +2 -2
  134. package/dist/tests/SecurityScanner.test.js +2 -520
  135. package/dist/tests/SecurityScanner.test.js.map +1 -1
  136. package/dist/tests/SkillMatcher.test.js +5 -5
  137. package/dist/tests/SkillMatcher.test.js.map +1 -1
  138. package/dist/tests/db/schema-migrations.test.js +8 -6
  139. package/dist/tests/db/schema-migrations.test.js.map +1 -1
  140. package/dist/tests/integration/events-batch-contract.test.d.ts +12 -0
  141. package/dist/tests/integration/events-batch-contract.test.d.ts.map +1 -0
  142. package/dist/tests/integration/events-batch-contract.test.js +69 -0
  143. package/dist/tests/integration/events-batch-contract.test.js.map +1 -0
  144. package/dist/tests/scoring/quality-score.test.d.ts +7 -0
  145. package/dist/tests/scoring/quality-score.test.d.ts.map +1 -0
  146. package/dist/tests/scoring/quality-score.test.js +78 -0
  147. package/dist/tests/scoring/quality-score.test.js.map +1 -0
  148. package/dist/tests/security/ContinuousSecurity.false-positives.test.d.ts +6 -0
  149. package/dist/tests/security/ContinuousSecurity.false-positives.test.d.ts.map +1 -0
  150. package/dist/tests/security/ContinuousSecurity.false-positives.test.js +89 -0
  151. package/dist/tests/security/ContinuousSecurity.false-positives.test.js.map +1 -0
  152. package/dist/tests/security/ContinuousSecurity.performance.test.d.ts +6 -0
  153. package/dist/tests/security/ContinuousSecurity.performance.test.d.ts.map +1 -0
  154. package/dist/tests/security/ContinuousSecurity.performance.test.js +177 -0
  155. package/dist/tests/security/ContinuousSecurity.performance.test.js.map +1 -0
  156. package/dist/tests/security/ContinuousSecurity.reporting.test.d.ts +6 -0
  157. package/dist/tests/security/ContinuousSecurity.reporting.test.d.ts.map +1 -0
  158. package/dist/tests/security/ContinuousSecurity.reporting.test.js +106 -0
  159. package/dist/tests/security/ContinuousSecurity.reporting.test.js.map +1 -0
  160. package/dist/tests/security/ContinuousSecurity.test.d.ts +9 -2
  161. package/dist/tests/security/ContinuousSecurity.test.d.ts.map +1 -1
  162. package/dist/tests/security/ContinuousSecurity.test.js +9 -336
  163. package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
  164. package/dist/tests/security/pii-detection.test.d.ts +7 -0
  165. package/dist/tests/security/pii-detection.test.d.ts.map +1 -0
  166. package/dist/tests/security/pii-detection.test.js +91 -0
  167. package/dist/tests/security/pii-detection.test.js.map +1 -0
  168. package/dist/tests/security/risk-trend.test.d.ts +6 -0
  169. package/dist/tests/security/risk-trend.test.d.ts.map +1 -0
  170. package/dist/tests/security/risk-trend.test.js +68 -0
  171. package/dist/tests/security/risk-trend.test.js.map +1 -0
  172. package/dist/tests/security/scanner-regression-guard.test.d.ts +12 -0
  173. package/dist/tests/security/scanner-regression-guard.test.d.ts.map +1 -0
  174. package/dist/tests/security/scanner-regression-guard.test.js +111 -0
  175. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -0
  176. package/dist/tests/services/aidefence-feedback.test.d.ts +6 -0
  177. package/dist/tests/services/aidefence-feedback.test.d.ts.map +1 -0
  178. package/dist/tests/services/aidefence-feedback.test.js +115 -0
  179. package/dist/tests/services/aidefence-feedback.test.js.map +1 -0
  180. package/dist/tests/services/dep-quarantine-check.test.d.ts +5 -0
  181. package/dist/tests/services/dep-quarantine-check.test.d.ts.map +1 -0
  182. package/dist/tests/services/dep-quarantine-check.test.js +92 -0
  183. package/dist/tests/services/dep-quarantine-check.test.js.map +1 -0
  184. package/dist/tests/services/skill-config-schema.test.d.ts +5 -0
  185. package/dist/tests/services/skill-config-schema.test.d.ts.map +1 -0
  186. package/dist/tests/services/skill-config-schema.test.js +98 -0
  187. package/dist/tests/services/skill-config-schema.test.js.map +1 -0
  188. package/dist/tests/unit/api-client-events.test.d.ts +10 -0
  189. package/dist/tests/unit/api-client-events.test.d.ts.map +1 -0
  190. package/dist/tests/unit/api-client-events.test.js +73 -0
  191. package/dist/tests/unit/api-client-events.test.js.map +1 -0
  192. package/dist/tests/unit/event-batcher.test.d.ts +13 -0
  193. package/dist/tests/unit/event-batcher.test.d.ts.map +1 -0
  194. package/dist/tests/unit/event-batcher.test.js +155 -0
  195. package/dist/tests/unit/event-batcher.test.js.map +1 -0
  196. package/dist/tests/unit/services/skill-installation-extended.test.d.ts +8 -0
  197. package/dist/tests/unit/services/skill-installation-extended.test.d.ts.map +1 -0
  198. package/dist/tests/unit/services/skill-installation-extended.test.js +423 -0
  199. package/dist/tests/unit/services/skill-installation-extended.test.js.map +1 -0
  200. package/dist/tests/unit/services/skill-installation.service.test.js +0 -390
  201. package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
  202. package/package.json +7 -7
@@ -0,0 +1,73 @@
1
+ /**
2
+ * SMI-4119: SkillsmithApiClient event-batching integration tests.
3
+ *
4
+ * Verifies:
5
+ * - `recordEvent()` enqueues without an immediate POST.
6
+ * - `flushEvents()` drains the queue and issues a single POST with
7
+ * `X-Skillsmith-Batched: true` and `{ events: [...] }` body.
8
+ */
9
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
10
+ import { SkillsmithApiClient } from '../../src/api/client.js';
11
+ describe('SMI-4119: SkillsmithApiClient event batching', () => {
12
+ const originalFetch = globalThis.fetch;
13
+ let fetchMock;
14
+ // SMI-4119: Track clients so we can dispose the batcher after each test.
15
+ // Without dispose() the batcher keeps `beforeExit` / `SIGINT` / `SIGTERM`
16
+ // listeners on `process`, leaking across tests and triggering
17
+ // MaxListenersExceededWarning in long Vitest runs.
18
+ const clients = [];
19
+ beforeEach(() => {
20
+ fetchMock = vi
21
+ .fn()
22
+ .mockResolvedValue(new Response('', { status: 200, headers: { 'Content-Type': 'text/plain' } }));
23
+ globalThis.fetch = fetchMock;
24
+ });
25
+ afterEach(async () => {
26
+ for (const c of clients.splice(0)) {
27
+ try {
28
+ await c.flushEvents();
29
+ }
30
+ catch {
31
+ /* noop */
32
+ }
33
+ c.disposeEventBatcher();
34
+ }
35
+ globalThis.fetch = originalFetch;
36
+ vi.restoreAllMocks();
37
+ });
38
+ const mkEvent = (anonId) => ({
39
+ event: 'skill_view',
40
+ anonymous_id: anonId,
41
+ });
42
+ it('recordEvent does not POST immediately', async () => {
43
+ const client = new SkillsmithApiClient({ baseUrl: 'https://test.invalid' });
44
+ clients.push(client);
45
+ const res = await client.recordEvent(mkEvent('aaaaaaaaaaaaaaaa'));
46
+ expect(res).toEqual({ ok: true });
47
+ expect(fetchMock).not.toHaveBeenCalled();
48
+ });
49
+ it('flushEvents drains queued events via a single POST with X-Skillsmith-Batched', async () => {
50
+ const client = new SkillsmithApiClient({ baseUrl: 'https://test.invalid' });
51
+ clients.push(client);
52
+ await client.recordEvent(mkEvent('aaaaaaaaaaaaaaaa'));
53
+ await client.recordEvent(mkEvent('bbbbbbbbbbbbbbbb'));
54
+ await client.flushEvents();
55
+ expect(fetchMock).toHaveBeenCalledTimes(1);
56
+ const [url, init] = fetchMock.mock.calls[0];
57
+ expect(url).toBe('https://test.invalid/events');
58
+ expect(init.method).toBe('POST');
59
+ const headers = init.headers;
60
+ expect(headers['X-Skillsmith-Batched']).toBe('true');
61
+ const body = JSON.parse(String(init.body));
62
+ expect(body.events).toHaveLength(2);
63
+ expect(body.events[0].anonymous_id).toBe('aaaaaaaaaaaaaaaa');
64
+ });
65
+ it('offlineMode short-circuits recordEvent (no enqueue, no POST)', async () => {
66
+ const client = new SkillsmithApiClient({ baseUrl: 'https://test.invalid', offlineMode: true });
67
+ const res = await client.recordEvent(mkEvent('aaaaaaaaaaaaaaaa'));
68
+ expect(res).toEqual({ ok: true });
69
+ await client.flushEvents();
70
+ expect(fetchMock).not.toHaveBeenCalled();
71
+ });
72
+ });
73
+ //# sourceMappingURL=api-client-events.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client-events.test.js","sourceRoot":"","sources":["../../../tests/unit/api-client-events.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SMI-4119: EventBatcher unit tests.
3
+ *
4
+ * Covers:
5
+ * - size flush : 20 events → 1 POST (single flush)
6
+ * - time flush : wait elapses → flush
7
+ * - exit drain : `beforeExit` triggers flush
8
+ * - failure : first POST fails, retry succeeds
9
+ * - double-fail : two consecutive failures drop silently (no throw)
10
+ * - ordering : batch is delivered in enqueue order
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=event-batcher.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-batcher.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/event-batcher.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * SMI-4119: EventBatcher unit tests.
3
+ *
4
+ * Covers:
5
+ * - size flush : 20 events → 1 POST (single flush)
6
+ * - time flush : wait elapses → flush
7
+ * - exit drain : `beforeExit` triggers flush
8
+ * - failure : first POST fails, retry succeeds
9
+ * - double-fail : two consecutive failures drop silently (no throw)
10
+ * - ordering : batch is delivered in enqueue order
11
+ */
12
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
13
+ import { EventBatcher } from '../../src/api/event-batcher.js';
14
+ const makeEvent = (anonId, skillId) => ({
15
+ event: 'skill_view',
16
+ anonymous_id: anonId,
17
+ skill_id: skillId,
18
+ });
19
+ describe('SMI-4119: EventBatcher', () => {
20
+ beforeEach(() => {
21
+ vi.useFakeTimers();
22
+ });
23
+ afterEach(() => {
24
+ vi.useRealTimers();
25
+ vi.restoreAllMocks();
26
+ });
27
+ it('flushes a single batch when queue reaches maxBatchSize', async () => {
28
+ const flushFn = vi
29
+ .fn()
30
+ .mockResolvedValue(undefined);
31
+ const batcher = new EventBatcher(flushFn, {
32
+ maxBatchSize: 20,
33
+ maxWaitMs: 10_000,
34
+ registerExitHandlers: false,
35
+ });
36
+ for (let i = 0; i < 20; i++)
37
+ batcher.enqueue(makeEvent(`a${i}`));
38
+ // Size-trigger schedules microtask flush; let it run.
39
+ await vi.runAllTimersAsync();
40
+ expect(flushFn).toHaveBeenCalledTimes(1);
41
+ expect(flushFn.mock.calls[0][0]).toHaveLength(20);
42
+ batcher.dispose();
43
+ });
44
+ it('flushes on wall-clock timeout even with < maxBatchSize events', async () => {
45
+ const flushFn = vi
46
+ .fn()
47
+ .mockResolvedValue(undefined);
48
+ const batcher = new EventBatcher(flushFn, {
49
+ maxBatchSize: 20,
50
+ maxWaitMs: 10_000,
51
+ registerExitHandlers: false,
52
+ });
53
+ batcher.enqueue(makeEvent('only-one'));
54
+ expect(flushFn).not.toHaveBeenCalled();
55
+ await vi.advanceTimersByTimeAsync(10_000);
56
+ expect(flushFn).toHaveBeenCalledTimes(1);
57
+ expect(flushFn.mock.calls[0][0]).toHaveLength(1);
58
+ batcher.dispose();
59
+ });
60
+ it('preserves enqueue order in the delivered batch', async () => {
61
+ const flushFn = vi
62
+ .fn()
63
+ .mockResolvedValue(undefined);
64
+ const batcher = new EventBatcher(flushFn, {
65
+ maxBatchSize: 5,
66
+ maxWaitMs: 10_000,
67
+ registerExitHandlers: false,
68
+ });
69
+ const ids = [
70
+ 'aaaaaaaaaaaaaaaa',
71
+ 'bbbbbbbbbbbbbbbb',
72
+ 'cccccccccccccccc',
73
+ 'dddddddddddddddd',
74
+ 'eeeeeeeeeeeeeeee',
75
+ ];
76
+ for (const id of ids)
77
+ batcher.enqueue(makeEvent(id));
78
+ await vi.runAllTimersAsync();
79
+ expect(flushFn).toHaveBeenCalledTimes(1);
80
+ expect(flushFn.mock.calls[0][0].map((e) => e.anonymous_id)).toEqual(ids);
81
+ batcher.dispose();
82
+ });
83
+ it('retries once after 2s when first flush fails, then succeeds', async () => {
84
+ const flushFn = vi
85
+ .fn()
86
+ .mockRejectedValueOnce(new Error('network down'))
87
+ .mockResolvedValueOnce(undefined);
88
+ const batcher = new EventBatcher(flushFn, {
89
+ maxBatchSize: 2,
90
+ maxWaitMs: 10_000,
91
+ retryDelayMs: 2_000,
92
+ registerExitHandlers: false,
93
+ });
94
+ batcher.enqueue(makeEvent('a1a1a1a1a1a1a1a1'));
95
+ batcher.enqueue(makeEvent('b2b2b2b2b2b2b2b2'));
96
+ await vi.advanceTimersByTimeAsync(0);
97
+ await vi.advanceTimersByTimeAsync(2_000);
98
+ await vi.runAllTimersAsync();
99
+ expect(flushFn).toHaveBeenCalledTimes(2);
100
+ batcher.dispose();
101
+ });
102
+ it('drops the batch silently after two consecutive failures', async () => {
103
+ const flushFn = vi
104
+ .fn()
105
+ .mockRejectedValue(new Error('network down'));
106
+ const batcher = new EventBatcher(flushFn, {
107
+ maxBatchSize: 1,
108
+ maxWaitMs: 10_000,
109
+ retryDelayMs: 500,
110
+ registerExitHandlers: false,
111
+ });
112
+ batcher.enqueue(makeEvent('a1a1a1a1a1a1a1a1'));
113
+ // Let the initial flush, retry delay, and second attempt all resolve.
114
+ await vi.runAllTimersAsync();
115
+ expect(flushFn).toHaveBeenCalledTimes(2);
116
+ // No unhandled rejections — batcher swallowed both failures.
117
+ batcher.dispose();
118
+ });
119
+ it('drains queued events when flush() is invoked (exit-drain surrogate)', async () => {
120
+ const flushFn = vi
121
+ .fn()
122
+ .mockResolvedValue(undefined);
123
+ const batcher = new EventBatcher(flushFn, {
124
+ maxBatchSize: 20,
125
+ maxWaitMs: 10_000,
126
+ registerExitHandlers: false,
127
+ });
128
+ batcher.enqueue(makeEvent('x1x1x1x1x1x1x1x1'));
129
+ batcher.enqueue(makeEvent('y2y2y2y2y2y2y2y2'));
130
+ expect(flushFn).not.toHaveBeenCalled();
131
+ await batcher.flush();
132
+ expect(flushFn).toHaveBeenCalledTimes(1);
133
+ expect(flushFn.mock.calls[0][0]).toHaveLength(2);
134
+ batcher.dispose();
135
+ });
136
+ it('triggers flush on the real `beforeExit` process event', async () => {
137
+ const flushFn = vi
138
+ .fn()
139
+ .mockResolvedValue(undefined);
140
+ const batcher = new EventBatcher(flushFn, {
141
+ maxBatchSize: 20,
142
+ maxWaitMs: 10_000,
143
+ drainTimeoutMs: 1_000,
144
+ registerExitHandlers: true,
145
+ });
146
+ batcher.enqueue(makeEvent('z1z1z1z1z1z1z1z1'));
147
+ process.emit('beforeExit', 0);
148
+ // Let the drain promise and its race timeout flush.
149
+ await vi.runAllTimersAsync();
150
+ await Promise.resolve();
151
+ expect(flushFn).toHaveBeenCalledTimes(1);
152
+ batcher.dispose();
153
+ });
154
+ });
155
+ //# sourceMappingURL=event-batcher.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-batcher.test.js","sourceRoot":"","sources":["../../../tests/unit/event-batcher.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SMI-3863, SMI-3510: Extended SkillInstallationService Tests
3
+ *
4
+ * Split from skill-installation.service.test.ts to meet 500-line limit.
5
+ * Covers: pre-install confirmation gate, content hash verification, install tips.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=skill-installation-extended.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-installation-extended.test.d.ts","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation-extended.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}