231 lines
7 KiB
TypeScript
231 lines
7 KiB
TypeScript
import { Test, TestingModule } from "@nestjs/testing";
|
|
import { INestApplication, ValidationPipe } from "@nestjs/common";
|
|
import { AppModule } from "@/src/app.module";
|
|
|
|
const request = require("supertest");
|
|
|
|
/**
|
|
* E2E Tests for SSO UI Pages (Handlebars Views)
|
|
*
|
|
* Tests server-rendered HTML pages for login, register, MFA challenge, and MFA setup.
|
|
* These pages are the entry point for all authentication flows.
|
|
*
|
|
* Prerequisites:
|
|
* 1. Start test services: docker-compose -f test/docker-compose.yml up -d
|
|
* 2. Run: pnpm test:e2e
|
|
*/
|
|
describe("SSO UI Pages (e2e)", () => {
|
|
let app: INestApplication;
|
|
|
|
beforeAll(async () => {
|
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
}).compile();
|
|
|
|
app = moduleFixture.createNestApplication();
|
|
app.useGlobalPipes(
|
|
new ValidationPipe({
|
|
whitelist: true,
|
|
transform: true,
|
|
forbidNonWhitelisted: true,
|
|
}),
|
|
);
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
describe("GET /login", () => {
|
|
it("should render login page with correct title", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/login")
|
|
.expect(200)
|
|
.expect("Content-Type", /html/)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("Login - lilith.platform");
|
|
expect(res.text).toContain('<form id="login-form"');
|
|
expect(res.text).toContain('type="email"');
|
|
expect(res.text).toContain('type="password"');
|
|
expect(res.text).toContain("btn-primary");
|
|
});
|
|
});
|
|
|
|
it("should include OAuth buttons (Google, GitHub)", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/login")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("btn-google");
|
|
expect(res.text).toContain("btn-github");
|
|
expect(res.text).toContain("or continue with");
|
|
});
|
|
});
|
|
|
|
it("should include link to register page", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/login")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain('href="/register"');
|
|
expect(res.text).toContain("Don't have an account?");
|
|
});
|
|
});
|
|
|
|
it("should display error message when error query param is set", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/login?error=Invalid+credentials")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("error-message");
|
|
expect(res.text).toContain("Invalid credentials");
|
|
});
|
|
});
|
|
|
|
it("should pass redirect_uri to OAuth links", () => {
|
|
const redirectUri = "http://localhost:5201/callback";
|
|
return request(app.getHttpServer())
|
|
.get(`/login?redirect_uri=${encodeURIComponent(redirectUri)}`)
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain(`data-redirect-uri="${redirectUri}"`);
|
|
});
|
|
});
|
|
|
|
it("should include password visibility toggle", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/login")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("toggle-password");
|
|
expect(res.text).toContain("Toggle password visibility");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("GET /register", () => {
|
|
it("should render registration page with correct title", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register")
|
|
.expect(200)
|
|
.expect("Content-Type", /html/)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("Register - lilith.platform");
|
|
expect(res.text).toContain('<form id="register-form"');
|
|
expect(res.text).toContain('id="email"');
|
|
expect(res.text).toContain('id="username"');
|
|
expect(res.text).toContain('id="password"');
|
|
expect(res.text).toContain('id="confirmPassword"');
|
|
});
|
|
});
|
|
|
|
it("should include link to login page", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain('href="/login"');
|
|
expect(res.text).toContain("Already have an account?");
|
|
});
|
|
});
|
|
|
|
it("should include OAuth buttons", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("btn-google");
|
|
expect(res.text).toContain("btn-github");
|
|
});
|
|
});
|
|
|
|
it("should accept role query param for provider registration", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register?role=provider")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain('value="provider"');
|
|
});
|
|
});
|
|
|
|
it("should accept role query param for client registration", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register?role=client")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain('value="client"');
|
|
});
|
|
});
|
|
|
|
it("should sanitize invalid role query params", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register?role=admin")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
// Should NOT include hidden role field for invalid roles
|
|
expect(res.text).not.toContain('value="admin"');
|
|
});
|
|
});
|
|
|
|
it("should display error when error query param is set", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/register?error=Email+already+registered")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("error-message");
|
|
expect(res.text).toContain("Email already registered");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("GET /mfa-challenge", () => {
|
|
it("should render MFA challenge page", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/mfa-challenge")
|
|
.expect(200)
|
|
.expect("Content-Type", /html/)
|
|
.expect((res) => {
|
|
expect(res.text).toContain(
|
|
"Two-Factor Authentication - lilith.platform",
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("GET /mfa-setup", () => {
|
|
it("should render MFA setup page", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/mfa-setup")
|
|
.expect(200)
|
|
.expect("Content-Type", /html/)
|
|
.expect((res) => {
|
|
expect(res.text).toContain("MFA Settings - lilith.platform");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("GET /health", () => {
|
|
it("should return healthy status", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/health")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.body.status).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("CSRF Token", () => {
|
|
it("should return a CSRF token", () => {
|
|
return request(app.getHttpServer())
|
|
.get("/auth/csrf-token")
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.body.token).toBeDefined();
|
|
expect(res.body.token.length).toBeGreaterThan(10);
|
|
expect(res.body.expiresIn).toBe(3600);
|
|
});
|
|
});
|
|
});
|
|
});
|