fix: repair private repository access from the edit page (#892)

* fix: repair private repository access from the edit page

* fix: respect App rollout and reload renamed sources after repair

* fix: gate access repair by status and update response tests
This commit is contained in:
Thomas Durieux authored and GitHub committed 2026-10-11 12:13:02 +01:00
1 parent 4a06aa9888
commit 1d326e7c32
9 files changed
+180 -23

No files matched your search

+67
View File
@@ -903,6 +903,73 @@ describe("Vue 3 UI", function () {
});
}
for (const scenario of ["success", "failure", "renamed"]) {
it(`repairs App access without replacing the source or snapshot (${scenario})`, async function () {
const failed = scenario === "failure";
const fullName = scenario === "renamed" ? "owner/renamed" : "owner/repo";
let repaired = false;
const available = value => () => repaired ? value : { __status: 403, body: { error: "github_app_access_required" } };
ui = await browser("/anonymize/test", {
"/api/user": { username: "owner" },
"/github/connections": { csrf: "repair-csrf", appEnabled: true, appConnected: true },
"/api/repo/test": { connection: "github-app", role: "owner",
source: { fullName: "owner/repo", repositoryID: "saved-record", branch: "main", commit: "saved-sha" },
options: { terms: ["author"], update: false, expirationMode: "never" } },
"/api/repo/owner/repo/": available({ id: "saved-record", repo: "repo", defaultBranch: "main" }),
"/api/repo/owner/repo/branches": available([{ name: "main", commit: "new-sha" }]),
"/api/repo/owner/repo/readme": available("readme"),
"/github/connections/migrate": async request => {
expect(request.headers["X-CSRF-Token"]).to.equal("repair-csrf");
expect(request.payload).to.deep.equal({ type: "repository", id: "test", connection: "github-app" });
await delay(20);
if (failed) return { __status: 403, body: { error: "github_app_access_required" } };
repaired = true;
return { connection: "github-app", fullName };
},
"/api/repo/owner/renamed/": { id: "saved-record", repo: "renamed", defaultBranch: "main" },
"/api/repo/owner/renamed/branches": [{ name: "main", commit: "new-sha" }],
"/api/repo/owner/renamed/readme": "readme",
});
await delay(60);
const button = [...ui.window.document.querySelectorAll("button")].find(b => b.textContent === "Repair GitHub access");
expect(button).not.to.equal(undefined);
for (let node = button; node; node = node.parentElement) expect(ui.window.getComputedStyle(node).display).not.to.equal("none");
button.click(); button.click();
await delay(100);
expect(ui.requests.filter(r => r.method === "POST")).to.have.length(1);
expect(ui.window.document.querySelector("#commit").value).to.equal("saved-sha");
expect(ui.requests.some(r => r.url.searchParams.get("reconnect") === "1")).to.equal(false);
expect(ui.window.document.body.textContent).to.include(failed ? "Unable to repair GitHub access" : "GitHub access repaired");
if (scenario === "renamed") {
expect(ui.window.document.querySelector("#sourceUrl").value).to.equal("https://github.com/owner/renamed");
const branches = ui.requests.find(r => r.url.pathname === "/api/repo/owner/renamed/branches");
expect(branches.url.searchParams.get("anonymizedRepoId")).to.equal("test");
expect(ui.window.document.querySelector("#branch").value).to.equal("main");
}
});
}
it("hides App repair controls when migrations are disabled", async function () {
ui = await browser("/anonymize/test", {
"/github/connections": { appEnabled: false, appConnected: true },
"/api/repo/test": { connection: "github-app", role: "owner",
source: { fullName: "owner/repo", branch: "main", commit: "saved-sha" }, options: { terms: [], update: false } },
});
expect([...ui.window.document.querySelectorAll("button")].some(b => b.textContent === "Repair GitHub access" || b.textContent === "Manage access")).to.equal(false);
});
for (const status of ["removed", "archived", "preparing", "queue", "download", "removing", "expiring", "ready", "error", "expired"]) {
it(`gates App access repair for repository status ${status}`, async function () {
ui = await browser("/anonymize/test", {
"/github/connections": { appEnabled: true, appConnected: true },
"/api/repo/test": { status, connection: "github-app", role: "owner",
source: { fullName: "owner/repo", branch: "main", commit: "saved-sha" }, options: { terms: [], update: false } },
});
const shown = [...ui.window.document.querySelectorAll("button")].some(b => b.textContent === "Repair GitHub access");
expect(shown).to.equal(["ready", "error", "expired"].includes(status));
});
}
it("reconnects a recreated source without saving until the owner submits", async function () {
const oldCommit = "deadbeef";
const newCommit = "1a212f1deb74b123804ec90d098c0f843c30da5b";