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>
213 lines
7.9 KiB
TypeScript
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);
|
|
});
|
|
});
|