Skip to content

Commit 9e20339

Browse files
committed
test(mothership): cover a job dispatch retried after a usage outage and ended at its last attempt
1 parent 82a9f61 commit 9e20339

1 file changed

Lines changed: 37 additions & 8 deletions

File tree

‎apps/sim/lib/mothership/jobs/lane.integration.ts‎

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,13 @@ async function runsOf(scheduleId: string) {
272272
.where(eq(jobExecutionLogs.scheduleId, scheduleId))
273273
}
274274

275+
/** The usage gate's answer when the payer's usage can't be read. */
276+
const usageOutage = {
277+
isExceeded: true,
278+
message: 'Usage could not be checked.',
279+
reason: 'usage_unavailable' as const,
280+
}
281+
275282
/** Makes a schedule due again now, as its next occurrence arriving would. */
276283
async function dueAgain(scheduleId: string) {
277284
const dueAt = new Date(Date.now() - 1_000)
@@ -647,26 +654,48 @@ describe.runIf(Boolean(redisUrl))('the scheduled job lane', () => {
647654
await reconcileUntilSettled(occurrenceId)
648655
})
649656

650-
it('retries an occurrence whose usage could not be read, and skips one whose billing is blocked', async () => {
657+
it('retries an occurrence whose usage could not be read, and runs it once usage reads again', async () => {
651658
setEnvFlags({ isHosted: true })
652659
const { scheduleId, occurrenceId } = await seedJob()
653-
vi.mocked(checkAttributedUsageLimits).mockResolvedValueOnce({
654-
isExceeded: true,
655-
message: 'Usage could not be checked.',
656-
reason: 'usage_unavailable',
657-
})
660+
vi.mocked(checkAttributedUsageLimits).mockResolvedValueOnce(usageOutage)
658661
await runJobLane(new Date())
659662
expect(await runsOf(scheduleId)).toEqual([
660663
expect.objectContaining({ status: 'queued', outcome: null, dispatchFailures: 1 }),
661664
])
662665

666+
vi.mocked(checkAttributedUsageLimits).mockResolvedValueOnce({ isExceeded: false })
667+
await reconcileJobRuns(new Date(Date.now() + JOB_DISPATCH_GRACE_MS + 1_000))
668+
await reconcileUntilSettled(occurrenceId)
669+
expect(legsFor(occurrenceId)).toHaveLength(1)
670+
expect(await runsOf(scheduleId)).toEqual([
671+
expect.objectContaining({ status: 'complete', outcome: 'not_met', dispatchFailures: 1 }),
672+
])
673+
})
674+
675+
it('ends an occurrence whose usage stays unreadable at its last attempt, and skips one whose billing is blocked', async () => {
676+
setEnvFlags({ isHosted: true })
677+
const outage = await seedJob()
678+
await claimDueJobOccurrences(new Date(), 10)
679+
for (let attempt = 1; attempt <= 5; attempt++) {
680+
vi.mocked(checkAttributedUsageLimits).mockResolvedValueOnce(usageOutage)
681+
expect(await dispatchJobOccurrence(outage.occurrenceId)).toBe(
682+
attempt < 5 ? 'retry' : 'failed'
683+
)
684+
}
685+
expect(await runsOf(outage.scheduleId)).toEqual([
686+
expect.objectContaining({ outcome: 'failed', summary: 'The run could not be started.' }),
687+
])
688+
expect(legsFor(outage.occurrenceId)).toHaveLength(0)
689+
690+
const blocked = await seedJob()
691+
await claimDueJobOccurrences(new Date(), 10)
663692
vi.mocked(checkAttributedUsageLimits).mockResolvedValueOnce({
664693
isExceeded: true,
665694
message: 'Billing is blocked for this account.',
666695
reason: 'billing_blocked',
667696
})
668-
expect(await dispatchJobOccurrence(occurrenceId)).toBe('skipped_budget')
669-
expect(await runsOf(scheduleId)).toEqual([
697+
expect(await dispatchJobOccurrence(blocked.occurrenceId)).toBe('skipped_budget')
698+
expect(await runsOf(blocked.scheduleId)).toEqual([
670699
expect.objectContaining({
671700
outcome: 'skipped_budget',
672701
summary: 'Billing is blocked for this account.',

0 commit comments

Comments
 (0)