mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-04-21 12:56:05 +02:00
164 lines
5.0 KiB
JavaScript
164 lines
5.0 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|