Add Vault, Consul, eBPF connector plugins and thorough integration e2e tests

Backend:
- Add SecretsManager=9 type, Vault=550 and Consul=551 providers to IntegrationEnums
- Create VaultConnectorPlugin (GET /v1/sys/health), ConsulConnectorPlugin
  (GET /v1/status/leader), EbpfAgentConnectorPlugin (GET /api/v1/health)
- Register all 3 plugins in Program.cs and WebService.csproj
- Extend Concelier JobRegistrationExtensions with 20 additional advisory
  source connectors (ghsa, kev, epss, debian, ubuntu, alpine, suse, etc.)
- Add connector project references to Concelier WebService.csproj so
  Type.GetType() can resolve job classes at runtime
- Fix job kind names to match SourceDefinitions IDs (jpcert not jvn,
  oracle not vndr-oracle, etc.)

Infrastructure:
- Add Consul service to docker-compose.integrations.yml (127.1.2.8:8500)
- Add runtime-host nginx fixture to docker-compose.integration-fixtures.yml
  (127.1.1.9:80)

Frontend:
- Mirror SecretsManager/Vault/Consul enum additions in integration.models.ts
- Fix Secrets tab route type from RepoSource to SecretsManager
- Add SecretsManager to parseType() and TYPE_DISPLAY_NAMES

E2E tests (117/117 passing):
- vault-consul-secrets.e2e.spec.ts: compose health, probes, CRUD, UI
- runtime-hosts.e2e.spec.ts: fixture probe, CRUD, hosts tab
- advisory-sync.e2e.spec.ts: 21 sources sync accepted, catalog, management
- ui-onboarding-wizard.e2e.spec.ts: wizard steps for registry/scm/ci
- ui-integration-detail.e2e.spec.ts: detail tabs, health data
- ui-crud-operations.e2e.spec.ts: search, sort, delete
- helpers.ts: shared configs, API helpers, screenshot util
- Updated playwright.integrations.config.ts with reporter and CI retries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-31 14:39:08 +03:00
parent 4a570b2842
commit 2fef38b093
25 changed files with 2091 additions and 140 deletions

View File

@@ -0,0 +1,165 @@
/**
* 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,
} 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: 'networkidle',
timeout: 30_000,
});
await page.waitForTimeout(2_000);
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);
});
});