368 lines
16 KiB
TypeScript
368 lines
16 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|