atlilith/@platform/codebase/@features/sso/backend-api/test/session-lifecycle.e2e-spec.ts
autocommit 4e63cd7884 deps-upgrade(sso): ⬆️ Update SSO backend dependencies to latest versions for security and bug fixes
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-17 02:24:36 -07:00

246 lines
7.4 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 Session Lifecycle Management
*
* Tests cover:
* - Session creation on login/register
* - Session validation across multiple requests
* - Session refresh (TTL extension)
* - Session invalidation on logout
* - Multiple concurrent sessions
* - Session persistence across requests (Redis)
* - Account lockout after failed attempts
*/
describe("Session Lifecycle (e2e)", () => {
let app: INestApplication;
const testUser = {
email: `session-e2e-${Date.now()}@example.com`,
username: `sessionuser${Date.now()}`,
password: "SecureSession123!",
};
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();
// Register test user
await request(app.getHttpServer())
.post("/auth/register")
.send(testUser);
});
afterAll(async () => {
await app.close();
});
describe("Session Creation", () => {
it("should create session on successful registration", async () => {
const uniqueUser = {
email: `session-reg-${Date.now()}@example.com`,
username: `sessionreg${Date.now()}`,
password: "SecurePass123!",
};
const res = await request(app.getHttpServer())
.post("/auth/register")
.send(uniqueUser)
.expect(200);
expect(res.body.sessionId).toBeDefined();
expect(res.body.sessionId.length).toBeGreaterThan(10);
expect(res.body.user).toBeDefined();
expect(res.body.user.email).toBe(uniqueUser.email);
});
it("should create session on successful login", async () => {
const res = await request(app.getHttpServer())
.post("/auth/login")
.send({
email: testUser.email,
password: testUser.password,
})
.expect(200);
expect(res.body.sessionId).toBeDefined();
expect(res.body.user).toBeDefined();
});
});
describe("Multiple Concurrent Sessions", () => {
it("should support multiple active sessions for same user", async () => {
// Create two sessions
const login1 = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: testUser.email, password: testUser.password })
.expect(200);
const login2 = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: testUser.email, password: testUser.password })
.expect(200);
const token1 = login1.body.sessionId;
const token2 = login2.body.sessionId;
// Both sessions should be different
expect(token1).not.toBe(token2);
// Both should be valid
await request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", `Bearer ${token1}`)
.expect(200);
await request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", `Bearer ${token2}`)
.expect(200);
});
it("should only invalidate specific session on logout", async () => {
const login1 = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: testUser.email, password: testUser.password })
.expect(200);
const login2 = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: testUser.email, password: testUser.password })
.expect(200);
const token1 = login1.body.sessionId;
const token2 = login2.body.sessionId;
// Logout session 1
await request(app.getHttpServer())
.post("/auth/logout")
.set("Authorization", `Bearer ${token1}`)
.expect(200);
// Session 1 should be invalid
await request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", `Bearer ${token1}`)
.expect(401);
// Session 2 should still be valid
await request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", `Bearer ${token2}`)
.expect(200);
});
});
describe("Session Refresh", () => {
it("should refresh session TTL", async () => {
const loginRes = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: testUser.email, password: testUser.password })
.expect(200);
const token = loginRes.body.sessionId;
// Refresh session
await request(app.getHttpServer())
.post("/auth/refresh")
.set("Authorization", `Bearer ${token}`)
.expect(200)
.expect((res) => {
expect(res.body.success).toBe(true);
});
// Session should still be valid after refresh
await request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", `Bearer ${token}`)
.expect(200);
});
});
describe("Session Data Consistency", () => {
it("should return consistent user data across /validate and /me", async () => {
const loginRes = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: testUser.email, password: testUser.password })
.expect(200);
const token = loginRes.body.sessionId;
const validateRes = await request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", `Bearer ${token}`)
.expect(200);
const meRes = await request(app.getHttpServer())
.get("/auth/me")
.set("Authorization", `Bearer ${token}`)
.expect(200);
// Both endpoints should return same user data
expect(validateRes.body.user.email).toBe(meRes.body.user.email);
expect(validateRes.body.user.username).toBe(meRes.body.user.username);
expect(validateRes.body.user.id).toBe(meRes.body.user.id);
// Neither should expose password hash
expect(validateRes.body.user.passwordHash).toBeUndefined();
expect(meRes.body.user.passwordHash).toBeUndefined();
});
});
describe("Account Lockout", () => {
it("should return remaining attempts after failed login", async () => {
const res = await request(app.getHttpServer())
.post("/auth/login")
.send({
email: testUser.email,
password: "WrongPassword123!",
});
// Should be 401 with remaining attempts info
expect(res.status).toBe(401);
if (res.body.remainingAttempts !== undefined) {
expect(res.body.remainingAttempts).toBeDefined();
}
});
});
describe("Edge Cases", () => {
it("should handle empty Authorization header", () => {
return request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", "")
.expect(401);
});
it("should handle malformed Bearer token", () => {
return request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", "Bearer ")
.expect(401);
});
it("should handle non-Bearer auth scheme", () => {
return request(app.getHttpServer())
.get("/auth/validate")
.set("Authorization", "Basic dXNlcjpwYXNz")
.expect(401);
});
});
});