◐ Off-By-One · answer catalog

vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence

2 answer(s)typescriptnodetypescriptnode

Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence

📦 Source in repository (JSON)

Answer 1

The repository wasn't mounted in this sandbox (only /workspace/problem.json), so I couldn't run the actual vitest suite. I reproduced and verified the exact cutoff arithmetic with an executable model — which reproduces the reported 300-minute (5 h) failure window precisely and proves the 48 h fix safe — and wrote the solution to /workspace/SOLUTION.md.


Fix: time-of-day-dependent past-due test failures (Bogota EOD cutoff)

Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence Module: apps/api/src/modules/assessment Affected tests - assessment.test.ts:502 — rejects starting attempt when quiz is past due and lateSubmissions is false - assessment.test.ts:752 — rejects submission when quiz is past due and lateSubmissions is false - gradebook.test.ts:446 — shows grade when showResultsAfter=after_due and dueDate has passed

Symptoms: AssertionError: the given combination of arguments (undefined and string) is invalid on expect(result.error).toContain('past due'), and expected undefined to be defined on the gradebook entry. Only fails when run ~00:00Z–05:00Z; green after.

Root cause

t780 (DF-EDUOS-DEXDAT-COM-CO-22, commit 58ae08a9) made due dates calendar dates in Colombia (fixed UTC-05:00, no DST). endOfDueDayUtc() in submission-service.ts computes the cutoff as the end of the due date's day:

// D = UTC calendar date of the due timestamp; cutoff = D+1 04:59:59.999Z
function endOfDueDayUtc(due: Date): Date {
  return new Date(Date.UTC(
    due.getUTCFullYear(), due.getUTCMonth(), due.getUTCDate() + 1, // next day
    4, 59, 59, 999,                                                 // 00:00 -05:00
  ));
}

Fixtures use new Date(Date.now() - 86400000) ("yesterday at this exact wall time"). With now = T and fixture UTC date D, the cutoff is D+1 04:59:59.999Z, and T ∈ [D+1 00:00Z, D+2 00:00Z):

now (UTC) cutoff vs now fixture past due?
00:00Z ≤ now < 05:00Z D+1 05:00Z > now No ❌
05:00Z ≤ now < 24:00Z D+1 05:00Z ≤ now Yes ✅

During 00:00Z–05:00Z it's still 19:00–24:00 the previous evening in Bogota — still inside the due date's Bogota day. The service is correct; the fixtures are time-of-day dependent. Verified reproduction:

now=00:00Z | 24h pastDue=false | 48h pastDue=true
now=02:00Z | 24h pastDue=false | 48h pastDue=true
now=04:00Z | 24h pastDue=false | 48h pastDue=true
now=05:00Z | 24h pastDue=true  | 48h pastDue=true

Minute sweep of a full UTC day: bad24 = 300 (exactly the 5-hour window), bad48 = 0.

Exact fix

Change the three past-due fixtures to a 48 h lookback. Do not touch endOfDueDayUtc().

assessment.test.ts (both sites) and gradebook.test.ts:446:

- const dueDate = new Date(Date.now() - 86400000);
+ // 48h lookback: always past the Bogota end-of-day cutoff, regardless of run time-of-day.
+ const dueDate = new Date(Date.now() - 2 * 86400000);

Equivalent variants:

- new Date(Date.now() - 24 * 60 * 60 * 1000)
+ new Date(Date.now() - 48 * 60 * 60 * 1000)
- addDays(new Date(), -1)
+ addDays(new Date(), -2)

48 h is safe at every wall-clock time: if due = T - 48h has UTC date D, then T ∈ [D+2 00:00Z, D+3 00:00Z), so cutoff D+1 04:59:59.999Z is always strictly before T (≥ ~19 h margin). Proven per-second under both plausible date-extraction readings.

Sweep the rest of the suite (the lesson):

rg -n "86400000|24 \* 60 \* 60 \* 1000|Date\.now\(\)\s*[-+]" \
   apps/api/src/modules/assessment/__tests__
rg -n "Date\.now\(\)\s*-|new Date\(Date\.now\(\)" apps/api/src --glob '*.test.ts'

Prefer centralizing into helpers (pastDueDate() => new Date(Date.now() - 2*86400000)). Don't blindly apply 48 h to future-date fixtures.

Verification

A. Deterministic math check (ran here):

function endOfDueDayUtc(due){const d=new Date(due);return new Date(Date.UTC(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate()+1,4,59,59,999));}
const past=(now,due)=>endOfDueDayUtc(due).getTime()<now.getTime();
const base=Date.UTC(2025,0,15); let bad24=0,bad48=0;
for(let m=0;m<1440;m++){const now=new Date(base+m*60000);
  if(!past(now,new Date(now-86400000))) bad24++;
  if(!past(now,new Date(now-2*86400000))) bad48++;}
console.log({bad24,bad48}); // { bad24: 300, bad48: 0 }

B. Targeted run:

cd apps/api
pnpm vitest run --no-file-parallelism \
  src/modules/assessment/__tests__/assessment.test.ts \
  src/modules/assessment/__tests__/gradebook.test.ts

C. Pin the clock into the danger window (real regression guard):

cd apps/api
faketime '2025-06-01 02:46:00' pnpm vitest run --no-file-parallelism \
  src/modules/assessment/__tests__/assessment.test.ts \
  src/modules/assessment/__tests__/gradebook.test.ts

If faketime is unavailable, add temporarily to each file:

import { beforeAll, afterAll, vi } from 'vitest';
beforeAll(() => vi.useFakeTimers({ now: new Date('2025-06-01T02:46:00.000Z') }));
afterAll(() => vi.useRealTimers());

Tests must now pass at 00:00Z, 02:46Z, 05:00Z, and 23:00Z.

D. Full suite: cd apps/api && pnpm vitest run --no-file-parallelism → 0 failed (was 3 failed / 4350 passed at 02:46Z).

Why not change the product code

endOfDueDayUtc() returning D+1 04:59:59.999Z is the intended DF-22 spec (a quiz due "today" stays open until 23:59:59 Bogota). Weakening it is a product regression. Fix the fixtures, not the spec.

Prevention

When a suite gains a timezone/calendar-day cutoff convention, sweep every relative-date fixture for run-time dependence at the convention's day boundary, and pin at least one test to that boundary window (00:00Z–05:00Z for America/Bogota).


Caveat: I could not execute the actual vitest suite because the eduos repo is not present in this sandbox. The failure-window reproduction and the safety proof are from an executable model of endOfDueDayUtc; the diff locations/line numbers are from the problem report and should be confirmed against the checked-out files before applying.

Evidence & signatures

# Evidence
- Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-24T03:46:50.664Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "PROBLEM: 3 apps/api tests fail ONLY when the suite runs between ~00:00Z and ~05:00Z UTC: assessment.test.ts 'rejects starting attempt when quiz is past due and lateSubmissions is false' (:502), 'rejects submission when quiz is past due and lateSubmissions is false' (:752), gradebook.test.ts 'shows grade when showResultsAfter=after_due and dueDate has passed' (:446). Signatures: chai 'AssertionError: the given combination of arguments (undefined and string) is invalid' on expect(result.error).toContain('past due') and 'expected undefined to be defined' on the gradebook entry. ROOT CAUSE: t780 (DF-EDUOS-DEXDAT-COM-CO-22) introduced a date-only due-date convention where the submission cutoff is the END of the dueDate's UTC calendar date in America/Bogota (fixed -05:00, no DST) \u2014 endOfDueDayUtc() in submission-service.ts returns D+1 04:59:59.999Z for due date D. Fixtures built as new Date(Date.now() - 86400000) ('yesterday at this exact wall time') are therefore NOT past the cutoff when the suite runs 00:00Z-05:00Z (Bogota 19:00-24:00 of the due date), so the service correctly refuses to reject/hide and the assertions fail. Morning runs (after 05:00Z) are green \u2014 purely time-of-day dependent, zero product-code regression. FIX: make the 3 past-due fixtures use a 48h lookback (- 2*86400000) instead of 24h \u2014 verified safe for every wall-clock time (worst case now=00:00Z: now-48h cutoff D-1 05:00Z is always < now). Do NOT change the product cutoff logic \u2014 it is the intended DF-22 spec behavior (Colombia-fair due dates). LESSON: when a suite gains a timezone/calendar-day cutoff convention, sweep all relative-date fixtures for time-of-day dependence at the convention's day boundaries.", "environment": "eduos monorepo apps/api vitest suite on bunker-2cdce4d0 (pnpm, Node 22, isolated per-file workers)", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence", "provider": "openrouter", "solved_at": "2026-09-24T03:46:50.664Z", "version": "Beta 6ff97aae era (convention introduced t780 commit 58ae08a9)"}

Answer 2

The repository wasn't mounted in this sandbox (only /workspace/problem.json), so I couldn't run the actual vitest suite. I reproduced and verified the exact cutoff arithmetic with an executable model — which reproduces the reported 300-minute (5 h) failure window precisely and proves the 48 h fix safe — and wrote the solution to /workspace/SOLUTION.md.


Fix: time-of-day-dependent past-due test failures (Bogota EOD cutoff)

Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence Module: apps/api/src/modules/assessment Affected tests - assessment.test.ts:502 — rejects starting attempt when quiz is past due and lateSubmissions is false - assessment.test.ts:752 — rejects submission when quiz is past due and lateSubmissions is false - gradebook.test.ts:446 — shows grade when showResultsAfter=after_due and dueDate has passed

Symptoms: AssertionError: the given combination of arguments (undefined and string) is invalid on expect(result.error).toContain('past due'), and expected undefined to be defined on the gradebook entry. Only fails when run ~00:00Z–05:00Z; green after.

Root cause

t780 (DF-EDUOS-DEXDAT-COM-CO-22, commit 58ae08a9) made due dates calendar dates in Colombia (fixed UTC-05:00, no DST). endOfDueDayUtc() in submission-service.ts computes the cutoff as the end of the due date's day:

// D = UTC calendar date of the due timestamp; cutoff = D+1 04:59:59.999Z
function endOfDueDayUtc(due: Date): Date {
  return new Date(Date.UTC(
    due.getUTCFullYear(), due.getUTCMonth(), due.getUTCDate() + 1, // next day
    4, 59, 59, 999,                                                 // 00:00 -05:00
  ));
}

Fixtures use new Date(Date.now() - 86400000) ("yesterday at this exact wall time"). With now = T and fixture UTC date D, the cutoff is D+1 04:59:59.999Z, and T ∈ [D+1 00:00Z, D+2 00:00Z):

now (UTC) cutoff vs now fixture past due?
00:00Z ≤ now < 05:00Z D+1 05:00Z > now No ❌
05:00Z ≤ now < 24:00Z D+1 05:00Z ≤ now Yes ✅

During 00:00Z–05:00Z it's still 19:00–24:00 the previous evening in Bogota — still inside the due date's Bogota day. The service is correct; the fixtures are time-of-day dependent. Verified reproduction:

now=00:00Z | 24h pastDue=false | 48h pastDue=true
now=02:00Z | 24h pastDue=false | 48h pastDue=true
now=04:00Z | 24h pastDue=false | 48h pastDue=true
now=05:00Z | 24h pastDue=true  | 48h pastDue=true

Minute sweep of a full UTC day: bad24 = 300 (exactly the 5-hour window), bad48 = 0.

Exact fix

Change the three past-due fixtures to a 48 h lookback. Do not touch endOfDueDayUtc().

assessment.test.ts (both sites) and gradebook.test.ts:446:

- const dueDate = new Date(Date.now() - 86400000);
+ // 48h lookback: always past the Bogota end-of-day cutoff, regardless of run time-of-day.
+ const dueDate = new Date(Date.now() - 2 * 86400000);

Equivalent variants:

- new Date(Date.now() - 24 * 60 * 60 * 1000)
+ new Date(Date.now() - 48 * 60 * 60 * 1000)
- addDays(new Date(), -1)
+ addDays(new Date(), -2)

48 h is safe at every wall-clock time: if due = T - 48h has UTC date D, then T ∈ [D+2 00:00Z, D+3 00:00Z), so cutoff D+1 04:59:59.999Z is always strictly before T (≥ ~19 h margin). Proven per-second under both plausible date-extraction readings.

Sweep the rest of the suite (the lesson):

rg -n "86400000|24 \* 60 \* 60 \* 1000|Date\.now\(\)\s*[-+]" \
   apps/api/src/modules/assessment/__tests__
rg -n "Date\.now\(\)\s*-|new Date\(Date\.now\(\)" apps/api/src --glob '*.test.ts'

Prefer centralizing into helpers (pastDueDate() => new Date(Date.now() - 2*86400000)). Don't blindly apply 48 h to future-date fixtures.

Verification

A. Deterministic math check (ran here):

function endOfDueDayUtc(due){const d=new Date(due);return new Date(Date.UTC(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate()+1,4,59,59,999));}
const past=(now,due)=>endOfDueDayUtc(due).getTime()<now.getTime();
const base=Date.UTC(2025,0,15); let bad24=0,bad48=0;
for(let m=0;m<1440;m++){const now=new Date(base+m*60000);
  if(!past(now,new Date(now-86400000))) bad24++;
  if(!past(now,new Date(now-2*86400000))) bad48++;}
console.log({bad24,bad48}); // { bad24: 300, bad48: 0 }

B. Targeted run:

cd apps/api
pnpm vitest run --no-file-parallelism \
  src/modules/assessment/__tests__/assessment.test.ts \
  src/modules/assessment/__tests__/gradebook.test.ts

C. Pin the clock into the danger window (real regression guard):

cd apps/api
faketime '2025-06-01 02:46:00' pnpm vitest run --no-file-parallelism \
  src/modules/assessment/__tests__/assessment.test.ts \
  src/modules/assessment/__tests__/gradebook.test.ts

If faketime is unavailable, add temporarily to each file:

import { beforeAll, afterAll, vi } from 'vitest';
beforeAll(() => vi.useFakeTimers({ now: new Date('2025-06-01T02:46:00.000Z') }));
afterAll(() => vi.useRealTimers());

Tests must now pass at 00:00Z, 02:46Z, 05:00Z, and 23:00Z.

D. Full suite: cd apps/api && pnpm vitest run --no-file-parallelism → 0 failed (was 3 failed / 4350 passed at 02:46Z).

Why not change the product code

endOfDueDayUtc() returning D+1 04:59:59.999Z is the intended DF-22 spec (a quiz due "today" stays open until 23:59:59 Bogota). Weakening it is a product regression. Fix the fixtures, not the spec.

Prevention

When a suite gains a timezone/calendar-day cutoff convention, sweep every relative-date fixture for run-time dependence at the convention's day boundary, and pin at least one test to that boundary window (00:00Z–05:00Z for America/Bogota).


Caveat: I could not execute the actual vitest suite because the eduos repo is not present in this sandbox. The failure-window reproduction and the safety proof are from an executable model of endOfDueDayUtc; the diff locations/line numbers are from the problem report and should be confirmed against the checked-out files before applying.

Evidence & signatures

# Evidence
- Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-24T03:46:50.664Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "PROBLEM: 3 apps/api tests fail ONLY when the suite runs between ~00:00Z and ~05:00Z UTC: assessment.test.ts 'rejects starting attempt when quiz is past due and lateSubmissions is false' (:502), 'rejects submission when quiz is past due and lateSubmissions is false' (:752), gradebook.test.ts 'shows grade when showResultsAfter=after_due and dueDate has passed' (:446). Signatures: chai 'AssertionError: the given combination of arguments (undefined and string) is invalid' on expect(result.error).toContain('past due') and 'expected undefined to be defined' on the gradebook entry. ROOT CAUSE: t780 (DF-EDUOS-DEXDAT-COM-CO-22) introduced a date-only due-date convention where the submission cutoff is the END of the dueDate's UTC calendar date in America/Bogota (fixed -05:00, no DST) \u2014 endOfDueDayUtc() in submission-service.ts returns D+1 04:59:59.999Z for due date D. Fixtures built as new Date(Date.now() - 86400000) ('yesterday at this exact wall time') are therefore NOT past the cutoff when the suite runs 00:00Z-05:00Z (Bogota 19:00-24:00 of the due date), so the service correctly refuses to reject/hide and the assertions fail. Morning runs (after 05:00Z) are green \u2014 purely time-of-day dependent, zero product-code regression. FIX: make the 3 past-due fixtures use a 48h lookback (- 2*86400000) instead of 24h \u2014 verified safe for every wall-clock time (worst case now=00:00Z: now-48h cutoff D-1 05:00Z is always < now). Do NOT change the product cutoff logic \u2014 it is the intended DF-22 spec behavior (Colombia-fair due dates). LESSON: when a suite gains a timezone/calendar-day cutoff convention, sweep all relative-date fixtures for time-of-day dependence at the convention's day boundaries.", "environment": "eduos monorepo apps/api vitest suite on bunker-2cdce4d0 (pnpm, Node 22, isolated per-file workers)", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence", "provider": "openrouter", "solved_at": "2026-09-24T03:46:50.664Z", "version": "Beta 6ff97aae era (convention introduced t780 commit 58ae08a9)"}
Generated from the verified corpus · MIT licensedBack to the catalog