57 lines
3.4 KiB
TypeScript
57 lines
3.4 KiB
TypeScript
// No tool and no agent creates a repository in an organisation (novox/hq issue 1124, ADR 0428 §8): the forge makes
|
|
// whoever creates an organisation's repository its administrator, and an administrator can take its protection off.
|
|
// So gitea_create_repo creates only under the tools' own user, with no owner argument; the tools' team keeps
|
|
// can_create_org_repo false; and the tools' token has no write:organization. The organisation's repositories are
|
|
// made by the module's setup login from its config value `repositories` (cmd/repositories).
|
|
|
|
import { test, after } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
|
|
import { GiteaClient } from "../dist/client.js";
|
|
import { ConfiguredToken, TOKEN_SCOPES } from "../dist/token.js";
|
|
import { getGiteaTools } from "../dist/tools/index.js";
|
|
import { TOOLS_TEAM_UNITS } from "../dist/tools-account.js";
|
|
|
|
const sent: { method: string; path: string; body: any }[] = [];
|
|
|
|
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
const chunks: Buffer[] = [];
|
|
for await (const c of req) chunks.push(c as Buffer);
|
|
const raw = Buffer.concat(chunks).toString();
|
|
const path = new URL(req.url ?? "", "http://x").pathname.replace(/^\/api\/v1/, "");
|
|
const body = raw ? JSON.parse(raw) : undefined;
|
|
sent.push({ method: req.method ?? "GET", path, body });
|
|
res.setHeader("Content-Type", "application/json");
|
|
if (req.method === "POST" && path === "/user/repos") {
|
|
res.statusCode = 201;
|
|
res.end(JSON.stringify({ full_name: `mesh-tools/${body.name}`, name: body.name, owner: { login: "mesh-tools" },
|
|
private: body.private, html_url: "", clone_url: "", default_branch: "main" }));
|
|
return;
|
|
}
|
|
res.statusCode = 404;
|
|
res.end(JSON.stringify({ message: "not found" }));
|
|
});
|
|
await new Promise<void>((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
after(() => new Promise<void>((r) => server.close(() => r())));
|
|
const { port } = server.address() as { port: number };
|
|
const client = new GiteaClient(`http://127.0.0.1:${port}`, new ConfiguredToken("t"));
|
|
const tool = getGiteaTools(client).find((t) => t.name === "gitea_create_repo")!;
|
|
|
|
test("gitea_create_repo has no owner argument and posts only to the tools' own user's repositories (1124/AC8)", async () => {
|
|
assert.equal("owner" in tool.input, false, "the tool takes no owner");
|
|
assert.equal(GiteaClient.prototype.createRepo.length, 1, "createRepo takes the repository alone, no owner");
|
|
// An owner given anyway is not an argument of the tool: the repository is the tools' user's all the same.
|
|
await tool.run({ owner: "novox", name: "mesh-delivery" });
|
|
await client.createRepo({ name: "x" });
|
|
assert.deepEqual(sent.map((s) => `${s.method} ${s.path}`), ["POST /user/repos", "POST /user/repos"]);
|
|
assert.ok(sent.every((s) => !s.path.startsWith("/orgs/")), "nothing is sent to an organisation");
|
|
assert.doesNotMatch(tool.description, /organisation/i, "the description offers no organisation");
|
|
});
|
|
|
|
test("the tools' team cannot create in the organisation and its token holds no write:organization (1124/AC8)", () => {
|
|
assert.equal(TOOLS_TEAM_UNITS.can_create_org_repo, false);
|
|
assert.ok(!TOKEN_SCOPES.includes("write:organization"), `scopes ${TOKEN_SCOPES.join(", ")}`);
|
|
assert.ok(TOKEN_SCOPES.every((s) => !/organization|admin/.test(s)), `scopes ${TOKEN_SCOPES.join(", ")}`);
|
|
});
|