Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions tests-js/bugreport-proxy-worker.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,53 @@ test("non-error routing is untouched", async () => {
new Request("https://proxy.example/update", { method: "GET" }), SECRET_ENV);
assert.equal(unauth.status, 401, "a wrong/absent secret still 401s first");
});

const FIXTURE = join(dirname(fileURLToPath(import.meta.url)),
"..", "tests", "fixtures", "bugreport_contract", "automatic_report.md");
const REPORT_ENV = { GITHUB_TOKEN: "g", TARGET_REPO: "a/b" };

// POST a report to the Worker with GitHub stubbed; returns the response, its JSON
// body, and every request the Worker sent upstream.
async function postReport(worker, payload) {
const realFetch = globalThis.fetch;
const upstream = [];
globalThis.fetch = async (u, init) => {
upstream.push({ url: String(u), body: init && init.body ? JSON.parse(init.body) : null });
return new Response(JSON.stringify({ html_url: "https://github.com/a/b/issues/1", number: 1 }),
{ status: 201, headers: { "Content-Type": "application/json" } });
};
try {
const res = await worker.fetch(new Request("https://proxy.example/", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) }), REPORT_ENV);
return { res, body: await res.json(), upstream };
} finally {
globalThis.fetch = realFetch;
}
}

test("a body without the report's sections is still filed, flagged in the note", async () => {
const worker = await loadWorker();
for (const payload of [{ title: "%n", body: "%s%s%s" }, { title: "A".repeat(128), body: "name" }]) {
const { res, upstream } = await postReport(worker, payload);
assert.equal(res.status, 201, "a report is never refused for its shape");
assert.equal(upstream.length, 1);
const filed = upstream[0].body.body;
assert.ok(filed.startsWith("> Filed through the localm bug reporter on behalf of a user."), filed.slice(0, 120));
assert.match(filed, /does not have the sections a report built by localm contains/);
assert.ok(filed.endsWith(payload.body));
}
});

test("a report built by localm is filed, prefixed with the reporter note", async () => {
const worker = await loadWorker();
const report = readFileSync(FIXTURE, "utf8");
const { res, body, upstream } = await postReport(worker, { title: "localm bug report", body: report });
assert.equal(res.status, 201);
assert.equal(body.number, 1);
assert.equal(upstream.length, 1);
const filed = upstream[0].body.body;
assert.ok(filed.startsWith("> Filed through the localm bug reporter on behalf of a user."), filed.slice(0, 120));
assert.ok(filed.endsWith(report), "the report follows the note unchanged");
assert.doesNotMatch(filed, /does not have the sections/, "a real report is not flagged");
});
27 changes: 24 additions & 3 deletions tools/bugreport-proxy/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ async function fileReport(request, env) {
let payload;
try { payload = await request.json(); } catch { return json({ error: "invalid JSON" }, 400); }
const title = (String(payload.title || "").slice(0, 200).trim()) || "localm bug report";
const body = String(payload.body || "").slice(0, 60000);
if (!body.trim()) return json({ error: "empty report body" }, 400);
const issue = { title, body };
const report = String(payload.body || "").slice(0, 60000);
if (!report.trim()) return json({ error: "empty report body" }, 400);
const note = looksLikeLocalmReport(report) ? REPORTER_NOTE : REPORTER_NOTE_UNSHAPED;
const issue = { title, body: note + report };
if (env.ISSUE_LABELS) {
const labels = env.ISSUE_LABELS.split(",").map((s) => s.trim()).filter(Boolean);
if (labels.length) issue.labels = labels;
Expand All @@ -119,6 +120,26 @@ async function fileReport(request, env) {
return json({ ok: true, url: data.html_url, number: data.number }, 201);
}

// Prepended to the body of every issue this proxy files.
const REPORTER_NOTE =
"> Filed through the localm bug reporter on behalf of a user. " +
"This text was not written by the repository owner.\n\n";

// Prepended instead of REPORTER_NOTE when the body lacks the REPORT_SECTIONS headings.
const REPORTER_NOTE_UNSHAPED =
"> Filed through the localm bug reporter on behalf of a user. " +
"This text was not written by the repository owner.\n" +
"> It does not have the sections a report built by localm contains.\n\n";

// Section headings every report built by localm's reporter contains.
const REPORT_SECTIONS = ["## What happened", "## Environment"];

// True when *text* has every heading in REPORT_SECTIONS on a line of its own.
function looksLikeLocalmReport(text) {
const lines = text.split(/\r?\n/).map((line) => line.trim());
return REPORT_SECTIONS.every((h) => lines.includes(h));
}

// ---------------------------- issues ------------------------------------

async function listIssues(request, env, url) {
Expand Down
Loading