const apiBase = process.env.API_BASE || 'http://127.0.0.1:8787'; const tenantId = process.env.TENANT_ID || '00000000-0000-0000-0000-000000000001'; const userId = process.env.USER_ID || '00000000-0000-0000-0000-000000000101'; const ids = { region: '00000000-0000-0000-0000-000000000301', question: '00000000-0000-0000-0000-000000000401', vocabularyUnit: '00000000-0000-0000-0000-000000000811', vocabularyWord: '00000000-0000-0000-0000-000000000812', scorelineSchool: '00000000-0000-0000-0000-000000000831', }; function url(path, query = {}) { const target = new URL(path, apiBase); for (const [key, value] of Object.entries(query)) { if (value !== undefined && value !== null && value !== '') { target.searchParams.set(key, String(value)); } } return target; } async function request(path, options = {}) { const response = await fetch(url(path, options.query), { method: options.method || 'GET', headers: { 'content-type': 'application/json', 'x-tenant-id': tenantId, 'x-user-id': userId, ...(options.headers || {}), }, body: options.body ? JSON.stringify(options.body) : undefined, }); const payload = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(`${options.method || 'GET'} ${path} failed: ${response.status} ${JSON.stringify(payload)}`); } return payload; } function assert(condition, message) { if (!condition) throw new Error(message); } async function check(name, fn) { const result = await fn(); console.log(`[PASS] ${name}`); return result; } async function main() { await check('health', async () => { const payload = await request('/health'); assert(payload.ok === true, 'health response should be ok'); }); await check('profile me', async () => { const payload = await request('/api/profile/me'); assert(payload.item?.userId === userId, 'profile should belong to smoke user'); assert(payload.item?.stats?.vocabulary?.totalWords >= 1, 'profile should include vocabulary stats'); }); await check('scoreline fields', async () => { const payload = await request('/api/scoreline/fields', { query: { regionId: ids.region } }); assert(payload.items?.some(item => item.fieldKey === 'minScore'), 'scoreline fields should include minScore'); }); await check('scoreline schools and records', async () => { const schools = await request('/api/scoreline/schools', { query: { regionId: ids.region } }); assert(schools.items?.some(item => item.id === ids.scorelineSchool), 'scoreline school should exist'); const records = await request('/api/scoreline/records', { query: { regionId: ids.region, pageSize: 5 } }); assert(records.total >= 1, 'scoreline records should have data'); const years = await request('/api/scoreline/years', { query: { regionId: ids.region } }); assert(years.items?.includes(2026), 'scoreline years should include 2026'); }); await check('question videos', async () => { const single = await request(`/api/questions/${ids.question}/videos`); assert(single.total >= 1, 'question should have videos'); const batch = await request('/api/questions/videos/batch', { method: 'POST', body: { questionIds: [ids.question] }, }); assert(batch.data?.[ids.question]?.hasVideo === true, 'batch video lookup should mark question as having video'); }); await check('vocabulary progress and favorite', async () => { const stats = await request('/api/learning/vocabulary/stats', { query: { unitId: ids.vocabularyUnit } }); assert(stats.item?.totalWords >= 1, 'word stats should count smoke word'); const progress = await request('/api/learning/vocabulary/progress', { method: 'POST', body: { userId, wordId: ids.vocabularyWord, status: 'mastered', correctDelta: 1 }, }); assert(progress.item?.status === 'mastered', 'word progress should update to mastered'); const favorite = await request('/api/learning/vocabulary/favorites', { method: 'POST', body: { userId, wordId: ids.vocabularyWord, favorite: true }, }); assert(favorite.favorite === true, 'word favorite should be true'); const favorites = await request('/api/learning/vocabulary/favorites', { query: { unitId: ids.vocabularyUnit } }); assert(favorites.items?.some(item => item.wordId === ids.vocabularyWord), 'favorite list should include smoke word'); }); console.log('Core API smoke complete.'); } main().catch(error => { console.error(error); process.exitCode = 1; });