fix(installer): harden admin smoke and rollback cleanup
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
resetFailedUnit,
|
||||
type ResetFailedDependencies,
|
||||
} from "../src/lib/systemd";
|
||||
import type { MutationResult } from "../src/lib/process";
|
||||
import { runRollbackStages } from "../src/lib/rollback";
|
||||
|
||||
function result(exitCode: number, stdout = "", stderr = ""): MutationResult {
|
||||
return { exitCode, stdout, stderr };
|
||||
}
|
||||
|
||||
function dependencies(
|
||||
reset: MutationResult,
|
||||
inspect: MutationResult,
|
||||
calls: string[] = [],
|
||||
): ResetFailedDependencies {
|
||||
return {
|
||||
reset: async (unit) => {
|
||||
calls.push(`reset:${unit}`);
|
||||
return reset;
|
||||
},
|
||||
inspect: async (unit) => {
|
||||
calls.push(`inspect:${unit}`);
|
||||
return inspect;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("systemd rollback cleanup", () => {
|
||||
test("успешный reset-failed подтверждается наблюдением", async () => {
|
||||
const calls: string[] = [];
|
||||
await expect(
|
||||
resetFailedUnit(
|
||||
"hy2xs-admin",
|
||||
dependencies(
|
||||
result(0),
|
||||
result(0, "LoadState=loaded\nActiveState=inactive\n"),
|
||||
calls,
|
||||
),
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
expect(calls).toEqual(["reset:hy2xs-admin", "inspect:hy2xs-admin"]);
|
||||
});
|
||||
|
||||
test("уже выгруженный юнит является успешным конечным состоянием", async () => {
|
||||
await expect(
|
||||
resetFailedUnit(
|
||||
"hy2xs-admin",
|
||||
dependencies(
|
||||
result(5, "", "Unit hy2xs-admin.service not loaded."),
|
||||
result(0, "LoadState=not-found\nActiveState=inactive\n"),
|
||||
),
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("уже выгруженный юнит не создаёт manual-recovery failure", async () => {
|
||||
const failures = await runRollbackStages([
|
||||
{
|
||||
name: "reset failed hy2xs-admin",
|
||||
run: async () =>
|
||||
resetFailedUnit(
|
||||
"hy2xs-admin",
|
||||
dependencies(
|
||||
result(5, "", "Unit hy2xs-admin.service not loaded."),
|
||||
result(0, "LoadState=not-found\nActiveState=inactive\n"),
|
||||
),
|
||||
),
|
||||
},
|
||||
]);
|
||||
expect(failures).toEqual([]);
|
||||
});
|
||||
|
||||
test("решение не зависит от английского текста stderr", async () => {
|
||||
await expect(
|
||||
resetFailedUnit(
|
||||
"hysteria-server",
|
||||
dependencies(
|
||||
result(5, "", "модуль не загружен"),
|
||||
result(0, "LoadState=not-found\nActiveState=inactive\n"),
|
||||
),
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("сохранившееся failed-состояние остаётся настоящим отказом", async () => {
|
||||
await expect(
|
||||
resetFailedUnit(
|
||||
"hysteria-server",
|
||||
dependencies(
|
||||
result(1, "", "access denied"),
|
||||
result(0, "LoadState=loaded\nActiveState=failed\n"),
|
||||
),
|
||||
),
|
||||
).rejects.toThrow("remains failed");
|
||||
});
|
||||
|
||||
test("ошибка наблюдения не выдаётся за успешную уборку", async () => {
|
||||
await expect(
|
||||
resetFailedUnit(
|
||||
"hysteria-server",
|
||||
dependencies(result(1), result(1, "", "Failed to connect to bus")),
|
||||
),
|
||||
).rejects.toThrow("cannot verify systemd state");
|
||||
});
|
||||
|
||||
test("неполный ответ systemctl show отклоняется fail-closed", async () => {
|
||||
await expect(
|
||||
resetFailedUnit(
|
||||
"hysteria-server",
|
||||
dependencies(result(0), result(0, "LoadState=loaded\n")),
|
||||
),
|
||||
).rejects.toThrow("did not return LoadState and ActiveState");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user