Files
git.stella-ops.org/src/Web/StellaOps.Web/tests/e2e/integrations/vault-consul-secrets.e2e.spec.ts
master 744637c7c6 Replace fixed waits with waitForAngular in UI tests
The 3s waitForTimeout after page.goto wasn't enough for Angular to
bootstrap and render content. Replace with waitForAngular() helper
that waits for actual DOM elements (nav, headings) up to 15s, with
5s fallback.

32 calls updated across 10 test files.

Also adds waitForAngular to helpers.ts export.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:31:34 +03:00

213 lines
7.9 KiB
TypeScript

/**
* 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);
});
});