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.
228 lines
9.0 KiB
TypeScript
228 lines
9.0 KiB
TypeScript
import { router } from 'kea-router'
|
|
import { expectLogic } from 'kea-test-utils'
|
|
|
|
import { useMocks } from '~/mocks/jest'
|
|
import { initKeaTests } from '~/test/init'
|
|
|
|
import { logsSceneLogic } from './logsSceneLogic'
|
|
|
|
describe('logsSceneLogic', () => {
|
|
let logic: ReturnType<typeof logsSceneLogic.build>
|
|
|
|
beforeEach(async () => {
|
|
useMocks({
|
|
post: {
|
|
'/v1/environments/:team_id/logs/query/': () => [200, { results: [], maxExportableLogs: 5000 }],
|
|
'/v1/environments/:team_id/logs/sparkline/': () => [200, []],
|
|
},
|
|
})
|
|
initKeaTests()
|
|
logic = logsSceneLogic()
|
|
logic.mount()
|
|
|
|
await expectLogic(logic).toFinishAllListeners()
|
|
})
|
|
|
|
afterEach(() => {
|
|
logic.unmount()
|
|
})
|
|
|
|
describe('URL parameter parsing', () => {
|
|
it.each([
|
|
['JSON string array', '["error","warn"]', ['error', 'warn']],
|
|
['single item JSON array', '["info"]', ['info']],
|
|
['empty JSON array', '[]', []],
|
|
])('parses severityLevels from %s', async (_, urlValue, expected) => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { severityLevels: urlValue })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.filters.severityLevels).toEqual(expected)
|
|
})
|
|
|
|
it.each([
|
|
['JSON string array', '["my-service","other-service"]', ['my-service', 'other-service']],
|
|
['single item JSON array', '["api"]', ['api']],
|
|
])('parses serviceNames from %s', async (_, urlValue, expected) => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { serviceNames: urlValue })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.filters.serviceNames).toEqual(expected)
|
|
})
|
|
|
|
it('filters out malformed JSON as invalid severity level', async () => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { severityLevels: 'not-valid-json[' })
|
|
}).toFinishAllListeners()
|
|
|
|
// parseTagsFilter falls back to comma-separated parsing, then validation filters invalid levels
|
|
expect(logic.values.filters.severityLevels).toEqual([])
|
|
})
|
|
|
|
it('filters out non-array JSON as invalid severity level', async () => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { severityLevels: '"just-a-string"' })
|
|
}).toFinishAllListeners()
|
|
|
|
// parseTagsFilter falls back to comma-separated parsing, then validation filters invalid levels
|
|
expect(logic.values.filters.severityLevels).toEqual([])
|
|
})
|
|
|
|
it('handles comma-separated values via parseTagsFilter', async () => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { severityLevels: 'error,warn,info' })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.filters.severityLevels).toEqual(['error', 'warn', 'info'])
|
|
})
|
|
|
|
it.each([
|
|
['a valid lens', 'patterns', 'patterns'],
|
|
['an unrecognised lens falls back to the default', 'nonsense', 'logs'],
|
|
])('applies viewMode from the URL: %s', async (_, urlValue, expected) => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { viewMode: urlValue })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.viewMode).toEqual(expected)
|
|
})
|
|
|
|
it('syncs lens switches back to the URL, dropping the param for the default lens', async () => {
|
|
// The round-trip contract for shareable lens links: switching to Patterns writes
|
|
// ?viewMode=patterns, and returning to Logs (the default) removes the param
|
|
// instead of pinning viewMode=logs into every copied URL.
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setViewMode('patterns')
|
|
}).toFinishAllListeners()
|
|
expect(router.values.searchParams.viewMode).toEqual('patterns')
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setViewMode('logs')
|
|
}).toFinishAllListeners()
|
|
expect(router.values.searchParams.viewMode).toBeUndefined()
|
|
})
|
|
|
|
it.each([
|
|
['completely invalid value', '["invalid-level"]', []],
|
|
['typo in valid level', '["debug123"]', []],
|
|
['mix of valid and invalid', '["error","not-a-level","warn"]', ['error', 'warn']],
|
|
['invalid comma-separated', 'invalid,also-invalid', []],
|
|
])('filters out invalid severity levels (%s)', async (_, urlValue, expected) => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { severityLevels: urlValue })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.filters.severityLevels).toEqual(expected)
|
|
})
|
|
|
|
it('parses a stringified filterGroup from the URL (e.g. a cross-product session link)', async () => {
|
|
const filterGroup = {
|
|
type: 'AND',
|
|
values: [{ type: 'OR', values: [{ key: 'insightsSessionId', value: ['sess-1'], operator: 'exact' }] }],
|
|
}
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { filterGroup: JSON.stringify(filterGroup) })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.filters.filterGroup).toEqual(filterGroup)
|
|
})
|
|
|
|
it('ignores a malformed filterGroup in the URL', async () => {
|
|
const before = logic.values.filters.filterGroup
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { filterGroup: '{not valid json' })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.filters.filterGroup).toEqual(before)
|
|
})
|
|
})
|
|
|
|
describe('activeTab URL sync', () => {
|
|
it('defaults to viewer', () => {
|
|
expect(logic.values.activeTab).toEqual('viewer')
|
|
})
|
|
|
|
it.each([
|
|
['viewer', 'viewer'],
|
|
['anomalies', 'anomalies'],
|
|
['configuration', 'configuration'],
|
|
])('parses valid activeTab "%s" from URL', async (urlValue, expected) => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { activeTab: urlValue })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.activeTab).toEqual(expected)
|
|
})
|
|
|
|
it.each([
|
|
['unknown string', 'invalid'],
|
|
['array', ['viewer']],
|
|
['object', { key: 'viewer' }],
|
|
['number', 42],
|
|
])('ignores invalid activeTab (%s)', async (_, urlValue) => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { activeTab: urlValue })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.activeTab).toEqual('viewer')
|
|
})
|
|
|
|
it('syncs activeTab to URL on setActiveTab', async () => {
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setActiveTab('configuration')
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.activeTab).toEqual('configuration')
|
|
expect(router.values.searchParams).toHaveProperty('activeTab', 'configuration')
|
|
})
|
|
|
|
it('removes activeTab from URL when set to default', async () => {
|
|
// First set to non-default
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setActiveTab('configuration')
|
|
}).toFinishAllListeners()
|
|
|
|
// Then set back to default
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setActiveTab('viewer')
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.activeTab).toEqual('viewer')
|
|
expect(router.values.searchParams).not.toHaveProperty('activeTab')
|
|
})
|
|
})
|
|
|
|
describe('facetNameSearch URL sync', () => {
|
|
it('parses facetNameSearch from URL', async () => {
|
|
await expectLogic(logic, () => {
|
|
router.actions.push('/logs', { facetNameSearch: 'namespace' })
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.facetNameSearch).toEqual('namespace')
|
|
})
|
|
|
|
it('syncs facetNameSearch to URL on setFacetNameSearch', async () => {
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setFacetNameSearch('kube')
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.facetNameSearch).toEqual('kube')
|
|
expect(router.values.searchParams).toHaveProperty('facetNameSearch', 'kube')
|
|
})
|
|
|
|
it('removes facetNameSearch from URL when cleared', async () => {
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setFacetNameSearch('kube')
|
|
}).toFinishAllListeners()
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setFacetNameSearch('')
|
|
}).toFinishAllListeners()
|
|
|
|
expect(logic.values.facetNameSearch).toEqual('')
|
|
expect(router.values.searchParams).not.toHaveProperty('facetNameSearch')
|
|
})
|
|
})
|
|
})
|