fix: maintenance index name, shared host read only view and badge polling

This commit is contained in:
LukeGus committed 2026-10-01 13:14:07 -05:00
1 parent 5ff5ae19d9
commit 3b58415f8f
16 files changed
+166 -37

No files matched your search

@@ -28,14 +28,29 @@ const input = {
};
describe("host maintenance", () => {
it("scopes reads and edits to the host owner and enforces edit permission", async () => {
it("shows a shared host the owner's maintenance read only and enforces edit permission", async () => {
// The mock serves every host to every user, as if alice shared host 1.
server = await startServer({ hosts: [host(1), host(2, "bob")] });
expect(
(await server.request("POST", "/maintenance/1", { body: input })).status,
).toBe(200);
expect(
(await server.request("GET", "/maintenance", { user: "bob" })).body,
).toEqual([]);
const started = await server.request("POST", "/maintenance/1", {
body: input,
});
expect(started.status).toBe(200);
expect(started.body).toMatchObject({ owned: true });
const listed = (
await server.request("GET", "/maintenance", { user: "bob" })
).body as Array<{
hostId: number;
state: { owned: boolean; active: unknown };
}>;
expect(listed).toHaveLength(1);
expect(listed[0].hostId).toBe(1);
expect(listed[0].state.owned).toBe(false);
expect(listed[0].state.active).toBeTruthy();
const read = await server.request("GET", "/maintenance/1", { user: "bob" });
expect(read.status).toBe(200);
expect(read.body).toMatchObject({ owned: false });
expect(
(
await server.request("POST", "/maintenance/1", {
@@ -43,9 +58,9 @@ describe("host maintenance", () => {
body: { action: "end" },
})
).status,
).toBe(404);
).toBe(403);
expect(
(await server.request("GET", "/maintenance/1", { user: "bob" })).status,
(await server.request("GET", "/maintenance/99", { user: "bob" })).status,
).toBe(404);
expect(
(await server.request("POST", "/maintenance/1x", { body: input })).status,
@@ -224,4 +224,36 @@ describe("adopting the automation tables", () => {
expect(count(db, "p_automations_automations")).toBe(0);
expect(count(db, "p_automations_run_steps")).toBe(0);
});
it("creates host maintenance with one row per owner and host, removed with either", async () => {
db = await createTestDb(pluginDir, {
before: (sqlite) => sqlite.exec(NOTIFICATION_CHANNELS_DDL),
});
const maintenanceIndexes = indexes(db, "p_automations_host_maintenance");
expect(maintenanceIndexes).toContain(
"idx_automation_maintenance_host_owner",
);
expect(maintenanceIndexes).not.toContain("maintenance_host_owner");
db.sqlite.exec(`
INSERT INTO users (id, username) VALUES ('alice', 'alice');
INSERT INTO ssh_data (id) VALUES (1);
INSERT INTO ssh_data (id) VALUES (2);
INSERT INTO p_automations_host_maintenance (user_id, host_id, state) VALUES ('alice', 1, '{}');
INSERT INTO p_automations_host_maintenance (user_id, host_id, state) VALUES ('alice', 2, '{}');
`);
expect(() =>
db.sqlite
.prepare(
"INSERT INTO p_automations_host_maintenance (user_id, host_id, state) VALUES ('alice', 1, '{}')",
)
.run(),
).toThrow();
db.sqlite.prepare("DELETE FROM ssh_data WHERE id = 1").run();
expect(count(db, "p_automations_host_maintenance")).toBe(1);
db.sqlite.prepare("DELETE FROM users WHERE id = 'alice'").run();
expect(count(db, "p_automations_host_maintenance")).toBe(0);
});
});
@@ -71,6 +71,13 @@ describe("maintenance UI", () => {
expect(view.queryByRole("button", { name: "End maintenance" })).toBeNull();
expect(view.queryByLabelText("Reason")).toBeNull();
});
it("shows a shared host's maintenance read only even with edit permission", async () => {
const view = await render(apiFor({ ...active, owned: false }));
await waitFor(() => expect(view.getByText("OS upgrade")).toBeTruthy());
expect(view.getByText(locales.maintenance.sharedReadOnly)).toBeTruthy();
expect(view.queryByRole("button", { name: "End maintenance" })).toBeNull();
expect(view.queryByLabelText("Reason")).toBeNull();
});
it("submits a recurring UTC schedule with its grace period", async () => {
const api = apiFor(emptyMaintenance());
const view = await render(api);