mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-06-08 00:23:55 +02:00
Fix 9 bugs and add 103 tests for core anonymization, config, and routing (#669)
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
const { expect } = require("chai");
|
||||
|
||||
/**
|
||||
* Tests for Conference logic bugs.
|
||||
*
|
||||
* The key bug was that Conference._repositories was initialized as []
|
||||
* (truthy), so repositories() always returned the empty array without
|
||||
* querying the database. The fix initializes it as null.
|
||||
*/
|
||||
|
||||
describe("Conference._repositories initialization", function () {
|
||||
it("empty array [] is truthy (demonstrates the root cause)", function () {
|
||||
// This is why `if (this._repositories) return this._repositories;`
|
||||
// was always short-circuiting - an empty array is truthy in JS
|
||||
expect([]).to.not.be.null;
|
||||
expect([]).to.not.be.undefined;
|
||||
// In a boolean context, [] is truthy:
|
||||
expect(!![]).to.be.true;
|
||||
});
|
||||
|
||||
it("null is falsy (the fix)", function () {
|
||||
// After the fix, _repositories starts as null so the DB query runs
|
||||
expect(!!null).to.be.false;
|
||||
});
|
||||
|
||||
it("simulates the fixed repositories() cache behavior", function () {
|
||||
// Simulate the Conference class behavior
|
||||
class FakeConference {
|
||||
constructor() {
|
||||
this._repositories = null; // fixed: was []
|
||||
}
|
||||
repositories() {
|
||||
if (this._repositories) return this._repositories;
|
||||
// In real code this would query the DB
|
||||
this._repositories = [{ id: "repo1" }, { id: "repo2" }];
|
||||
return this._repositories;
|
||||
}
|
||||
}
|
||||
|
||||
const conf = new FakeConference();
|
||||
const repos = conf.repositories();
|
||||
expect(repos).to.have.length(2);
|
||||
expect(repos[0].id).to.equal("repo1");
|
||||
|
||||
// Second call uses the cache
|
||||
const repos2 = conf.repositories();
|
||||
expect(repos2).to.equal(repos); // same reference
|
||||
});
|
||||
|
||||
it("demonstrates the old buggy behavior (always returned empty array)", function () {
|
||||
class BuggyConference {
|
||||
constructor() {
|
||||
this._repositories = []; // old buggy initialization
|
||||
}
|
||||
repositories() {
|
||||
if (this._repositories) return this._repositories;
|
||||
// This line was NEVER reached because [] is truthy
|
||||
this._repositories = [{ id: "repo1" }];
|
||||
return this._repositories;
|
||||
}
|
||||
}
|
||||
|
||||
const conf = new BuggyConference();
|
||||
const repos = conf.repositories();
|
||||
// The bug: always returns empty array, DB query never runs
|
||||
expect(repos).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PullRequest.check() async expiration", function () {
|
||||
it("async check() allows awaiting expire()", async function () {
|
||||
// Simulates the fix: check() is now async so expire() can be awaited
|
||||
let expired = false;
|
||||
const fakePR = {
|
||||
status: "ready",
|
||||
options: {
|
||||
expirationMode: "date",
|
||||
expirationDate: new Date(Date.now() - 1000), // in the past
|
||||
},
|
||||
async expire() {
|
||||
expired = true;
|
||||
this.status = "expired";
|
||||
},
|
||||
async check() {
|
||||
if (
|
||||
this.options.expirationMode !== "never" &&
|
||||
this.status === "ready" &&
|
||||
this.options.expirationDate
|
||||
) {
|
||||
if (this.options.expirationDate <= new Date()) {
|
||||
await this.expire();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
await fakePR.check();
|
||||
expect(expired).to.be.true;
|
||||
expect(fakePR.status).to.equal("expired");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Admin MongoDB query safety", function () {
|
||||
function escapeRegex(str) {
|
||||
return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
it("escapes regex special characters in search input", function () {
|
||||
const malicious = ".*";
|
||||
const escaped = escapeRegex(malicious);
|
||||
expect(escaped).to.equal("\\.\\*");
|
||||
});
|
||||
|
||||
it("escapes parentheses that could cause ReDoS", function () {
|
||||
const input = "((((((((((a]))))))))";
|
||||
const escaped = escapeRegex(input);
|
||||
// Escaped string should be safe to compile as regex
|
||||
expect(() => new RegExp(escaped)).to.not.throw();
|
||||
});
|
||||
|
||||
it("preserves alphanumeric characters", function () {
|
||||
const input = "normalSearch123";
|
||||
expect(escapeRegex(input)).to.equal("normalSearch123");
|
||||
});
|
||||
|
||||
it("escapes dots so they match literally", function () {
|
||||
const input = "file.txt";
|
||||
const escaped = escapeRegex(input);
|
||||
const regex = new RegExp(escaped);
|
||||
expect(regex.test("file.txt")).to.be.true;
|
||||
expect(regex.test("fileXtxt")).to.be.false;
|
||||
});
|
||||
|
||||
describe("empty $or guard", function () {
|
||||
it("empty $or array would fail in MongoDB", function () {
|
||||
// MongoDB requires $or to have at least one expression
|
||||
// The fix: only add { $or: status } when status.length > 0
|
||||
const status = [];
|
||||
const query = [];
|
||||
|
||||
// Fixed logic:
|
||||
if (status.length > 0) {
|
||||
query.push({ $or: status });
|
||||
}
|
||||
|
||||
// When no filters are selected, query should be empty
|
||||
// (no $or clause at all)
|
||||
expect(query).to.have.length(0);
|
||||
});
|
||||
|
||||
it("adds $or when status filters are present", function () {
|
||||
const status = [{ status: "ready" }, { status: "error" }];
|
||||
const query = [];
|
||||
|
||||
if (status.length > 0) {
|
||||
query.push({ $or: status });
|
||||
}
|
||||
|
||||
expect(query).to.have.length(1);
|
||||
expect(query[0].$or).to.have.length(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user