@boxxy/model 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/closeouts/build-close-out-schema.d.ts +31 -0
- package/dist/closeouts/close-out-answer-schema.d.ts +3 -0
- package/dist/closeouts/close-out-schema.d.ts +3 -0
- package/dist/closeouts/design-close-out-schema.d.ts +30 -0
- package/dist/closeouts/friction-schema.d.ts +14 -0
- package/dist/closeouts/open-item-schema.d.ts +3 -0
- package/dist/closeouts/review-close-out-schema.d.ts +40 -0
- package/dist/closeouts/triage-close-out-schema.d.ts +40 -0
- package/dist/closeouts/types.d.ts +221 -0
- package/dist/dispatch/build-applied-change.d.ts +6 -0
- package/dist/dispatch/collect-seat-sync.d.ts +14 -0
- package/dist/dispatch/pick-seat-take.d.ts +10 -0
- package/dist/dispatch/types.d.ts +11 -0
- package/dist/{outbox-fact-schema.d.ts → facts/outbox-fact-schema.d.ts} +1 -1
- package/dist/facts/parse-new-fact.d.ts +2 -0
- package/dist/facts/types.d.ts +191 -0
- package/dist/facts/work-fact-schemas.d.ts +293 -0
- package/dist/index.d.ts +64 -23
- package/dist/items/build-item-views.d.ts +4 -0
- package/dist/items/collect-retries.d.ts +3 -0
- package/dist/items/collect-settled.d.ts +3 -0
- package/dist/items/derive-item-state.d.ts +17 -0
- package/dist/{live-levers.d.ts → items/live-levers.d.ts} +1 -1
- package/dist/items/types.d.ts +12 -0
- package/dist/pipeline/advance.d.ts +10 -0
- package/dist/pipeline/constants.d.ts +2 -0
- package/dist/pipeline/count-trailing.d.ts +1 -0
- package/dist/pipeline/find-position.d.ts +9 -0
- package/dist/pipeline/find-step.d.ts +3 -0
- package/dist/pipeline/merge-by-time.d.ts +9 -0
- package/dist/pipeline/position-schema.d.ts +3 -0
- package/dist/pipeline/types.d.ts +28 -0
- package/dist/planning/define-admission.d.ts +3 -0
- package/dist/planning/define-gate.d.ts +3 -0
- package/dist/planning/define-signal.d.ts +3 -0
- package/dist/planning/is-filter-match.d.ts +3 -0
- package/dist/planning/limit-fill-schema.d.ts +3 -0
- package/dist/{signal-key.d.ts → planning/signal-key.d.ts} +1 -1
- package/dist/planning/types.d.ts +155 -0
- package/dist/ports/types.d.ts +289 -0
- package/dist/reports/constants.d.ts +3 -0
- package/dist/reports/cost-to-land-schema.d.ts +29 -0
- package/dist/reports/outbox-status.d.ts +16 -0
- package/dist/reports/stage-time-schema.d.ts +25 -0
- package/dist/reports/types.d.ts +5 -0
- package/dist/rows/actor-ref-schema.d.ts +2 -0
- package/dist/rows/attempt-row-schema.d.ts +3 -0
- package/dist/rows/attempt-session-row-schema.d.ts +3 -0
- package/dist/{brand-schemas.d.ts → rows/brand-schemas.d.ts} +5 -2
- package/dist/rows/caller-identity-schema.d.ts +3 -0
- package/dist/rows/criterion-schema.d.ts +3 -0
- package/dist/rows/decision-row-schema.d.ts +34 -0
- package/dist/rows/evidence-row-schema.d.ts +3 -0
- package/dist/rows/item-row-schema.d.ts +28 -0
- package/dist/rows/item-view-schema.d.ts +3 -0
- package/dist/rows/lease-row-schema.d.ts +3 -0
- package/dist/rows/lever-row-schema.d.ts +12 -0
- package/dist/rows/limit-schema.d.ts +3 -0
- package/dist/rows/limit-seat-row-schema.d.ts +3 -0
- package/dist/rows/note-row-schema.d.ts +3 -0
- package/dist/rows/notice-row-schema.d.ts +3 -0
- package/dist/rows/pull-request-row-schema.d.ts +3 -0
- package/dist/rows/relation-row-schema.d.ts +3 -0
- package/dist/{row-schemas.d.ts → rows/row-schemas.d.ts} +2 -2
- package/dist/rows/runner-row-schema.d.ts +3 -0
- package/dist/rows/spawn-request-schema.d.ts +3 -0
- package/dist/rules/define-phase.d.ts +3 -0
- package/dist/rules/load-rejection-schema.d.ts +3 -0
- package/dist/rules/types.d.ts +244 -0
- package/dist/test-utils/build-mock-cost-to-land.d.ts +2 -0
- package/dist/test-utils/build-mock-evidence-row.d.ts +1 -1
- package/dist/test-utils/build-mock-fact-id.d.ts +1 -1
- package/dist/test-utils/build-mock-frame-item.d.ts +1 -1
- package/dist/test-utils/build-mock-frame.d.ts +2 -0
- package/dist/test-utils/build-mock-item-rules.d.ts +2 -0
- package/dist/test-utils/build-mock-lease-row.d.ts +1 -1
- package/dist/test-utils/build-mock-outbox-fact.d.ts +2 -0
- package/dist/test-utils/build-mock-phase-kind.d.ts +7 -0
- package/dist/test-utils/build-mock-pipeline.d.ts +7 -0
- package/dist/test-utils/build-mock-relation-row.d.ts +2 -0
- package/dist/test-utils/build-mock-settled.d.ts +9 -0
- package/dist/test-utils/build-mock-snapshot.d.ts +1 -1
- package/dist/test-utils/build-mock-v1-attempt-row.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-item-row.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-item-view.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-lever-row.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-pull-request-row.d.ts +2 -0
- package/dist/test-utils/build-mock-work-fact.d.ts +12 -0
- package/dist/test-utils/create-fake-notifier.d.ts +9 -0
- package/dist/test-utils/create-fake-pepys-reader.d.ts +13 -0
- package/dist/test-utils/fact-sink-contract.d.ts +14 -0
- package/dist/test-utils/pepys-reader-contract.d.ts +17 -0
- package/dist/{types/core.d.ts → types.d.ts} +93 -43
- package/package.json +13 -3
- package/src/{build-close-out-schema.ts → closeouts/build-close-out-schema.ts} +5 -10
- package/src/closeouts/close-out-answer-schema.ts +42 -0
- package/src/closeouts/close-out-schema.ts +13 -0
- package/src/closeouts/design-close-out-schema.ts +20 -0
- package/src/closeouts/friction-schema.ts +11 -0
- package/src/closeouts/open-item-schema.ts +8 -0
- package/src/closeouts/review-close-out-schema.ts +38 -0
- package/src/closeouts/triage-close-out-schema.ts +29 -0
- package/src/closeouts/types.ts +230 -0
- package/src/dispatch/build-applied-change.ts +57 -0
- package/src/dispatch/collect-seat-sync.ts +86 -0
- package/src/dispatch/pick-seat-take.ts +39 -0
- package/src/dispatch/types.ts +14 -0
- package/src/facts/outbox-fact-schema.ts +22 -0
- package/src/facts/parse-new-fact.ts +16 -0
- package/src/facts/types.ts +235 -0
- package/src/facts/work-fact-schemas.ts +201 -0
- package/src/index.ts +147 -55
- package/src/items/build-item-views.ts +146 -0
- package/src/items/collect-retries.ts +12 -0
- package/src/items/collect-settled.ts +20 -0
- package/src/items/derive-item-state.ts +95 -0
- package/src/{live-levers.ts → items/live-levers.ts} +1 -1
- package/src/items/types.ts +26 -0
- package/src/pipeline/advance.ts +78 -0
- package/src/pipeline/constants.ts +12 -0
- package/src/pipeline/count-trailing.ts +16 -0
- package/src/pipeline/find-position.ts +74 -0
- package/src/pipeline/find-step.ts +6 -0
- package/src/pipeline/merge-by-time.ts +33 -0
- package/src/pipeline/position-schema.ts +27 -0
- package/src/pipeline/types.ts +47 -0
- package/src/{define-admission.ts → planning/define-admission.ts} +2 -2
- package/src/{define-gate.ts → planning/define-gate.ts} +2 -2
- package/src/{define-signal.ts → planning/define-signal.ts} +2 -2
- package/src/planning/is-filter-match.ts +10 -0
- package/src/planning/limit-fill-schema.ts +12 -0
- package/src/{signal-key.ts → planning/signal-key.ts} +1 -1
- package/src/planning/types.ts +216 -0
- package/src/ports/types.ts +412 -0
- package/src/reports/constants.ts +6 -0
- package/src/reports/cost-to-land-schema.ts +47 -0
- package/src/reports/outbox-status.ts +22 -0
- package/src/reports/stage-time-schema.ts +21 -0
- package/src/reports/types.ts +7 -0
- package/src/rows/actor-ref-schema.ts +17 -0
- package/src/rows/attempt-row-schema.ts +69 -0
- package/src/rows/attempt-session-row-schema.ts +12 -0
- package/src/{brand-schemas.ts → rows/brand-schemas.ts} +5 -2
- package/src/rows/caller-identity-schema.ts +7 -0
- package/src/rows/criterion-schema.ts +9 -0
- package/src/rows/decision-row-schema.ts +34 -0
- package/src/rows/evidence-row-schema.ts +13 -0
- package/src/rows/item-row-schema.ts +32 -0
- package/src/rows/item-view-schema.ts +31 -0
- package/src/rows/lease-row-schema.ts +12 -0
- package/src/rows/lever-row-schema.ts +17 -0
- package/src/rows/limit-schema.ts +12 -0
- package/src/rows/limit-seat-row-schema.ts +12 -0
- package/src/rows/note-row-schema.ts +12 -0
- package/src/rows/notice-row-schema.ts +17 -0
- package/src/rows/pull-request-row-schema.ts +15 -0
- package/src/rows/relation-row-schema.ts +13 -0
- package/src/rows/row-schemas.ts +30 -0
- package/src/rows/runner-row-schema.ts +9 -0
- package/src/rows/spawn-request-schema.ts +15 -0
- package/src/rules/define-phase.ts +12 -0
- package/src/rules/load-rejection-schema.ts +51 -0
- package/src/rules/types.ts +291 -0
- package/src/test-utils/build-mock-cost-to-land.ts +31 -0
- package/src/test-utils/build-mock-evidence-row.ts +2 -2
- package/src/test-utils/build-mock-fact-id.ts +3 -3
- package/src/test-utils/build-mock-frame-item.ts +10 -5
- package/src/test-utils/build-mock-frame.ts +13 -0
- package/src/test-utils/build-mock-item-rules.ts +17 -0
- package/src/test-utils/build-mock-lease-row.ts +2 -2
- package/src/test-utils/build-mock-outbox-fact.ts +22 -0
- package/src/test-utils/build-mock-phase-kind.ts +29 -0
- package/src/test-utils/build-mock-pipeline.ts +52 -0
- package/src/test-utils/{build-mock-link-row.ts → build-mock-relation-row.ts} +4 -4
- package/src/test-utils/build-mock-settled.ts +18 -0
- package/src/test-utils/build-mock-snapshot.ts +10 -2
- package/src/test-utils/{build-mock-attempt-row.ts → build-mock-v1-attempt-row.ts} +28 -9
- package/src/test-utils/{build-mock-item-row.ts → build-mock-v1-item-row.ts} +7 -3
- package/src/test-utils/build-mock-v1-item-view.ts +29 -0
- package/src/test-utils/{build-mock-lever-row.ts → build-mock-v1-lever-row.ts} +5 -3
- package/src/test-utils/build-mock-v1-pull-request-row.ts +26 -0
- package/src/test-utils/build-mock-work-fact.ts +179 -0
- package/src/test-utils/create-fake-notifier.ts +54 -0
- package/src/test-utils/create-fake-pepys-reader.ts +73 -0
- package/src/test-utils/fact-sink-contract.ts +103 -0
- package/src/test-utils/pepys-reader-contract.ts +117 -0
- package/src/{types/core.ts → types.ts} +183 -59
- package/dist/actor-ref-schema.d.ts +0 -2
- package/dist/build-applied-change.d.ts +0 -13
- package/dist/build-close-out-schema.d.ts +0 -3
- package/dist/build-item-views.d.ts +0 -2
- package/dist/define-admission.d.ts +0 -3
- package/dist/define-gate.d.ts +0 -3
- package/dist/define-signal.d.ts +0 -3
- package/dist/derive-item-state.d.ts +0 -2
- package/dist/fact-schemas.d.ts +0 -5
- package/dist/find-slot-take.d.ts +0 -5
- package/dist/friction-schema.d.ts +0 -3
- package/dist/test-utils/build-mock-attempt-row.d.ts +0 -2
- package/dist/test-utils/build-mock-item-row.d.ts +0 -2
- package/dist/test-utils/build-mock-item-rows.d.ts +0 -2
- package/dist/test-utils/build-mock-item-view.d.ts +0 -2
- package/dist/test-utils/build-mock-lever-row.d.ts +0 -2
- package/dist/test-utils/build-mock-link-row.d.ts +0 -2
- package/dist/test-utils/build-mock-pull-request-row.d.ts +0 -2
- package/dist/types/dispatch.d.ts +0 -82
- package/dist/types/facts.d.ts +0 -154
- package/dist/types/planning.d.ts +0 -94
- package/dist/types/ports.d.ts +0 -218
- package/dist/types/registries.d.ts +0 -150
- package/src/actor-ref-schema.ts +0 -9
- package/src/build-applied-change.ts +0 -71
- package/src/build-item-views.ts +0 -128
- package/src/derive-item-state.ts +0 -76
- package/src/fact-schemas.ts +0 -132
- package/src/find-slot-take.ts +0 -12
- package/src/friction-schema.ts +0 -10
- package/src/outbox-fact-schema.ts +0 -25
- package/src/row-schemas.ts +0 -89
- package/src/test-utils/build-mock-item-rows.ts +0 -14
- package/src/test-utils/build-mock-item-view.ts +0 -8
- package/src/test-utils/build-mock-pull-request-row.ts +0 -16
- package/src/types/dispatch.ts +0 -95
- package/src/types/facts.ts +0 -166
- package/src/types/planning.ts +0 -136
- package/src/types/ports.ts +0 -262
- package/src/types/registries.ts +0 -185
- /package/dist/test-utils/{seed-faker.d.ts → pin-faker-seed.d.ts} +0 -0
- /package/dist/{format-key-path.d.ts → utils/format-key-path.d.ts} +0 -0
- /package/dist/{to-json-value.d.ts → utils/to-json-value.d.ts} +0 -0
- /package/src/test-utils/{seed-faker.ts → pin-faker-seed.ts} +0 -0
- /package/src/{format-key-path.ts → utils/format-key-path.ts} +0 -0
- /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
|
+
}
|