Skip to content

Commit 7c9cf1f

Browse files
test(environment): update test fixtures to include run environment
1 parent cb98206 commit 7c9cf1f

4 files changed

Lines changed: 133 additions & 2 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"workflow_run_id": 1,
3+
"config_id": 1,
4+
"environment": {
5+
"source": "log_parse",
6+
"image": null,
7+
"framework_version": null,
8+
"framework_sha": null,
9+
"torch_version": null,
10+
"python_version": null,
11+
"cuda_version": null,
12+
"rocm_version": null,
13+
"driver_version": null,
14+
"gpu_sku": null,
15+
"extra": {}
16+
}
17+
}

packages/app/scripts/capture-cypress-fixtures.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
*
44
* Hits the public production API by default and writes one JSON file per
55
* endpoint into cypress/fixtures/api/. The cypress e2e suite uses these
6-
* fixtures via cy.intercept so tests run with no database.
6+
* fixtures via server-side `FIXTURES_MODE` (E2E_FIXTURES=1) so tests run
7+
* with no database.
78
*
89
* Usage:
910
* pnpm --filter app capture:fixtures (prod)
@@ -154,6 +155,11 @@ async function main() {
154155
precision: string;
155156
isl: number;
156157
osl: number;
158+
// Optional: only present after the env-key PR ships. The capture script
159+
// uses these to fetch a representative `/api/v1/run-environment` response;
160+
// the route uses them as its sole identifier.
161+
workflow_run_id?: number;
162+
config_id?: number;
157163
}
158164
const benchmarks = await fetchJson<BenchmarkRow[]>(
159165
`/api/v1/benchmarks?model=${encodeURIComponent(BENCHMARK_MODEL)}`,
@@ -188,6 +194,51 @@ async function main() {
188194
`/api/v1/workflow-info?date=${encodeURIComponent(latestDate)}`,
189195
);
190196

197+
// run-environment: fired by `useRunEnvironment` every time the Reproduce
198+
// drawer opens. We need a fixture so cypress' fixture mode doesn't 500.
199+
// Try to pull a real one from prod, falling back to an all-nulls /
200+
// log_parse placeholder. The placeholder is the worst-case end-state the
201+
// drawer is designed to render (every env-only field shows "(not
202+
// recorded)" with the "Some fields are approximated…" hint), so it's
203+
// production-realistic even before the upstream env.json artifact lands.
204+
const RUN_ENV_PLACEHOLDER = {
205+
workflow_run_id: 1,
206+
config_id: 1,
207+
environment: {
208+
source: 'log_parse',
209+
image: null,
210+
framework_version: null,
211+
framework_sha: null,
212+
torch_version: null,
213+
python_version: null,
214+
cuda_version: null,
215+
rocm_version: null,
216+
driver_version: null,
217+
gpu_sku: null,
218+
extra: {},
219+
},
220+
};
221+
let runEnvironment: unknown = RUN_ENV_PLACEHOLDER;
222+
const sampleRow = benchmarks.find((b) => b.workflow_run_id && b.config_id);
223+
if (sampleRow) {
224+
const envUrl =
225+
`${baseUrl}/api/v1/run-environment` +
226+
`?workflow_run_id=${sampleRow.workflow_run_id}` +
227+
`&config_id=${sampleRow.config_id}`;
228+
try {
229+
const res = await fetch(envUrl);
230+
if (res.ok) runEnvironment = await res.json();
231+
} catch {
232+
// Network or parse failure — keep the placeholder; logged below.
233+
}
234+
}
235+
if (runEnvironment === RUN_ENV_PLACEHOLDER) {
236+
console.log(
237+
' (note) run-environment: using placeholder — either prod predates the env PR, ' +
238+
'the benchmark_environments table is empty, or the route is unavailable.',
239+
);
240+
}
241+
191242
const N = TOP_DATES_PER_PARTITION;
192243
const sizes: [string, number][] = [
193244
[
@@ -250,6 +301,7 @@ async function main() {
250301
}),
251302
],
252303
['workflow-info', await writeFixture('workflow-info', workflowInfo)],
304+
['run-environment', await writeFixture('run-environment', runEnvironment)],
253305
];
254306

255307
for (const [name, bytes] of sizes) {

packages/app/src/app/api/v1/run-environment/route.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,62 @@ describe('GET /api/v1/run-environment', () => {
101101
expect(res.status).toBe(500);
102102
});
103103
});
104+
105+
// Separate suite because FIXTURES_MODE is read at module-eval time — the only
106+
// way to flip it for a single test is to reset module cache + dynamic-import.
107+
describe('GET /api/v1/run-environment (FIXTURES_MODE)', () => {
108+
it('short-circuits to the loaded fixture and never hits the env query', async () => {
109+
vi.resetModules();
110+
const mockLoadFixture = vi.fn(() => ({
111+
workflow_run_id: 1,
112+
config_id: 1,
113+
environment: { ...env, source: 'log_parse' },
114+
}));
115+
vi.doMock('@semianalysisai/inferencex-db/connection', () => ({
116+
getDb: mockGetDb,
117+
JSON_MODE: false,
118+
FIXTURES_MODE: true,
119+
}));
120+
vi.doMock('@semianalysisai/inferencex-db/queries/environments', () => ({
121+
getEnvironmentForRunConfig: mockGetEnvironment,
122+
}));
123+
vi.doMock('@/lib/api-cache', () => ({
124+
cachedQuery: (fn: (...args: any[]) => any) => fn,
125+
cachedJson: (data: unknown) => Response.json(data),
126+
}));
127+
vi.doMock('@/lib/test-fixtures', () => ({ loadFixture: mockLoadFixture }));
128+
129+
const { GET: GETwithFixtures } = await import('./route');
130+
const res = await GETwithFixtures(req(`/api/v1/run-environment?${VALID_QS}`));
131+
132+
expect(res.status).toBe(200);
133+
const body = await res.json();
134+
expect(body.environment.source).toBe('log_parse');
135+
expect(mockLoadFixture).toHaveBeenCalledWith('run-environment');
136+
expect(mockGetEnvironment).not.toHaveBeenCalled();
137+
});
138+
139+
it('still 400s on missing params before consulting the fixture', async () => {
140+
vi.resetModules();
141+
const mockLoadFixture = vi.fn();
142+
vi.doMock('@semianalysisai/inferencex-db/connection', () => ({
143+
getDb: mockGetDb,
144+
JSON_MODE: false,
145+
FIXTURES_MODE: true,
146+
}));
147+
vi.doMock('@semianalysisai/inferencex-db/queries/environments', () => ({
148+
getEnvironmentForRunConfig: mockGetEnvironment,
149+
}));
150+
vi.doMock('@/lib/api-cache', () => ({
151+
cachedQuery: (fn: (...args: any[]) => any) => fn,
152+
cachedJson: (data: unknown) => Response.json(data),
153+
}));
154+
vi.doMock('@/lib/test-fixtures', () => ({ loadFixture: mockLoadFixture }));
155+
156+
const { GET: GETwithFixtures } = await import('./route');
157+
const res = await GETwithFixtures(req('/api/v1/run-environment?config_id=42'));
158+
159+
expect(res.status).toBe(400);
160+
expect(mockLoadFixture).not.toHaveBeenCalled();
161+
});
162+
});

packages/app/src/app/api/v1/run-environment/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { type NextRequest, NextResponse } from 'next/server';
22

3-
import { JSON_MODE, getDb } from '@semianalysisai/inferencex-db/connection';
3+
import { FIXTURES_MODE, JSON_MODE, getDb } from '@semianalysisai/inferencex-db/connection';
44
import * as jsonProvider from '@semianalysisai/inferencex-db/json-provider';
55
import { getEnvironmentForRunConfig } from '@semianalysisai/inferencex-db/queries/environments';
66

77
import { cachedJson, cachedQuery } from '@/lib/api-cache';
8+
import { loadFixture } from '@/lib/test-fixtures';
89

910
export const dynamic = 'force-dynamic';
1011

@@ -37,6 +38,8 @@ export async function GET(request: NextRequest) {
3738
);
3839
}
3940

41+
if (FIXTURES_MODE) return cachedJson(loadFixture('run-environment'));
42+
4043
try {
4144
const env = await getCachedEnvironment(workflowRunId, configId);
4245
if (env === null) {

0 commit comments

Comments
 (0)