@boxxy/dispatch 0.1.0 → 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.
- package/README.md +4 -1
- package/dist/attempts/attempt-machine.d.ts +804 -0
- package/dist/attempts/attempt-moves.d.ts +2 -0
- package/dist/attempts/bind-attempt-session.d.ts +14 -0
- package/dist/attempts/build-attempt-change.d.ts +73 -0
- package/dist/attempts/build-attempt-fact.d.ts +3 -0
- package/dist/attempts/end-item-attempts.d.ts +18 -0
- package/dist/attempts/is-same-guard.d.ts +5 -0
- package/dist/attempts/pick-call-attempt.d.ts +10 -0
- package/dist/attempts/stop-stray-session.d.ts +11 -0
- package/dist/attempts/types.d.ts +142 -0
- package/dist/attempts/write-attempt-move.d.ts +7 -0
- package/dist/attempts/write-session-bind.d.ts +3 -0
- package/dist/claims/build-snapshot-hash.d.ts +2 -0
- package/dist/claims/claim-next.d.ts +6 -0
- package/dist/claims/pick-kept-session.d.ts +9 -0
- package/dist/claims/request-wake.d.ts +3 -0
- package/dist/claims/resume-claim.d.ts +3 -0
- package/dist/claims/start-claim.d.ts +3 -0
- package/dist/claims/types.d.ts +44 -0
- package/dist/closeouts/build-settle-plans.d.ts +17 -0
- package/dist/closeouts/close-out-attempt.d.ts +3 -0
- package/dist/closeouts/effect-handlers.d.ts +2 -0
- package/dist/closeouts/pick-settlement.d.ts +14 -0
- package/dist/closeouts/settle-attempt.d.ts +3 -0
- package/dist/closeouts/types.d.ts +50 -0
- package/dist/index.d.ts +38 -0
- package/dist/notices/constants.d.ts +4 -0
- package/dist/notices/create-null-notifier.d.ts +2 -0
- package/dist/notices/send-notices.d.ts +2 -0
- package/dist/notices/types.d.ts +11 -0
- package/dist/outbox/build-backoff-ms.d.ts +2 -0
- package/dist/outbox/build-initial-sender-state.d.ts +2 -0
- package/dist/outbox/build-outbox-status.d.ts +15 -0
- package/dist/outbox/constants.d.ts +2 -0
- package/dist/outbox/measure-wire-bytes.d.ts +2 -0
- package/dist/outbox/pick-sender-move.d.ts +10 -0
- package/dist/outbox/run-sender-once.d.ts +2 -0
- package/dist/outbox/take-batch.d.ts +3 -0
- package/dist/outbox/types.d.ts +61 -0
- package/dist/pull-requests/build-link-change.d.ts +11 -0
- package/dist/pull-requests/build-state-fact.d.ts +8 -0
- package/dist/pull-requests/collect-attempt-trailers.d.ts +2 -0
- package/dist/pull-requests/collect-seen-state.d.ts +7 -0
- package/dist/pull-requests/constants.d.ts +3 -0
- package/dist/pull-requests/link-pull-request.d.ts +2 -0
- package/dist/pull-requests/parse-pull-request-url.d.ts +2 -0
- package/dist/pull-requests/types.d.ts +37 -0
- package/dist/pull-requests/watch-pull-requests.d.ts +2 -0
- package/dist/readings/act-on-reading.d.ts +3 -0
- package/dist/readings/find-waiting-since.d.ts +9 -0
- package/dist/readings/pick-reading-action.d.ts +2 -0
- package/dist/readings/types.d.ts +79 -0
- package/dist/sessions/build-keep-writes.d.ts +9 -0
- package/dist/sessions/build-kept-clear.d.ts +2 -0
- package/dist/sessions/forget-sessions.d.ts +2 -0
- package/dist/sessions/is-kept-due.d.ts +2 -0
- package/dist/sessions/resume-kept-session.d.ts +6 -0
- package/dist/sessions/types.d.ts +19 -0
- package/dist/test-utils/apply-memory-change.d.ts +3 -0
- package/dist/test-utils/build-memory-reader.d.ts +3 -0
- package/dist/test-utils/build-mock-bind-change.d.ts +7 -0
- package/dist/test-utils/build-mock-claim-change.d.ts +2 -0
- package/dist/test-utils/build-mock-file-item-change.d.ts +2 -0
- package/dist/test-utils/build-mock-lever-change.d.ts +2 -0
- package/dist/test-utils/build-mock-new-id.d.ts +2 -0
- package/dist/test-utils/build-mock-note-change.d.ts +2 -0
- package/dist/test-utils/build-mock-pause-change.d.ts +2 -0
- package/dist/test-utils/build-mock-pull-request-change.d.ts +8 -0
- package/dist/test-utils/build-mock-relation-change.d.ts +7 -0
- package/dist/test-utils/build-mock-running-reading.d.ts +6 -0
- package/dist/test-utils/build-mock-settle-change.d.ts +7 -0
- package/dist/test-utils/build-mock-start-change.d.ts +2 -0
- package/dist/test-utils/claim-store-attempt-reader-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-claim-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-close-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-contract.d.ts +2 -0
- package/dist/test-utils/claim-store-copy-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-exclusive-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-guard-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-item-reader-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-outbox-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-race-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-rollback-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-seat-cases.d.ts +2 -0
- package/dist/test-utils/claim-store-session-cases.d.ts +2 -0
- package/dist/test-utils/collect-memory-limit-fills.d.ts +3 -0
- package/dist/test-utils/compare-text.d.ts +1 -0
- package/dist/test-utils/create-fake-backoff.d.ts +2 -0
- package/dist/test-utils/create-fake-clock.d.ts +5 -0
- package/dist/test-utils/create-fake-fact-sink.d.ts +8 -0
- package/dist/test-utils/create-fake-forge.d.ts +17 -0
- package/dist/test-utils/create-fake-worker.d.ts +42 -0
- package/dist/test-utils/create-memory-claim-store.d.ts +2 -0
- package/dist/test-utils/create-test-dispatch.d.ts +46 -0
- package/dist/test-utils/forge-contract.d.ts +15 -0
- package/dist/test-utils/is-same-held-seat.d.ts +2 -0
- package/dist/test-utils/types.d.ts +19 -0
- package/dist/test-utils/worker-contract.d.ts +14 -0
- package/dist/timeouts.d.ts +2 -0
- package/dist/types.d.ts +13 -0
- package/package.json +15 -2
- package/src/attempts/attempt-machine.ts +147 -0
- package/src/attempts/attempt-moves.ts +218 -0
- package/src/attempts/bind-attempt-session.ts +61 -0
- package/src/attempts/build-attempt-change.ts +540 -0
- package/src/attempts/build-attempt-fact.ts +135 -0
- package/src/attempts/end-item-attempts.ts +95 -0
- package/src/attempts/is-same-guard.ts +22 -0
- package/src/attempts/pick-call-attempt.ts +32 -0
- package/src/attempts/stop-stray-session.ts +48 -0
- package/src/attempts/types.ts +231 -0
- package/src/attempts/write-attempt-move.ts +41 -0
- package/src/attempts/write-session-bind.ts +47 -0
- package/src/claims/build-snapshot-hash.ts +9 -0
- package/src/claims/claim-next.ts +306 -0
- package/src/claims/pick-kept-session.ts +41 -0
- package/src/claims/request-wake.ts +17 -0
- package/src/claims/resume-claim.ts +56 -0
- package/src/claims/start-claim.ts +99 -0
- package/src/claims/types.ts +67 -0
- package/src/closeouts/build-settle-plans.ts +173 -0
- package/src/closeouts/close-out-attempt.ts +156 -0
- package/src/closeouts/effect-handlers.ts +232 -0
- package/src/closeouts/pick-settlement.ts +80 -0
- package/src/closeouts/settle-attempt.ts +161 -0
- package/src/closeouts/types.ts +87 -0
- package/src/index.ts +61 -0
- package/src/notices/constants.ts +7 -0
- package/src/notices/create-null-notifier.ts +6 -0
- package/src/notices/send-notices.ts +41 -0
- package/src/notices/types.ts +14 -0
- package/src/outbox/build-backoff-ms.ts +14 -0
- package/src/outbox/build-initial-sender-state.ts +14 -0
- package/src/outbox/build-outbox-status.ts +43 -0
- package/src/outbox/constants.ts +15 -0
- package/src/outbox/measure-wire-bytes.ts +31 -0
- package/src/outbox/pick-sender-move.ts +37 -0
- package/src/outbox/run-sender-once.ts +174 -0
- package/src/outbox/take-batch.ts +35 -0
- package/src/outbox/types.ts +76 -0
- package/src/pull-requests/build-link-change.ts +95 -0
- package/src/pull-requests/build-state-fact.ts +39 -0
- package/src/pull-requests/collect-attempt-trailers.ts +20 -0
- package/src/pull-requests/collect-seen-state.ts +19 -0
- package/src/pull-requests/constants.ts +5 -0
- package/src/pull-requests/link-pull-request.ts +119 -0
- package/src/pull-requests/parse-pull-request-url.ts +30 -0
- package/src/pull-requests/types.ts +63 -0
- package/src/pull-requests/watch-pull-requests.ts +308 -0
- package/src/readings/act-on-reading.ts +318 -0
- package/src/readings/find-waiting-since.ts +46 -0
- package/src/readings/pick-reading-action.ts +315 -0
- package/src/readings/types.ts +98 -0
- package/src/sessions/build-keep-writes.ts +55 -0
- package/src/sessions/build-kept-clear.ts +12 -0
- package/src/sessions/forget-sessions.ts +106 -0
- package/src/sessions/is-kept-due.ts +27 -0
- package/src/sessions/resume-kept-session.ts +25 -0
- package/src/sessions/types.ts +39 -0
- package/src/test-utils/apply-memory-change.ts +252 -0
- package/src/test-utils/build-memory-reader.ts +364 -0
- package/src/test-utils/build-mock-bind-change.ts +43 -0
- package/src/test-utils/build-mock-claim-change.ts +52 -0
- package/src/test-utils/build-mock-file-item-change.ts +20 -0
- package/src/test-utils/build-mock-lever-change.ts +18 -0
- package/src/test-utils/build-mock-new-id.ts +12 -0
- package/src/test-utils/build-mock-note-change.ts +18 -0
- package/src/test-utils/build-mock-pause-change.ts +19 -0
- package/src/test-utils/build-mock-pull-request-change.ts +63 -0
- package/src/test-utils/build-mock-relation-change.ts +40 -0
- package/src/test-utils/build-mock-running-reading.ts +19 -0
- package/src/test-utils/build-mock-settle-change.ts +70 -0
- package/src/test-utils/build-mock-start-change.ts +38 -0
- package/src/test-utils/claim-store-attempt-reader-cases.ts +357 -0
- package/src/test-utils/claim-store-claim-cases.ts +314 -0
- package/src/test-utils/claim-store-close-cases.ts +95 -0
- package/src/test-utils/claim-store-contract.ts +28 -0
- package/src/test-utils/claim-store-copy-cases.ts +100 -0
- package/src/test-utils/claim-store-exclusive-cases.ts +243 -0
- package/src/test-utils/claim-store-guard-cases.ts +383 -0
- package/src/test-utils/claim-store-item-reader-cases.ts +471 -0
- package/src/test-utils/claim-store-outbox-cases.ts +281 -0
- package/src/test-utils/claim-store-race-cases.ts +150 -0
- package/src/test-utils/claim-store-rollback-cases.ts +175 -0
- package/src/test-utils/claim-store-seat-cases.ts +270 -0
- package/src/test-utils/claim-store-session-cases.ts +289 -0
- package/src/test-utils/collect-memory-limit-fills.ts +15 -0
- package/src/test-utils/compare-text.ts +8 -0
- package/src/test-utils/create-fake-backoff.ts +16 -0
- package/src/test-utils/create-fake-clock.ts +20 -0
- package/src/test-utils/create-fake-fact-sink.ts +74 -0
- package/src/test-utils/create-fake-forge.ts +117 -0
- package/src/test-utils/create-fake-worker.ts +303 -0
- package/src/test-utils/create-memory-claim-store.ts +163 -0
- package/src/test-utils/create-test-dispatch.ts +597 -0
- package/src/test-utils/forge-contract.ts +171 -0
- package/src/test-utils/is-same-held-seat.ts +11 -0
- package/src/test-utils/types.ts +23 -0
- package/src/test-utils/worker-contract.ts +192 -0
- package/src/timeouts.ts +17 -0
- package/src/types.ts +32 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ItemRow, StoreChange } from '@boxxy/model';
|
|
2
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
3
|
+
|
|
4
|
+
export function buildMockFileItemChange(item: ItemRow): StoreChange {
|
|
5
|
+
const { keptSession: _keptSession, ...filed } = item;
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
at: item.filedAt,
|
|
9
|
+
writes: [{ op: 'insert', table: 'items', row: item }],
|
|
10
|
+
facts: [
|
|
11
|
+
{
|
|
12
|
+
id: buildMockFactId(),
|
|
13
|
+
type: 'boxxy.item.filed',
|
|
14
|
+
entity: `boxxy:item:${item.id}`,
|
|
15
|
+
actor: 'boxxy:owner:local',
|
|
16
|
+
payload: filed,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LeverRow, StoreChange } from '@boxxy/model';
|
|
2
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
3
|
+
|
|
4
|
+
export function buildMockLeverChange(lever: LeverRow): StoreChange {
|
|
5
|
+
return {
|
|
6
|
+
at: lever.at,
|
|
7
|
+
writes: [{ op: 'insert', table: 'levers', row: lever }],
|
|
8
|
+
facts: [
|
|
9
|
+
{
|
|
10
|
+
id: buildMockFactId(),
|
|
11
|
+
type: 'boxxy.lever.recorded',
|
|
12
|
+
entity: `boxxy:item:${lever.item}`,
|
|
13
|
+
actor: lever.actor,
|
|
14
|
+
payload: lever,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NewId } from '@boxxy/model';
|
|
2
|
+
|
|
3
|
+
// A new-ID source that gives version 7 UUIDs in order: 1, 2, 3, … in the last group.
|
|
4
|
+
export function buildMockNewId(): NewId {
|
|
5
|
+
let count = 0;
|
|
6
|
+
|
|
7
|
+
return () => {
|
|
8
|
+
count += 1;
|
|
9
|
+
|
|
10
|
+
return `0190a6f4-0000-7000-8000-${String(count).padStart(12, '0')}`;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { NoteRow, StoreChange } from '@boxxy/model';
|
|
2
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
3
|
+
|
|
4
|
+
export function buildMockNoteChange(note: NoteRow): StoreChange {
|
|
5
|
+
return {
|
|
6
|
+
at: note.at,
|
|
7
|
+
writes: [{ op: 'insert', table: 'notes', row: note }],
|
|
8
|
+
facts: [
|
|
9
|
+
{
|
|
10
|
+
id: buildMockFactId(),
|
|
11
|
+
type: 'boxxy.item.noted',
|
|
12
|
+
entity: `boxxy:item:${note.item}`,
|
|
13
|
+
actor: note.by,
|
|
14
|
+
payload: { seq: note.seq, text: note.text },
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { RunnerId, StoreChange } from '@boxxy/model';
|
|
3
|
+
|
|
4
|
+
// The fact catalog has no runner fact, so the pause writes the row and no fact.
|
|
5
|
+
export function buildMockPauseChange(runner: RunnerId): StoreChange {
|
|
6
|
+
return {
|
|
7
|
+
at: brandSchemas.instant.parse(1500),
|
|
8
|
+
writes: [
|
|
9
|
+
{
|
|
10
|
+
op: 'update',
|
|
11
|
+
table: 'runners',
|
|
12
|
+
key: { id: runner },
|
|
13
|
+
expect: { paused: false },
|
|
14
|
+
set: { paused: true },
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
facts: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { AttemptRow, PullRequestRow, StoreChange } from '@boxxy/model';
|
|
3
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
4
|
+
import { buildMockV1PullRequestRow } from '@boxxy/model/test-utils/build-mock-v1-pull-request-row';
|
|
5
|
+
|
|
6
|
+
interface PullRequestInput {
|
|
7
|
+
readonly attempt: AttemptRow;
|
|
8
|
+
readonly state: PullRequestRow['state'];
|
|
9
|
+
readonly number?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// The hook links a pull request of `acme/lamps` to the attempt, with its evidence row.
|
|
13
|
+
export function buildMockPullRequestChange(input: PullRequestInput): StoreChange {
|
|
14
|
+
const attempt = input.attempt;
|
|
15
|
+
const linkedAt = brandSchemas.instant.parse(4000);
|
|
16
|
+
const factId = buildMockFactId();
|
|
17
|
+
const number = input.number ?? 7;
|
|
18
|
+
|
|
19
|
+
const pullRequest = buildMockV1PullRequestRow({
|
|
20
|
+
repo: brandSchemas.repoName.parse('acme/lamps'),
|
|
21
|
+
number,
|
|
22
|
+
attempt: attempt.id,
|
|
23
|
+
githubId: 2_000_000 + number,
|
|
24
|
+
state: input.state,
|
|
25
|
+
checkedAt: linkedAt,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
at: linkedAt,
|
|
30
|
+
writes: [
|
|
31
|
+
{ op: 'insert', table: 'pull_requests', row: pullRequest },
|
|
32
|
+
{
|
|
33
|
+
op: 'insert',
|
|
34
|
+
table: 'evidence',
|
|
35
|
+
row: {
|
|
36
|
+
factId,
|
|
37
|
+
attempt: attempt.id,
|
|
38
|
+
kind: 'pull_request',
|
|
39
|
+
ref: pullRequest.url,
|
|
40
|
+
observed: null,
|
|
41
|
+
source: 'hook',
|
|
42
|
+
at: linkedAt,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
facts: [
|
|
47
|
+
{
|
|
48
|
+
id: factId,
|
|
49
|
+
type: 'boxxy.pull_request.linked',
|
|
50
|
+
entity: `boxxy:item:${attempt.item}`,
|
|
51
|
+
actor: `boxxy:hook:${attempt.id}`,
|
|
52
|
+
payload: {
|
|
53
|
+
attempt: attempt.id,
|
|
54
|
+
repo: pullRequest.repo,
|
|
55
|
+
number,
|
|
56
|
+
url: pullRequest.url,
|
|
57
|
+
pullRequestId: String(pullRequest.githubId),
|
|
58
|
+
source: 'hook',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { ItemId, StoreChange } from '@boxxy/model';
|
|
3
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
4
|
+
|
|
5
|
+
interface RelationInput {
|
|
6
|
+
readonly source: ItemId;
|
|
7
|
+
readonly target: ItemId;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildMockRelationChange(input: RelationInput): StoreChange {
|
|
11
|
+
const type = brandSchemas.relationTypeName.parse('blocks');
|
|
12
|
+
const addedAt = brandSchemas.instant.parse(3500);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
at: addedAt,
|
|
16
|
+
writes: [
|
|
17
|
+
{
|
|
18
|
+
op: 'insert',
|
|
19
|
+
table: 'relations',
|
|
20
|
+
row: {
|
|
21
|
+
source: input.source,
|
|
22
|
+
target: input.target,
|
|
23
|
+
type,
|
|
24
|
+
addedBy: 'boxxy:owner:local',
|
|
25
|
+
addedAt,
|
|
26
|
+
removedAt: null,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
facts: [
|
|
31
|
+
{
|
|
32
|
+
id: buildMockFactId(),
|
|
33
|
+
type: 'boxxy.relation.added',
|
|
34
|
+
entity: `boxxy:item:${input.source}`,
|
|
35
|
+
actor: 'boxxy:owner:local',
|
|
36
|
+
payload: { source: input.source, target: input.target, type },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SessionReading } from '@boxxy/model';
|
|
2
|
+
import { brandSchemas } from '@boxxy/model';
|
|
3
|
+
|
|
4
|
+
type RunningReading = Extract<SessionReading, { kind: 'running' }>;
|
|
5
|
+
|
|
6
|
+
// A working session with an agent session ID, no caller and no usage limit, active at time 0.
|
|
7
|
+
export function buildMockRunningReading(overrides: Partial<RunningReading> = {}): RunningReading {
|
|
8
|
+
return {
|
|
9
|
+
kind: 'running',
|
|
10
|
+
atcSession: brandSchemas.atcSessionId.parse('atc-1'),
|
|
11
|
+
agentSession: brandSchemas.agentSessionId.parse('agent-1'),
|
|
12
|
+
caller: null,
|
|
13
|
+
attention: 'working',
|
|
14
|
+
lastActivityAt: brandSchemas.instant.parse(0),
|
|
15
|
+
lastOwnerAt: brandSchemas.instant.parse(0),
|
|
16
|
+
usageLimitResetsAt: null,
|
|
17
|
+
...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { AttemptRow, StoreChange } from '@boxxy/model';
|
|
3
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
4
|
+
|
|
5
|
+
interface SettleInput {
|
|
6
|
+
readonly attempt: AttemptRow;
|
|
7
|
+
readonly state: 'succeeded' | 'failed';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// A settle under the live lease and hold generations: it releases the lease and the seats. A
|
|
11
|
+
// succeeded attempt ends `built`; a failed one ends `lost` with `closeout-missing`.
|
|
12
|
+
export function buildMockSettleChange(input: SettleInput): StoreChange {
|
|
13
|
+
const attempt = input.attempt;
|
|
14
|
+
const endedAt = brandSchemas.instant.parse(3000);
|
|
15
|
+
const isSucceeded = input.state === 'succeeded';
|
|
16
|
+
const failure = isSucceeded ? null : 'closeout-missing';
|
|
17
|
+
const outcome = isSucceeded ? brandSchemas.outcomeName.parse('built') : 'lost';
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
at: endedAt,
|
|
21
|
+
under: {
|
|
22
|
+
item: attempt.item,
|
|
23
|
+
phase: attempt.phase,
|
|
24
|
+
gen: attempt.leaseGen,
|
|
25
|
+
holdGen: attempt.holdGen,
|
|
26
|
+
},
|
|
27
|
+
writes: [
|
|
28
|
+
{
|
|
29
|
+
op: 'update',
|
|
30
|
+
table: 'attempts',
|
|
31
|
+
key: { id: attempt.id },
|
|
32
|
+
expect: { state: attempt.state },
|
|
33
|
+
set: { state: input.state, failure, outcome, endedAt },
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
op: 'update',
|
|
37
|
+
table: 'leases',
|
|
38
|
+
key: { item: attempt.item, phase: attempt.phase, gen: attempt.leaseGen },
|
|
39
|
+
expect: { releasedAt: null },
|
|
40
|
+
set: { releasedAt: endedAt },
|
|
41
|
+
},
|
|
42
|
+
{ op: 'release-seats', release: { attempt: attempt.id, holdGen: attempt.holdGen } },
|
|
43
|
+
],
|
|
44
|
+
facts: [
|
|
45
|
+
{
|
|
46
|
+
id: buildMockFactId(),
|
|
47
|
+
type: 'boxxy.attempt.settled',
|
|
48
|
+
entity: `boxxy:item:${attempt.item}`,
|
|
49
|
+
actor: `boxxy:runner:${attempt.runner}`,
|
|
50
|
+
payload: {
|
|
51
|
+
attempt: attempt.id,
|
|
52
|
+
phase: attempt.phase,
|
|
53
|
+
round: attempt.round,
|
|
54
|
+
state: input.state,
|
|
55
|
+
failure,
|
|
56
|
+
outcome,
|
|
57
|
+
returnedOutcome: null,
|
|
58
|
+
closing: null,
|
|
59
|
+
asleep: false,
|
|
60
|
+
sessionStopped: true,
|
|
61
|
+
endedAt,
|
|
62
|
+
criteria: [],
|
|
63
|
+
matched: [],
|
|
64
|
+
frictionDropped: 0,
|
|
65
|
+
skippedEffects: [],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { brandSchemas } from '@boxxy/model';
|
|
2
|
+
import type { AttemptRow, CallerIdentity, StoreChange } from '@boxxy/model';
|
|
3
|
+
import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
|
|
4
|
+
import invariant from 'tiny-invariant';
|
|
5
|
+
|
|
6
|
+
// The start of a `starting` attempt on its own atc session, with the caller the worker reported.
|
|
7
|
+
export function buildMockStartChange(
|
|
8
|
+
attempt: AttemptRow,
|
|
9
|
+
caller: CallerIdentity | null = null,
|
|
10
|
+
): StoreChange {
|
|
11
|
+
const atcSession = attempt.atcSession;
|
|
12
|
+
const startedAt = brandSchemas.instant.parse(2500);
|
|
13
|
+
|
|
14
|
+
invariant(atcSession !== null, 'a started attempt names its atc session');
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
at: startedAt,
|
|
18
|
+
under: { item: attempt.item, phase: attempt.phase, gen: attempt.leaseGen },
|
|
19
|
+
writes: [
|
|
20
|
+
{
|
|
21
|
+
op: 'update',
|
|
22
|
+
table: 'attempts',
|
|
23
|
+
key: { id: attempt.id },
|
|
24
|
+
expect: { state: 'starting' },
|
|
25
|
+
set: { state: 'running', atcSession, caller, startedAt },
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
facts: [
|
|
29
|
+
{
|
|
30
|
+
id: buildMockFactId(),
|
|
31
|
+
type: 'boxxy.attempt.started',
|
|
32
|
+
entity: `boxxy:item:${attempt.item}`,
|
|
33
|
+
actor: `boxxy:runner:${attempt.runner}`,
|
|
34
|
+
payload: { attempt: attempt.id, atcSession, resumeFallback: false, caller },
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { expect } from 'bun:test';
|
|
2
|
+
import { brandSchemas } from '@boxxy/model';
|
|
3
|
+
import { buildMockV1AttemptRow } from '@boxxy/model/test-utils/build-mock-v1-attempt-row';
|
|
4
|
+
import { buildMockV1ItemRow } from '@boxxy/model/test-utils/build-mock-v1-item-row';
|
|
5
|
+
import { buildMockClaimChange } from './build-mock-claim-change.ts';
|
|
6
|
+
import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
|
|
7
|
+
import { buildMockPauseChange } from './build-mock-pause-change.ts';
|
|
8
|
+
import { buildMockPullRequestChange } from './build-mock-pull-request-change.ts';
|
|
9
|
+
import { buildMockSettleChange } from './build-mock-settle-change.ts';
|
|
10
|
+
import { buildMockStartChange } from './build-mock-start-change.ts';
|
|
11
|
+
import type { ClaimStoreCase } from './types.ts';
|
|
12
|
+
|
|
13
|
+
export const claimStoreAttemptReaderCases: readonly ClaimStoreCase[] = [
|
|
14
|
+
[
|
|
15
|
+
'#readLastLeaseGen returns 0 for an item phase with no lease',
|
|
16
|
+
async (store) => {
|
|
17
|
+
const generation = await store.read((transaction) =>
|
|
18
|
+
transaction.readLastLeaseGen(
|
|
19
|
+
buildMockV1ItemRow().id,
|
|
20
|
+
brandSchemas.phaseName.parse('build'),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return { received: generation, expected: 0 };
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
'#readLastLeaseGen returns the highest gen after a released lease',
|
|
29
|
+
async (store) => {
|
|
30
|
+
const item = buildMockV1ItemRow();
|
|
31
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting', leaseGen: 3 });
|
|
32
|
+
|
|
33
|
+
await store.exclusive(async (transaction) => {
|
|
34
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
35
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
36
|
+
await transaction.applyChange(buildMockSettleChange({ attempt, state: 'failed' }));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const generation = await store.read((transaction) =>
|
|
40
|
+
transaction.readLastLeaseGen(item.id, attempt.phase),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return { received: generation, expected: 3 };
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
'#listLiveAttempts returns every attempt that has not settled, a sleeping one too',
|
|
48
|
+
async (store) => {
|
|
49
|
+
const live = buildMockV1AttemptRow({
|
|
50
|
+
state: 'starting',
|
|
51
|
+
claimedAt: brandSchemas.instant.parse(1000),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const sleeping = buildMockV1AttemptRow({
|
|
55
|
+
state: 'sleeping',
|
|
56
|
+
claimedAt: brandSchemas.instant.parse(1100),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const settled = buildMockV1AttemptRow({ state: 'starting' });
|
|
60
|
+
|
|
61
|
+
await store.exclusive(async (transaction) => {
|
|
62
|
+
for (const attempt of [live, sleeping, settled]) {
|
|
63
|
+
await transaction.applyChange(
|
|
64
|
+
buildMockFileItemChange(buildMockV1ItemRow({ id: attempt.item })),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await transaction.applyChange(buildMockSettleChange({ attempt: settled, state: 'failed' }));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const listed = await store.read((transaction) => transaction.listLiveAttempts());
|
|
74
|
+
|
|
75
|
+
return { received: listed.map((attempt) => attempt.id), expected: [live.id, sleeping.id] };
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
'#listItemAttempts returns the attempts of one item, oldest first',
|
|
80
|
+
async (store) => {
|
|
81
|
+
const item = buildMockV1ItemRow();
|
|
82
|
+
|
|
83
|
+
const second = buildMockV1AttemptRow({
|
|
84
|
+
item: item.id,
|
|
85
|
+
state: 'starting',
|
|
86
|
+
number: 2,
|
|
87
|
+
leaseGen: 2,
|
|
88
|
+
claimedAt: brandSchemas.instant.parse(4000),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const first = buildMockV1AttemptRow({
|
|
92
|
+
item: item.id,
|
|
93
|
+
state: 'starting',
|
|
94
|
+
claimedAt: brandSchemas.instant.parse(1000),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const other = buildMockV1AttemptRow({ state: 'starting' });
|
|
98
|
+
|
|
99
|
+
await store.exclusive(async (transaction) => {
|
|
100
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
101
|
+
|
|
102
|
+
await transaction.applyChange(
|
|
103
|
+
buildMockFileItemChange(buildMockV1ItemRow({ id: other.item })),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await transaction.applyChange(buildMockClaimChange(first));
|
|
107
|
+
await transaction.applyChange(buildMockSettleChange({ attempt: first, state: 'failed' }));
|
|
108
|
+
await transaction.applyChange(buildMockClaimChange(second));
|
|
109
|
+
await transaction.applyChange(buildMockClaimChange(other));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const listed = await store.read((transaction) => transaction.listItemAttempts(item.id));
|
|
113
|
+
|
|
114
|
+
return { received: listed.map((attempt) => attempt.id), expected: [first.id, second.id] };
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
'#listSucceededAttemptsWithoutPullRequest skips an attempt with a linked pull request',
|
|
119
|
+
async (store) => {
|
|
120
|
+
const first = buildMockV1ItemRow();
|
|
121
|
+
const second = buildMockV1ItemRow();
|
|
122
|
+
const linked = buildMockV1AttemptRow({ item: first.id, state: 'starting' });
|
|
123
|
+
const unlinked = buildMockV1AttemptRow({ item: second.id, state: 'starting' });
|
|
124
|
+
|
|
125
|
+
await store.exclusive(async (transaction) => {
|
|
126
|
+
await transaction.applyChange(buildMockFileItemChange(first));
|
|
127
|
+
await transaction.applyChange(buildMockFileItemChange(second));
|
|
128
|
+
await transaction.applyChange(buildMockClaimChange(linked));
|
|
129
|
+
await transaction.applyChange(buildMockClaimChange(unlinked));
|
|
130
|
+
|
|
131
|
+
await transaction.applyChange(
|
|
132
|
+
buildMockSettleChange({ attempt: linked, state: 'succeeded' }),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
await transaction.applyChange(
|
|
136
|
+
buildMockSettleChange({ attempt: unlinked, state: 'succeeded' }),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await transaction.applyChange(
|
|
140
|
+
buildMockPullRequestChange({ attempt: linked, state: 'open' }),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const listed = await store.read((transaction) =>
|
|
145
|
+
transaction.listSucceededAttemptsWithoutPullRequest(),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return { received: listed.map((attempt) => attempt.id), expected: [unlinked.id] };
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
'#findAttemptBySession returns the attempt with an atc session id',
|
|
153
|
+
async (store) => {
|
|
154
|
+
const item = buildMockV1ItemRow();
|
|
155
|
+
|
|
156
|
+
const attempt = buildMockV1AttemptRow({
|
|
157
|
+
item: item.id,
|
|
158
|
+
state: 'starting',
|
|
159
|
+
atcSession: brandSchemas.atcSessionId.parse('atc-session-1'),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await store.exclusive(async (transaction) => {
|
|
163
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
164
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
165
|
+
await transaction.applyChange(buildMockStartChange(attempt));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const found = await store.read((transaction) =>
|
|
169
|
+
transaction.findAttemptBySession(brandSchemas.atcSessionId.parse('atc-session-1')),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return { received: found?.id, expected: attempt.id };
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
'#findAttemptBySession returns the latest of two attempts on one kept session',
|
|
177
|
+
async (store) => {
|
|
178
|
+
const item = buildMockV1ItemRow();
|
|
179
|
+
const atcSession = brandSchemas.atcSessionId.parse('atc-session-1');
|
|
180
|
+
|
|
181
|
+
const earlier = buildMockV1AttemptRow({
|
|
182
|
+
item: item.id,
|
|
183
|
+
state: 'starting',
|
|
184
|
+
atcSession,
|
|
185
|
+
claimedAt: brandSchemas.instant.parse(1000),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const later = buildMockV1AttemptRow({
|
|
189
|
+
item: item.id,
|
|
190
|
+
state: 'starting',
|
|
191
|
+
number: 2,
|
|
192
|
+
leaseGen: 2,
|
|
193
|
+
atcSession,
|
|
194
|
+
claimedAt: brandSchemas.instant.parse(4000),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await store.exclusive(async (transaction) => {
|
|
198
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
199
|
+
await transaction.applyChange(buildMockClaimChange(earlier));
|
|
200
|
+
await transaction.applyChange(buildMockSettleChange({ attempt: earlier, state: 'failed' }));
|
|
201
|
+
await transaction.applyChange(buildMockClaimChange(later));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const found = await store.read((transaction) => transaction.findAttemptBySession(atcSession));
|
|
205
|
+
|
|
206
|
+
return { received: found?.id, expected: later.id };
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
[
|
|
210
|
+
'#markPullRequestChecked sets the checked time of a watched pull request',
|
|
211
|
+
async (store) => {
|
|
212
|
+
const item = buildMockV1ItemRow();
|
|
213
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
214
|
+
|
|
215
|
+
await store.exclusive(async (transaction) => {
|
|
216
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
217
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
218
|
+
await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'open' }));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await store.exclusive((transaction) =>
|
|
222
|
+
transaction.markPullRequestChecked(
|
|
223
|
+
{ repo: brandSchemas.repoName.parse('acme/lamps'), number: 7 },
|
|
224
|
+
brandSchemas.instant.parse(9000),
|
|
225
|
+
),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const watched = await store.read((transaction) => transaction.listWatchedPullRequests());
|
|
229
|
+
|
|
230
|
+
return { received: watched[0]?.checkedAt, expected: brandSchemas.instant.parse(9000) };
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
'#readPullRequestRow returns a merged pull request, and null for one never linked',
|
|
235
|
+
async (store) => {
|
|
236
|
+
const item = buildMockV1ItemRow();
|
|
237
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
238
|
+
const repo = brandSchemas.repoName.parse('acme/lamps');
|
|
239
|
+
|
|
240
|
+
await store.exclusive(async (transaction) => {
|
|
241
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
242
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
243
|
+
await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'merged' }));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const found = await store.read((transaction) =>
|
|
247
|
+
transaction.readPullRequestRow({ repo, number: 7 }),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const missing = await store.read((transaction) =>
|
|
251
|
+
transaction.readPullRequestRow({ repo, number: 8 }),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return { received: [found?.state, missing], expected: ['merged', null] };
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
[
|
|
258
|
+
'#listWatchedPullRequests returns an open pull request with its GitHub id and url',
|
|
259
|
+
async (store) => {
|
|
260
|
+
const item = buildMockV1ItemRow();
|
|
261
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
262
|
+
|
|
263
|
+
await store.exclusive(async (transaction) => {
|
|
264
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
265
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
266
|
+
await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'merged' }));
|
|
267
|
+
|
|
268
|
+
await transaction.applyChange(
|
|
269
|
+
buildMockPullRequestChange({ attempt, state: 'open', number: 8 }),
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const watched = await store.read((transaction) => transaction.listWatchedPullRequests());
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
received: watched.map((pullRequest) => [
|
|
277
|
+
pullRequest.number,
|
|
278
|
+
pullRequest.githubId,
|
|
279
|
+
pullRequest.url,
|
|
280
|
+
]),
|
|
281
|
+
expected: [[8, 2_000_008, 'https://github.com/acme/lamps/pull/8']],
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
[
|
|
286
|
+
'#listEvidence returns the evidence of one attempt',
|
|
287
|
+
async (store) => {
|
|
288
|
+
const item = buildMockV1ItemRow();
|
|
289
|
+
const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
|
|
290
|
+
|
|
291
|
+
await store.exclusive(async (transaction) => {
|
|
292
|
+
await transaction.applyChange(buildMockFileItemChange(item));
|
|
293
|
+
await transaction.applyChange(buildMockClaimChange(attempt));
|
|
294
|
+
await transaction.applyChange(buildMockPullRequestChange({ attempt, state: 'open' }));
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const evidence: unknown = await store.read((transaction) =>
|
|
298
|
+
transaction.listEvidence(attempt.id),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
received: evidence,
|
|
303
|
+
expected: [
|
|
304
|
+
{
|
|
305
|
+
factId: expect.stringMatching(/^[0-9a-f]{8}-[0-9a-f]{4}-7/u),
|
|
306
|
+
attempt: attempt.id,
|
|
307
|
+
kind: 'pull_request',
|
|
308
|
+
ref: 'https://github.com/acme/lamps/pull/7',
|
|
309
|
+
observed: null,
|
|
310
|
+
source: 'hook',
|
|
311
|
+
at: 4000,
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
[
|
|
318
|
+
'#writeHeartbeat inserts the runner row on its first beat, not paused',
|
|
319
|
+
async (store) => {
|
|
320
|
+
const runner = brandSchemas.runnerId.parse('runner-1');
|
|
321
|
+
|
|
322
|
+
await store.exclusive((transaction) =>
|
|
323
|
+
transaction.writeHeartbeat(runner, brandSchemas.instant.parse(1000)),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const read: unknown = await store.read((transaction) => transaction.readRunner(runner));
|
|
327
|
+
|
|
328
|
+
return { received: read, expected: { id: 'runner-1', heartbeatAt: 1000, paused: false } };
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
[
|
|
332
|
+
'#writeHeartbeat moves the heartbeat and keeps the paused flag',
|
|
333
|
+
async (store) => {
|
|
334
|
+
const runner = brandSchemas.runnerId.parse('runner-1');
|
|
335
|
+
|
|
336
|
+
await store.exclusive(async (transaction) => {
|
|
337
|
+
await transaction.writeHeartbeat(runner, brandSchemas.instant.parse(1000));
|
|
338
|
+
await transaction.applyChange(buildMockPauseChange(runner));
|
|
339
|
+
await transaction.writeHeartbeat(runner, brandSchemas.instant.parse(2000));
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const read: unknown = await store.read((transaction) => transaction.readRunner(runner));
|
|
343
|
+
|
|
344
|
+
return { received: read, expected: { id: 'runner-1', heartbeatAt: 2000, paused: true } };
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
'#readRunner returns null before the first heartbeat',
|
|
349
|
+
async (store) => {
|
|
350
|
+
const read = await store.read((transaction) =>
|
|
351
|
+
transaction.readRunner(brandSchemas.runnerId.parse('runner-1')),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
return { received: read, expected: null };
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
];
|