Skip to content

Commit 64ad198

Browse files
add vitest for JobState job cancellation
1 parent 32c1476 commit 64ad198

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { createTestingPinia } from "@pinia/testing";
2+
import { getFakeRegisteredUser } from "@tests/test-data";
3+
import { getLocalVue } from "@tests/vitest/helpers";
4+
import { mount } from "@vue/test-utils";
5+
import flushPromises from "flush-promises";
6+
import { beforeEach, describe, expect, it, vi } from "vitest";
7+
8+
import type { RegisteredUser } from "@/api";
9+
import { useServerMock } from "@/api/client/__mocks__";
10+
import type { JobBaseModel, JobState as JobStateType, ShowFullJobResponse } from "@/api/jobs";
11+
import { useUserStore } from "@/stores/userStore";
12+
13+
import JobState from "./JobState.vue";
14+
15+
vi.mock("vue-router/composables", () => ({
16+
useRoute: vi.fn(() => ({})),
17+
}));
18+
19+
const localVue = getLocalVue();
20+
const { server, http } = useServerMock();
21+
22+
const FAKE_USER = getFakeRegisteredUser({ id: "user-123" });
23+
24+
const BASE_JOB: JobBaseModel = {
25+
model_class: "Job",
26+
id: "job-abc",
27+
state: "running",
28+
tool_id: "some_tool",
29+
create_time: "2024-01-01T00:00:00",
30+
update_time: "2024-01-01T00:01:00",
31+
};
32+
33+
const FULL_JOB = {
34+
...BASE_JOB,
35+
inputs: {},
36+
outputs: {},
37+
output_collections: {},
38+
params: {},
39+
user_id: "user-123",
40+
} as ShowFullJobResponse;
41+
42+
const SELECTORS = {
43+
STOP_BTN: ".stop-job-btn",
44+
STATE_BADGE: ".job-state-badge",
45+
};
46+
47+
function mountJobState(job: JobBaseModel | ShowFullJobResponse, user: RegisteredUser | null = FAKE_USER) {
48+
const pinia = createTestingPinia({ createSpy: vi.fn });
49+
const userStore = useUserStore();
50+
userStore.currentUser = user;
51+
return mount(JobState as object, {
52+
propsData: { job },
53+
localVue,
54+
pinia,
55+
});
56+
}
57+
58+
describe("JobState.vue", () => {
59+
beforeEach(() => {
60+
server.use(
61+
http.delete("/api/jobs/{job_id}", ({ response }) => {
62+
return response(200).json(true);
63+
}),
64+
);
65+
});
66+
67+
it("renders the job state badge with the job state text", async () => {
68+
const wrapper = mountJobState(BASE_JOB);
69+
await flushPromises();
70+
expect(wrapper.find(SELECTORS.STATE_BADGE).text()).toContain("running");
71+
});
72+
73+
it("shows the stop button for a non-terminal state when using JobBaseModel (no user_id)", async () => {
74+
const wrapper = mountJobState({ ...BASE_JOB, state: "running" });
75+
await flushPromises();
76+
expect(wrapper.find(SELECTORS.STOP_BTN).exists()).toBe(true);
77+
});
78+
79+
it("hides the stop button for terminal states", async () => {
80+
for (const state of ["ok", "error", "deleted", "failed"] as JobStateType[]) {
81+
const wrapper = mountJobState({ ...BASE_JOB, state });
82+
await flushPromises();
83+
expect(wrapper.find(SELECTORS.STOP_BTN).exists()).toBe(false);
84+
}
85+
});
86+
87+
it("shows the stop button when ShowFullJobResponse user_id matches current user", async () => {
88+
const wrapper = mountJobState({ ...FULL_JOB, state: "running", user_id: FAKE_USER.id });
89+
await flushPromises();
90+
expect(wrapper.find(SELECTORS.STOP_BTN).exists()).toBe(true);
91+
});
92+
93+
it("hides the stop button when ShowFullJobResponse user_id does not match current user", async () => {
94+
const wrapper = mountJobState({ ...FULL_JOB, state: "running", user_id: "someone-else" });
95+
await flushPromises();
96+
expect(wrapper.find(SELECTORS.STOP_BTN).exists()).toBe(false);
97+
});
98+
99+
it("hides the stop button when no user is logged in", async () => {
100+
const wrapper = mountJobState(BASE_JOB, null);
101+
await flushPromises();
102+
expect(wrapper.find(SELECTORS.STOP_BTN).exists()).toBe(false);
103+
});
104+
105+
it("calls DELETE /api/jobs/{job_id} when stop button is clicked", async () => {
106+
let deletedJobId: string | undefined;
107+
server.use(
108+
http.delete("/api/jobs/{job_id}", ({ response, params }) => {
109+
deletedJobId = params.job_id;
110+
return response(200).json(true);
111+
}),
112+
);
113+
114+
const wrapper = mountJobState({ ...BASE_JOB, state: "running" });
115+
await flushPromises();
116+
117+
await wrapper.find(SELECTORS.STOP_BTN).trigger("click");
118+
await flushPromises();
119+
120+
expect(deletedJobId).toBe(BASE_JOB.id);
121+
});
122+
123+
it("disables the stop button while stopping is in progress", async () => {
124+
let resolveDelete!: () => void;
125+
server.use(
126+
http.delete("/api/jobs/{job_id}", ({ response }) => {
127+
return new Promise((resolve) => {
128+
resolveDelete = () => resolve(response(200).json(true));
129+
});
130+
}),
131+
);
132+
133+
const wrapper = mountJobState({ ...BASE_JOB, state: "running" });
134+
await flushPromises();
135+
136+
const stopBtn = wrapper.find(SELECTORS.STOP_BTN);
137+
stopBtn.trigger("click");
138+
await flushPromises();
139+
140+
expect(stopBtn.classes()).toContain("g-disabled");
141+
142+
resolveDelete();
143+
await flushPromises();
144+
145+
expect(stopBtn.classes()).not.toContain("g-disabled");
146+
});
147+
});

0 commit comments

Comments
 (0)