Files
livedash-node/tests/integration/user-invitation.test.ts
Kaj Kowalski 60d1b72aba feat: implement platform management system with authentication and dashboard
- Add PlatformUser model with roles (SUPER_ADMIN, ADMIN, SUPPORT)
- Implement platform authentication with NextAuth
- Create platform dashboard showing companies, users, and sessions
- Add platform API endpoints for company management
- Update landing page with SaaS design
- Include test improvements and accessibility updates
2025-06-28 12:41:50 +02:00

458 lines
11 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { createMocks } from "node-mocks-http";
import { GET, POST } from "@/app/api/dashboard/users/route";
import { prisma } from "@/lib/prisma";
// Mock the database
const mockUser = {
id: "admin-user-id",
email: "admin@example.com",
role: "ADMIN",
companyId: "test-company-id",
};
const mockCompany = {
id: "test-company-id",
name: "Test Company",
};
const mockExistingUsers = [
{
id: "user-1",
email: "existing@example.com",
role: "USER",
companyId: "test-company-id",
},
{
id: "user-2",
email: "admin@example.com",
role: "ADMIN",
companyId: "test-company-id",
},
];
describe("User Invitation Integration Tests", () => {
beforeEach(() => {
// Mock Prisma methods
prisma.user = {
findMany: async () => mockExistingUsers,
findUnique: async () => mockUser,
create: async (data: any) => ({
id: "new-user-id",
email: data.data.email,
role: data.data.role,
companyId: data.data.companyId,
createdAt: new Date(),
updatedAt: new Date(),
passwordHash: null,
isActive: true,
lastLoginAt: null,
}),
} as any;
prisma.company = {
findUnique: async () => mockCompany,
} as any;
});
afterEach(() => {
// Clean up any mocks
});
describe("GET /api/dashboard/users", () => {
it("should return users for authenticated admin", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"content-type": "application/json",
},
});
// Mock authentication
(req as any).auth = {
user: mockUser,
};
await GET(req as any);
expect(res._getStatusCode()).toBe(200);
const data = JSON.parse(res._getData());
expect(data.users).toHaveLength(2);
expect(data.users[0].email).toBe("existing@example.com");
});
it("should deny access for non-admin users", async () => {
const { req, res } = createMocks({
method: "GET",
});
// Mock non-admin user
(req as any).auth = {
user: { ...mockUser, role: "USER" },
};
await GET(req as any);
expect(res._getStatusCode()).toBe(403);
const data = JSON.parse(res._getData());
expect(data.error).toBe("Access denied. Admin role required.");
});
it("should deny access for unauthenticated requests", async () => {
const { req, res } = createMocks({
method: "GET",
});
await GET(req as any);
expect(res._getStatusCode()).toBe(401);
const data = JSON.parse(res._getData());
expect(data.error).toBe("Unauthorized");
});
});
describe("POST /api/dashboard/users", () => {
it("should successfully invite a new user", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "newuser@example.com",
role: "USER",
},
headers: {
"content-type": "application/json",
},
});
// Mock authentication
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(201);
const data = JSON.parse(res._getData());
expect(data.message).toBe("User invited successfully");
expect(data.user.email).toBe("newuser@example.com");
expect(data.user.role).toBe("USER");
});
it("should prevent duplicate email invitations", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "existing@example.com",
role: "USER",
},
});
// Mock Prisma to simulate existing user
prisma.user.findUnique = async () => mockExistingUsers[0] as any;
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData());
expect(data.error).toBe("User with this email already exists");
});
it("should validate email format", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "invalid-email",
role: "USER",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData());
expect(data.error).toContain("Invalid email format");
});
it("should validate role values", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "test@example.com",
role: "INVALID_ROLE",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData());
expect(data.error).toContain("Invalid role");
});
it("should require email field", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
role: "USER",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData());
expect(data.error).toContain("Email is required");
});
it("should require role field", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "test@example.com",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData());
expect(data.error).toContain("Role is required");
});
it("should deny access for non-admin users", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "test@example.com",
role: "USER",
},
});
(req as any).auth = {
user: { ...mockUser, role: "USER" },
};
await POST(req as any);
expect(res._getStatusCode()).toBe(403);
const data = JSON.parse(res._getData());
expect(data.error).toBe("Access denied. Admin role required.");
});
it("should handle database errors gracefully", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "test@example.com",
role: "USER",
},
});
// Mock database error
prisma.user.create = async () => {
throw new Error("Database connection failed");
};
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(500);
const data = JSON.parse(res._getData());
expect(data.error).toBe("Internal server error");
});
it("should handle different role types correctly", async () => {
const roles = ["USER", "ADMIN", "AUDITOR"];
for (const role of roles) {
const { req, res } = createMocks({
method: "POST",
body: {
email: `${role.toLowerCase()}@example.com`,
role: role,
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(201);
const data = JSON.parse(res._getData());
expect(data.user.role).toBe(role);
}
});
it("should associate user with correct company", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "newuser@example.com",
role: "USER",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(201);
const data = JSON.parse(res._getData());
expect(data.user.companyId).toBe(mockUser.companyId);
});
});
describe("Email Validation Edge Cases", () => {
it("should handle very long email addresses", async () => {
const longEmail = "a".repeat(250) + "@example.com";
const { req, res } = createMocks({
method: "POST",
body: {
email: longEmail,
role: "USER",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData());
expect(data.error).toContain("Email too long");
});
it("should handle special characters in email", async () => {
const specialEmail = "test+tag@example-domain.co.uk";
const { req, res } = createMocks({
method: "POST",
body: {
email: specialEmail,
role: "USER",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(201);
const data = JSON.parse(res._getData());
expect(data.user.email).toBe(specialEmail);
});
it("should normalize email case", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
email: "TEST@EXAMPLE.COM",
role: "USER",
},
});
(req as any).auth = {
user: mockUser,
};
await POST(req as any);
expect(res._getStatusCode()).toBe(201);
const data = JSON.parse(res._getData());
expect(data.user.email).toBe("test@example.com");
});
});
describe("Concurrent Request Handling", () => {
it("should handle concurrent invitations for the same email", async () => {
const email = "concurrent@example.com";
// Create multiple requests for the same email
const requests = Array.from({ length: 3 }, () => {
const { req } = createMocks({
method: "POST",
body: { email, role: "USER" },
});
(req as any).auth = { user: mockUser };
return req;
});
// Execute requests concurrently
const results = await Promise.allSettled(
requests.map(req => POST(req as any))
);
// Only one should succeed, others should fail with conflict
const successful = results.filter(r => r.status === "fulfilled").length;
expect(successful).toBe(1);
});
});
describe("Rate Limiting", () => {
it("should handle multiple rapid invitations", async () => {
const emails = [
"user1@example.com",
"user2@example.com",
"user3@example.com",
"user4@example.com",
"user5@example.com",
];
const results = [];
for (const email of emails) {
const { req, res } = createMocks({
method: "POST",
body: { email, role: "USER" },
});
(req as any).auth = { user: mockUser };
await POST(req as any);
results.push({
email,
status: res._getStatusCode(),
data: JSON.parse(res._getData()),
});
}
// All should succeed (no rate limiting implemented yet)
results.forEach(result => {
expect(result.status).toBe(201);
expect(result.data.user.email).toBe(result.email);
});
});
});
});