context detemrinistic + randomized searches and fix for setup from stella-ops.local rather 127.1.0.*

This commit is contained in:
master
2026-03-06 14:41:05 +02:00
parent 973cc8b335
commit 49763be70b
28 changed files with 1557 additions and 234 deletions

View File

@@ -0,0 +1,226 @@
import { expect, test, type Page } from '@playwright/test';
import {
buildResponse,
setupAuthenticatedSession,
setupBasicMocks,
typeInSearch,
waitForEntityCards,
waitForResults,
} from './unified-search-fixtures';
const criticalFindingCard = {
entityKey: 'cve:CVE-2024-21626',
entityType: 'finding',
domain: 'findings',
title: 'CVE-2024-21626 in api-gateway',
snippet: 'Reachable critical vulnerability detected in production workload.',
score: 0.96,
severity: 'critical',
actions: [
{
label: 'Open finding',
actionType: 'navigate',
route: '/security/triage?q=CVE-2024-21626',
isPrimary: true,
},
],
sources: ['findings'],
metadata: {},
};
const criticalFindingsResponse = buildResponse(
'critical findings',
[criticalFindingCard],
{
summary: 'One critical finding matched. Ask AdvisoryAI for triage guidance.',
template: 'finding_overview',
confidence: 'high',
sourceCount: 1,
domainsCovered: ['findings'],
},
);
const cveFollowupResponse = buildResponse(
'CVE-2024-21626',
[criticalFindingCard],
{
summary: 'Follow-up query for CVE-2024-21626 returned one finding.',
template: 'finding_overview',
confidence: 'high',
sourceCount: 1,
domainsCovered: ['findings'],
},
);
test.describe('Unified Search - Contextual Suggestions', () => {
test.beforeEach(async ({ page }) => {
await setupBasicMocks(page);
await setupAuthenticatedSession(page);
});
test('updates empty-state chips automatically when route changes', async ({ page }) => {
await page.goto('/security/triage');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
const searchInput = page.locator('app-global-search input[type="text"]');
await searchInput.focus();
await expect(page.locator('.search__results')).toBeVisible({ timeout: 10_000 });
await expect(page.locator('.search__suggestions .search__chip', {
hasText: /critical findings/i,
}).first()).toBeVisible();
await page.goto('/ops/policy');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
await searchInput.focus();
await expect(page.locator('.search__results')).toBeVisible({ timeout: 10_000 });
await expect(page.locator('.search__suggestions .search__chip', {
hasText: /failing policy gates/i,
}).first()).toBeVisible();
await page.goto('/ops/timeline');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
await searchInput.focus();
await expect(page.locator('.search__results')).toBeVisible({ timeout: 10_000 });
await expect(page.locator('.search__suggestions .search__chip', {
hasText: /failed deployments/i,
}).first()).toBeVisible();
});
test('promotes follow-up chips from recent search actions on the same page scope', async ({ page }) => {
await page.route('**/search/query**', (route) => {
const payload = route.request().postDataJSON() as { q?: string };
const query = String(payload?.q ?? '');
const response = query.toLowerCase().includes('cve-2024-21626')
? cveFollowupResponse
: criticalFindingsResponse;
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response),
});
});
await page.goto('/security/triage');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
await typeInSearch(page, 'CVE-2024-21626');
await waitForResults(page);
await waitForEntityCards(page, 1);
await page.locator('app-entity-card').first().click();
await expect(page).toHaveURL(/\/security\/triage\?q=CVE-2024-21626/i);
const searchInput = page.locator('app-global-search input[type="text"]');
await searchInput.focus();
await expect(page.locator('.search__results')).toBeVisible({ timeout: 10_000 });
await expect(page.locator('.search__suggestions .search__chip', {
hasText: /follow up:\s*CVE-2024-21626/i,
}).first()).toBeVisible();
});
test('chat search-for-more emits ambient lastAction and route context in follow-up search requests', async ({
page,
}) => {
await mockChatEndpoints(page);
const capturedRequests: Array<Record<string, unknown>> = [];
await page.route('**/search/query**', (route) => {
const body = route.request().postDataJSON() as Record<string, unknown>;
capturedRequests.push(body);
const query = String(body['q'] ?? '').toLowerCase();
const response = query.includes('critical findings')
? criticalFindingsResponse
: cveFollowupResponse;
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response),
});
});
await page.goto('/security/triage');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
await typeInSearch(page, 'critical findings');
await waitForResults(page);
await waitForEntityCards(page, 1);
await page.locator('.entity-card__action--ask-ai').first().click();
await expect(page.locator('.assistant-drawer')).toBeVisible({ timeout: 10_000 });
await page.locator('.search-more-link').click();
await expect.poll(() =>
capturedRequests.some((request) =>
String(request['q'] ?? '').toLowerCase().includes('cve-2024-21626')),
).toBe(true);
const handoffRequest = capturedRequests.find((request) =>
String(request['q'] ?? '').toLowerCase().includes('cve-2024-21626'));
const ambient = handoffRequest?.['ambient'] as Record<string, unknown> | undefined;
const lastAction = ambient?.['lastAction'] as Record<string, unknown> | undefined;
expect(ambient).toBeDefined();
expect(String(ambient?.['currentRoute'] ?? '')).toContain('/security/triage');
expect(lastAction?.['action']).toBe('chat_search_for_more');
expect(lastAction?.['source']).toBe('advisory_ai_chat');
expect(lastAction?.['domain']).toBe('findings');
});
});
async function mockChatEndpoints(page: Page): Promise<void> {
await page.route('**/api/v1/advisory-ai/conversations', async (route) => {
if (route.request().method() !== 'POST') {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
}
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
conversationId: 'conv-context-1',
tenantId: 'test-tenant',
userId: 'tester',
context: {},
turns: [],
createdAt: '2026-02-25T00:00:00.000Z',
updatedAt: '2026-02-25T00:00:00.000Z',
}),
});
});
await page.route('**/api/v1/advisory-ai/conversations/*/turns', async (route) => {
if (route.request().method() !== 'POST') {
return route.continue();
}
const ssePayload = [
'event: progress',
'data: {"stage":"searching"}',
'',
'event: token',
'data: {"content":"CVE-2024-21626 remains relevant for this finding. "}',
'',
'event: citation',
'data: {"type":"finding","path":"CVE-2024-21626","verified":true}',
'',
'event: done',
'data: {"turnId":"turn-context-1","groundingScore":0.92}',
'',
].join('\n');
return route.fulfill({
status: 200,
headers: {
'content-type': 'text/event-stream; charset=utf-8',
'cache-control': 'no-cache',
},
body: ssePayload,
});
});
}