mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
fix(sync): let a linked desktop enable mirrored plugins it does not trust on its own (#1632)
* fix(sync): let a linked desktop enable mirrored plugins it does not trust on its own * fix(sync): switch off an untrusted mirrored plugin when the server updates it * fix: require local consent for mirrored plugin activation * docs: describe consent fields on the plugin state endpoint
This commit is contained in:
1 parent
9b0af07607
commit
b3b08b4ff2
17 files changed
+515
-36
No files matched your search
@@ -176,6 +176,7 @@ const manageMock = vi.hoisted(() => ({
|
||||
deletePluginData: vi.fn(),
|
||||
planStateChange: vi.fn(),
|
||||
setPluginState: vi.fn(),
|
||||
enablePluginWithConsent: vi.fn(),
|
||||
getDeveloperMode: vi.fn(),
|
||||
setDeveloperMode: vi.fn(),
|
||||
stageUpload: vi.fn(),
|
||||
@@ -215,6 +216,12 @@ const onboardingMock = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("../../../plugins/onboarding.js", () => onboardingMock);
|
||||
|
||||
const syncPluginsMock = vi.hoisted(() => ({
|
||||
isServerManaged: vi.fn((_id: string) => true),
|
||||
}));
|
||||
|
||||
vi.mock("../../../sync/client/plugins.js", () => syncPluginsMock);
|
||||
|
||||
const pluginRoutes = (await import("../../../database/routes/plugins.js"))
|
||||
.default;
|
||||
|
||||
@@ -1139,6 +1146,65 @@ describe("plugins route", () => {
|
||||
expect(manageMock.installPlugin).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("on a linked desktop", () => {
|
||||
const switchOn = (id: string) =>
|
||||
fetch(`${baseUrl}/plugins/${id}/state`, {
|
||||
method: "PATCH",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
enabled: true,
|
||||
version: "1.0.0",
|
||||
capabilities: ["kv:own"],
|
||||
}),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
manageMock.isLinkedDesktop.mockResolvedValue(true);
|
||||
state.plugins.set("sample-plugin", makePlugin({ state: "disabled" }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
syncPluginsMock.isServerManaged.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
it("refuses to switch a plugin the server decides", async () => {
|
||||
const res = await switchOn("sample-plugin");
|
||||
expect(res.status).toBe(409);
|
||||
expect((await res.json()).code).toBe("MANAGED_BY_SERVER");
|
||||
expect(manageMock.setPluginState).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lets the desktop's admin switch one the server does not", async () => {
|
||||
syncPluginsMock.isServerManaged.mockImplementation(() => false);
|
||||
manageMock.enablePluginWithConsent.mockResolvedValue({
|
||||
enable: [],
|
||||
disable: [],
|
||||
});
|
||||
const res = await switchOn("sample-plugin");
|
||||
expect(res.status).toBe(200);
|
||||
expect(manageMock.enablePluginWithConsent).toHaveBeenCalledWith(
|
||||
"sample-plugin",
|
||||
expect.objectContaining({
|
||||
version: "1.0.0",
|
||||
capabilities: ["kv:own"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("says per plugin whether the server decides it", async () => {
|
||||
state.plugins.set("other", makePlugin({ id: "other" }));
|
||||
syncPluginsMock.isServerManaged.mockImplementation(
|
||||
(id) => id === "other",
|
||||
);
|
||||
const listed = (await (
|
||||
await fetch(`${baseUrl}/plugins`)
|
||||
).json()) as Array<{ id: string; managedByServer: boolean }>;
|
||||
expect(
|
||||
Object.fromEntries(listed.map((p) => [p.id, p.managedByServer])),
|
||||
).toEqual({ "sample-plugin": false, other: true });
|
||||
});
|
||||
});
|
||||
|
||||
it("previews a state change without making it", async () => {
|
||||
state.plugins.set("sample-plugin", makePlugin());
|
||||
manageMock.planStateChange.mockResolvedValue({
|
||||
|
||||
Reference in new issue
Block a user