mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Every first-party Python caller and test now names the mount the API actually answers on. Mechanical: URL literals only, and only where the /api/ was ours -- a preceding word character means it was a module path like insights/api/ or a third-party host like slack.com/api/, and those are untouched. Left saying /api/, each for a reason rather than for safety: - products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints - services/llm-gateway deploys separately and is under a code freeze, so moving its two calls would break it during the skew - the mcp_store and Slack/Teams redirect_uris are registered with third parties and must byte-match what was registered - the Wizard and Raycast client-metadata paths are OAuth client_ids - /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the ingress routes to the Rust fleets, held by released SDK versions Test URLconfs moved with the requests that call them, and the Datastore user-selection fixtures now carry the leading slash that request.path has.
63 lines
3.0 KiB
TypeScript
63 lines
3.0 KiB
TypeScript
import { randomString } from '../utils'
|
|
import { expect, test } from '../utils/workspace-test-base'
|
|
|
|
// Capture and flags are served by separate Rust services fronted by the Caddy dev proxy,
|
|
// not by the Django server Playwright's baseURL points at.
|
|
const PROXY_BASE_URL = process.env.E2E_PROXY_URL || 'http://localhost:8010'
|
|
|
|
test.describe('Event ingestion', () => {
|
|
test('an event sent to the real capture endpoint becomes queryable', async ({ request, playwrightSetup }) => {
|
|
test.setTimeout(120_000)
|
|
const workspace = await playwrightSetup.createWorkspace({ skip_onboarding: true, no_demo_data: true })
|
|
const eventName = randomString('e2e_ingestion_')
|
|
const authHeaders = { Authorization: `Bearer ${workspace.personal_api_key}` }
|
|
let apiToken = ''
|
|
|
|
await test.step('fetch the team api token', async () => {
|
|
const team = await request.get(`/v1/environments/${workspace.team_id}/`, { headers: authHeaders })
|
|
expect(team.ok()).toBe(true)
|
|
apiToken = (await team.json()).api_token
|
|
})
|
|
|
|
await test.step('send an event through the capture endpoint', async () => {
|
|
const captured = await request.post(`${PROXY_BASE_URL}/e/`, {
|
|
data: {
|
|
api_key: apiToken,
|
|
event: eventName,
|
|
distinct_id: randomString('e2e-user-'),
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
})
|
|
expect(captured.ok()).toBe(true)
|
|
})
|
|
|
|
await test.step('poll until the event has been ingested and is queryable', async () => {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const resp = await request.post(`/v1/environments/${workspace.team_id}/query/`, {
|
|
headers: authHeaders,
|
|
data: {
|
|
// Without this the endpoint serves a cached result for up to 5 minutes,
|
|
// so the first poll's zero would be replayed for the rest of the window.
|
|
refresh: 'force_blocking',
|
|
query: {
|
|
kind: 'InsightsQLQuery',
|
|
query: `SELECT count() FROM events WHERE event = '${eventName}'`,
|
|
},
|
|
},
|
|
})
|
|
// A 4xx other than rate limiting means the query itself is bad — waiting won't fix it.
|
|
if (resp.status() >= 400 && resp.status() < 500 && resp.status() !== 429) {
|
|
throw new Error(`query endpoint returned ${resp.status()}: ${await resp.text()}`)
|
|
}
|
|
const body = await resp.json()
|
|
return Number(body.results?.[0]?.[0] ?? 0)
|
|
},
|
|
{ timeout: 90_000, intervals: [2_000, 5_000] }
|
|
)
|
|
.toBeGreaterThan(0)
|
|
})
|
|
})
|
|
})
|