shoc-frontend-new/src/test/domain/vendors/api/vendors-api.test.ts

422 lines
13 KiB
TypeScript
Raw Normal View History

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,
}),
],
});
});
});