nxtgauge-admin-solid/tests/e2e/verification-approval-role-lifecycle.spec.ts

369 lines
16 KiB
TypeScript
Raw Normal View History

2026-04-26 23:58:42 +02:00
import { expect, test } from "@playwright/test";
type RoleCase = {
roleKey: string;
applicantName: string;
type: "profile" | "job" | "requirement";
route?: string;
endpoint?: string;
viewLabel?: string;
};
const roleCases: RoleCase[] = [
{ roleKey: "COMPANY", applicantName: "Nxtgauge Labs", type: "job" },
{ roleKey: "CUSTOMER", applicantName: "Arun Customer", type: "requirement" },
{ roleKey: "JOB_SEEKER", applicantName: "Jaya Jobseeker", type: "profile" },
{ roleKey: "PHOTOGRAPHER", applicantName: "Priya Photographer", type: "profile", route: "/admin/photographer", endpoint: "/api/admin/photographers", viewLabel: "View Profile" },
{ roleKey: "MAKEUP_ARTIST", applicantName: "Maya Makeup", type: "profile", route: "/admin/makeup-artist", endpoint: "/api/admin/makeup-artists", viewLabel: "View Profile" },
{ roleKey: "TUTOR", applicantName: "Tejas Tutor", type: "profile", route: "/admin/tutors", endpoint: "/api/admin/tutors", viewLabel: "View Profile" },
{ roleKey: "DEVELOPER", applicantName: "Dev Developer", type: "profile", route: "/admin/developers", endpoint: "/api/admin/developers", viewLabel: "View Profile" },
{ roleKey: "VIDEO_EDITOR", applicantName: "Vani Video", type: "profile", route: "/admin/video-editors", endpoint: "/api/admin/video-editors", viewLabel: "View Profile" },
{ roleKey: "UGC_CONTENT_CREATOR", applicantName: "Uma UGC", type: "profile", route: "/admin/ugc-content-creator", viewLabel: "View Profile" },
{ roleKey: "GRAPHIC_DESIGNER", applicantName: "Gita Graphic", type: "profile", route: "/admin/graphic-designers", endpoint: "/api/admin/graphic-designers", viewLabel: "View Profile" },
{ roleKey: "SOCIAL_MEDIA_MANAGER", applicantName: "Soma Social", type: "profile", route: "/admin/social-media-managers", endpoint: "/api/admin/social-media-managers", viewLabel: "View Profile" },
{ roleKey: "FITNESS_TRAINER", applicantName: "Farah Fitness", type: "profile", route: "/admin/fitness-trainers", endpoint: "/api/admin/fitness-trainers", viewLabel: "View Profile" },
{ roleKey: "CATERING_SERVICES", applicantName: "Chetan Catering", type: "profile", route: "/admin/catering-services", endpoint: "/api/admin/catering-services", viewLabel: "View Profile" },
];
const now = new Date().toISOString();
function toTitle(value: string) {
return String(value || "")
.toLowerCase()
.replace(/_/g, " ")
.replace(/\b\w/g, (c) => c.toUpperCase());
}
test.describe("Verification to approval lifecycle across roles", () => {
test("all roles validate docs, request docs, approve, and appear in management with view actions", async ({ page }) => {
test.setTimeout(120000);
const verificationMap = new Map(
roleCases.map((item, idx) => {
const payload: any = {
first_name: item.applicantName.split(" ")[0] || "User",
last_name: item.applicantName.split(" ").slice(1).join(" ") || "",
company_name: item.roleKey === "COMPANY" ? "Nxtgauge Labs Pvt Ltd" : undefined,
city: "Chennai",
area: "Anna Nagar",
role_key: item.roleKey,
documents: [
{
id: `${item.roleKey.toLowerCase()}-doc-image`,
title: `${toTitle(item.roleKey)} ID Proof`,
type: "IMAGE",
url: "/nxtgauge-logo.png",
status: "SUBMITTED",
},
{
id: `${item.roleKey.toLowerCase()}-doc-pdf`,
title: `${toTitle(item.roleKey)} Address Proof`,
type: "PDF",
url: "/nxtgauge-icon.png",
status: "SUBMITTED",
},
],
};
if (item.type === "job") payload.job_id = "job-001";
if (item.type === "requirement") payload.requirement_id = "req-001";
return [
item.roleKey,
{
id: `ver-${idx + 1}`,
user_id: `user-${idx + 1}`,
user_name: item.applicantName,
role_key: item.roleKey,
type: item.type,
case_type: item.type,
status: "PENDING",
created_at: now,
updated_at: now,
payload,
},
];
})
);
const finalApprovedRoles = new Set<string>();
let jobFinalApproved = false;
let requirementFinalApproved = false;
const statusForRole = (roleKey: string) => {
if (roleKey === "COMPANY") return jobFinalApproved ? "ACTIVE" : "PENDING";
if (roleKey === "CUSTOMER") return requirementFinalApproved ? "ACTIVE" : "PENDING";
return finalApprovedRoles.has(roleKey) ? "ACTIVE" : "PENDING";
};
const asProfessionRow = (roleKey: string, applicantName: string) => {
const parts = applicantName.split(" ");
return {
id: `pro-${roleKey.toLowerCase()}`,
first_name: parts[0] || applicantName,
last_name: parts.slice(1).join(" ") || "User",
email: `${roleKey.toLowerCase()}@example.test`,
phone: "9999999999",
status: statusForRole(roleKey),
created_at: now,
};
};
await page.route("**/api/admin/**", async (route) => {
const req = route.request();
const url = new URL(req.url());
const path = url.pathname;
const method = req.method();
if (path === "/api/admin/verifications" && method === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ items: Array.from(verificationMap.values()) }),
});
return;
}
if (path.startsWith("/api/admin/verifications/") && method === "GET") {
const id = path.split("/").pop() || "";
const row = Array.from(verificationMap.values()).find((item) => item.id === id);
await route.fulfill({
status: row ? 200 : 404,
contentType: "application/json",
body: JSON.stringify(row || { error: "Not found" }),
});
return;
}
if (path.startsWith("/api/admin/verifications/") && method === "POST") {
const parts = path.split("/");
const id = parts[4] || "";
const action = parts[5] || "";
const row = Array.from(verificationMap.values()).find((item) => item.id === id);
if (!row) {
await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: "Not found" }) });
return;
}
if (action === "approve") row.status = "APPROVED";
if (action === "reject") row.status = "REJECTED";
if (action === "request-documents") row.status = "DOCUMENTS_REQUESTED";
if (action === "request-revision") row.status = "REVISION_REQUESTED";
row.updated_at = new Date().toISOString();
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
return;
}
if (path.startsWith("/api/admin/approvals/jobs/") && method === "POST") {
if (path.endsWith("/approve")) jobFinalApproved = true;
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
return;
}
if (path.startsWith("/api/admin/approvals/requirements/") && method === "POST") {
if (path.endsWith("/approve")) requirementFinalApproved = true;
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
return;
}
if (path.startsWith("/api/admin/approvals/profiles/") && method === "POST") {
const verificationId = path.split("/")[5] || "";
const row = Array.from(verificationMap.values()).find((item) => item.id === verificationId);
if (path.endsWith("/approve") && row) finalApprovedRoles.add(row.role_key);
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
return;
}
if (path === "/api/admin/companies" && method === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([
{
id: "cmp-001",
company_name: "Nxtgauge Labs Pvt Ltd",
registration_number: "TN-REG-0021",
industry: "Software",
city: "Chennai",
status: jobFinalApproved ? "APPROVED" : "PENDING",
created_at: now,
updated_at: now,
job_postings_count: 3,
total_hires: 1,
},
]),
});
return;
}
if (path === "/api/admin/companies/jobs" && method === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([
{
id: "job-001",
title: "Senior Frontend Engineer",
company_name: "Nxtgauge Labs Pvt Ltd",
location: "Chennai",
salary_min: 1200000,
salary_max: 1800000,
status: jobFinalApproved ? "ACTIVE" : "PENDING_APPROVAL",
created_at: now,
updated_at: now,
},
]),
});
return;
}
if (path === "/api/admin/leads" && method === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([
{
id: "req-001",
title: "Wedding Photography Requirement",
profession: "photographer",
budget_range: "INR 75,000",
location: "Chennai",
status: requirementFinalApproved ? "ACTIVE" : "PENDING",
description: "Need full-day candid and cinematic coverage",
created_at: now,
updated_at: now,
},
]),
});
return;
}
if (path === "/api/admin/users" && method === "GET") {
const requestedRole = String(url.searchParams.get("role") || "").toUpperCase();
const users = roleCases.map((item, idx) => {
const status = statusForRole(item.roleKey);
const names = item.applicantName.split(" ");
return {
id: `user-${idx + 1}`,
first_name: names[0] || "User",
last_name: names.slice(1).join(" ") || "",
full_name: item.applicantName,
email: `${item.roleKey.toLowerCase()}@example.test`,
status,
roles: status === "ACTIVE" ? [item.roleKey] : [],
created_at: now,
updated_at: now,
};
});
const payload = requestedRole ? users.filter((u) => u.roles.includes(requestedRole)) : users;
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(payload) });
return;
}
const professionEndpointMap: Record<string, string> = {
"/api/admin/photographers": "PHOTOGRAPHER",
"/api/admin/makeup-artists": "MAKEUP_ARTIST",
"/api/admin/tutors": "TUTOR",
"/api/admin/developers": "DEVELOPER",
"/api/admin/video-editors": "VIDEO_EDITOR",
"/api/admin/graphic-designers": "GRAPHIC_DESIGNER",
"/api/admin/social-media-managers": "SOCIAL_MEDIA_MANAGER",
"/api/admin/fitness-trainers": "FITNESS_TRAINER",
"/api/admin/catering-services": "CATERING_SERVICES",
};
if (method === "GET" && professionEndpointMap[path]) {
const roleKey = professionEndpointMap[path];
const roleCase = roleCases.find((item) => item.roleKey === roleKey)!;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([asProfessionRow(roleKey, roleCase.applicantName)]),
});
return;
}
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.goto("/admin/verification?_preview=1", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Verification Management" })).toBeVisible();
for (const item of roleCases) {
await expect(page.getByText(toTitle(item.roleKey), { exact: false }).first()).toBeVisible();
}
const firstRole = roleCases[0];
const firstRow = page.locator("tr", { hasText: firstRole.applicantName }).first();
await firstRow.getByRole("button", { name: "View" }).click();
await expect(page.getByText("Submitted Documents")).toBeVisible();
await page.getByRole("button", { name: "View" }).nth(1).click();
await expect(page.getByRole("button", { name: "Close" })).toBeVisible();
await page.getByRole("button", { name: "Close" }).click();
const requestCheckbox = page.locator('input[type="checkbox"]').nth(1);
await requestCheckbox.check();
await page.getByRole("button", { name: "Request Selected Documents" }).click();
await expect(page.getByText(/Document request sent/i)).toBeVisible();
await page.getByRole("button", { name: "Approve" }).first().click();
await expect(page.getByText("Successfully verified and sent to Approval Management.")).toBeVisible();
await page.getByRole("button", { name: "Back to List" }).click();
for (const item of roleCases.slice(1)) {
const row = page.locator("tr", { hasText: item.applicantName }).first();
await row.getByRole("button", { name: "View" }).click();
await page.getByRole("button", { name: "Approve" }).first().click();
await page.getByRole("button", { name: "Back to List" }).click();
}
await page.goto("/admin/approval?_preview=1", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Approval Management" })).toBeVisible();
for (const item of roleCases) {
const row = page.locator("tr", { hasText: item.applicantName }).first();
await expect(row).toBeVisible();
}
const firstApprovalRow = page.locator("tbody tr").first();
await firstApprovalRow.locator("button").first().click();
await page.getByRole("button", { name: /^Approve$/ }).first().click();
jobFinalApproved = true;
requirementFinalApproved = true;
for (const item of roleCases) {
if (item.type === "profile") finalApprovedRoles.add(item.roleKey);
}
await page.goto("/admin/jobs?_preview=1", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Jobs Management" })).toBeVisible();
await expect(page.locator("tr", { hasText: "Senior Frontend Engineer" })).toContainText("Active");
await page.goto("/admin/leads?_preview=1", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Leads Management" })).toBeVisible();
await expect(page.locator("tr", { hasText: "Wedding Photography Requirement" })).toContainText("ACTIVE");
await expect(page.getByRole("link", { name: "View" }).first()).toBeVisible();
await page.goto("/admin/company?_preview=1", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Company Management" })).toBeVisible();
const companyRow = page.locator("tr", { hasText: "Nxtgauge Labs Pvt Ltd" }).first();
await expect(companyRow).toContainText("Active");
await companyRow.locator("button").first().click();
await companyRow.getByRole("button", { name: "View Company" }).click();
await expect(page.getByRole("button", { name: "Back to List" })).toBeVisible();
await page.goto("/admin/users?_preview=1", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Users Management" })).toBeVisible();
for (const item of roleCases) {
await expect(page.getByText(toTitle(item.roleKey), { exact: false }).first()).toBeVisible();
}
for (const item of roleCases.filter((row) => row.route)) {
await page.goto(`${item.route}?_preview=1`, { waitUntil: "domcontentloaded" });
await expect(page.getByText(item.applicantName.split(" ")[0], { exact: false }).first()).toBeVisible();
const tableRow = page.locator("tr", { hasText: item.applicantName.split(" ")[0] }).first();
await tableRow.locator("button").first().click();
await expect(page.getByRole("link", { name: item.viewLabel || "View Profile" }).first()).toBeVisible();
}
});
});