fix: keep settings links on profile and fill the project table (#887)

This commit is contained in:
Thomas Durieux authored and GitHub committed 2026-10-09 13:20:27 +01:00
1 parent c67d8ec3b2
commit 7319a5a744
10 files changed
+152 -25

No files matched your search

+13 -1
View File
@@ -166,7 +166,8 @@ describe("owner access notifications", () => {
describe("owner email settings", () => {
const utils = require("../src/server/routes/route-utils");
const router = require("../src/server/routes/github-app").githubAppRouter;
const endpoint = router.stack.find(layer => layer.route?.path === "/connections/email").route.stack[0].handle;
const endpoint = router.stack.find(layer => layer.route?.path === "/connections/email" && layer.route.methods.post).route.stack[0].handle;
const readEmail = router.stack.find(layer => layer.route?.path === "/connections/email" && layer.route.methods.get).route.stack[0].handle;
const csrf = router.stack.find(layer => !layer.route && layer.handle.toString().includes("x-csrf-token")).handle;
let oldUser, oldUpdate, writes;
beforeEach(() => {
@@ -177,6 +178,17 @@ describe("owner email settings", () => {
});
afterEach(() => { utils.getUser = oldUser; User.updateOne = oldUpdate; });
const response = () => ({ statusCode: 200, status(code) { this.statusCode = code; return this; }, json(body) { this.body = body; } });
for (const email of ["", "primary@example.com"]) it(`loads the account email and persists its save token (${email || "no address"})`, async () => {
utils.getUser = async () => ({ id: "signed-in-owner", model: { emails: [], notificationEmail: email } });
let saved = false;
const req = { sessionID: "profile-session", session: { save: done => { saved = true; done(); } } };
const res = response();
await readEmail(req, res);
expect(saved).to.equal(true);
expect(res.body).to.deep.equal({ notificationEmail: email, csrf: req.session.githubConnectionCSRF });
expect(res.body.csrf).to.be.a("string").with.length(64);
expect(writes).to.have.length(0);
});
it("updates only the signed-in owner's address", async () => {
const res = response();
await endpoint({ body: { email: " owner@example.com ", ownerId: "someone-else" } }, res);
+58 -1
View File
@@ -17,7 +17,8 @@ async function browser(route = "/", overrides = {}, storage = {}) {
errors.push(error.message);
});
virtualConsole.on("error", error => errors.push(error?.message || String(error)));
const dom = new JSDOM('<!doctype html><html><head></head><body><div id="app"></div></body></html>', {
const base = fs.readFileSync(path.join(publicDir, "index.html"), "utf8").match(/<base\b[^>]*>/i)?.[0] || "";
const dom = new JSDOM(`<!doctype html><html><head>${base}</head><body><div id="app"></div></body></html>`, {
resources: {
interceptors: [requestInterceptor(request => {
const url = request.url;
@@ -80,6 +81,62 @@ describe("Vue 3 UI", function () {
let ui;
afterEach(() => ui?.close());
it("keeps native settings section links on the profile page", async () => {
ui = await browser("/profile", { "/api/user": { username: "owner" } });
const account = ui.window.document.querySelector('.paper-settings-toc a[href="#settings-account"]');
expect(account.href).to.equal("http://localhost/profile#settings-account");
account.click();
await delay(30);
expect(ui.window.location.pathname).to.equal("/profile");
expect(ui.window.location.hash).to.equal("#settings-account");
expect(ui.window.document.querySelector("#settings-account")).not.to.equal(null);
expect(ui.errors).to.deep.equal([]);
});
it("loads and saves account email separately from defaults with CSRF", async () => {
ui = await browser("/profile", {
"/api/user": { username: "owner" },
"/github/connections/email": request => request.method === "POST"
? { notificationEmail: request.payload.email.trim() }
: { notificationEmail: "old@example.com", csrf: "profile-csrf" },
});
expect(ui.window.document.querySelector("#account-email").value).to.equal("old@example.com");
await ui.input("#account-email", "new@example.com");
ui.window.document.querySelector('.paper-account-email button[type="submit"]').click();
await delay(30);
const save = ui.requests.find(r => r.url.pathname === "/github/connections/email" && r.method === "POST");
expect(save.payload).to.deep.equal({ email: "new@example.com" });
expect(save.headers["X-CSRF-Token"]).to.equal("profile-csrf");
expect(ui.requests.filter(r => r.url.pathname === "/api/user/default" && r.method === "POST")).to.have.length(0);
expect(ui.window.document.querySelector(".paper-account-email").textContent).to.include("Email address saved.");
expect(ui.errors).to.deep.equal([]);
});
it("retries email loading and keeps failed edits while preventing duplicate saves", async () => {
let failLoad = true, finishSave;
ui = await browser("/profile", {
"/api/user": { username: "owner" },
"/github/connections/email": request => request.method === "POST"
? new Promise(resolve => { finishSave = () => resolve({ __status: 503, body: {} }); })
: failLoad ? { __status: 503, body: {} } : { notificationEmail: "", csrf: "profile-csrf" },
});
expect(ui.window.document.querySelector("#account-email").disabled).to.equal(true);
failLoad = false;
ui.window.document.querySelector('.paper-account-email button[type="button"]').click();
await delay(30);
await ui.input("#account-email", "owner+alerts@example.com");
const form = ui.window.document.querySelector(".paper-account-email");
form.dispatchEvent(new ui.window.Event("submit", { bubbles: true, cancelable: true }));
form.dispatchEvent(new ui.window.Event("submit", { bubbles: true, cancelable: true }));
await delay(10);
expect(ui.requests.filter(r => r.url.pathname === "/github/connections/email" && r.method === "POST")).to.have.length(1);
expect(ui.window.document.querySelector("#account-email").disabled).to.equal(true);
finishSave(); await delay(30);
expect(ui.window.document.querySelector("#account-email").value).to.equal("owner+alerts@example.com");
expect(form.querySelector('[role="alert"]').textContent).to.include("Unable to save");
expect(ui.errors).to.deep.equal([]);
});
it("prompts owners without email and saves their alert address with CSRF", async () => {
ui = await browser("/dashboard", {
"/api/user": { username: "owner", notificationEmail: "", emailNotificationsEnabled: true, showEmailPrompt: true, emailPromptCSRF: "email-csrf" },