|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +/** |
| 4 | + * [#20671] What the `time` write door admits, this driver stores as the wall |
| 5 | + * clock it names, and reads back identically. |
| 6 | + * |
| 7 | + * The engine's record validator now refuses a time of day with a `Z` or an |
| 8 | + * offset (`"10:00Z"`, `"10:00+08:00"`) and an instant the `time` rule does not |
| 9 | + * read (`"+010000-01-01T10:00:00Z"`), with `VALIDATION_FAILED` / `invalid_time` |
| 10 | + * before any driver write (`packages/objectql/src/engine-time-write-zone-less.test.ts`). |
| 11 | + * Measured on the base through REST over this driver, the process in |
| 12 | + * America/New_York, both were stored verbatim here: `"10:00Z"` read back |
| 13 | + * `"10:00Z"`, while PostgreSQL read the same write back as `"10:00:00"`. The |
| 14 | + * refusal is therefore not this driver's. What it owes is the other half: a |
| 15 | + * plain `"10:00"` / `"10:00:00"`, and an ISO instant with a four-digit UTC |
| 16 | + * year, are stored as `HH:MM:SS` and found by it, with the process in a zone |
| 17 | + * that is not UTC so a host-zone reading would show. SQLite and PostgreSQL |
| 18 | + * read the same values back the same way in |
| 19 | + * `packages/rest/src/data-temporal-write-real-day-iso.test.ts`. |
| 20 | + */ |
| 21 | + |
| 22 | +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; |
| 23 | +import { InMemoryDriver } from './memory-driver.js'; |
| 24 | + |
| 25 | +const OBJECT = 'schedule_time_20671'; |
| 26 | +const FIELDS = { slot: { type: 'time' } }; |
| 27 | +const HOST_ZONE = 'America/New_York'; |
| 28 | + |
| 29 | +/** Each spelling the `time` write door admits → the wall clock it is stored and read back as. */ |
| 30 | +const ADMITTED: ReadonlyArray<readonly [string, unknown, string]> = [ |
| 31 | + ['hh-mm', '10:00', '10:00:00'], |
| 32 | + ['hh-mm-ss', '10:00:00', '10:00:00'], |
| 33 | + ['fraction', '10:00:00.250', '10:00:00.250'], |
| 34 | + ['utc-instant', '2026-07-15T10:00:00Z', '10:00:00'], |
| 35 | + ['offset-instant', '2026-07-15T18:00:00+08:00', '10:00:00'], |
| 36 | + ['naive-instant', '2026-07-15 10:00', '10:00:00'], |
| 37 | + ['a-date', new Date(Date.UTC(2026, 6, 15, 10)), '10:00:00'], |
| 38 | +]; |
| 39 | + |
| 40 | +const originalTz = process.env.TZ; |
| 41 | + |
| 42 | +describe('[#20671] every spelling the time write door admits is stored as its wall clock and read back identically', () => { |
| 43 | + let driver: InMemoryDriver; |
| 44 | + |
| 45 | + beforeAll(async () => { |
| 46 | + process.env.TZ = HOST_ZONE; |
| 47 | + expect(Intl.DateTimeFormat().resolvedOptions().timeZone, 'the host zone really changed').toBe(HOST_ZONE); |
| 48 | + driver = new InMemoryDriver({}); |
| 49 | + await driver.connect(); |
| 50 | + await driver.syncSchema(OBJECT, { name: OBJECT, fields: FIELDS }); |
| 51 | + for (const [id, slot] of ADMITTED) await driver.create(OBJECT, { id, slot }); |
| 52 | + await driver.create(OBJECT, { id: 'before', slot: '09:59:59' }); |
| 53 | + }); |
| 54 | + |
| 55 | + afterAll(() => { |
| 56 | + if (originalTz === undefined) delete process.env.TZ; |
| 57 | + else process.env.TZ = originalTz; |
| 58 | + }); |
| 59 | + |
| 60 | + it('reads each one back as its wall clock', async () => { |
| 61 | + for (const [id, , stored] of ADMITTED) { |
| 62 | + expect((await driver.findOne(OBJECT, { where: { id } }))?.slot, id).toBe(stored); |
| 63 | + } |
| 64 | + }); |
| 65 | + |
| 66 | + it('finds each one by the wall clock it names, and orders it after the second before', async () => { |
| 67 | + const ids = async (where: Record<string, unknown>) => |
| 68 | + (await driver.find(OBJECT, { where })).map((r) => r.id as string).sort(); |
| 69 | + const ten = ADMITTED.filter(([, , stored]) => stored === '10:00:00').map(([id]) => id).sort(); |
| 70 | + expect(await ids({ slot: { $eq: '10:00:00' } })).toEqual(ten); |
| 71 | + expect(await ids({ slot: { $eq: '10:00' } }), 'the HH:MM spelling').toEqual(ten); |
| 72 | + expect(await ids({ slot: { $gt: '09:59:59', $lt: '10:00:00.100' } })).toEqual(ten); |
| 73 | + }); |
| 74 | +}); |
0 commit comments