533 lines
16 KiB
JavaScript
533 lines
16 KiB
JavaScript
import { ref } from "vue";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { useWashSessionActions } from "@/composables/useWashSessionActions.js";
|
|
|
|
const createActions = (overrides = {}) => {
|
|
const request = vi.fn(async () => ({ data: { success: true } }));
|
|
const alertFn = vi.fn();
|
|
|
|
const options = {
|
|
request,
|
|
alertFn,
|
|
nearestDepartment: ref({
|
|
id: 1,
|
|
lanes: [{ id: 7 }],
|
|
}),
|
|
vehicleTypeSelect: ref(2),
|
|
washLaneId: ref(7),
|
|
washInProgress: ref(true),
|
|
washStartTime: ref(Date.now() - 30_000),
|
|
completedDurationMs: ref(null),
|
|
now: ref(Date.now()),
|
|
currentStep: ref(4),
|
|
steps: {
|
|
VEHICLE: 0,
|
|
QUESTIONS: 1,
|
|
SELECT_LANE: 2,
|
|
TASKS: 3,
|
|
WASH_IN_PROGRESS: 4,
|
|
COMPLETED: 5,
|
|
},
|
|
radioWashType: ref("Manual"),
|
|
activeTasks: ref([]),
|
|
completedTasks: ref({}),
|
|
saveProgress: vi.fn(),
|
|
clearProgress: vi.fn(),
|
|
startElapsedTimer: vi.fn(),
|
|
stopElapsedTimer: vi.fn(),
|
|
updateLaneAllowedServices: vi.fn(),
|
|
fetchWashSummary: vi.fn(),
|
|
enableMachineRelay: vi.fn(),
|
|
isServiceAllowed: vi.fn(() => true),
|
|
...overrides,
|
|
};
|
|
|
|
return {
|
|
request: options.request,
|
|
alertFn,
|
|
options,
|
|
actions: useWashSessionActions(options),
|
|
};
|
|
};
|
|
|
|
describe("useWashSessionActions property gate commands", () => {
|
|
it("opens property access gate with explicit lane id and tracks loading state", async () => {
|
|
const deferred = {};
|
|
deferred.promise = new Promise((resolve) => {
|
|
deferred.resolve = resolve;
|
|
});
|
|
|
|
const { actions, request } = createActions({
|
|
request: vi.fn(() => deferred.promise),
|
|
});
|
|
|
|
const commandPromise = actions.openPropertyAccessGate(9);
|
|
|
|
expect(actions.openingPropertyAccessGate.value).toBe(true);
|
|
deferred.resolve({ data: { success: true } });
|
|
await commandPromise;
|
|
|
|
expect(actions.openingPropertyAccessGate.value).toBe(false);
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 9,
|
|
command: "OPEN_PROPERTY_ACCESS_GATE",
|
|
customer_number: null,
|
|
license_plate: null,
|
|
});
|
|
});
|
|
|
|
it("opens property exit gate using active wash lane when lane argument is omitted", async () => {
|
|
const { actions, request } = createActions();
|
|
|
|
await actions.openPropertyExitGate();
|
|
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "OPEN_PROPERTY_EXIT_GATE",
|
|
customer_number: null,
|
|
license_plate: null,
|
|
});
|
|
});
|
|
|
|
it("warns and skips command when no lane id can be resolved", async () => {
|
|
const { actions, request, alertFn } = createActions({
|
|
washLaneId: ref(null),
|
|
});
|
|
|
|
const result = await actions.openPropertyAccessGate();
|
|
|
|
expect(result).toBeNull();
|
|
expect(request).not.toHaveBeenCalled();
|
|
expect(alertFn).toHaveBeenCalledWith("Ingen vaskebane valgt.");
|
|
});
|
|
|
|
it("keeps flow non-blocking when property gate command fails", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const { actions, alertFn } = createActions({
|
|
request: vi.fn(async () => {
|
|
throw new Error("command failed");
|
|
}),
|
|
});
|
|
|
|
const result = await actions.openPropertyAccessGate();
|
|
|
|
expect(result).toBeNull();
|
|
expect(actions.openingPropertyAccessGate.value).toBe(false);
|
|
expect(alertFn).toHaveBeenCalledWith("command failed");
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("can suppress property exit gate alerts for automatic wash completion", async () => {
|
|
const { actions, alertFn } = createActions({
|
|
request: vi.fn(async () => ({
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "No exit gate configured",
|
|
},
|
|
},
|
|
})),
|
|
});
|
|
|
|
const result = await actions.openPropertyExitGate(7, { suppressAlert: true });
|
|
|
|
expect(result).toBeNull();
|
|
expect(alertFn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("builds dynamic image URLs from ordered task button steps", () => {
|
|
const activeTasks = ref([
|
|
{
|
|
id: 101,
|
|
services: ["PROGRAM_PICKER"],
|
|
buttons: ["reset", 0],
|
|
dynamic_images_vehicle_type: 2,
|
|
},
|
|
{
|
|
id: 102,
|
|
buttons: [2, "start", 5],
|
|
dynamic_images_vehicle_type: 3,
|
|
},
|
|
]);
|
|
const completedTasks = ref({ 101: true });
|
|
const { actions } = createActions({
|
|
activeTasks,
|
|
completedTasks,
|
|
});
|
|
|
|
const url = new URL(actions.dynamicImageUrl.value, "https://app.example.test");
|
|
|
|
expect(JSON.parse(url.searchParams.get("buttons"))).toEqual(["program_picker", "reset", 0, 2, "start", 5]);
|
|
expect(url.searchParams.get("current_step")).toBe("3");
|
|
expect(url.searchParams.get("thumb_position")).toBe("2");
|
|
expect(actions.machineStartCurrentStep.value).toBe(3);
|
|
});
|
|
|
|
it("does not enter wash-in-progress state when the start command fails", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const washInProgress = ref(false);
|
|
const washLaneId = ref(null);
|
|
const washStartTime = ref(null);
|
|
const currentStep = ref(2);
|
|
const request = vi.fn(async (_url, _method, body) => {
|
|
if (body.command === "START") {
|
|
return {
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Edge gateway command timed out",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return { data: { success: true } };
|
|
});
|
|
|
|
const { actions, alertFn } = createActions({
|
|
request,
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
currentStep,
|
|
});
|
|
|
|
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
|
|
|
|
expect(result).toBe(false);
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(washLaneId.value).toBeNull();
|
|
expect(washStartTime.value).toBeNull();
|
|
expect(currentStep.value).toBe(2);
|
|
expect(actions.isStartingWash.value).toBe(false);
|
|
expect(alertFn).toHaveBeenCalledWith("Edge gateway command timed out");
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "START",
|
|
customer_number: 12345679,
|
|
license_plate: "AB12345",
|
|
wash_type: "Manual",
|
|
defer_relay_side_effects: true,
|
|
});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("surfaces action alerts when allowed-service setup fails before start", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const washInProgress = ref(false);
|
|
const washLaneId = ref(null);
|
|
const washStartTime = ref(null);
|
|
const currentStep = ref(2);
|
|
const updateLaneAllowedServices = vi.fn(async () => {
|
|
throw new Error("Edge gateway command timed out");
|
|
});
|
|
|
|
const { actions, request, alertFn } = createActions({
|
|
updateLaneAllowedServices,
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
currentStep,
|
|
});
|
|
|
|
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
|
|
|
|
expect(result).toBe(false);
|
|
expect(updateLaneAllowedServices).toHaveBeenCalledWith(7);
|
|
expect(request).not.toHaveBeenCalled();
|
|
expect(alertFn).toHaveBeenCalledWith("Edge gateway command timed out");
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(currentStep.value).toBe(2);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("rolls back the started wash when required post-start machine relay activation throws", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const washInProgress = ref(false);
|
|
const washLaneId = ref(null);
|
|
const washStartTime = ref(null);
|
|
const currentStep = ref(2);
|
|
const saveProgress = vi.fn();
|
|
const startElapsedTimer = vi.fn();
|
|
const fetchWashSummary = vi.fn();
|
|
const enableMachineRelay = vi.fn(async () => {
|
|
throw new Error("Edge gateway command timed out");
|
|
});
|
|
|
|
const { actions, request, alertFn } = createActions({
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
currentStep,
|
|
saveProgress,
|
|
startElapsedTimer,
|
|
fetchWashSummary,
|
|
radioWashType: ref("Machine"),
|
|
enableMachineRelay,
|
|
isServiceAllowed: vi.fn(() => true),
|
|
});
|
|
|
|
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
|
|
|
|
expect(result).toBe(false);
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(washLaneId.value).toBeNull();
|
|
expect(washStartTime.value).toBeNull();
|
|
expect(currentStep.value).toBe(2);
|
|
expect(saveProgress).not.toHaveBeenCalled();
|
|
expect(startElapsedTimer).not.toHaveBeenCalled();
|
|
expect(fetchWashSummary).not.toHaveBeenCalled();
|
|
expect(enableMachineRelay).toHaveBeenCalledWith(7);
|
|
expect(alertFn).toHaveBeenCalledWith("Edge gateway command timed out");
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "START",
|
|
customer_number: 12345679,
|
|
license_plate: "AB12345",
|
|
wash_type: "Machine",
|
|
defer_relay_side_effects: true,
|
|
});
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "STOP",
|
|
customer_number: null,
|
|
license_plate: null,
|
|
});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("rolls back the started wash when required post-start machine relay activation is rejected", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const washInProgress = ref(false);
|
|
const washLaneId = ref(null);
|
|
const washStartTime = ref(null);
|
|
const currentStep = ref(2);
|
|
const saveProgress = vi.fn();
|
|
const startElapsedTimer = vi.fn();
|
|
const fetchWashSummary = vi.fn();
|
|
const enableMachineRelay = vi.fn(async () => ({
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Machine relay unavailable",
|
|
},
|
|
},
|
|
}));
|
|
|
|
const { actions, request, alertFn } = createActions({
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
currentStep,
|
|
saveProgress,
|
|
startElapsedTimer,
|
|
fetchWashSummary,
|
|
radioWashType: ref("Machine"),
|
|
enableMachineRelay,
|
|
isServiceAllowed: vi.fn(() => true),
|
|
});
|
|
|
|
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
|
|
|
|
expect(result).toBe(false);
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(washLaneId.value).toBeNull();
|
|
expect(washStartTime.value).toBeNull();
|
|
expect(currentStep.value).toBe(2);
|
|
expect(saveProgress).not.toHaveBeenCalled();
|
|
expect(startElapsedTimer).not.toHaveBeenCalled();
|
|
expect(fetchWashSummary).not.toHaveBeenCalled();
|
|
expect(enableMachineRelay).toHaveBeenCalledWith(7);
|
|
expect(alertFn).toHaveBeenCalledWith("Machine relay unavailable");
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "START",
|
|
customer_number: 12345679,
|
|
license_plate: "AB12345",
|
|
wash_type: "Machine",
|
|
defer_relay_side_effects: true,
|
|
});
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "STOP",
|
|
customer_number: null,
|
|
license_plate: null,
|
|
});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("keeps the active wash open when the stop command fails", async () => {
|
|
const washInProgress = ref(true);
|
|
const washLaneId = ref(7);
|
|
const washStartTime = ref(Date.now() - 45_000);
|
|
const completedDurationMs = ref(null);
|
|
const clearProgress = vi.fn();
|
|
const stopElapsedTimer = vi.fn();
|
|
const request = vi.fn(async (_url, _method, body) => {
|
|
if (body.command === "STOP") {
|
|
return {
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Stop command failed",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return { data: { success: true } };
|
|
});
|
|
|
|
const { actions, alertFn } = createActions({
|
|
request,
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
completedDurationMs,
|
|
clearProgress,
|
|
stopElapsedTimer,
|
|
});
|
|
|
|
const result = await actions.onStopWash(7);
|
|
|
|
expect(result).toBe(false);
|
|
expect(alertFn).toHaveBeenCalledWith("Stop command failed");
|
|
expect(washInProgress.value).toBe(true);
|
|
expect(washLaneId.value).toBe(7);
|
|
expect(completedDurationMs.value).toBeNull();
|
|
expect(stopElapsedTimer).not.toHaveBeenCalled();
|
|
expect(clearProgress).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("clears active wash state when stop reports the lane is already unoccupied", async () => {
|
|
const washInProgress = ref(true);
|
|
const washLaneId = ref(7);
|
|
const washStartTime = ref(1_000);
|
|
const completedDurationMs = ref(null);
|
|
const now = ref(46_000);
|
|
const clearProgress = vi.fn();
|
|
const stopElapsedTimer = vi.fn();
|
|
const request = vi.fn(async (_url, _method, body) => {
|
|
if (body.command === "STOP") {
|
|
return {
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Failed to execute command: Cannot stop lane: Lane is not occupied.",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return { data: { success: true } };
|
|
});
|
|
|
|
const { actions, alertFn } = createActions({
|
|
request,
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
completedDurationMs,
|
|
now,
|
|
clearProgress,
|
|
stopElapsedTimer,
|
|
});
|
|
|
|
const result = await actions.onStopWash(7);
|
|
|
|
expect(result).toBe(true);
|
|
expect(alertFn).not.toHaveBeenCalled();
|
|
expect(completedDurationMs.value).toBe(45_000);
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(washLaneId.value).toBeNull();
|
|
expect(stopElapsedTimer).toHaveBeenCalledTimes(1);
|
|
expect(clearProgress).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("clears active wash state when stop reports the lane cannot invoice because it is unoccupied", async () => {
|
|
const washInProgress = ref(true);
|
|
const washLaneId = ref(7);
|
|
const washStartTime = ref(2_000);
|
|
const completedDurationMs = ref(null);
|
|
const now = ref(47_000);
|
|
const clearProgress = vi.fn();
|
|
const stopElapsedTimer = vi.fn();
|
|
const request = vi.fn(async (_url, _method, body) => {
|
|
if (body.command === "STOP") {
|
|
return {
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Failed to execute command: Lane ID 7 is not occupied; cannot invoice.",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return { data: { success: true } };
|
|
});
|
|
|
|
const { actions, alertFn } = createActions({
|
|
request,
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
completedDurationMs,
|
|
now,
|
|
clearProgress,
|
|
stopElapsedTimer,
|
|
});
|
|
|
|
const result = await actions.onStopWash(7);
|
|
|
|
expect(result).toBe(true);
|
|
expect(alertFn).not.toHaveBeenCalled();
|
|
expect(completedDurationMs.value).toBe(45_000);
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(washLaneId.value).toBeNull();
|
|
expect(stopElapsedTimer).toHaveBeenCalledTimes(1);
|
|
expect(clearProgress).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("clears active wash state only after the stop command succeeds", async () => {
|
|
const washInProgress = ref(true);
|
|
const washLaneId = ref(7);
|
|
const washStartTime = ref(1_000);
|
|
const completedDurationMs = ref(null);
|
|
const now = ref(61_000);
|
|
const clearProgress = vi.fn();
|
|
const stopElapsedTimer = vi.fn();
|
|
|
|
const { actions, request, alertFn } = createActions({
|
|
washInProgress,
|
|
washLaneId,
|
|
washStartTime,
|
|
completedDurationMs,
|
|
now,
|
|
clearProgress,
|
|
stopElapsedTimer,
|
|
});
|
|
|
|
const result = await actions.onStopWash(7);
|
|
|
|
expect(result).toBe(true);
|
|
expect(alertFn).not.toHaveBeenCalled();
|
|
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
|
lane_id: 7,
|
|
command: "STOP",
|
|
customer_number: null,
|
|
license_plate: null,
|
|
});
|
|
expect(completedDurationMs.value).toBe(60_000);
|
|
expect(washInProgress.value).toBe(false);
|
|
expect(washLaneId.value).toBeNull();
|
|
expect(stopElapsedTimer).toHaveBeenCalledTimes(1);
|
|
expect(clearProgress).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|