Skip to content

Commit baffb08

Browse files
committed
test(service-analytics): pin the draft preview's temporal storage form against the engine
The sixteen cells and two window ends written shorter than the stored instant answer engine.find's rows through queryDataset previewDrafts, over drafted rows in the canonical spelling and over the same instants respelled (a Date, no milliseconds, zone-naive, an offset). Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 207ce2d commit baffb08

1 file changed

Lines changed: 50 additions & 3 deletions

File tree

‎packages/services/service-analytics/src/__tests__/read-scope-temporal-coercion.test.ts‎

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
* (`IDataDriver.temporalFilterValue` / `temporalFilterColumnSql`) bound to the
1010
* object, and applies them after the shared lowering to every value
1111
* comparison. Both of its consumers pass the context's pair. Absent members are
12-
* identity.
12+
* identity. The draft preview, which has no driver, puts both sides of each
13+
* value comparison in the same storage form with `@objectstack/core`'s
14+
* `temporalStorageForm`, by the column's declared type.
1315
*
1416
* Measured at `d2f452b88`, before this change, with the comparand bound as
1517
* written: the read scope differed from `engine.find` on 7 of the 12
1618
* `datetime` cells below on SQLite and 9 of 12 on PostgreSQL 16 under a
1719
* non-UTC server, and on 0 of the 4 `date` cells. The native face, which runs
1820
* the read scope, differed on the same cells end to end; the ObjectQL face
19-
* (the engine) on none.
21+
* (the engine) on none. The preview differed on 7 of the 12 `datetime` cells
22+
* and on 0 of the 4 `date` cells.
2023
*
2124
* The PostgreSQL cells run where `OS_TEST_POSTGRES_URL` is set, and are a
2225
* named skip otherwise. Each asserts its server is not on UTC, because a UTC
@@ -28,7 +31,8 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2831
import { ObjectQL } from '@objectstack/objectql';
2932
import { SqlDriver } from '@objectstack/driver-sql';
3033
import { lowerFilterCondition, type Cube, type FilterCondition } from '@objectstack/spec/data';
31-
import type { AnalyticsService } from '../analytics-service.js';
34+
import { DatasetSchema } from '@objectstack/spec/ui';
35+
import { AnalyticsService } from '../analytics-service.js';
3236
import { AnalyticsServicePlugin } from '../plugin.js';
3337
import { compileScopedFilterToSql, type ReadScopeCompileOptions } from '../read-scope-sql.js';
3438

@@ -120,6 +124,14 @@ const ROWS = [
120124
{ id: 'r4', signed_at: '2026-07-29T10:00:00.000Z', due_on: '2026-07-29', note: 'n' },
121125
{ id: 'r5', signed_at: null, due_on: null, note: null },
122126
];
127+
/** The same instants and days as {@link ROWS}, drafted in other spellings an author can write. */
128+
const RESPELLED = [
129+
{ id: 'r1', signed_at: new Date(Date.UTC(2026, 6, 27, 10)), due_on: '2026-07-27T23:00:00Z', note: '2026-07-27' },
130+
{ id: 'r2', signed_at: '2026-07-28T00:00:00Z', due_on: '2026-07-28T00:00:00.000Z', note: '2026-07-28' },
131+
{ id: 'r3', signed_at: '2026-07-28 10:00:00', due_on: '2026-07-28', note: '2026-07-28 late' },
132+
{ id: 'r4', signed_at: '2026-07-29T18:00:00+08:00', due_on: '2026-07-29', note: 'n' },
133+
{ id: 'r5', signed_at: null, due_on: null, note: null },
134+
];
123135
const CUBE = {
124136
name: 'os21505_cube',
125137
title: 'Coercion',
@@ -257,6 +269,41 @@ for (const cell of DB_CELLS) {
257269
expect(echo.params).toContain(driver.temporalFilterValue(OBJECT, 'signed_at', '2026-07-28'));
258270
expect(echo.params).not.toContain('2026-07-28');
259271
});
272+
273+
it('the draft preview (queryDataset previewDrafts) answers the engine\'s rows over drafted rows in either spelling', async () => {
274+
// Window ends written shorter than the stored instant, beside the cells.
275+
const windows: Array<[string, [string, string]]> = [
276+
['datetime window to a zone-naive minute', ['2026-07-28', '2026-07-28T10:00']],
277+
['datetime window to a zone-naive second', ['2026-07-28', '2026-07-28T10:00:00']],
278+
];
279+
for (const [label, [start, end]] of windows) {
280+
expect(ids(await engine.find(OBJECT, { where: { signed_at: { $gte: start, $lte: end } }, fields: ['id'] } as never)), `${label}: engine.find`).toBe('r2,r3');
281+
}
282+
const dataset = DatasetSchema.parse({
283+
name: 'os21505_preview',
284+
label: 'Coercion preview',
285+
object: OBJECT,
286+
dimensions: [{ name: 'id', field: 'id', type: 'string' }, { name: 'signed_at', field: 'signed_at', type: 'date' }],
287+
measures: [{ name: 'row_count', aggregate: 'count' }],
288+
});
289+
for (const [spelling, drafted] of [['canonical', ROWS], ['respelled', RESPELLED]] as const) {
290+
// The live path is not wired, so an answer can only come from the preview.
291+
const svc = new AnalyticsService({
292+
sourceFieldMeta: (object: string, field: string) => (object === OBJECT && DECLARED[field] ? { type: DECLARED[field] } : undefined),
293+
queryCapabilities: () => ({ nativeSql: false, objectqlAggregate: true, inMemory: false }),
294+
executeAggregate: async () => { throw new Error('the live path ran: the preview did not answer'); },
295+
draftRowsResolver: async (object: string) => (object === OBJECT ? drafted.map((r) => ({ ...r })) : null),
296+
} as never);
297+
const preview = async (selection: Record<string, unknown>) =>
298+
ids((await svc.queryDataset(dataset as never, { dimensions: ['id'], measures: ['row_count'], ...selection } as never, { tenantId: 'org_A' } as never, { previewDrafts: true })).rows);
299+
for (const [label, where, expected] of CELLS) {
300+
expect(await preview({ runtimeFilter: where }), `${spelling} rows, ${label}: the preview`).toBe(expected);
301+
}
302+
for (const [label, dateRange] of windows) {
303+
expect(await preview({ timeDimensions: [{ dimension: 'signed_at', dateRange }] }), `${spelling} rows, ${label}: the preview`).toBe('r2,r3');
304+
}
305+
}
306+
});
260307
},
261308
);
262309
}

0 commit comments

Comments
 (0)