mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-02 00:50:39 +02:00
305 lines
8.8 KiB
JavaScript
305 lines
8.8 KiB
JavaScript
const { expect } = require("chai");
|
|
require("ts-node/register/transpile-only");
|
|
|
|
const ConferenceModel = require("../src/core/model/conference/conferences.model")
|
|
.default;
|
|
const {
|
|
applyConferenceForm,
|
|
} = require("../src/server/routes/conference");
|
|
const {
|
|
hasRepositorySourceChanged,
|
|
shouldReactivateExpiredRepository,
|
|
} = require("../src/server/routes/repository-private");
|
|
const {
|
|
processRemoveRepository,
|
|
createRemoveRepositoryProcessor,
|
|
} = require("../src/queue/processes/removeRepository");
|
|
const {
|
|
processRemoveCache,
|
|
createRemoveCacheProcessor,
|
|
} = require("../src/queue/processes/removeCache");
|
|
const { addRemovalJob } = require("../src/queue");
|
|
const {
|
|
repositoryMaintenanceQuery,
|
|
} = require("../src/server/schedule");
|
|
const Repository = require("../src/core/Repository").default;
|
|
const AnonymizedRepositoryModel = require("../src/core/model/anonymizedRepositories/anonymizedRepositories.model").default;
|
|
|
|
describe("conference edits", function () {
|
|
const form = {
|
|
name: "Updated",
|
|
startDate: "2026-01-01",
|
|
endDate: "2026-02-01",
|
|
url: "https://example.test",
|
|
options: { expirationMode: "never" },
|
|
};
|
|
|
|
it("preserves existing repository membership", function () {
|
|
const existing = [{ id: "507f1f77bcf86cd799439011", addDate: new Date() }];
|
|
const model = new ConferenceModel({ repositories: existing });
|
|
applyConferenceForm(model, form, false);
|
|
expect(model.repositories).to.have.length(1);
|
|
expect(model.repositories[0].id.toString()).to.equal(existing[0].id);
|
|
});
|
|
|
|
it("initializes repository membership for a new conference", function () {
|
|
const model = new ConferenceModel();
|
|
applyConferenceForm(model, form, true);
|
|
expect(model.repositories).to.deep.equal([]);
|
|
});
|
|
});
|
|
|
|
describe("repository update source detection", function () {
|
|
const model = {
|
|
source: {
|
|
commit: "abc123",
|
|
branch: "main",
|
|
repositoryName: "owner/repo",
|
|
},
|
|
};
|
|
|
|
it("does not redownload for an option-only edit", function () {
|
|
expect(
|
|
hasRepositorySourceChanged(model, {
|
|
fullName: "owner/repo",
|
|
source: { commit: "abc123", branch: "main" },
|
|
options: { image: false },
|
|
})
|
|
).to.equal(false);
|
|
});
|
|
|
|
it("redownloads when the commit, branch, or repository changes", function () {
|
|
expect(
|
|
hasRepositorySourceChanged(model, {
|
|
fullName: "owner/repo",
|
|
source: { commit: "def456", branch: "main" },
|
|
})
|
|
).to.equal(true);
|
|
expect(
|
|
hasRepositorySourceChanged(model, {
|
|
fullName: "owner/repo",
|
|
source: { commit: "abc123", branch: "next" },
|
|
})
|
|
).to.equal(true);
|
|
expect(
|
|
hasRepositorySourceChanged(model, {
|
|
fullName: "other/repo",
|
|
source: { commit: "abc123", branch: "main" },
|
|
})
|
|
).to.equal(true);
|
|
});
|
|
|
|
it("rebuilds an expired repository when its expiration is in the future", function () {
|
|
const now = new Date("2026-08-20T00:00:00.000Z");
|
|
expect(
|
|
shouldReactivateExpiredRepository(
|
|
{
|
|
status: "expired",
|
|
options: {
|
|
expirationMode: "redirect",
|
|
expirationDate: new Date("2027-05-01T03:57:53.395Z"),
|
|
},
|
|
},
|
|
now
|
|
)
|
|
).to.equal(true);
|
|
});
|
|
|
|
it("does not rebuild an expired repository with a stale expiration", function () {
|
|
const now = new Date("2026-08-20T00:00:00.000Z");
|
|
expect(
|
|
shouldReactivateExpiredRepository(
|
|
{
|
|
status: "expired",
|
|
options: {
|
|
expirationMode: "redirect",
|
|
expirationDate: new Date("2026-01-01T00:00:00.000Z"),
|
|
},
|
|
},
|
|
now
|
|
)
|
|
).to.equal(false);
|
|
});
|
|
|
|
it("rebuilds an expired repository configured never to expire", function () {
|
|
expect(
|
|
shouldReactivateExpiredRepository({
|
|
status: "expired",
|
|
options: { expirationMode: "never" },
|
|
})
|
|
).to.equal(true);
|
|
});
|
|
});
|
|
|
|
describe("removal workers", function () {
|
|
const job = { data: { repoId: "repo-1" } };
|
|
|
|
it("rejects the repository job after recording a removal error", async function () {
|
|
const statuses = [];
|
|
const failure = new Error("storage unavailable");
|
|
const repo = {
|
|
updateStatus: async (status, message) => statuses.push([status, message]),
|
|
remove: async () => {
|
|
throw failure;
|
|
},
|
|
};
|
|
|
|
let caught;
|
|
try {
|
|
await processRemoveRepository(job, {
|
|
connect: async () => undefined,
|
|
getRepository: async () => repo,
|
|
});
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
expect(caught).to.equal(failure);
|
|
expect(statuses[statuses.length - 1][1]).to.equal(failure.message);
|
|
});
|
|
|
|
it("records errors that happen before repository removal starts", async function () {
|
|
const statuses = [];
|
|
const failure = new Error("status write failed");
|
|
let statusCalls = 0;
|
|
const repo = {
|
|
updateStatus: async (status, message) => {
|
|
statusCalls++;
|
|
statuses.push([status, message]);
|
|
if (statusCalls === 1) throw failure;
|
|
},
|
|
remove: async () => undefined,
|
|
};
|
|
|
|
let caught;
|
|
try {
|
|
await processRemoveRepository(job, {
|
|
connect: async () => undefined,
|
|
getRepository: async () => repo,
|
|
});
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
expect(caught).to.equal(failure);
|
|
expect(statuses[statuses.length - 1][1]).to.equal(failure.message);
|
|
});
|
|
|
|
it("does not treat the BullMQ lock token as a database dependency", async function () {
|
|
const statuses = [];
|
|
const database = {
|
|
connect: async () => undefined,
|
|
getRepository: async () => ({
|
|
updateStatus: async (status) => statuses.push(status),
|
|
remove: async () => undefined,
|
|
}),
|
|
};
|
|
const processor = createRemoveRepositoryProcessor(database);
|
|
|
|
await processor(job, "bullmq-lock-token");
|
|
|
|
expect(statuses).to.include("removing");
|
|
});
|
|
|
|
it("does not add a duplicate when a removal job is live", async function () {
|
|
let additions = 0;
|
|
const queue = {
|
|
getJob: async () => ({
|
|
getState: async () => "active",
|
|
remove: async () => undefined,
|
|
}),
|
|
add: async () => {
|
|
additions++;
|
|
},
|
|
};
|
|
|
|
expect(await addRemovalJob("repo-1", queue)).to.equal(false);
|
|
expect(additions).to.equal(0);
|
|
});
|
|
|
|
it("replaces a terminal removal job with a retryable job", async function () {
|
|
let removed = false;
|
|
let added;
|
|
const queue = {
|
|
getJob: async () => ({
|
|
getState: async () => "failed",
|
|
remove: async () => {
|
|
removed = true;
|
|
},
|
|
}),
|
|
add: async (...args) => {
|
|
added = args;
|
|
},
|
|
};
|
|
|
|
expect(await addRemovalJob("repo-1", queue)).to.equal(true);
|
|
expect(removed).to.equal(true);
|
|
expect(added[0]).to.equal("repo-1");
|
|
expect(added[1]).to.deep.equal({ repoId: "repo-1" });
|
|
expect(added[2].jobId).to.equal("repo-repo-1");
|
|
expect(added[2].attempts).to.equal(3);
|
|
expect(added[2].removeOnFail).to.deep.equal({ count: 1000 });
|
|
});
|
|
|
|
it("rejects cache jobs when cache removal fails", async function () {
|
|
const failure = new Error("storage unavailable");
|
|
let caught;
|
|
try {
|
|
await processRemoveCache(job, {
|
|
connect: async () => undefined,
|
|
getRepository: async () => ({
|
|
removeCache: async () => {
|
|
throw failure;
|
|
},
|
|
}),
|
|
});
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
expect(caught).to.equal(failure);
|
|
});
|
|
|
|
it("keeps the BullMQ lock token out of cache worker injection", async function () {
|
|
let removed = false;
|
|
const processor = createRemoveCacheProcessor({
|
|
connect: async () => undefined,
|
|
getRepository: async () => ({
|
|
removeCache: async () => {
|
|
removed = true;
|
|
},
|
|
}),
|
|
});
|
|
|
|
await processor(job, "bullmq-lock-token");
|
|
|
|
expect(removed).to.equal(true);
|
|
});
|
|
});
|
|
|
|
describe("repository expiration maintenance", function () {
|
|
it("selects due repositories regardless of their cache reset flag", function () {
|
|
const now = new Date("2026-08-20T00:00:00.000Z");
|
|
const query = repositoryMaintenanceQuery(now);
|
|
|
|
expect(query).not.to.have.property("isReseted");
|
|
expect(query.$or[0]).to.deep.equal({
|
|
"options.expirationMode": { $in: ["redirect", "remove"] },
|
|
"options.expirationDate": { $lte: now },
|
|
});
|
|
});
|
|
|
|
it("marks a cache as present again after content is restored", async function () {
|
|
const model = new AnonymizedRepositoryModel({
|
|
repoId: "repo-cache-state",
|
|
owner: "507f1f77bcf86cd799439011",
|
|
isReseted: true,
|
|
status: "ready",
|
|
options: { terms: [], expirationMode: "never" },
|
|
source: { type: "GitHubStream", repositoryName: "owner/repo" },
|
|
});
|
|
const repo = new Repository(model);
|
|
|
|
await repo.markCachePresent();
|
|
|
|
expect(repo.model.isReseted).to.equal(false);
|
|
});
|
|
});
|