mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 03:53:13 +00:00
421 lines
13 KiB
TypeScript
421 lines
13 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||
import { API_PATHS } from "@/api/api-paths";
|
||
|
||
const apiGet = vi.fn();
|
||
const apiPost = vi.fn();
|
||
const apiPut = vi.fn();
|
||
const apiDelete = vi.fn();
|
||
|
||
vi.mock("@/api/api", () => ({
|
||
apiGet: (...args: unknown[]) => apiGet(...args),
|
||
apiPost: (...args: unknown[]) => apiPost(...args),
|
||
apiPut: (...args: unknown[]) => apiPut(...args),
|
||
apiDelete: (...args: unknown[]) => apiDelete(...args),
|
||
}));
|
||
|
||
import { vendorsApi } from "@/domain/vendors/api/vendors-api";
|
||
|
||
describe("vendorsApi.getList", () => {
|
||
beforeEach(() => {
|
||
apiGet.mockReset();
|
||
apiPost.mockReset();
|
||
apiPut.mockReset();
|
||
apiDelete.mockReset();
|
||
apiGet.mockResolvedValue({
|
||
Data: [
|
||
{
|
||
Id: 7,
|
||
CompanyId: "co-1",
|
||
CompanyName: "Gateway Plumbing",
|
||
ContactName: "Adam Whyte",
|
||
Email: "dispatch@gateway.test",
|
||
Phone: "314-555-0198",
|
||
Address: "1 Market St",
|
||
City: "St. Louis",
|
||
State: "MO",
|
||
IsActive: false,
|
||
PreferredContact: "Email",
|
||
Technicians: [
|
||
{
|
||
Id: 7,
|
||
ContactName: "Adam Whyte",
|
||
Email: "adam@gateway.test",
|
||
Phone: "314-555-0198",
|
||
TradeSpecialties: "Plumbing",
|
||
IsActive: false,
|
||
TotalJobs: 3,
|
||
},
|
||
],
|
||
},
|
||
],
|
||
TotalCount: 1,
|
||
});
|
||
});
|
||
|
||
it("forwards the status filter and maps list location fields", async () => {
|
||
const result = await vendorsApi.getList({ search: "gateway", isActive: false, page: 1 });
|
||
|
||
expect(apiGet).toHaveBeenCalledWith(API_PATHS.vendor.getList, {
|
||
searchParams: { search: "gateway", isActive: "false", page: 1, pageSize: 12 },
|
||
});
|
||
expect(result.items[0]).toMatchObject({
|
||
address: "1 Market St",
|
||
city: "St. Louis",
|
||
state: "MO",
|
||
isActive: false,
|
||
companyId: "co-1",
|
||
preferredContact: "Email",
|
||
technicians: [
|
||
expect.objectContaining({
|
||
id: 7,
|
||
email: "adam@gateway.test",
|
||
phone: "314-555-0198",
|
||
totalJobs: 3,
|
||
}),
|
||
],
|
||
});
|
||
});
|
||
|
||
it("synthesizes one technician for a legacy ungrouped response", async () => {
|
||
apiGet.mockResolvedValueOnce({
|
||
Data: [
|
||
{
|
||
Id: 9,
|
||
CompanyName: "Legacy Co",
|
||
ContactName: "Legacy Tech",
|
||
Email: "legacy@example.test",
|
||
Phone: "555-0109",
|
||
IsActive: true,
|
||
},
|
||
],
|
||
TotalCount: 1,
|
||
});
|
||
|
||
const result = await vendorsApi.getList();
|
||
|
||
expect(result.items[0]?.technicians).toEqual([
|
||
expect.objectContaining({ id: 9, contactName: "Legacy Tech", phone: "555-0109" }),
|
||
]);
|
||
});
|
||
|
||
it("forwards all selected facets as repeated indexed query parameters", async () => {
|
||
await vendorsApi.getList({
|
||
companies: ["Gateway Plumbing", "Metro HVAC"],
|
||
trades: ["Plumbing"],
|
||
locations: ["St. Louis, MO"],
|
||
jobBuckets: ["50-99"],
|
||
});
|
||
|
||
expect(apiGet).toHaveBeenCalledWith(API_PATHS.vendor.getList, {
|
||
searchParams: {
|
||
search: "",
|
||
page: 1,
|
||
pageSize: 12,
|
||
"companies[0]": "Gateway Plumbing",
|
||
"companies[1]": "Metro HVAC",
|
||
"trades[0]": "Plumbing",
|
||
"locations[0]": "St. Louis, MO",
|
||
"jobBuckets[0]": "50-99",
|
||
},
|
||
});
|
||
});
|
||
|
||
it("maps company profiles and labels returned by the facets endpoint", async () => {
|
||
apiGet.mockResolvedValueOnce({
|
||
data: {
|
||
companies: [
|
||
{
|
||
name: "Gateway Plumbing",
|
||
companyPhone: "314-555-0100",
|
||
email: "dispatch@gateway.test",
|
||
address: "1 Market St",
|
||
city: "St. Louis",
|
||
state: "MO",
|
||
zip: "63101",
|
||
googleMapsUrl: "https://maps.google.com/gateway",
|
||
},
|
||
],
|
||
trades: ["Plumbing"],
|
||
locations: [{ city: "St. Louis", state: "MO" }],
|
||
jobBuckets: [{ id: "50-99", label: "50–99" }],
|
||
},
|
||
});
|
||
|
||
const facets = await vendorsApi.getFacets();
|
||
|
||
expect(apiGet).toHaveBeenCalledWith(API_PATHS.vendor.facets, { searchParams: {} });
|
||
expect(facets.companies[0]).toMatchObject({
|
||
name: "Gateway Plumbing",
|
||
companyPhone: "314-555-0100",
|
||
googleMapsUrl: "https://maps.google.com/gateway",
|
||
});
|
||
expect(facets.locations[0]?.label).toBe("St. Louis, MO");
|
||
expect(facets.jobBuckets[0]).toEqual({ id: "50-99", label: "50–99" });
|
||
});
|
||
|
||
it("serializes the requested facet scope as an isActive query parameter", async () => {
|
||
apiGet.mockResolvedValue({
|
||
data: { companies: [], trades: [], locations: [], jobBuckets: [] },
|
||
});
|
||
|
||
await vendorsApi.getFacets({ isActive: true });
|
||
await vendorsApi.getFacets({ isActive: false });
|
||
|
||
expect(apiGet).toHaveBeenNthCalledWith(1, API_PATHS.vendor.facets, {
|
||
searchParams: { isActive: "true" },
|
||
});
|
||
expect(apiGet).toHaveBeenNthCalledWith(2, API_PATHS.vendor.facets, {
|
||
searchParams: { isActive: "false" },
|
||
});
|
||
});
|
||
|
||
it("sends all editable prototype fields when creating and updating", async () => {
|
||
const values = {
|
||
companyId: "42",
|
||
companyName: "Gateway Plumbing",
|
||
contactName: "Adam Whyte",
|
||
phone: "+1 314-555-0198",
|
||
companyPhone: "314-555-0100",
|
||
email: "dispatch@gateway.test",
|
||
address: "1 Market St",
|
||
city: "St. Louis",
|
||
state: "MO",
|
||
zip: "63101",
|
||
tradeSpecialties: "Plumbing, HVAC",
|
||
googleMapsUrl: "https://maps.google.com/gateway",
|
||
notes: "Preferred after-hours vendor",
|
||
totalJobs: 72,
|
||
isActive: true,
|
||
};
|
||
apiPost.mockResolvedValueOnce({ data: values });
|
||
apiPut.mockResolvedValueOnce({ data: { ...values, isActive: false } });
|
||
|
||
await vendorsApi.create(values);
|
||
await vendorsApi.update(7, { ...values, isActive: false });
|
||
|
||
expect(apiPost).toHaveBeenCalledWith(
|
||
API_PATHS.rest.vendors,
|
||
expect.objectContaining({
|
||
companyName: "Gateway Plumbing",
|
||
notes: values.notes,
|
||
companyId: 42,
|
||
phone: "(314) 555-0198",
|
||
companyPhone: "(314) 555-0100",
|
||
}),
|
||
);
|
||
expect(apiPost.mock.calls[0]?.[1]).not.toHaveProperty("preferredContact");
|
||
expect(apiPut).toHaveBeenCalledWith(
|
||
`${API_PATHS.rest.vendors}/7`,
|
||
expect.objectContaining({ companyName: "Gateway Plumbing", isActive: false }),
|
||
);
|
||
expect(apiPut.mock.calls[0]?.[1]).not.toHaveProperty("preferredContact");
|
||
expect(apiPost.mock.calls[0]?.[1]).not.toHaveProperty("totalJobs");
|
||
expect(apiPut.mock.calls[0]?.[1]).not.toHaveProperty("totalJobs");
|
||
});
|
||
|
||
it("sends a null companyId when free-text company has no company link", async () => {
|
||
apiPost.mockResolvedValueOnce({ data: {} });
|
||
await vendorsApi.create({
|
||
companyName: "Independent Vendor LLC",
|
||
contactName: "Indy Tech",
|
||
});
|
||
|
||
expect(apiPost).toHaveBeenCalledWith(
|
||
API_PATHS.rest.vendors,
|
||
expect.objectContaining({ companyName: "Independent Vendor LLC", companyId: null }),
|
||
);
|
||
});
|
||
|
||
it("does not send a non-numeric company id to the integer backend contract", async () => {
|
||
apiPost.mockResolvedValueOnce({ data: {} });
|
||
|
||
await vendorsApi.create({
|
||
companyId: "co-1",
|
||
companyName: "Gateway Plumbing",
|
||
contactName: "Adam Whyte",
|
||
});
|
||
|
||
expect(apiPost).toHaveBeenCalledWith(
|
||
API_PATHS.rest.vendors,
|
||
expect.objectContaining({ companyId: null }),
|
||
);
|
||
});
|
||
|
||
it("preserves a legacy phone on read and omits it from an unrelated update", async () => {
|
||
apiGet.mockResolvedValueOnce({
|
||
data: {
|
||
id: 7,
|
||
companyName: "Legacy Vendor",
|
||
contactName: "Taylor",
|
||
phone: "555-1234 ext 9",
|
||
},
|
||
});
|
||
apiPut.mockResolvedValueOnce({ data: {} });
|
||
|
||
const vendor = await vendorsApi.getById(7);
|
||
expect(vendor.phone).toBe("555-1234 ext 9");
|
||
|
||
await vendorsApi.update(7, { notes: "Updated only the notes" });
|
||
expect(apiPut).toHaveBeenCalledWith(`${API_PATHS.rest.vendors}/7`, {
|
||
notes: "Updated only the notes",
|
||
});
|
||
});
|
||
|
||
it("sends only changed fields and canonicalizes a supplied phone", async () => {
|
||
apiPut.mockResolvedValueOnce({ data: {} });
|
||
|
||
await vendorsApi.update(7, {
|
||
isActive: false,
|
||
companyId: null,
|
||
phone: "314-555-0198",
|
||
});
|
||
|
||
expect(apiPut).toHaveBeenCalledWith(`${API_PATHS.rest.vendors}/7`, {
|
||
isActive: false,
|
||
companyId: null,
|
||
phone: "(314) 555-0198",
|
||
});
|
||
});
|
||
|
||
it("maps the deactivation-impact payload including linked work orders", async () => {
|
||
apiGet.mockResolvedValueOnce({
|
||
data: {
|
||
vendorId: 7,
|
||
canDeactivate: false,
|
||
openWorkOrders: [
|
||
{
|
||
workOrderId: 101,
|
||
workOrderNumber: "WO-101",
|
||
workOrderTitle: "Boiler repair",
|
||
status: "Dispatched",
|
||
scheduledDate: "2025-01-02",
|
||
dispatchId: 501,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
|
||
const impact = await vendorsApi.getDeactivationImpact(7);
|
||
|
||
expect(apiGet).toHaveBeenCalledWith(
|
||
`${API_PATHS.rest.vendors}/7/${API_PATHS.vendor.deactivationImpact}`,
|
||
);
|
||
expect(impact).toEqual({
|
||
vendorId: 7,
|
||
canDeactivate: false,
|
||
openWorkOrders: [
|
||
{
|
||
workOrderId: 101,
|
||
workOrderNumber: "WO-101",
|
||
workOrderTitle: "Boiler repair",
|
||
status: "Dispatched",
|
||
scheduledDate: "2025-01-02",
|
||
dispatchId: 501,
|
||
},
|
||
],
|
||
});
|
||
});
|
||
|
||
it("deletes a vendor by id and surfaces the 409 error without retry swallowing", async () => {
|
||
apiDelete.mockResolvedValueOnce(undefined);
|
||
await vendorsApi.delete(7);
|
||
expect(apiDelete).toHaveBeenCalledWith(`${API_PATHS.rest.vendors}/7`);
|
||
|
||
const conflict = Object.assign(new Error("Conflict"), { response: { status: 409 } });
|
||
apiDelete.mockRejectedValueOnce(conflict);
|
||
await expect(vendorsApi.delete(8)).rejects.toThrow("Conflict");
|
||
});
|
||
});
|
||
|
||
describe("vendorsApi.getDirectoryList", () => {
|
||
beforeEach(() => {
|
||
apiGet.mockReset();
|
||
apiGet.mockResolvedValue({
|
||
Data: [
|
||
{
|
||
Id: 7,
|
||
CompanyId: "co-1",
|
||
CompanyName: "Gateway Plumbing",
|
||
ContactName: "Adam Whyte",
|
||
Email: "dispatch@gateway.test",
|
||
Phone: "314-555-0198",
|
||
Address: "1 Market St",
|
||
City: "St. Louis",
|
||
State: "MO",
|
||
IsActive: false,
|
||
PreferredContact: "Email",
|
||
Technicians: [
|
||
{
|
||
Id: 7,
|
||
ContactName: "Adam Whyte",
|
||
Email: "adam@gateway.test",
|
||
Phone: "314-555-0198",
|
||
TradeSpecialties: "Plumbing",
|
||
IsActive: false,
|
||
TotalJobs: 3,
|
||
},
|
||
],
|
||
},
|
||
],
|
||
TotalCount: 1,
|
||
});
|
||
});
|
||
|
||
it("targets the dedicated directory endpoint while getList keeps the legacy route", async () => {
|
||
await vendorsApi.getDirectoryList({ search: "gateway", isActive: false, page: 1 });
|
||
await vendorsApi.getList({ search: "gateway", isActive: false, page: 1 });
|
||
|
||
expect(API_PATHS.vendor.getDirectoryList).toBe("Vendor/GetVendorDirectoryList");
|
||
expect(API_PATHS.vendor.getList).toBe("Vendor/GetVendorList");
|
||
expect(apiGet).toHaveBeenNthCalledWith(1, API_PATHS.vendor.getDirectoryList, {
|
||
searchParams: { search: "gateway", isActive: "false", page: 1, pageSize: 12 },
|
||
});
|
||
expect(apiGet).toHaveBeenNthCalledWith(2, API_PATHS.vendor.getList, {
|
||
searchParams: { search: "gateway", isActive: "false", page: 1, pageSize: 12 },
|
||
});
|
||
});
|
||
|
||
it("builds directory query parameters with the same repeated facet encoding", async () => {
|
||
await vendorsApi.getDirectoryList({
|
||
companies: ["Gateway Plumbing", "Metro HVAC"],
|
||
trades: ["Plumbing"],
|
||
locations: ["St. Louis, MO"],
|
||
jobBuckets: ["50-99"],
|
||
});
|
||
|
||
expect(apiGet).toHaveBeenCalledWith(API_PATHS.vendor.getDirectoryList, {
|
||
searchParams: {
|
||
search: "",
|
||
page: 1,
|
||
pageSize: 12,
|
||
"companies[0]": "Gateway Plumbing",
|
||
"companies[1]": "Metro HVAC",
|
||
"trades[0]": "Plumbing",
|
||
"locations[0]": "St. Louis, MO",
|
||
"jobBuckets[0]": "50-99",
|
||
},
|
||
});
|
||
});
|
||
|
||
it("maps grouped company rows with the shared grouped-row mapper", async () => {
|
||
const result = await vendorsApi.getDirectoryList({ isActive: false, page: 1 });
|
||
|
||
expect(result.items[0]).toMatchObject({
|
||
companyId: "co-1",
|
||
companyName: "Gateway Plumbing",
|
||
address: "1 Market St",
|
||
city: "St. Louis",
|
||
state: "MO",
|
||
isActive: false,
|
||
technicians: [
|
||
expect.objectContaining({
|
||
id: 7,
|
||
contactName: "Adam Whyte",
|
||
email: "adam@gateway.test",
|
||
phone: "314-555-0198",
|
||
totalJobs: 3,
|
||
}),
|
||
],
|
||
});
|
||
});
|
||
});
|