/** * Vault & Consul Secrets Integration — End-to-End Tests * * Validates the full lifecycle for secrets-manager integrations: * 1. Docker compose health (Vault + Consul containers) * 2. Direct endpoint probes * 3. Connector plugin API (create, test-connection, health, delete) * 4. UI: Secrets tab shows created integrations * 5. UI: Integration detail page renders * * Prerequisites: * - Main Stella Ops stack running * - docker-compose.integrations.yml (includes Vault + Consul) */ 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('Secrets Integration — Compose Health', () => { test('Vault container is healthy', () => { expect(dockerHealthy('stellaops-vault'), 'Vault should be healthy').toBe(true); }); test('Consul container is healthy', () => { expect(dockerHealthy('stellaops-consul'), 'Consul should be healthy').toBe(true); }); }); // --------------------------------------------------------------------------- // 2. Direct Endpoint Probes // --------------------------------------------------------------------------- test.describe('Secrets Integration — Direct Probes', () => { test('Vault /v1/sys/health returns 200', async ({ playwright }) => { const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true }); try { const resp = await ctx.get('http://127.1.2.4:8200/v1/sys/health', { timeout: 10_000 }); expect(resp.status()).toBeLessThan(300); const body = await resp.json(); expect(body.initialized).toBe(true); expect(body.sealed).toBe(false); } finally { await ctx.dispose(); } }); test('Consul /v1/status/leader returns 200', async ({ playwright }) => { const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true }); try { const resp = await ctx.get('http://127.1.2.8:8500/v1/status/leader', { timeout: 10_000 }); expect(resp.status()).toBeLessThan(300); const body = await resp.text(); // Leader response is a quoted string like "127.0.0.1:8300" expect(body.length).toBeGreaterThan(2); } finally { await ctx.dispose(); } }); }); // --------------------------------------------------------------------------- // 3. Connector Lifecycle (API) // --------------------------------------------------------------------------- test.describe('Secrets Integration — Connector Lifecycle', () => { const createdIds: string[] = []; test('create Vault integration returns 201', async ({ apiRequest }) => { const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.vault, runId); createdIds.push(id); expect(id).toBeTruthy(); // Verify the integration was created with correct type const getResp = await apiRequest.get(`/api/v1/integrations/${id}`); expect(getResp.status()).toBe(200); const body = await getResp.json(); expect(body.type).toBe(9); // SecretsManager expect(body.provider).toBe(550); // Vault }); test('test-connection on Vault 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 Vault 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('create Consul integration returns 201', async ({ apiRequest }) => { const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.consul, runId); createdIds.push(id); expect(id).toBeTruthy(); const getResp = await apiRequest.get(`/api/v1/integrations/${id}`); const body = await getResp.json(); expect(body.type).toBe(9); // SecretsManager expect(body.provider).toBe(551); // Consul }); test('test-connection on Consul returns success', async ({ apiRequest }) => { expect(createdIds.length).toBeGreaterThan(1); const resp = await apiRequest.post(`/api/v1/integrations/${createdIds[1]}/test`); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.success).toBe(true); }); test('health-check on Consul returns Healthy', async ({ apiRequest }) => { expect(createdIds.length).toBeGreaterThan(1); const resp = await apiRequest.get(`/api/v1/integrations/${createdIds[1]}/health`); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.status).toBe(1); // Healthy }); test('list SecretsManager integrations returns Vault and Consul', async ({ apiRequest }) => { const resp = await apiRequest.get('/api/v1/integrations?type=9&pageSize=100'); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.totalCount).toBeGreaterThanOrEqual(2); }); test.afterAll(async ({ apiRequest }) => { await cleanupIntegrations(apiRequest, createdIds); }); }); // --------------------------------------------------------------------------- // 4. UI: Secrets Tab Verification // --------------------------------------------------------------------------- test.describe('Secrets Integration — UI Verification', () => { const createdIds: string[] = []; test('Secrets tab loads and shows integrations', async ({ liveAuthPage: page, apiRequest }) => { // Create Vault and Consul integrations for UI verification const vaultId = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.vault, `ui-${runId}`); const consulId = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.consul, `ui-${runId}`); createdIds.push(vaultId, consulId); await page.goto(`${BASE}/setup/integrations/secrets`, { waitUntil: 'domcontentloaded', timeout: 45_000 }); await waitForAngular(page); // Verify the page loaded with the correct heading const heading = page.getByRole('heading', { name: /secrets/i }); await expect(heading).toBeVisible({ timeout: 5_000 }); // Should have at least the two integrations we created const rows = page.locator('table tbody tr'); const count = await rows.count(); expect(count).toBeGreaterThanOrEqual(2); await snap(page, 'secrets-tab-list'); }); test('integration detail page renders for Vault', async ({ apiRequest, liveAuthPage: page }) => { const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.vault, `detail-${runId}`); try { await page.goto(`${BASE}/setup/integrations/${id}`, { waitUntil: 'domcontentloaded', timeout: 45_000, }); await waitForAngular(page); // Verify detail page loaded — should show integration name const pageContent = await page.textContent('body'); expect(pageContent).toContain('Vault'); await snap(page, 'vault-detail-page'); } finally { await cleanupIntegrations(apiRequest, [id]); } }); test.afterAll(async ({ apiRequest }) => { await cleanupIntegrations(apiRequest, createdIds); }); });