Files
git.stella-ops.org/src/Web/StellaOps.Web/e2e/identity-providers.e2e.spec.ts

330 lines
11 KiB
TypeScript

import { test, expect } from './fixtures/auth.fixture';
import { navigateAndWait, assertPageHasContent } from './helpers/nav.helper';
/**
* E2E tests for the Identity Providers settings page.
*
* These tests use the auth fixture which mocks the backend API.
* The MockIdentityProviderClient in app.config.ts serves mock data,
* so these tests verify UI rendering and interaction without a live backend.
*/
const sampleLdapProvider = {
id: 'e2e-ldap-id',
name: 'E2E LDAP',
type: 'ldap',
enabled: true,
configuration: {
host: 'ldap.e2e.test',
port: '389',
bindDn: 'cn=admin,dc=e2e,dc=test',
bindPassword: 'secret',
searchBase: 'dc=e2e,dc=test',
},
description: 'E2E LDAP test provider',
healthStatus: 'healthy',
createdAt: '2026-02-24T00:00:00Z',
updatedAt: '2026-02-24T00:00:00Z',
createdBy: 'e2e-admin',
updatedBy: 'e2e-admin',
};
const sampleSamlProvider = {
id: 'e2e-saml-id',
name: 'E2E SAML',
type: 'saml',
enabled: true,
configuration: {
spEntityId: 'stellaops-e2e-sp',
idpEntityId: 'https://idp.e2e.test',
idpSsoUrl: 'https://idp.e2e.test/sso',
},
description: 'E2E SAML test provider',
healthStatus: 'healthy',
createdAt: '2026-02-24T00:00:00Z',
updatedAt: '2026-02-24T00:00:00Z',
createdBy: 'e2e-admin',
updatedBy: 'e2e-admin',
};
const sampleOidcProvider = {
id: 'e2e-oidc-id',
name: 'E2E OIDC',
type: 'oidc',
enabled: false,
configuration: {
authority: 'https://oidc.e2e.test',
clientId: 'stellaops-e2e',
clientSecret: 'e2e-secret',
},
description: 'E2E OIDC test provider',
healthStatus: 'disabled',
createdAt: '2026-02-24T00:00:00Z',
updatedAt: '2026-02-24T00:00:00Z',
createdBy: 'e2e-admin',
updatedBy: 'e2e-admin',
};
test.describe('Identity Providers Settings Page', () => {
test('should load page and display content', async ({ authenticatedPage: page }) => {
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error' && /NG0\d{3,4}/.test(msg.text())) {
errors.push(msg.text());
}
});
// Mock the identity providers API
await page.route('**/api/v1/platform/identity-providers', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([sampleLdapProvider, sampleSamlProvider, sampleOidcProvider]),
});
} else {
route.continue();
}
});
await page.route('**/api/v1/platform/identity-providers/types', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ type: 'standard', displayName: 'Standard', requiredFields: [], optionalFields: [] },
{
type: 'ldap',
displayName: 'LDAP / Active Directory',
requiredFields: [
{ name: 'host', displayName: 'Host', fieldType: 'text', defaultValue: null, description: null },
{ name: 'port', displayName: 'Port', fieldType: 'number', defaultValue: '389', description: null },
{ name: 'bindDn', displayName: 'Bind DN', fieldType: 'text', defaultValue: null, description: null },
{ name: 'bindPassword', displayName: 'Bind Password', fieldType: 'secret', defaultValue: null, description: null },
{ name: 'searchBase', displayName: 'Search Base', fieldType: 'text', defaultValue: null, description: null },
],
optionalFields: [],
},
{
type: 'saml',
displayName: 'SAML 2.0',
requiredFields: [
{ name: 'spEntityId', displayName: 'SP Entity ID', fieldType: 'text', defaultValue: null, description: null },
{ name: 'idpEntityId', displayName: 'IdP Entity ID', fieldType: 'text', defaultValue: null, description: null },
],
optionalFields: [],
},
{
type: 'oidc',
displayName: 'OpenID Connect',
requiredFields: [
{ name: 'authority', displayName: 'Authority', fieldType: 'url', defaultValue: null, description: null },
{ name: 'clientId', displayName: 'Client ID', fieldType: 'text', defaultValue: null, description: null },
],
optionalFields: [],
},
]),
});
});
await navigateAndWait(page, '/settings/identity-providers', { timeout: 30_000 });
await page.waitForTimeout(2000);
await assertPageHasContent(page);
expect(errors).toHaveLength(0);
});
test('should show empty state with no providers', async ({ authenticatedPage: page }) => {
await page.route('**/api/v1/platform/identity-providers', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
} else {
route.continue();
}
});
await page.route('**/api/v1/platform/identity-providers/types', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
});
await navigateAndWait(page, '/settings/identity-providers', { timeout: 30_000 });
await page.waitForTimeout(2000);
const emptyState = page.locator('.idp-empty-state');
if (await emptyState.isVisible({ timeout: 5000 }).catch(() => false)) {
await expect(emptyState).toContainText('No identity providers');
}
});
test('should display provider cards with correct type badges', async ({ authenticatedPage: page }) => {
await page.route('**/api/v1/platform/identity-providers', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([sampleLdapProvider, sampleSamlProvider, sampleOidcProvider]),
});
} else {
route.continue();
}
});
await page.route('**/api/v1/platform/identity-providers/types', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
});
await navigateAndWait(page, '/settings/identity-providers', { timeout: 30_000 });
await page.waitForTimeout(2000);
// Verify provider names are visible
const ldapName = page.locator('text=E2E LDAP').first();
const samlName = page.locator('text=E2E SAML').first();
const oidcName = page.locator('text=E2E OIDC').first();
if (await ldapName.isVisible({ timeout: 5000 }).catch(() => false)) {
await expect(ldapName).toBeVisible();
}
if (await samlName.isVisible({ timeout: 5000 }).catch(() => false)) {
await expect(samlName).toBeVisible();
}
if (await oidcName.isVisible({ timeout: 5000 }).catch(() => false)) {
await expect(oidcName).toBeVisible();
}
});
test('should open add provider wizard on button click', async ({ authenticatedPage: page }) => {
await page.route('**/api/v1/platform/identity-providers', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
} else {
route.continue();
}
});
await page.route('**/api/v1/platform/identity-providers/types', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ type: 'standard', displayName: 'Standard', requiredFields: [], optionalFields: [] },
{ type: 'ldap', displayName: 'LDAP', requiredFields: [], optionalFields: [] },
{ type: 'saml', displayName: 'SAML', requiredFields: [], optionalFields: [] },
{ type: 'oidc', displayName: 'OIDC', requiredFields: [], optionalFields: [] },
]),
});
});
await navigateAndWait(page, '/settings/identity-providers', { timeout: 30_000 });
await page.waitForTimeout(2000);
const addButton = page.locator('button:has-text("Add Provider")').first();
if (await addButton.isVisible({ timeout: 5000 }).catch(() => false)) {
await addButton.click();
await page.waitForTimeout(1000);
// Wizard should be visible
const wizard = page.locator('app-add-provider-wizard, .wizard-overlay').first();
if (await wizard.isVisible({ timeout: 5000 }).catch(() => false)) {
await expect(wizard).toBeVisible();
}
}
});
test('should handle enable/disable toggle', async ({ authenticatedPage: page }) => {
await page.route('**/api/v1/platform/identity-providers', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([sampleLdapProvider]),
});
} else {
route.continue();
}
});
await page.route('**/api/v1/platform/identity-providers/types', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
});
// Mock disable endpoint
await page.route('**/api/v1/platform/identity-providers/*/disable', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ...sampleLdapProvider, enabled: false, healthStatus: 'disabled' }),
});
});
await navigateAndWait(page, '/settings/identity-providers', { timeout: 30_000 });
await page.waitForTimeout(2000);
// Find disable/enable toggle button
const toggleBtn = page.locator('button:has-text("Disable")').first();
if (await toggleBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
await toggleBtn.click();
await page.waitForTimeout(1000);
}
});
test('should handle delete provider', async ({ authenticatedPage: page }) => {
await page.route('**/api/v1/platform/identity-providers', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([sampleLdapProvider]),
});
} else {
route.continue();
}
});
await page.route('**/api/v1/platform/identity-providers/types', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
});
// Mock delete endpoint
await page.route('**/api/v1/platform/identity-providers/*', (route) => {
if (route.request().method() === 'DELETE') {
route.fulfill({ status: 204 });
} else {
route.continue();
}
});
await navigateAndWait(page, '/settings/identity-providers', { timeout: 30_000 });
await page.waitForTimeout(2000);
const deleteBtn = page.locator('button:has-text("Delete")').first();
if (await deleteBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
await deleteBtn.click();
await page.waitForTimeout(1000);
}
});
});