@boxxy/model 0.1.2 → 0.1.3

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 (234) hide show
  1. package/README.md +8 -6
  2. package/dist/closeouts/build-close-out-schema.d.ts +31 -0
  3. package/dist/closeouts/close-out-answer-schema.d.ts +3 -0
  4. package/dist/closeouts/close-out-schema.d.ts +3 -0
  5. package/dist/closeouts/design-close-out-schema.d.ts +30 -0
  6. package/dist/closeouts/friction-schema.d.ts +14 -0
  7. package/dist/closeouts/open-item-schema.d.ts +3 -0
  8. package/dist/closeouts/review-close-out-schema.d.ts +40 -0
  9. package/dist/closeouts/triage-close-out-schema.d.ts +40 -0
  10. package/dist/closeouts/types.d.ts +221 -0
  11. package/dist/dispatch/build-applied-change.d.ts +6 -0
  12. package/dist/dispatch/collect-seat-sync.d.ts +14 -0
  13. package/dist/dispatch/pick-seat-take.d.ts +10 -0
  14. package/dist/dispatch/types.d.ts +11 -0
  15. package/dist/{outbox-fact-schema.d.ts → facts/outbox-fact-schema.d.ts} +1 -1
  16. package/dist/facts/parse-new-fact.d.ts +2 -0
  17. package/dist/facts/types.d.ts +191 -0
  18. package/dist/facts/work-fact-schemas.d.ts +293 -0
  19. package/dist/index.d.ts +64 -23
  20. package/dist/items/build-item-views.d.ts +4 -0
  21. package/dist/items/collect-retries.d.ts +3 -0
  22. package/dist/items/collect-settled.d.ts +3 -0
  23. package/dist/items/derive-item-state.d.ts +17 -0
  24. package/dist/{live-levers.d.ts → items/live-levers.d.ts} +1 -1
  25. package/dist/items/types.d.ts +12 -0
  26. package/dist/pipeline/advance.d.ts +10 -0
  27. package/dist/pipeline/constants.d.ts +2 -0
  28. package/dist/pipeline/count-trailing.d.ts +1 -0
  29. package/dist/pipeline/find-position.d.ts +9 -0
  30. package/dist/pipeline/find-step.d.ts +3 -0
  31. package/dist/pipeline/merge-by-time.d.ts +9 -0
  32. package/dist/pipeline/position-schema.d.ts +3 -0
  33. package/dist/pipeline/types.d.ts +28 -0
  34. package/dist/planning/define-admission.d.ts +3 -0
  35. package/dist/planning/define-gate.d.ts +3 -0
  36. package/dist/planning/define-signal.d.ts +3 -0
  37. package/dist/planning/is-filter-match.d.ts +3 -0
  38. package/dist/planning/limit-fill-schema.d.ts +3 -0
  39. package/dist/{signal-key.d.ts → planning/signal-key.d.ts} +1 -1
  40. package/dist/planning/types.d.ts +155 -0
  41. package/dist/ports/types.d.ts +289 -0
  42. package/dist/reports/constants.d.ts +3 -0
  43. package/dist/reports/cost-to-land-schema.d.ts +29 -0
  44. package/dist/reports/outbox-status.d.ts +16 -0
  45. package/dist/reports/stage-time-schema.d.ts +25 -0
  46. package/dist/reports/types.d.ts +5 -0
  47. package/dist/rows/actor-ref-schema.d.ts +2 -0
  48. package/dist/rows/attempt-row-schema.d.ts +3 -0
  49. package/dist/rows/attempt-session-row-schema.d.ts +3 -0
  50. package/dist/{brand-schemas.d.ts → rows/brand-schemas.d.ts} +5 -2
  51. package/dist/rows/caller-identity-schema.d.ts +3 -0
  52. package/dist/rows/criterion-schema.d.ts +3 -0
  53. package/dist/rows/decision-row-schema.d.ts +34 -0
  54. package/dist/rows/evidence-row-schema.d.ts +3 -0
  55. package/dist/rows/item-row-schema.d.ts +28 -0
  56. package/dist/rows/item-view-schema.d.ts +3 -0
  57. package/dist/rows/lease-row-schema.d.ts +3 -0
  58. package/dist/rows/lever-row-schema.d.ts +12 -0
  59. package/dist/rows/limit-schema.d.ts +3 -0
  60. package/dist/rows/limit-seat-row-schema.d.ts +3 -0
  61. package/dist/rows/note-row-schema.d.ts +3 -0
  62. package/dist/rows/notice-row-schema.d.ts +3 -0
  63. package/dist/rows/pull-request-row-schema.d.ts +3 -0
  64. package/dist/rows/relation-row-schema.d.ts +3 -0
  65. package/dist/{row-schemas.d.ts → rows/row-schemas.d.ts} +2 -2
  66. package/dist/rows/runner-row-schema.d.ts +3 -0
  67. package/dist/rows/spawn-request-schema.d.ts +3 -0
  68. package/dist/rules/define-phase.d.ts +3 -0
  69. package/dist/rules/load-rejection-schema.d.ts +3 -0
  70. package/dist/rules/types.d.ts +244 -0
  71. package/dist/test-utils/build-mock-cost-to-land.d.ts +2 -0
  72. package/dist/test-utils/build-mock-evidence-row.d.ts +1 -1
  73. package/dist/test-utils/build-mock-fact-id.d.ts +1 -1
  74. package/dist/test-utils/build-mock-frame-item.d.ts +1 -1
  75. package/dist/test-utils/build-mock-frame.d.ts +2 -0
  76. package/dist/test-utils/build-mock-item-rules.d.ts +2 -0
  77. package/dist/test-utils/build-mock-lease-row.d.ts +1 -1
  78. package/dist/test-utils/build-mock-outbox-fact.d.ts +2 -0
  79. package/dist/test-utils/build-mock-phase-kind.d.ts +7 -0
  80. package/dist/test-utils/build-mock-pipeline.d.ts +7 -0
  81. package/dist/test-utils/build-mock-relation-row.d.ts +2 -0
  82. package/dist/test-utils/build-mock-settled.d.ts +9 -0
  83. package/dist/test-utils/build-mock-snapshot.d.ts +1 -1
  84. package/dist/test-utils/build-mock-v1-attempt-row.d.ts +2 -0
  85. package/dist/test-utils/build-mock-v1-item-row.d.ts +2 -0
  86. package/dist/test-utils/build-mock-v1-item-view.d.ts +2 -0
  87. package/dist/test-utils/build-mock-v1-lever-row.d.ts +2 -0
  88. package/dist/test-utils/build-mock-v1-pull-request-row.d.ts +2 -0
  89. package/dist/test-utils/build-mock-work-fact.d.ts +12 -0
  90. package/dist/test-utils/create-fake-notifier.d.ts +9 -0
  91. package/dist/test-utils/create-fake-pepys-reader.d.ts +13 -0
  92. package/dist/test-utils/fact-sink-contract.d.ts +14 -0
  93. package/dist/test-utils/pepys-reader-contract.d.ts +17 -0
  94. package/dist/{types/core.d.ts → types.d.ts} +93 -43
  95. package/package.json +13 -3
  96. package/src/{build-close-out-schema.ts → closeouts/build-close-out-schema.ts} +5 -10
  97. package/src/closeouts/close-out-answer-schema.ts +42 -0
  98. package/src/closeouts/close-out-schema.ts +13 -0
  99. package/src/closeouts/design-close-out-schema.ts +20 -0
  100. package/src/closeouts/friction-schema.ts +11 -0
  101. package/src/closeouts/open-item-schema.ts +8 -0
  102. package/src/closeouts/review-close-out-schema.ts +38 -0
  103. package/src/closeouts/triage-close-out-schema.ts +29 -0
  104. package/src/closeouts/types.ts +230 -0
  105. package/src/dispatch/build-applied-change.ts +57 -0
  106. package/src/dispatch/collect-seat-sync.ts +86 -0
  107. package/src/dispatch/pick-seat-take.ts +39 -0
  108. package/src/dispatch/types.ts +14 -0
  109. package/src/facts/outbox-fact-schema.ts +22 -0
  110. package/src/facts/parse-new-fact.ts +16 -0
  111. package/src/facts/types.ts +235 -0
  112. package/src/facts/work-fact-schemas.ts +201 -0
  113. package/src/index.ts +147 -55
  114. package/src/items/build-item-views.ts +146 -0
  115. package/src/items/collect-retries.ts +12 -0
  116. package/src/items/collect-settled.ts +20 -0
  117. package/src/items/derive-item-state.ts +95 -0
  118. package/src/{live-levers.ts → items/live-levers.ts} +1 -1
  119. package/src/items/types.ts +26 -0
  120. package/src/pipeline/advance.ts +78 -0
  121. package/src/pipeline/constants.ts +12 -0
  122. package/src/pipeline/count-trailing.ts +16 -0
  123. package/src/pipeline/find-position.ts +74 -0
  124. package/src/pipeline/find-step.ts +6 -0
  125. package/src/pipeline/merge-by-time.ts +33 -0
  126. package/src/pipeline/position-schema.ts +27 -0
  127. package/src/pipeline/types.ts +47 -0
  128. package/src/{define-admission.ts → planning/define-admission.ts} +2 -2
  129. package/src/{define-gate.ts → planning/define-gate.ts} +2 -2
  130. package/src/{define-signal.ts → planning/define-signal.ts} +2 -2
  131. package/src/planning/is-filter-match.ts +10 -0
  132. package/src/planning/limit-fill-schema.ts +12 -0
  133. package/src/{signal-key.ts → planning/signal-key.ts} +1 -1
  134. package/src/planning/types.ts +216 -0
  135. package/src/ports/types.ts +412 -0
  136. package/src/reports/constants.ts +6 -0
  137. package/src/reports/cost-to-land-schema.ts +47 -0
  138. package/src/reports/outbox-status.ts +22 -0
  139. package/src/reports/stage-time-schema.ts +21 -0
  140. package/src/reports/types.ts +7 -0
  141. package/src/rows/actor-ref-schema.ts +17 -0
  142. package/src/rows/attempt-row-schema.ts +69 -0
  143. package/src/rows/attempt-session-row-schema.ts +12 -0
  144. package/src/{brand-schemas.ts → rows/brand-schemas.ts} +5 -2
  145. package/src/rows/caller-identity-schema.ts +7 -0
  146. package/src/rows/criterion-schema.ts +9 -0
  147. package/src/rows/decision-row-schema.ts +34 -0
  148. package/src/rows/evidence-row-schema.ts +13 -0
  149. package/src/rows/item-row-schema.ts +32 -0
  150. package/src/rows/item-view-schema.ts +31 -0
  151. package/src/rows/lease-row-schema.ts +12 -0
  152. package/src/rows/lever-row-schema.ts +17 -0
  153. package/src/rows/limit-schema.ts +12 -0
  154. package/src/rows/limit-seat-row-schema.ts +12 -0
  155. package/src/rows/note-row-schema.ts +12 -0
  156. package/src/rows/notice-row-schema.ts +17 -0
  157. package/src/rows/pull-request-row-schema.ts +15 -0
  158. package/src/rows/relation-row-schema.ts +13 -0
  159. package/src/rows/row-schemas.ts +30 -0
  160. package/src/rows/runner-row-schema.ts +9 -0
  161. package/src/rows/spawn-request-schema.ts +15 -0
  162. package/src/rules/define-phase.ts +12 -0
  163. package/src/rules/load-rejection-schema.ts +51 -0
  164. package/src/rules/types.ts +291 -0
  165. package/src/test-utils/build-mock-cost-to-land.ts +31 -0
  166. package/src/test-utils/build-mock-evidence-row.ts +2 -2
  167. package/src/test-utils/build-mock-fact-id.ts +3 -3
  168. package/src/test-utils/build-mock-frame-item.ts +10 -5
  169. package/src/test-utils/build-mock-frame.ts +13 -0
  170. package/src/test-utils/build-mock-item-rules.ts +17 -0
  171. package/src/test-utils/build-mock-lease-row.ts +2 -2
  172. package/src/test-utils/build-mock-outbox-fact.ts +22 -0
  173. package/src/test-utils/build-mock-phase-kind.ts +29 -0
  174. package/src/test-utils/build-mock-pipeline.ts +52 -0
  175. package/src/test-utils/{build-mock-link-row.ts → build-mock-relation-row.ts} +4 -4
  176. package/src/test-utils/build-mock-settled.ts +18 -0
  177. package/src/test-utils/build-mock-snapshot.ts +10 -2
  178. package/src/test-utils/{build-mock-attempt-row.ts → build-mock-v1-attempt-row.ts} +28 -9
  179. package/src/test-utils/{build-mock-item-row.ts → build-mock-v1-item-row.ts} +7 -3
  180. package/src/test-utils/build-mock-v1-item-view.ts +29 -0
  181. package/src/test-utils/{build-mock-lever-row.ts → build-mock-v1-lever-row.ts} +5 -3
  182. package/src/test-utils/build-mock-v1-pull-request-row.ts +26 -0
  183. package/src/test-utils/build-mock-work-fact.ts +179 -0
  184. package/src/test-utils/create-fake-notifier.ts +54 -0
  185. package/src/test-utils/create-fake-pepys-reader.ts +73 -0
  186. package/src/test-utils/fact-sink-contract.ts +103 -0
  187. package/src/test-utils/pepys-reader-contract.ts +117 -0
  188. package/src/{types/core.ts → types.ts} +183 -59
  189. package/dist/actor-ref-schema.d.ts +0 -2
  190. package/dist/build-applied-change.d.ts +0 -13
  191. package/dist/build-close-out-schema.d.ts +0 -3
  192. package/dist/build-item-views.d.ts +0 -2
  193. package/dist/define-admission.d.ts +0 -3
  194. package/dist/define-gate.d.ts +0 -3
  195. package/dist/define-signal.d.ts +0 -3
  196. package/dist/derive-item-state.d.ts +0 -2
  197. package/dist/fact-schemas.d.ts +0 -5
  198. package/dist/find-slot-take.d.ts +0 -5
  199. package/dist/friction-schema.d.ts +0 -3
  200. package/dist/test-utils/build-mock-attempt-row.d.ts +0 -2
  201. package/dist/test-utils/build-mock-item-row.d.ts +0 -2
  202. package/dist/test-utils/build-mock-item-rows.d.ts +0 -2
  203. package/dist/test-utils/build-mock-item-view.d.ts +0 -2
  204. package/dist/test-utils/build-mock-lever-row.d.ts +0 -2
  205. package/dist/test-utils/build-mock-link-row.d.ts +0 -2
  206. package/dist/test-utils/build-mock-pull-request-row.d.ts +0 -2
  207. package/dist/types/dispatch.d.ts +0 -82
  208. package/dist/types/facts.d.ts +0 -154
  209. package/dist/types/planning.d.ts +0 -94
  210. package/dist/types/ports.d.ts +0 -218
  211. package/dist/types/registries.d.ts +0 -150
  212. package/src/actor-ref-schema.ts +0 -9
  213. package/src/build-applied-change.ts +0 -71
  214. package/src/build-item-views.ts +0 -128
  215. package/src/derive-item-state.ts +0 -76
  216. package/src/fact-schemas.ts +0 -132
  217. package/src/find-slot-take.ts +0 -12
  218. package/src/friction-schema.ts +0 -10
  219. package/src/outbox-fact-schema.ts +0 -25
  220. package/src/row-schemas.ts +0 -89
  221. package/src/test-utils/build-mock-item-rows.ts +0 -14
  222. package/src/test-utils/build-mock-item-view.ts +0 -8
  223. package/src/test-utils/build-mock-pull-request-row.ts +0 -16
  224. package/src/types/dispatch.ts +0 -95
  225. package/src/types/facts.ts +0 -166
  226. package/src/types/planning.ts +0 -136
  227. package/src/types/ports.ts +0 -262
  228. package/src/types/registries.ts +0 -185
  229. /package/dist/test-utils/{seed-faker.d.ts → pin-faker-seed.d.ts} +0 -0
  230. /package/dist/{format-key-path.d.ts → utils/format-key-path.d.ts} +0 -0
  231. /package/dist/{to-json-value.d.ts → utils/to-json-value.d.ts} +0 -0
  232. /package/src/test-utils/{seed-faker.ts → pin-faker-seed.ts} +0 -0
  233. /package/src/{format-key-path.ts → utils/format-key-path.ts} +0 -0
  234. /package/src/{to-json-value.ts → utils/to-json-value.ts} +0 -0
@@ -0,0 +1,54 @@
1
+ import type { HoldNotice, NoticeOutcome, Notifier } from '../ports/types.ts';
2
+
3
+ export interface FakeNotifier extends Notifier {
4
+ // each notice that was sent, in order
5
+ readonly told: readonly HoldNotice[];
6
+
7
+ // the number of calls, sent or failed
8
+ readonly calls: () => number;
9
+
10
+ // the next call fails with the reason
11
+ failNext: (reason: string) => void;
12
+
13
+ // every call fails with the reason until `recover`
14
+ failAlways: (reason: string) => void;
15
+ recover: () => void;
16
+ }
17
+
18
+ export function createFakeNotifier(): FakeNotifier {
19
+ const told: HoldNotice[] = [];
20
+ let calls = 0;
21
+ let next: string | null = null;
22
+ let always: string | null = null;
23
+
24
+ return {
25
+ told,
26
+ calls: () => calls,
27
+ failNext: (reason) => {
28
+ next = reason;
29
+ },
30
+ failAlways: (reason) => {
31
+ always = reason;
32
+ },
33
+ recover: () => {
34
+ always = null;
35
+ },
36
+ tellHold: (notice) => {
37
+ calls += 1;
38
+
39
+ const reason = next ?? always;
40
+
41
+ next = null;
42
+
43
+ if (reason !== null) {
44
+ const failed: NoticeOutcome = { kind: 'failed', reason };
45
+
46
+ return Promise.resolve(failed);
47
+ }
48
+
49
+ told.push(notice);
50
+
51
+ return Promise.resolve({ kind: 'sent' });
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,73 @@
1
+ import type { PepysAnswer, PepysReader } from '../ports/types.ts';
2
+ import { stageSteps } from '../reports/constants.ts';
3
+ import type { CostToLand, StageTime } from '../reports/types.ts';
4
+ import type { ItemId } from '../types.ts';
5
+
6
+ interface FakeValues {
7
+ readonly cost?: CostToLand;
8
+ readonly stageTime?: StageTime;
9
+ }
10
+
11
+ export interface FakePepysReader extends PepysReader {
12
+ // an item with no value set answers as pepys does for an item it holds no fact of
13
+ set: (item: ItemId, values: FakeValues) => void;
14
+
15
+ // the next call, of either method, answers `unavailable` with this reason; each call of
16
+ // `failNext` fails one more call
17
+ failNext: (reason: string) => void;
18
+ }
19
+
20
+ export function createFakePepysReader(environment = 'production'): FakePepysReader {
21
+ const costs = new Map<ItemId, CostToLand>();
22
+ const stageTimes = new Map<ItemId, StageTime>();
23
+
24
+ const failures: string[] = [];
25
+
26
+ const answer = <Value>(value: Value): Promise<PepysAnswer<Value>> => {
27
+ const reason = failures.shift();
28
+
29
+ const answered: PepysAnswer<Value> =
30
+ reason === undefined ? { kind: 'value', value } : { kind: 'unavailable', reason };
31
+
32
+ return Promise.resolve(answered);
33
+ };
34
+
35
+ return {
36
+ set: (item, values) => {
37
+ if (values.cost !== undefined) {
38
+ costs.set(item, values.cost);
39
+ }
40
+
41
+ if (values.stageTime !== undefined) {
42
+ stageTimes.set(item, values.stageTime);
43
+ }
44
+ },
45
+ failNext: (reason) => {
46
+ failures.push(reason);
47
+ },
48
+ readCostToLand: (item) => answer(costs.get(item) ?? buildEmptyCost(item)),
49
+ readStageTime: (item) => answer(stageTimes.get(item) ?? buildEmptyStageTime(item, environment)),
50
+ };
51
+ }
52
+
53
+ function buildEmptyCost(item: ItemId): CostToLand {
54
+ return {
55
+ item: `boxxy:item:${item}`,
56
+ landedAt: null,
57
+ attempts: 0,
58
+ lines: [],
59
+ totals: { requests: 0, costUSD: 0, reworkCostUSD: 0, requestsWithoutCost: 0 },
60
+ };
61
+ }
62
+
63
+ function buildEmptyStageTime(item: ItemId, environment: string): StageTime {
64
+ return {
65
+ item: `boxxy:item:${item}`,
66
+ environment,
67
+ steps: stageSteps.map((step) => ({ step, minutes: null, endedAt: null })),
68
+ rework: { count: 0, minutes: 0 },
69
+ reviewWaitMinutes: null,
70
+ sleepMinutes: 0,
71
+ pullRequests: [],
72
+ };
73
+ }
@@ -0,0 +1,103 @@
1
+ import type { FactId, FactSink, OutboxFact } from '@boxxy/model';
2
+ import { buildMockOutboxFact } from '@boxxy/model/test-utils/build-mock-outbox-fact';
3
+
4
+ export interface FactSinkHarness {
5
+ readonly sink: FactSink;
6
+
7
+ // the fact IDs the log holds, in the order they arrived, a copy of an ID counted once
8
+ readStored: () => Promise<readonly FactId[]>;
9
+
10
+ // the same fact, changed so that the sink refuses a batch that holds it
11
+ corrupt: (fact: OutboxFact) => OutboxFact;
12
+ }
13
+
14
+ export type FactSinkCase = readonly [
15
+ title: string,
16
+ check: (harness: FactSinkHarness) => Promise<{ received: unknown; expected: unknown }>,
17
+ ];
18
+
19
+ // The cases that every adapter of the sink port must pass: the fake, and the pepys adapter behind
20
+ // ingest's route handler. Each case gets a harness with an empty log.
21
+ export const factSinkContract: readonly FactSinkCase[] = [
22
+ [
23
+ 'a batch is stored whole and in order',
24
+ async (harness) => {
25
+ const batch = buildFacts(3, 1);
26
+
27
+ const answer = await harness.sink.send(batch);
28
+
29
+ return {
30
+ received: { answer, stored: await harness.readStored() },
31
+ expected: { answer: { kind: 'stored' }, stored: batch.map((fact) => fact.factId) },
32
+ };
33
+ },
34
+ ],
35
+ [
36
+ 'the same batch sent twice is stored as one copy of each fact',
37
+ async (harness) => {
38
+ const batch = buildFacts(2, 1);
39
+
40
+ await harness.sink.send(batch);
41
+
42
+ const answer = await harness.sink.send(batch);
43
+
44
+ return {
45
+ received: { answer, stored: await harness.readStored() },
46
+ expected: { answer: { kind: 'stored' }, stored: batch.map((fact) => fact.factId) },
47
+ };
48
+ },
49
+ ],
50
+ [
51
+ 'two batches sent one after the other keep their order',
52
+ async (harness) => {
53
+ const first = buildFacts(2, 1);
54
+ const second = buildFacts(2, 3);
55
+
56
+ await harness.sink.send(first);
57
+ await harness.sink.send(second);
58
+
59
+ return {
60
+ received: await harness.readStored(),
61
+ expected: [...first, ...second].map((fact) => fact.factId),
62
+ };
63
+ },
64
+ ],
65
+ [
66
+ 'a batch with a bad fact is refused whole and stores none of it',
67
+ async (harness) => {
68
+ const [good, bad] = buildPair();
69
+
70
+ const answer = await harness.sink.send([good, harness.corrupt(bad)]);
71
+
72
+ return {
73
+ received: { kind: answer.kind, stored: await harness.readStored() },
74
+ expected: { kind: 'refused-batch', stored: [] },
75
+ };
76
+ },
77
+ ],
78
+ [
79
+ 'a good batch is stored after a bad one was refused',
80
+ async (harness) => {
81
+ const [good, bad] = buildPair();
82
+
83
+ await harness.sink.send([good, harness.corrupt(bad)]);
84
+
85
+ const answer = await harness.sink.send([good]);
86
+
87
+ return {
88
+ received: { answer, stored: await harness.readStored() },
89
+ expected: { answer: { kind: 'stored' }, stored: [good.factId] },
90
+ };
91
+ },
92
+ ],
93
+ ];
94
+
95
+ function buildFacts(count: number, firstSeq: number): OutboxFact[] {
96
+ return Array.from({ length: count }, (_unused, index) =>
97
+ buildMockOutboxFact({ seq: firstSeq + index }),
98
+ );
99
+ }
100
+
101
+ function buildPair(): [OutboxFact, OutboxFact] {
102
+ return [buildMockOutboxFact({ seq: 1 }), buildMockOutboxFact({ seq: 2 })];
103
+ }
@@ -0,0 +1,117 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import type { PepysReader } from '../ports/types.ts';
3
+ import type { CostToLand } from '../reports/types.ts';
4
+ import { brandSchemas } from '../rows/brand-schemas.ts';
5
+ import type { ItemId } from '../types.ts';
6
+ import { buildMockCostToLand } from './build-mock-cost-to-land.ts';
7
+
8
+ export interface PepysReaderHarness {
9
+ readonly reader: PepysReader;
10
+
11
+ // the environment the reader passes to the stage times
12
+ readonly environment: string;
13
+
14
+ // pepys holds this cost for the item
15
+ writeCost: (item: ItemId, cost: CostToLand) => Promise<void>;
16
+
17
+ // every later call finds pepys down
18
+ takeDown: () => Promise<void>;
19
+ }
20
+
21
+ export type PepysReaderCase = readonly [
22
+ title: string,
23
+ check: (harness: PepysReaderHarness) => Promise<{ received: unknown; expected: unknown }>,
24
+ ];
25
+
26
+ // The cases that every adapter of the reader port must pass: the fake, and the pepys adapter
27
+ // behind the plug-in's queries. Each case gets a harness with an empty pepys.
28
+ export const pepysReaderContract: readonly PepysReaderCase[] = [
29
+ [
30
+ 'an item that pepys holds no fact of gives the empty cost, not unavailable',
31
+ async (harness) => {
32
+ const item = buildItemId();
33
+
34
+ return {
35
+ received: await harness.reader.readCostToLand(item),
36
+ expected: {
37
+ kind: 'value',
38
+ value: {
39
+ item: `boxxy:item:${item}`,
40
+ landedAt: null,
41
+ attempts: 0,
42
+ lines: [],
43
+ totals: { requests: 0, costUSD: 0, reworkCostUSD: 0, requestsWithoutCost: 0 },
44
+ },
45
+ },
46
+ };
47
+ },
48
+ ],
49
+ [
50
+ 'an item that pepys holds no fact of gives seven steps with null minutes',
51
+ async (harness) => {
52
+ const item = buildItemId();
53
+ const empty = { minutes: null, endedAt: null };
54
+
55
+ return {
56
+ received: await harness.reader.readStageTime(item),
57
+ expected: {
58
+ kind: 'value',
59
+ value: {
60
+ item: `boxxy:item:${item}`,
61
+ environment: harness.environment,
62
+ steps: [
63
+ { step: 'triage', ...empty },
64
+ { step: 'design', ...empty },
65
+ { step: 'build', ...empty },
66
+ { step: 'review', ...empty },
67
+ { step: 'merge', ...empty },
68
+ { step: 'release', ...empty },
69
+ { step: 'deploy', ...empty },
70
+ ],
71
+ rework: { count: 0, minutes: 0 },
72
+ reviewWaitMinutes: null,
73
+ sleepMinutes: 0,
74
+ pullRequests: [],
75
+ },
76
+ },
77
+ };
78
+ },
79
+ ],
80
+ [
81
+ 'a cost that pepys holds comes back whole',
82
+ async (harness) => {
83
+ const item = buildItemId();
84
+ const cost = buildMockCostToLand({ item: `boxxy:item:${item}` });
85
+
86
+ await harness.writeCost(item, cost);
87
+
88
+ return {
89
+ received: await harness.reader.readCostToLand(item),
90
+ expected: { kind: 'value', value: cost },
91
+ };
92
+ },
93
+ ],
94
+ [
95
+ 'a pepys that is down gives unavailable with a reason for both reads',
96
+ async (harness) => {
97
+ const item = buildItemId();
98
+
99
+ await harness.takeDown();
100
+
101
+ return {
102
+ received: [
103
+ await harness.reader.readCostToLand(item),
104
+ await harness.reader.readStageTime(item),
105
+ ].map((answer) => ({ kind: answer.kind, hasReason: 'reason' in answer })),
106
+ expected: [
107
+ { kind: 'unavailable', hasReason: true },
108
+ { kind: 'unavailable', hasReason: true },
109
+ ],
110
+ };
111
+ },
112
+ ],
113
+ ];
114
+
115
+ function buildItemId(): ItemId {
116
+ return brandSchemas.itemId.parse(faker.string.uuid({ version: 7 }));
117
+ }