mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 18:30:39 +09:00
169 lines
4.4 KiB
TypeScript
169 lines
4.4 KiB
TypeScript
|
|
import express from "express";
|
||
|
|
import request from "supertest";
|
||
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||
|
|
|
||
|
|
const mockRegistry = vi.hoisted(() => ({
|
||
|
|
getById: vi.fn(),
|
||
|
|
getByKey: vi.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
const mockLifecycle = vi.hoisted(() => ({
|
||
|
|
load: vi.fn(),
|
||
|
|
upgrade: vi.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
vi.mock("../services/plugin-registry.js", () => ({
|
||
|
|
pluginRegistryService: () => mockRegistry,
|
||
|
|
}));
|
||
|
|
|
||
|
|
vi.mock("../services/plugin-lifecycle.js", () => ({
|
||
|
|
pluginLifecycleManager: () => mockLifecycle,
|
||
|
|
}));
|
||
|
|
|
||
|
|
vi.mock("../services/activity-log.js", () => ({
|
||
|
|
logActivity: vi.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
vi.mock("../services/live-events.js", () => ({
|
||
|
|
publishGlobalLiveEvent: vi.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
async function createApp(actor: Record<string, unknown>, loaderOverrides: Record<string, unknown> = {}) {
|
||
|
|
const [{ pluginRoutes }, { errorHandler }] = await Promise.all([
|
||
|
|
import("../routes/plugins.js"),
|
||
|
|
import("../middleware/index.js"),
|
||
|
|
]);
|
||
|
|
|
||
|
|
const loader = {
|
||
|
|
installPlugin: vi.fn(),
|
||
|
|
...loaderOverrides,
|
||
|
|
};
|
||
|
|
|
||
|
|
const app = express();
|
||
|
|
app.use(express.json());
|
||
|
|
app.use((req, _res, next) => {
|
||
|
|
req.actor = actor as typeof req.actor;
|
||
|
|
next();
|
||
|
|
});
|
||
|
|
app.use("/api", pluginRoutes({} as never, loader as never));
|
||
|
|
app.use(errorHandler);
|
||
|
|
|
||
|
|
return { app, loader };
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("plugin install and upgrade authz", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.resetAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("rejects plugin installation for non-admin board users", async () => {
|
||
|
|
const { app, loader } = await createApp({
|
||
|
|
type: "board",
|
||
|
|
userId: "user-1",
|
||
|
|
source: "session",
|
||
|
|
isInstanceAdmin: false,
|
||
|
|
companyIds: ["company-1"],
|
||
|
|
});
|
||
|
|
|
||
|
|
const res = await request(app)
|
||
|
|
.post("/api/plugins/install")
|
||
|
|
.send({ packageName: "paperclip-plugin-example" });
|
||
|
|
|
||
|
|
expect(res.status).toBe(403);
|
||
|
|
expect(loader.installPlugin).not.toHaveBeenCalled();
|
||
|
|
}, 20_000);
|
||
|
|
|
||
|
|
it("allows instance admins to install plugins", async () => {
|
||
|
|
const pluginId = "11111111-1111-4111-8111-111111111111";
|
||
|
|
const pluginKey = "paperclip.example";
|
||
|
|
const discovered = {
|
||
|
|
manifest: {
|
||
|
|
id: pluginKey,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
mockRegistry.getByKey.mockResolvedValue({
|
||
|
|
id: pluginId,
|
||
|
|
pluginKey,
|
||
|
|
packageName: "paperclip-plugin-example",
|
||
|
|
version: "1.0.0",
|
||
|
|
});
|
||
|
|
mockRegistry.getById.mockResolvedValue({
|
||
|
|
id: pluginId,
|
||
|
|
pluginKey,
|
||
|
|
packageName: "paperclip-plugin-example",
|
||
|
|
version: "1.0.0",
|
||
|
|
});
|
||
|
|
mockLifecycle.load.mockResolvedValue(undefined);
|
||
|
|
|
||
|
|
const { app, loader } = await createApp(
|
||
|
|
{
|
||
|
|
type: "board",
|
||
|
|
userId: "admin-1",
|
||
|
|
source: "session",
|
||
|
|
isInstanceAdmin: true,
|
||
|
|
companyIds: [],
|
||
|
|
},
|
||
|
|
{ installPlugin: vi.fn().mockResolvedValue(discovered) },
|
||
|
|
);
|
||
|
|
|
||
|
|
const res = await request(app)
|
||
|
|
.post("/api/plugins/install")
|
||
|
|
.send({ packageName: "paperclip-plugin-example" });
|
||
|
|
|
||
|
|
expect(res.status).toBe(200);
|
||
|
|
expect(loader.installPlugin).toHaveBeenCalledWith({
|
||
|
|
packageName: "paperclip-plugin-example",
|
||
|
|
version: undefined,
|
||
|
|
});
|
||
|
|
expect(mockLifecycle.load).toHaveBeenCalledWith(pluginId);
|
||
|
|
}, 20_000);
|
||
|
|
|
||
|
|
it("rejects plugin upgrades for non-admin board users", async () => {
|
||
|
|
const pluginId = "11111111-1111-4111-8111-111111111111";
|
||
|
|
const { app } = await createApp({
|
||
|
|
type: "board",
|
||
|
|
userId: "user-1",
|
||
|
|
source: "session",
|
||
|
|
isInstanceAdmin: false,
|
||
|
|
companyIds: ["company-1"],
|
||
|
|
});
|
||
|
|
|
||
|
|
const res = await request(app)
|
||
|
|
.post(`/api/plugins/${pluginId}/upgrade`)
|
||
|
|
.send({});
|
||
|
|
|
||
|
|
expect(res.status).toBe(403);
|
||
|
|
expect(mockRegistry.getById).not.toHaveBeenCalled();
|
||
|
|
expect(mockLifecycle.upgrade).not.toHaveBeenCalled();
|
||
|
|
}, 20_000);
|
||
|
|
|
||
|
|
it("allows instance admins to upgrade plugins", async () => {
|
||
|
|
const pluginId = "11111111-1111-4111-8111-111111111111";
|
||
|
|
mockRegistry.getById.mockResolvedValue({
|
||
|
|
id: pluginId,
|
||
|
|
pluginKey: "paperclip.example",
|
||
|
|
version: "1.0.0",
|
||
|
|
});
|
||
|
|
mockLifecycle.upgrade.mockResolvedValue({
|
||
|
|
id: pluginId,
|
||
|
|
version: "1.1.0",
|
||
|
|
});
|
||
|
|
|
||
|
|
const { app } = await createApp({
|
||
|
|
type: "board",
|
||
|
|
userId: "admin-1",
|
||
|
|
source: "session",
|
||
|
|
isInstanceAdmin: true,
|
||
|
|
companyIds: [],
|
||
|
|
});
|
||
|
|
|
||
|
|
const res = await request(app)
|
||
|
|
.post(`/api/plugins/${pluginId}/upgrade`)
|
||
|
|
.send({ version: "1.1.0" });
|
||
|
|
|
||
|
|
expect(res.status).toBe(200);
|
||
|
|
expect(mockLifecycle.upgrade).toHaveBeenCalledWith(pluginId, "1.1.0");
|
||
|
|
}, 20_000);
|
||
|
|
});
|