Files
pleno-vue/tests/unit/request-queue-progress.spec.js
T
Jeppe Bundgaard eaf4b965d5 Add runtime support for service sets, release management enhancements, and new unit tests
- Extend `releaseTimeline.js` and related modules for runtime-aware service sets and URLs.
- Update the release session builder to include `service_set` handling.
- Introduce release service definitions, status tones, and enhanced runtime display text.
- Add new dataset mode options, preview rows, and warnings for release configurations in `ConfigurationReleaseManager.vue`.
- Implement extensive utility methods for data service handling, target context, and deployment endpoints.
- Add `vite-api-proxy.spec.js` and extend `release-bootstrap.spec.js` to ensure coverage for new functionality.
2026-05-21 11:41:50 +02:00

749 lines
27 KiB
JavaScript

// @vitest-environment jsdom
import { mount } from "@vue/test-utils";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import axios from "axios";
import RequestQueueProgress from "@/components/global/RequestQueueProgress.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { API_URL } from "@/config.js";
import { __resetReleaseTimelineForTests, configureReleaseRuntime } from "@/services/releaseTimeline.js";
import {
__configureRequestQueueForTests,
__resetRequestQueueForTests,
enqueueRequest,
reportComponentMissingPermission,
requestQueueState,
} from "@/services/requestQueue.js";
const flushMicrotasks = async () => {
await Promise.resolve();
await Promise.resolve();
};
const flushManyMicrotasks = async (rounds = 10) => {
for (let index = 0; index < rounds; index += 1) {
await Promise.resolve();
}
};
const createDeferred = () => {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
const triggerShiftTriplePress = async () => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Shift" }));
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Shift" }));
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Shift" }));
await flushMicrotasks();
};
describe("RequestQueueProgress", () => {
const resetSessionUserState = () => {
SessionUser.isSubuser.value = false;
SessionUser.user.display_name.value = null;
SessionUser.user.customer_number.value = null;
SessionUser.user.group_id.value = null;
SessionUser.user.email.value = null;
SessionUser.user.phone.number.value = null;
SessionUser.subuser.id.value = null;
SessionUser.subuser.username.value = null;
SessionUser.subuser.name.value = null;
SessionUser.subuser.email.value = null;
SessionUser.subuser.grants.value = [];
SessionUser.subuser.selectedGrantCustomerNumber.value = null;
};
beforeEach(() => {
vi.useFakeTimers();
localStorage.clear();
__resetReleaseTimelineForTests();
__resetRequestQueueForTests();
__configureRequestQueueForTests({ maxConcurrentGet: 1, maxConcurrentOther: 1, spacingMs: 0 });
vi.stubGlobal(
"fetch",
vi.fn(async () => ({ ok: true, status: 200 }))
);
resetSessionUserState();
});
afterEach(() => {
__resetRequestQueueForTests();
__resetReleaseTimelineForTests();
resetSessionUserState();
localStorage.clear();
vi.restoreAllMocks();
vi.unstubAllGlobals();
vi.useRealTimers();
});
it("is hidden by default and becomes visible + expanded on shift 3 times", async () => {
const wrapper = mount(RequestQueueProgress);
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
const first = createDeferred();
const second = createDeferred();
const requestOne = enqueueRequest(() => first.promise);
const requestTwo = enqueueRequest(() => second.promise);
await flushMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
await triggerShiftTriplePress();
expect(wrapper.get("[data-testid='request-queue-progress']").text()).toContain("1 active, 1 queued");
expect(wrapper.find("[data-testid='request-queue-progress-details']").exists()).toBe(true);
first.resolve({ status: 200 });
await flushManyMicrotasks();
expect(wrapper.get("[data-testid='request-queue-progress']").text()).toContain("Request queue:");
second.resolve({ status: 200 });
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
expect(wrapper.get("[data-testid='request-queue-progress']").text()).toContain("Request queue complete: 2/2");
vi.advanceTimersByTime(2600);
await flushMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
});
it("stays hidden until shift 3 times even for small batches", async () => {
const wrapper = mount(RequestQueueProgress);
const onlyRequest = createDeferred();
const request = enqueueRequest(() => onlyRequest.promise);
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
await triggerShiftTriplePress();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
onlyRequest.resolve({ status: 200 });
await request;
await flushManyMicrotasks();
});
it("closes when escape is pressed while open", async () => {
const wrapper = mount(RequestQueueProgress);
const first = createDeferred();
const second = createDeferred();
const requestOne = enqueueRequest(() => first.promise, { method: "GET", url: "/esc-close-1" });
const requestTwo = enqueueRequest(() => second.promise, { method: "GET", url: "/esc-close-2" });
await flushManyMicrotasks();
await triggerShiftTriplePress();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
first.resolve({ status: 200 });
second.resolve({ status: 200 });
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
});
it("dispatches system-search close event on shift 3x for superuser", async () => {
vi.spyOn(SessionUser, "canAccessSuperUser").mockReturnValue(true);
const closeEventListener = vi.fn();
window.addEventListener("system-search:close", closeEventListener);
mount(RequestQueueProgress);
await triggerShiftTriplePress();
expect(closeEventListener).toHaveBeenCalled();
expect(closeEventListener.mock.calls.some(([event]) => event?.type === "system-search:close")).toBe(true);
window.removeEventListener("system-search:close", closeEventListener);
});
it("expands upward with active endpoint details, method icon and timer", async () => {
const wrapper = mount(RequestQueueProgress);
const first = createDeferred();
const second = createDeferred();
const requestOne = enqueueRequest(() => first.promise, { method: "POST", url: "/orders" });
const requestTwo = enqueueRequest(() => second.promise, { method: "GET", url: "/order/items" });
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-errors-box']").exists()).toBe(true);
expect(wrapper.find("[data-testid='request-queue-missing-permissions-box']").exists()).toBe(true);
expect(wrapper.find("[data-testid='request-queue-runtime-box']").exists()).toBe(true);
expect(wrapper.find("[data-testid='request-queue-user-box']").exists()).toBe(true);
const details = wrapper.get("[data-testid='request-queue-progress-details']");
expect(details.text()).toContain("Active requests");
expect(details.text()).toContain("POST");
expect(details.text()).toContain("/orders");
expect(details.text()).toMatch(/ms|s/);
expect(details.find(".fa-plus-circle").exists()).toBe(true);
const runtimeBox = wrapper.get("[data-testid='request-queue-runtime-box']");
expect(runtimeBox.text()).toContain("API URL");
expect(runtimeBox.text()).toContain("Current host");
expect(runtimeBox.text()).toContain("Environment");
expect(runtimeBox.text()).toContain("Commit");
expect(runtimeBox.text()).toContain("Version time");
expect(runtimeBox.text()).toContain("Outgoing requests");
expect(runtimeBox.text()).toContain("Ingoing responses");
expect(runtimeBox.text()).toContain("Outgoing bandwidth");
expect(runtimeBox.text()).toContain("Ingoing bandwidth");
const userBox = wrapper.get("[data-testid='request-queue-user-box']");
expect(userBox.text()).toContain("User type");
expect(userBox.text()).toContain("User");
first.resolve({ status: 200 });
second.resolve({ status: 200 });
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
});
it("tracks runtime ingoing/outgoing request counts and bandwidth totals", async () => {
const wrapper = mount(RequestQueueProgress);
const first = createDeferred();
const second = createDeferred();
const requestOne = enqueueRequest(() => first.promise, {
method: "POST",
url: "/runtime-metrics-a",
requestData: {
headers: { "X-Trace": "a" },
data: { payload: "first" },
},
});
const requestTwo = enqueueRequest(() => second.promise, {
method: "GET",
url: "/runtime-metrics-b",
requestData: {
params: { page: 1, limit: 10 },
},
});
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
first.resolve({ status: 200, data: { ok: true, item: "a" } });
second.resolve({ status: 200, data: { ok: true, item: "b" } });
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
expect(requestQueueState.networkTotals.outgoingRequests).toBe(2);
expect(requestQueueState.networkTotals.ingoingResponses).toBe(2);
expect(requestQueueState.networkTotals.outgoingBytes).toBeGreaterThan(0);
expect(requestQueueState.networkTotals.ingoingBytes).toBeGreaterThan(0);
const runtimeBox = wrapper.get("[data-testid='request-queue-runtime-box']");
expect(runtimeBox.text()).toContain("Outgoing requests");
expect(runtimeBox.text()).toContain("Ingoing responses");
expect(runtimeBox.text()).toContain("Outgoing bandwidth");
expect(runtimeBox.text()).toContain("Ingoing bandwidth");
});
it("shows the active session release bundle, app URLs, and connected services", async () => {
configureReleaseRuntime({
generated_at: "2026-05-19T09:30:00.000Z",
trace_id: "trace-request-panel",
channel: { slug: "canary", name: "Canary", default_channel: false },
versions: {
frontend: {
version_label: "frontend-canary",
commit_sha: "c0ffee0000001111222233334444555566667777",
repository: "truckwash/front-end-vue",
branch: "release/canary",
status: "deployed",
},
api: {
version_label: "api-canary",
commit_sha: "feedface00001111222233334444555566667777",
repository: "truckwash/backend-php",
branch: "release/canary",
status: "deployed",
},
service_set: {
id: 53,
name: "Canary isolated stack",
stack: {
frontend: { id: 51, label: "canary-frontend", coolify_service_uuid: "frontend-service-uuid" },
api: { id: 52, label: "canary-api", coolify_service_uuid: "api-service-uuid" },
database: { id: 54, resource_name: "canary-db", resource_uuid: "database-resource-uuid" },
redis: { id: 55, resource_name: "canary-redis", resource_uuid: "redis-resource-uuid" },
minio: { id: 56, resource_name: "canary-minio", resource_uuid: "minio-resource-uuid" },
},
},
bundle_id: 31,
bundle: { id: 31, version_label: "canary-bundle", status: "deployed" },
},
urls: {
frontend_base_url: "https://api-v2.truckwash.io/canary/frontend",
api_base_url: "https://api-v2.truckwash.io/canary/api",
},
availability: { configured: true, missing: [], status: "ready" },
});
const wrapper = mount(RequestQueueProgress);
await triggerShiftTriplePress();
await flushManyMicrotasks();
const runtimeBox = wrapper.get("[data-testid='request-queue-runtime-box']");
expect(runtimeBox.text()).toContain("Session release");
expect(runtimeBox.text()).toContain("Canary");
expect(runtimeBox.text()).toContain("trace-request-panel");
expect(runtimeBox.text()).toContain("#31 canary-bundle");
expect(runtimeBox.text()).toContain("Canary isolated stack");
expect(wrapper.get("[data-testid='request-queue-release-app-frontend']").text()).toContain("frontend-canary");
expect(wrapper.get("[data-testid='request-queue-release-app-frontend']").text()).toContain(
"https://api-v2.truckwash.io/canary/frontend"
);
expect(wrapper.get("[data-testid='request-queue-release-app-api']").text()).toContain("api-canary");
expect(wrapper.get("[data-testid='request-queue-release-service-database']").text()).toContain("canary-db #54");
expect(wrapper.get("[data-testid='request-queue-release-service-redis']").text()).toContain("canary-redis #55");
expect(wrapper.get("[data-testid='request-queue-release-service-minio']").text()).toContain("canary-minio #56");
});
it("shows subuser details in the user box", async () => {
SessionUser.isSubuser.value = true;
SessionUser.subuser.name.value = "Sub User";
SessionUser.subuser.id.value = 77;
SessionUser.subuser.email.value = "subuser@example.com";
SessionUser.subuser.grants.value = [{ billing_customer_number: 12345, permissions: [] }];
SessionUser.subuser.selectedGrantCustomerNumber.value = 12345;
const wrapper = mount(RequestQueueProgress);
const first = createDeferred();
const second = createDeferred();
const requestOne = enqueueRequest(() => first.promise, { method: "GET", url: "/subuser-check-1" });
const requestTwo = enqueueRequest(() => second.promise, { method: "GET", url: "/subuser-check-2" });
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
const userBox = wrapper.get("[data-testid='request-queue-user-box']");
expect(userBox.text()).toContain("User type");
expect(userBox.text()).toContain("Subuser");
expect(userBox.text()).toContain("Sub User");
expect(userBox.text()).toContain("subuser@example.com");
expect(userBox.text()).toContain("12345");
first.resolve({ status: 200 });
second.resolve({ status: 200 });
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
});
it("shows ping and bookings insights in the bottom of the middle panel", async () => {
const wrapper = mount(RequestQueueProgress);
const pingRequest = createDeferred();
const bookingsRequest = createDeferred();
const requestOne = enqueueRequest(() => pingRequest.promise, { method: "GET", url: "/ping" });
const requestTwo = enqueueRequest(() => bookingsRequest.promise, { method: "GET", url: "/order-bookings" });
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
pingRequest.resolve({ status: 200 });
bookingsRequest.resolve({ status: 200 });
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
const insights = wrapper.get("[data-testid='request-queue-bottom-request-insights']");
expect(insights.text()).toContain("Ping");
expect(insights.text()).toContain("Bookings");
expect(insights.text()).toContain("ms");
const pingInsight = wrapper.get("[data-testid='request-queue-insight-ping']");
const bookingsInsight = wrapper.get("[data-testid='request-queue-insight-bookings']");
expect(pingInsight.text()).toMatch(/now|s ago|m ago/);
expect(bookingsInsight.text()).toMatch(/now|s ago|m ago/);
});
it("does not emit recursive update errors while processing many requests", async () => {
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
mount(RequestQueueProgress);
await triggerShiftTriplePress();
await flushManyMicrotasks();
const requests = Array.from({ length: 30 }, (_, index) =>
enqueueRequest(async () => ({ status: 200, data: { index } }), {
method: "GET",
url: `/bulk-recursive-check-${index}`,
})
);
await Promise.all(requests);
await flushManyMicrotasks(30);
const hasRecursiveError = [...errorSpy.mock.calls, ...warnSpy.mock.calls].some((call) =>
call.join(" ").includes("Maximum recursive updates exceeded")
);
expect(hasRecursiveError).toBe(false);
});
it("stores errors with request and response payloads and respects configured error limit", async () => {
__configureRequestQueueForTests({
errorHistoryLimit: 1,
missingPermissionsLimit: 1,
retryByStatusCode: {},
payloadMaxChars: 8000,
});
const wrapper = mount(RequestQueueProgress);
const requestOne = enqueueRequest(
async () => {
throw {
message: "First failure",
response: { status: 500, data: { reason: "first" } },
};
},
{
method: "POST",
url: "/first-error",
requestData: { data: { requestBody: "first" } },
}
).catch((error) => error);
const requestTwo = enqueueRequest(
async () => {
throw {
message: "Second failure",
response: {
status: 403,
data: {
success: false,
data: {
message: "Missing permission(s)",
permissions: ["department_notification_sms_get"],
},
meta: [],
includes: [],
},
},
};
},
{
method: "PATCH",
url: "/second-error",
requestData: { data: { requestBody: "second" } },
}
).catch((error) => error);
await Promise.all([requestOne, requestTwo]);
await flushManyMicrotasks();
expect(requestQueueState.errorRequests.length).toBe(1);
expect(requestQueueState.errorRequests[0].url).toBe("/second-error");
expect(requestQueueState.errorRequests[0].requestText).toContain("requestBody");
expect(requestQueueState.errorRequests[0].responseText).toContain("Missing permission(s)");
expect(requestQueueState.missingPermissions.length).toBe(1);
expect(requestQueueState.missingPermissions[0].permission).toBe("department_notification_sms_get");
await triggerShiftTriplePress();
await flushManyMicrotasks();
const errorsBox = wrapper.get("[data-testid='request-queue-errors-box']");
expect(errorsBox.text()).toContain("Errors (1)");
expect(errorsBox.text()).toContain("/second-error");
expect(errorsBox.text()).toContain("Request and response data");
expect(errorsBox.text()).toContain("Request");
expect(errorsBox.text()).toContain("Response");
const missingPermissionsBox = wrapper.get("[data-testid='request-queue-missing-permissions-box']");
expect(missingPermissionsBox.text()).toContain("department_notification_sms_get");
await wrapper.get("[data-testid='request-queue-clear-errors']").trigger("click");
await flushManyMicrotasks();
expect(requestQueueState.errorRequests.length).toBe(0);
expect(wrapper.get("[data-testid='request-queue-errors-box']").text()).toContain("No stored errors");
expect(requestQueueState.missingPermissions.length).toBe(1);
await wrapper.get("[data-testid='request-queue-clear-permissions']").trigger("click");
await flushManyMicrotasks();
expect(requestQueueState.missingPermissions.length).toBe(0);
expect(wrapper.get("[data-testid='request-queue-missing-permissions-box']").text()).toContain(
"No missing permissions detected"
);
});
it("stays visible after errors occur", async () => {
__configureRequestQueueForTests({ errorHistoryLimit: 2 });
const wrapper = mount(RequestQueueProgress);
const failed = enqueueRequest(
async () => {
throw {
message: "Failure should keep panel visible",
response: { status: 500, data: { detail: "boom" } },
};
},
{
method: "DELETE",
url: "/error-visible",
requestData: { data: { id: 123 } },
}
).catch((error) => error);
await failed;
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(false);
await triggerShiftTriplePress();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
vi.advanceTimersByTime(30_000);
await flushManyMicrotasks();
expect(wrapper.find("[data-testid='request-queue-progress']").exists()).toBe(true);
await flushManyMicrotasks();
const errorsBox = wrapper.get("[data-testid='request-queue-errors-box']");
expect(errorsBox.text()).toContain("Errors");
expect(errorsBox.text()).toContain("/error-visible");
});
it("stores missing permissions when response payload is stringified JSON", async () => {
__configureRequestQueueForTests({
missingPermissionsLimit: 5,
retryByStatusCode: {},
});
const request = enqueueRequest(
async () => {
throw {
message: "Request failed with status code 403",
response: {
status: 403,
data: JSON.stringify({
success: false,
data: {
message: "Missing permission(s)",
permissions: ["department_notification_sms_get"],
},
meta: [],
includes: [],
}),
},
};
},
{
method: "GET",
url: "/notification/sms",
}
).catch((error) => error);
await request;
await flushManyMicrotasks();
expect(requestQueueState.missingPermissions.length).toBe(1);
expect(requestQueueState.missingPermissions[0].permission).toBe("department_notification_sms_get");
});
it("stores component permissions with COMPONENT method and keeps them below API entries", async () => {
const wrapper = mount(RequestQueueProgress);
const apiRequest = enqueueRequest(
async () => {
throw {
message: "Request failed with status code 403",
response: {
status: 403,
data: {
success: false,
data: {
message: "Missing permission(s)",
permissions: ["department_notification_sms_get"],
},
},
},
};
},
{
method: "GET",
url: "/notification/sms",
}
).catch((error) => error);
await apiRequest;
reportComponentMissingPermission("goals_department_create", {
source: "DepartmentGoals:CreateGoalButton",
});
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
const rows = wrapper
.get("[data-testid='request-queue-missing-permissions-box']")
.findAll(".request-queue-progress__list-item--permission")
.map((item) => item.text());
expect(rows.length).toBe(2);
expect(rows[0]).toContain("department_notification_sms_get");
expect(rows[0]).toContain("GET");
expect(rows[1]).toContain("goals_department_create");
expect(rows[1]).toContain("COMPONENT");
});
it("throttles repeated component permission reports", async () => {
__configureRequestQueueForTests({
componentPermissionReportWindowMs: 5000,
missingPermissionsLimit: 10,
});
reportComponentMissingPermission("goals_department_create", {
source: "DepartmentGoals:CreateGoalButton",
});
reportComponentMissingPermission("goals_department_create", {
source: "DepartmentGoals:CreateGoalButton",
});
await flushManyMicrotasks();
expect(requestQueueState.missingPermissions.length).toBe(1);
expect(requestQueueState.missingPermissions[0].count).toBe(1);
expect(requestQueueState.missingPermissions[0].method).toBe("COMPONENT");
vi.advanceTimersByTime(5100);
reportComponentMissingPermission("goals_department_create", {
source: "DepartmentGoals:CreateGoalButton",
});
await flushManyMicrotasks();
expect(requestQueueState.missingPermissions.length).toBe(1);
expect(requestQueueState.missingPermissions[0].count).toBe(2);
});
it("shows add button for missing permissions when superuser impersonates a user", async () => {
localStorage.setItem("superuser_token", "su-token");
SessionUser.user.group_id.value = 19;
const wrapper = mount(RequestQueueProgress);
const request = enqueueRequest(
async () => {
throw {
message: "Request failed with status code 403",
response: {
status: 403,
data: {
success: false,
data: {
message: "Missing permission(s)",
permissions: ["department_notification_sms_get"],
},
},
},
};
},
{
method: "GET",
url: "/notification/sms",
}
).catch((error) => error);
await request;
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
expect(
wrapper.find("[data-testid='request-queue-permission-action-department_notification_sms_get-GET']").exists()
).toBe(true);
});
it("shows add button for COMPONENT missing permissions when superuser impersonates a user", async () => {
localStorage.setItem("superuser_token", "su-token");
SessionUser.user.group_id.value = 19;
const wrapper = mount(RequestQueueProgress);
reportComponentMissingPermission("goals_department_create", {
source: "DepartmentGoals:CreateGoalButton",
});
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
expect(
wrapper.find("[data-testid='request-queue-permission-action-goals_department_create-COMPONENT']").exists()
).toBe(true);
});
it("adds missing permission with superuser token and shows disabled checkmark on success", async () => {
localStorage.setItem("superuser_token", "su-token");
SessionUser.user.group_id.value = 19;
const axiosPostSpy = vi.spyOn(axios, "post").mockResolvedValue({ status: 200, data: {} });
const wrapper = mount(RequestQueueProgress);
const request = enqueueRequest(
async () => {
throw {
message: "Request failed with status code 403",
response: {
status: 403,
data: {
success: false,
data: {
message: "Missing permission(s)",
permissions: ["department_notification_sms_get"],
},
},
},
};
},
{
method: "GET",
url: "/notification/sms",
}
).catch((error) => error);
await request;
await flushManyMicrotasks();
await triggerShiftTriplePress();
await flushManyMicrotasks();
const actionButton = wrapper.get(
"[data-testid='request-queue-permission-action-department_notification_sms_get-GET']"
);
await actionButton.trigger("click");
await flushManyMicrotasks();
expect(axiosPostSpy).toHaveBeenCalledWith(
`${API_URL}/roles/permissions`,
{
group_id: 19,
permission_id: "department_notification_sms_get",
},
{
headers: {
Authorization: "Bearer su-token",
},
}
);
expect(actionButton.attributes("disabled")).toBeDefined();
expect(actionButton.find(".fa-check").exists()).toBe(true);
axiosPostSpy.mockRestore();
});
});