Skip to content

Commit 3857b68

Browse files
test(gateway): verify models endpoint contract
1 parent 0ee031c commit 3857b68

3 files changed

Lines changed: 60 additions & 9 deletions

File tree

docs/FUNCTIONAL_TEST_STRATEGY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ really a test of a third-party framework or engine we merely depend on:
4646
dependency (binary/model) is absent, so it runs on a dev Mac + release builds and SKIPS in a
4747
plain `npm ci` CI rather than failing. This is where "the feature works" is proven.
4848
- **swift unit (XCTest / swift-testing via SwiftPM)** - pure logic inside a Swift package.
49-
- **e2e (Playwright Electron)** - the rendered app tour (e2e/, 26 tests today).
49+
- **e2e (Playwright Electron)** - the rendered app tour (e2e/, 27 tests today).
5050
- **gateway smoke (`npm run smoke`)** - the OpenAI `/v1` surface against a running app.
5151

5252
## Surface matrix (status - keep current)
@@ -65,7 +65,7 @@ really a test of a third-party framework or engine we merely depend on:
6565
| Capture -> OCR -> memory ingest seam | integration | vitest: feed a fixture frame through the ingest path into a temp DB | TODO |
6666
| RAG end-to-end (retrieve -> prompt -> answer) | integration | vitest: seed a temp SQLite, run retrieval, assert grounded context | TODO |
6767
| Vault (kdbx + Argon2) | integration | vitest vs temp dir | DONE (`vault-service.test.ts`) |
68-
| Renderer surfaces render | e2e | Playwright tour | DONE (26) |
68+
| Renderer surfaces render | e2e | Playwright tour | DONE (27) |
6969
| Pro dictation / clipboard / replay | e2e + unit | Playwright (`pro.spec.ts`) + pro unit | PARTIAL |
7070

7171
## Rules

docs/P0_P2_INTEGRATION_COVERAGE.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ manual claim does not count as complete integration coverage.
77

88
## Current status - 2026-07-17
99

10-
- Scope: 155 journeys - 74 P0, 71 P1, and 10 P2.
11-
- Covered by direct integration or E2E evidence: 29 - 22 P0, 6 P1, and 1 P2.
12-
- Left: 126 - including partially covered journeys whose exact release behavior is not yet proven.
10+
- Status snapshot:
11+
- P0: 74 total, 23 covered, 51 left.
12+
- P1: 71 total, 6 covered, 65 left.
13+
- P2: 10 total, 1 covered, 9 left.
14+
- Overall: 155 total, 30 covered, 125 left.
1315
- Green gates today:
1416
- `npm test`: 202 files passed, 1 skipped; 2,190 tests passed, 1 skipped.
1517
- `npm run test:coverage`: 96.75% statements, 91.54% branches, 96.14% functions,
1618
97.52% lines.
1719
- `npm run test:db`: 13 files and 105 real SQLite integration tests passed; Electron ABI
1820
restored afterward.
19-
- `npm run test:e2e`: 26 Playwright Electron tests passed against fresh synthetic temp profiles.
21+
- `npm run test:e2e`: 27 Playwright Electron tests passed against fresh synthetic temp profiles.
2022
- Both TypeScript projects pass.
21-
- Not yet a clean handoff: the exact P0-P2 journey count is 29/155, and strict ESLint currently
22-
exposes a legacy backlog. Neither is hidden by the coverage percentage.
23+
- Not yet a clean handoff: release-journey coverage remains incomplete, and strict ESLint exposes
24+
a legacy backlog. Neither is hidden by the coverage percentage.
2325

2426
## Covered P0 journeys
2527

@@ -37,6 +39,9 @@ manual claim does not count as complete integration coverage.
3739
- #63 - Image cancellation keeps text. `MemoryChat.tool-image-cancel.test.tsx` drives the real
3840
rendered MemoryChat path with the image engine as the external boundary and verifies the text
3941
turn persists.
42+
- #79 - Gateway models endpoint. `e2e/smoke.spec.ts` starts the real Electron gateway, seeds an
43+
active model only inside the disposable profile, calls `/v1/models` over HTTP, and verifies
44+
modality metadata in both supported response shapes.
4045
- #87 - Replay timeline renders. `e2e/pro.spec.ts` launches the real Pro build path with synthetic
4146
data and verifies Replay renders instead of the upgrade screen.
4247
- #94 - Delete all removes capture corpus. `pro/main/__tests__/personal-data.integration.test.ts`
@@ -188,7 +193,6 @@ manual claim does not count as complete integration coverage.
188193
- #76 - Expired connector becomes error.
189194
- #77 - Dead connector does not hang all tools.
190195
- #78 - Connector delete removes secrets.
191-
- #79 - Gateway models endpoint.
192196
- #80 - Gateway chat streaming.
193197
- #81 - Gateway image route.
194198
- #82 - Gateway failure envelope.

e2e/smoke.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,50 @@ test('system:health IPC returns the component list', async () => {
109109
expect(ids).toContain('chat')
110110
expect(ids).toContain('gateway')
111111
})
112+
113+
test('gateway /v1/models serves active local models with modality metadata', async () => {
114+
const modelsDir = path.join(userDataDir, 'models')
115+
fs.mkdirSync(modelsDir, { recursive: true })
116+
fs.writeFileSync(path.join(modelsDir, 'e2e-active.gguf'), 'synthetic gateway model fixture')
117+
fs.writeFileSync(
118+
path.join(modelsDir, 'active-model.json'),
119+
JSON.stringify({ id: 'e2e-active-model', primary: 'e2e-active.gguf', mmproj: null })
120+
)
121+
122+
let catalog: {
123+
object?: string
124+
data?: Array<{ id?: string; object?: string; kind?: string }>
125+
models?: Array<{ name?: string; model?: string; kind?: string }>
126+
} = {}
127+
await expect
128+
.poll(
129+
async () => {
130+
try {
131+
const response = await fetch('http://127.0.0.1:7878/v1/models')
132+
if (!response.ok) return false
133+
catalog = await response.json()
134+
return catalog.data?.some((model) => model.id === 'e2e-active-model') ?? false
135+
} catch {
136+
return false
137+
}
138+
},
139+
{ timeout: 10000 }
140+
)
141+
.toBe(true)
142+
143+
expect(catalog.object).toBe('list')
144+
expect(catalog.data).toContainEqual(
145+
expect.objectContaining({
146+
id: 'e2e-active-model',
147+
object: 'model',
148+
kind: 'chat'
149+
})
150+
)
151+
expect(catalog.models).toContainEqual(
152+
expect.objectContaining({
153+
name: 'e2e-active-model',
154+
model: 'e2e-active-model',
155+
kind: 'chat'
156+
})
157+
)
158+
})

0 commit comments

Comments
 (0)