/** * Runtime Host Integration — End-to-End Tests * * Validates the full lifecycle for runtime-host integrations (eBPF Agent): * 1. Fixture compose health * 2. Direct endpoint probe * 3. Connector plugin API (create, test-connection, health, delete) * 4. UI: Runtimes / Hosts tab shows created integration * * Prerequisites: * - Main Stella Ops stack running * - docker-compose.integration-fixtures.yml (includes runtime-host-fixture) */ import { execSync } from 'child_process'; import { test, expect } from './live-auth.fixture'; import { INTEGRATION_CONFIGS, createIntegrationViaApi, cleanupIntegrations, snap, waitForAngular, } from './helpers'; const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local'; const runId = process.env['E2E_RUN_ID'] || 'run1'; function dockerHealthy(containerName: string): boolean { try { const out = execSync( `docker ps --filter "name=${containerName}" --format "{{.Status}}"`, { encoding: 'utf-8', timeout: 5_000 }, ).trim(); return out.includes('(healthy)') || (out.startsWith('Up') && !out.includes('health: starting')); } catch { return false; } } // --------------------------------------------------------------------------- // 1. Compose Health // --------------------------------------------------------------------------- test.describe('Runtime Host — Compose Health', () => { test('runtime-host-fixture container is healthy', () => { expect( dockerHealthy('stellaops-runtime-host-fixture'), 'runtime-host-fixture should be healthy', ).toBe(true); }); }); // --------------------------------------------------------------------------- // 2. Direct Endpoint Probe // --------------------------------------------------------------------------- test.describe('Runtime Host — Direct Probe', () => { test('eBPF agent /api/v1/health returns 200 with healthy status', async ({ playwright }) => { const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true }); try { const resp = await ctx.get('http://127.1.1.9/api/v1/health', { timeout: 10_000 }); expect(resp.status()).toBeLessThan(300); const body = await resp.json(); expect(body.status).toBe('healthy'); expect(body.agent).toBe('ebpf'); expect(body.probes_loaded).toBeGreaterThan(0); } finally { await ctx.dispose(); } }); test('eBPF agent /api/v1/info returns agent details', async ({ playwright }) => { const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true }); try { const resp = await ctx.get('http://127.1.1.9/api/v1/info', { timeout: 10_000 }); expect(resp.status()).toBeLessThan(300); const body = await resp.json(); expect(body.agent_type).toBe('ebpf'); expect(body.probes).toBeDefined(); expect(body.probes.length).toBeGreaterThan(0); } finally { await ctx.dispose(); } }); }); // --------------------------------------------------------------------------- // 3. Connector Lifecycle (API) // --------------------------------------------------------------------------- test.describe('Runtime Host — Connector Lifecycle', () => { const createdIds: string[] = []; test('create eBPF Agent integration returns 201', async ({ apiRequest }) => { const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.ebpfAgent, runId); createdIds.push(id); expect(id).toBeTruthy(); const getResp = await apiRequest.get(`/api/v1/integrations/${id}`); expect(getResp.status()).toBe(200); const body = await getResp.json(); expect(body.type).toBe(5); // RuntimeHost expect(body.provider).toBe(500); // EbpfAgent }); test('test-connection on eBPF Agent returns success', async ({ apiRequest }) => { expect(createdIds.length).toBeGreaterThan(0); const resp = await apiRequest.post(`/api/v1/integrations/${createdIds[0]}/test`); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.success).toBe(true); }); test('health-check on eBPF Agent returns Healthy', async ({ apiRequest }) => { expect(createdIds.length).toBeGreaterThan(0); const resp = await apiRequest.get(`/api/v1/integrations/${createdIds[0]}/health`); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.status).toBe(1); // Healthy }); test('list RuntimeHost integrations returns at least 1', async ({ apiRequest }) => { const resp = await apiRequest.get('/api/v1/integrations?type=5&pageSize=100'); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.totalCount).toBeGreaterThanOrEqual(1); }); test.afterAll(async ({ apiRequest }) => { await cleanupIntegrations(apiRequest, createdIds); }); }); // --------------------------------------------------------------------------- // 4. UI: Runtimes / Hosts Tab // --------------------------------------------------------------------------- test.describe('Runtime Host — UI Verification', () => { const createdIds: string[] = []; test.beforeAll(async ({ apiRequest }) => { const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.ebpfAgent, `ui-${runId}`); createdIds.push(id); }); test('Runtimes / Hosts tab loads and shows integration', async ({ liveAuthPage: page }) => { await page.goto(`${BASE}/setup/integrations/runtime-hosts`, { waitUntil: 'domcontentloaded', timeout: 45_000, }); await waitForAngular(page); const heading = page.getByRole('heading', { name: /runtime host/i }); await expect(heading).toBeVisible({ timeout: 5_000 }); const rows = page.locator('table tbody tr'); const count = await rows.count(); expect(count).toBeGreaterThanOrEqual(1); await snap(page, 'runtime-hosts-tab'); }); test.afterAll(async ({ apiRequest }) => { await cleanupIntegrations(apiRequest, createdIds); }); });