Files
anonymous_github/test/conference-pricing.test.js
T

205 lines
6.5 KiB
JavaScript

const { expect } = require("chai");
/**
* Tests for Conference.toJSON() price calculation logic.
*
* Replicates the pricing algorithm from src/core/Conference.ts to test
* the math in isolation without needing MongoDB.
*/
// ---------------------------------------------------------------------------
// Replicated price calculation from Conference.toJSON()
// ---------------------------------------------------------------------------
function calculatePrice(plan, repositories, endDate) {
const pricePerHourPerRepo = plan.pricePerRepository / 30;
let price = 0;
const today = new Date() > endDate ? endDate : new Date();
repositories.forEach((r) => {
const removeDate =
r.removeDate && r.removeDate < today ? r.removeDate : today;
price +=
(Math.max(removeDate.getTime() - r.addDate.getTime(), 0) /
1000 /
60 /
60 /
24) *
pricePerHourPerRepo;
});
return price;
}
function countActiveRepos(repositories) {
return repositories.filter((r) => !r.removeDate).length;
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("Conference price calculation", function () {
const DAY_MS = 24 * 60 * 60 * 1000;
describe("basic pricing", function () {
it("returns 0 for no repositories", function () {
const price = calculatePrice(
{ pricePerRepository: 3 },
[],
new Date(Date.now() + 30 * DAY_MS)
);
expect(price).to.equal(0);
});
it("returns 0 for free plan", function () {
const addDate = new Date(Date.now() - 10 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 0 },
[{ addDate }],
new Date(Date.now() + 30 * DAY_MS)
);
expect(price).to.equal(0);
});
it("calculates price for one repo over 10 days", function () {
const now = new Date();
const addDate = new Date(now.getTime() - 10 * DAY_MS);
const endDate = new Date(now.getTime() + 20 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate }],
endDate
);
// pricePerHourPerRepo = 3/30 = 0.1 per day
// duration ≈ 10 days
// price ≈ 10 * 0.1 = 1.0
expect(price).to.be.closeTo(1.0, 0.01);
});
it("calculates price for multiple repos", function () {
const now = new Date();
const addDate1 = new Date(now.getTime() - 10 * DAY_MS);
const addDate2 = new Date(now.getTime() - 5 * DAY_MS);
const endDate = new Date(now.getTime() + 20 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate: addDate1 }, { addDate: addDate2 }],
endDate
);
// repo1: 10 days * 0.1 = 1.0
// repo2: 5 days * 0.1 = 0.5
// total ≈ 1.5
expect(price).to.be.closeTo(1.5, 0.01);
});
});
describe("removed repositories", function () {
it("uses removeDate as end for removed repos", function () {
const now = new Date();
const addDate = new Date(now.getTime() - 10 * DAY_MS);
const removeDate = new Date(now.getTime() - 5 * DAY_MS);
const endDate = new Date(now.getTime() + 20 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate, removeDate }],
endDate
);
// Only charged for 5 days (add to remove), not 10
// 5 * 0.1 = 0.5
expect(price).to.be.closeTo(0.5, 0.01);
});
it("uses today if removeDate is in the future", function () {
const now = new Date();
const addDate = new Date(now.getTime() - 10 * DAY_MS);
const removeDate = new Date(now.getTime() + 5 * DAY_MS); // future
const endDate = new Date(now.getTime() + 20 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate, removeDate }],
endDate
);
// removeDate is in the future, so today is used instead
// ≈ 10 days * 0.1 = 1.0
expect(price).to.be.closeTo(1.0, 0.01);
});
});
describe("expired conference", function () {
it("caps at endDate when conference is expired", function () {
const endDate = new Date(Date.now() - 5 * DAY_MS); // 5 days ago
const addDate = new Date(endDate.getTime() - 10 * DAY_MS); // 15 days ago
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate }],
endDate
);
// Conference ended 5 days ago, repo was added 10 days before that
// Only charged for 10 days (add to end)
// 10 * 0.1 = 1.0
expect(price).to.be.closeTo(1.0, 0.01);
});
});
describe("edge cases", function () {
it("handles zero-duration repository (add and remove same time)", function () {
const now = new Date();
const addDate = new Date(now.getTime() - 5 * DAY_MS);
const removeDate = addDate; // same time
const endDate = new Date(now.getTime() + 20 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate, removeDate }],
endDate
);
expect(price).to.equal(0);
});
it("handles removeDate before addDate via Math.max", function () {
const now = new Date();
const addDate = new Date(now.getTime() - 5 * DAY_MS);
const removeDate = new Date(now.getTime() - 10 * DAY_MS); // before addDate
const endDate = new Date(now.getTime() + 20 * DAY_MS);
const price = calculatePrice(
{ pricePerRepository: 3 },
[{ addDate, removeDate }],
endDate
);
// Math.max ensures negative duration becomes 0
expect(price).to.equal(0);
});
});
});
describe("Conference active repository count", function () {
it("counts repos without removeDate", function () {
const repos = [
{ addDate: new Date() },
{ addDate: new Date(), removeDate: new Date() },
{ addDate: new Date() },
];
expect(countActiveRepos(repos)).to.equal(2);
});
it("returns 0 when all repos are removed", function () {
const repos = [
{ addDate: new Date(), removeDate: new Date() },
{ addDate: new Date(), removeDate: new Date() },
];
expect(countActiveRepos(repos)).to.equal(0);
});
it("returns 0 for empty list", function () {
expect(countActiveRepos([])).to.equal(0);
});
it("counts all repos when none are removed", function () {
const repos = [
{ addDate: new Date() },
{ addDate: new Date() },
{ addDate: new Date() },
];
expect(countActiveRepos(repos)).to.equal(3);
});
});