117 lines
3.5 KiB
TypeScript
117 lines
3.5 KiB
TypeScript
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");
|
|
});
|
|
});
|