feat: finish the ai plugin (B18)

This commit is contained in:
LukeGus committed 2026-09-24 13:43:26 -05:00
1 parent 6b4d1432ab
commit 21937a6b02
193 files changed
+23429 -4244

No files matched your search

+12
View File
@@ -38,6 +38,14 @@ export async function activate(ctx: PluginContext) {
registerFleetRoutes(ctx.http.router<Router>(), repo, ctx);
// The service is gated on fleets.view; changing a fleet also needs
// fleets.manage, as its routes do.
async function requireManage(): Promise<void> {
if (!(await ctx.rbac.has("manage"))) {
throw new Error("Missing permission fleets.manage");
}
}
const service: FleetsService = {
list: async () => {
const userId = ctx.currentActor();
@@ -54,12 +62,16 @@ export async function activate(ctx: PluginContext) {
create: async (input) => {
const userId = ctx.currentActor();
if (!userId) throw new Error("fleets.access.create needs an actor");
await requireManage();
const created = await repo.create(userId, input);
return { id: created.id, name: created.name };
},
addMember: async (fleetId, hostId) => {
const userId = ctx.currentActor();
if (!userId) throw new Error("fleets.access.addMember needs an actor");
await requireManage();
// A host joins a fleet only if the caller can see it.
if (!(await ctx.hosts.get(hostId))) throw new Error("Host not found");
const fleet = await repo.findById(userId, fleetId);
if (!fleet) throw new Error("Fleet not found");
await repo.addMember(fleetId, hostId);
@@ -0,0 +1,38 @@
import { afterEach, describe, expect, it } from "vitest";
import { startServer, type TestServer } from "./helpers";
let server: TestServer | null = null;
afterEach(async () => {
await server?.close();
server = null;
});
interface FleetsService {
create: (input: { name: string }) => Promise<{ id: number; name: string }>;
addMember: (fleetId: number, hostId: number) => Promise<void>;
}
describe("fleets.access", () => {
it("needs fleets.manage to change a fleet", async () => {
server = await startServer({ permissions: ["fleets.view"] });
const service = server.mock.services.get("fleets.access") as FleetsService;
await expect(
server.mock.ctx.asUser("user-1", () => service.create({ name: "prod" })),
).rejects.toThrow("fleets.manage");
});
it("adds only hosts the caller can see", async () => {
server = await startServer();
const service = server.mock.services.get("fleets.access") as FleetsService;
const run = <T>(fn: () => Promise<T>) =>
server!.mock.ctx.asUser("user-1", fn);
const fleet = await run(() => service.create({ name: "prod" }));
await run(() => service.addMember(fleet.id, 10));
await expect(run(() => service.addMember(fleet.id, 99))).rejects.toThrow(
"Host not found",
);
});
});