fix: retain replacement reservations and bound expiration extensions

This commit is contained in:
Thomas Durieux committed 2026-11-02 21:27:11 +00:00
1 parent d7a5243054
commit afd7a8ff3b
5 files changed
+46 -4

No files matched your search

+23
View File
@@ -85,6 +85,29 @@ async function rejects(promise, message) {
expect(body.file.used).to.equal(1);
} finally { utils.getUser = originalUser; }
});
for (const replacement of [true, false]) {
it(`retains a reservation during retirement and ${replacement ? "replacement" : "releases it after permanent removal"}`, async () => {
const Repository = require("../src/core/Repository").default;
const db = require("../src/server/database");
const wasConnected = db.isConnected;
config.FREE_AUTHOR_REPOSITORIES = 1;
const model = await Repo.create({ repoId: "published", owner, billingManaged: true, status: "ready", size: { storage: 10 } });
await reserveRepository(owner, "published", 20);
const repo = new Repository(model);
repo.resetSate = async () => {
expect((await Repo.findOne({ repoId: "published" })).status).to.equal("removing");
await rejects(reserveRepository(owner, "competitor", 1), "quota_exceeded");
};
db.isConnected = true;
try { await repo.remove({ replacement }); }
finally { db.isConnected = wasConnected; }
expect((await Repo.findOne({ repoId: "published" })).status).to.equal(replacement ? "preparing" : "removed");
if (replacement) {
await rejects(reserveRepository(owner, "competitor", 1), "quota_exceeded");
expect((await BillingQuota.findById(`user:${owner}`)).allocations[0].bytes).to.equal(20);
} else await reserveRepository(owner, "competitor", 1);
});
}
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");
+17
View File
@@ -124,6 +124,23 @@ describe("repository activity dates", () => {
else expect(saved.$unset).to.have.property("billingNeverExpiresUntil", 1);
});
}
it("rejects repeated extensions beyond one year before changing stored settings", async () => {
const repo = repository(); auth(repo);
stub(require("../src/config").default, "BILLING_ENABLED", true);
repo.model.options.expirationMode = "remove";
repo.model.options.expirationDate = new Date();
let writes = 0;
stub(Model, "updateOne", () => ({ exec: async () => { writes++; return { matchedCount: 1 }; } }));
const extend = handler("repository-private", "/:repoId/extend", "post");
await extend({}, { json: () => {} });
await extend({}, { json: () => {} });
const savedDate = repo.model.options.expirationDate;
let error;
try { await extend({}, { json: () => {} }); } catch (caught) { error = caught; }
expect(error?.message).to.equal("invalid_billing_expiration");
expect(writes).to.equal(2);
expect(repo.model.options.expirationDate).to.deep.equal(savedDate);
});
it("dates a dashboard expiration extension", async () => {
const repo = repository(); auth(repo); let saved;
stub(Model, "updateOne", (_filter, update) => ({ exec: async () => { saved = update.$set; } }));
+1 -1
View File
@@ -106,7 +106,7 @@ describe("reconnecting a recreated repository", () => {
expect(saved.update.$set.status).to.equal("preparing");
expect(saved.update.$set.statusDate).to.be.instanceOf(Date);
expect(saved.update.$set.source.commitDate).to.deep.equal(new Date("2026-09-29T00:00:00Z"));
expect(removals).to.deep.equal([{ accessRevision: "old" }]);
expect(removals).to.deep.equal([{ accessRevision: "old", replacement: true }]);
expect(jobs[0][1]).to.deep.equal({ repoId: "recreated" });
});
it("rebuilds when only the repository identity changed", async () => {