fix: preserve paid hosting deadlines, quotas and review snapshots

This commit is contained in:
Thomas Durieux committed 2026-11-02 21:03:29 +00:00
1 parent 7d7f95506a
commit 036eddfa0e
10 files changed
+116 -27

No files matched your search

+11
View File
@@ -43,6 +43,17 @@ async function rejects(promise, message) {
await Promise.all([reserveRepository(owner, id, 90), reserveRepository(owner, id, 1)]);
expect((await BillingQuota.findById(`user:${owner}`)).allocations[0].bytes).to.equal(90);
});
it("does not seed personal capacity with conference-funded or pass-funded repositories", async () => {
await BillingGrant.create([
{ _id: "conf-grant", owner, scope: "conference:funded", status: "paid", until: future(), planId: "conference" },
{ _id: "pass-grant", owner, scope: "repo:pass", status: "paid", until: future(), planId: "author_pass" },
]);
await Repo.create([...["a", "b", "c"].map(repoId => ({ repoId, owner, conference: "funded", status: "ready", size: { storage: 80 } })),
{ repoId: "pass", owner, status: "ready", size: { storage: 80 } }]);
await reserveRepository(owner, "personal", 10);
const ledger = await BillingQuota.findById(`user:${owner}`);
expect(ledger.allocations.map(a => a.repoId)).to.deep.equal(["personal"]);
});
it("counts legacy repositories and releases removed repositories", async () => {
await Repo.create(["one", "two", "three"].map(repoId => ({ repoId, owner, status: "ready", size: { storage: 10 } })));
await rejects(reserveRepository(owner, "four", 1), "quota_exceeded");
+21
View File
@@ -66,6 +66,27 @@ describe("repository activity dates", () => {
expect(saved.source.commit).to.equal("abc123");
expect(saved).not.to.have.property("publishedAt");
});
for (const mode of ["never", "remove"]) {
it(`persists the billing deadline when settings change to ${mode}`, async () => {
const config = require("../src/config").default;
const expiration = require("../src/core/billing/expiration");
const quotas = require("../src/core/billing/quotas");
const repo = repository(); auth(repo); let saved;
const deadline = new Date("2027-06-01");
repo.model.options.expirationMode = "remove";
repo.model.billingNeverExpiresUntil = deadline;
stub(config, "BILLING_ENABLED", true);
stub(expiration, "neverExpirationDeadline", async () => deadline);
stub(quotas, "assertConferenceAdmission", async () => {});
stub(Model, "updateOne", (_filter, update) => ({ exec: async () => { saved = update; return { matchedCount: 1 }; } }));
await handler("repository-private", "/:repoId/", "post")({ body: {
repoId: repo.repoId, fullName: "owner/repo", source: { commit: "abc123", branch: "main" },
terms: [], options: { expirationMode: mode, expirationDate: "2027-01-01" },
} }, { json: () => {} });
if (mode === "never") expect(saved.$set.billingNeverExpiresUntil).to.deep.equal(deadline);
else expect(saved.$unset).to.have.property("billingNeverExpiresUntil", 1);
});
}
it("dates a dashboard expiration extension", async () => {
const repo = repository(); auth(repo); let saved;
stub(Model, "updateOne", (_filter, update) => ({ exec: async () => { saved = update.$set; } }));
+31
View File
@@ -72,6 +72,37 @@ describe("repository refresh and restoration", () => {
});
}
for (const force of [true, false]) {
it(`preserves the published snapshot when a ${force ? "manual" : "automatic"} refresh exceeds capacity`, async () => {
const config = require("../src/config").default;
const quotas = require("../src/core/billing/quotas");
const Stream = require("../src/core/source/GitHubStream").default;
const repo = repository("ready");
repo.model.billingManaged = true;
repo.model.options.update = true;
repo.model.lastView = new Date("2020-01-01");
stub(config, "BILLING_ENABLED", true);
stub(db, "isConnected", false);
stub(quotas, "allowance", async () => ({ plan: "author_monthly" }));
stub(quotas, "reserveRepository", async (_owner, _id, bytes) => {
expect(bytes).to.equal(999);
throw new Error("quota_exceeded");
});
stub(Stream.prototype, "getFiles", async function () {
expect(this.data.commit).to.equal("new-sha"); return [{ size: 999 }];
});
repo.getToken = async () => "test-token";
stub(github, "getRepositoryFromGitHub", async () => ({ fullName: "owner/repo", model: {},
branches: async () => [{ name: "main", commit: "new-sha" }],
}));
repo.resetSate = async () => { throw new Error("must preserve files"); };
let error;
try { await repo.updateIfNeeded({ force }); } catch (caught) { error = caught; }
expect(error?.message).to.equal(force ? "quota_exceeded" : undefined);
expect(repo.model.source.commit).to.equal("saved-sha");
expect(repo.status).to.equal("ready");
});
}
it("preserves a removed repository and reports a GitHub access failure", async () => {
const repo = repository("removed");
const failure = new Error("token_expired");
+3 -1
View File
@@ -237,11 +237,13 @@ describe("Vue 3 UI", function () {
ui = await browser("/anonymize?billingDraft=saved-task&billing=success", { ...repositorySource,
"/api/billing/": { ...taskBilling, allowance: { plan: "author_monthly", repositories: 20 } },
"/api/billing/refresh": { ok: true, pending: false },
"/api/billing/draft/saved-task": { id: "saved-task", path: "/anonymize", values: { sourceUrl: "https://github.com/owner/repo", terms: "Restored Author", repoId: "saved-repo", source: { branch: "main" }, options: { expirationMode: "never" } } },
"/api/billing/draft/saved-task": { id: "saved-task", path: "/anonymize", values: { sourceUrl: "https://github.com/owner/repo", terms: "Restored Author", repoId: "saved-repo", source: { branch: "main" }, options: { expirationMode: "never", image: false, pdf: false }, conference: "paid-conf" } },
"/api/conferences/paid-conf": { endDate: "2027-05-01", options: { image: true, pdf: true } },
});
await delay(150);
expect(ui.window.document.querySelector("#sourceUrl")._field.binding.state.terms).to.equal("Restored Author");
expect(ui.window.document.querySelector("#sourceUrl")._field.binding.state.repoId).to.equal("saved-repo");
expect(ui.window.document.querySelector("#sourceUrl")._field.binding.state.options).to.include({ expirationMode: "never", image: false, pdf: false });
expect(ui.app.router.currentRoute.value.query).to.deep.equal({});
expect(ui.window.document.querySelector("#sourceUrl")._field.binding.state.hostingNeedsPayment()).to.equal(false);
expect(ui.window.document.querySelector("#repository-hosting").textContent).to.include("subscription is active");