mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-19 22:27:20 +02:00
* fix: retain recipient suppression after access recovery * fix: migrate cold starts and fence resource deactivation * docs: stop every writer before alert reconciliation
931 lines
71 KiB
JavaScript
931 lines
71 KiB
JavaScript
const { expect } = require("chai");
|
|
const mongoose = require("mongoose");
|
|
require("ts-node/register/transpile-only");
|
|
const Users = require("../src/core/model/users/users.model").default;
|
|
const config = require("../src/config").default;
|
|
const alerts = require("../src/core/owner-notifications");
|
|
const { notifyOwnerAccessProblem: notify, resetOwnerAccessAlerts: reset, withOwnerAccessRecovery: recover } = alerts;
|
|
const describeMongo = process.env.TEST_MONGODB_URI ? describe : describe.skip;
|
|
|
|
describeMongo("recipient access alerts in MongoDB", function () {
|
|
this.timeout(15000);
|
|
let owner, otherOwner, resource, other, sent, nativeFetch, originalConfig;
|
|
const coll = kind => mongoose.connection.collection({ repository: "anonymizedrepositories", gist: "anonymizedgists", "pull-request": "anonymizedpullrequests" }[kind]);
|
|
const guards = () => mongoose.connection.collection(alerts.RECIPIENT_ALERTS);
|
|
const recipient = () => alerts.recipientAlertKey("owner@example.com");
|
|
async function create(ownerId, kind = "repository") {
|
|
const id = new mongoose.Types.ObjectId(); await coll(kind).insertOne({ _id: id, owner: ownerId, [{ repository: "repoId", gist: "gistId", "pull-request": "pullRequestId" }[kind]]: String(id), status: "ready" }); return { kind, id: String(id) };
|
|
}
|
|
before(async () => {
|
|
nativeFetch = global.fetch; originalConfig = { ...config };
|
|
await mongoose.connect(process.env.TEST_MONGODB_URI, { dbName: "recipient_alerts_" + Date.now() });
|
|
Object.assign(config, { RESEND_API_KEY: "test-key", EMAIL_FROM: "alerts@example.com", APP_HOSTNAME: "anonymous.example.com" });
|
|
});
|
|
after(async () => { global.fetch = nativeFetch; Object.assign(config, originalConfig); await mongoose.connection.dropDatabase(); await mongoose.disconnect(); });
|
|
beforeEach(async () => {
|
|
for (const c of Object.values(mongoose.connection.collections)) await c.deleteMany({});
|
|
owner = await Users.create({ username: "owner", emails: [{ email: "owner@example.com", default: true }] });
|
|
otherOwner = await Users.create({ username: "other-owner", notificationEmail: "OWNER@example.com" });
|
|
resource = await create(owner._id); other = await create(otherOwner._id, "gist"); sent = [];
|
|
global.fetch = async (_url, options) => { sent.push(options); return { ok: true, status: 200 }; };
|
|
});
|
|
|
|
it("allows only one sender across owners, resources and case variations under concurrent failures", async () => {
|
|
await Promise.all(Array.from({ length: 40 }, (_, i) => notify(i % 2 ? owner.id : otherOwner.id, "token_expired", i % 2 ? resource : other)));
|
|
expect(sent).to.have.length(1); expect(await guards().countDocuments({ _id: recipient() })).to.equal(1);
|
|
expect(await mongoose.connection.collection("owner_access_alert_events").countDocuments({ state: "accepted" })).to.equal(1);
|
|
});
|
|
for (const kind of ["repository", "gist", "pull-request"]) {
|
|
for (const status of ["archived", "expired", "expiring", "removed", "removing"]) it(`does not notify for ${status} ${kind}`, async () => {
|
|
const inactive = await create(owner._id, kind);
|
|
await coll(kind).updateOne({ _id: new mongoose.Types.ObjectId(inactive.id) }, { $set: { status } });
|
|
await notify(owner.id, "token_expired", inactive);
|
|
expect(sent).to.have.length(0); expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect(await mongoose.connection.collection("owner_access_alert_events").countDocuments()).to.equal(0);
|
|
});
|
|
}
|
|
it("cancels a send when the source is removed during audit preparation", async () => {
|
|
const events = mongoose.connection.collection("owner_access_alert_events"), insert = events.insertOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
events.insertOne = async function (...args) { entered(); await new Promise(resolve => { release = resolve; }); return insert.apply(this, args); };
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate;
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { status: "removed" } });
|
|
} finally { events.insertOne = insert; release(); await pending; }
|
|
expect(sent).to.have.length(0); expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("records the triggering resource and categorized failure without its address or source name", async () => {
|
|
await notify(owner.id, "token_expired", resource);
|
|
const records = await mongoose.connection.collection("owner_access_alert_events").find().toArray();
|
|
expect(records).to.have.length(2);
|
|
for (const record of records) {
|
|
expect(String(record.owner)).to.equal(owner.id); expect(String(record.resourceId)).to.equal(resource.id);
|
|
expect(record.resourceKind).to.equal(resource.kind); expect(record.generation).to.equal(null);
|
|
expect(JSON.stringify(record)).not.to.include("owner@example.com");
|
|
}
|
|
expect(records.find(record => record.state === "prepared").failureCode).to.equal("token_expired");
|
|
});
|
|
for (const state of ["accepted", "failed"]) it(`restores a missing guard from ${state} provider history before serving traffic`, async () => {
|
|
await mongoose.connection.collection("owner_access_alert_events").insertOne({ _id: "old/completion", attemptId: "old",
|
|
recipientKey: recipient(), state, completedAt: new Date(1) });
|
|
await Promise.all(Array.from({ length: 5 }, () => alerts.initializeOwnerAccessAlerts()));
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recipient_notified");
|
|
await notify(owner.id, "token_expired", resource); await notify(otherOwner.id, "token_expired", other);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(0);
|
|
await alerts.initializeOwnerAccessAlerts(); expect(sent).to.have.length(0);
|
|
});
|
|
it("does not restore a delivery guard from an uncompleted preparation event", async () => {
|
|
await mongoose.connection.collection("owner_access_alert_events").insertOne({ _id: "prepared", recipientKey: recipient(), state: "prepared", startedAt: new Date(1) });
|
|
await alerts.initializeOwnerAccessAlerts(); expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("resumes provider history restoration after a checkpointed startup failure", async () => {
|
|
const events = mongoose.connection.collection("owner_access_alert_events");
|
|
const keys = Array.from({ length: 105 }, (_, i) => alerts.recipientAlertKey(`old${i}@example.com`));
|
|
await events.insertMany(keys.map((recipientKey, i) => ({ _id: String(i).padStart(3, "0") + "/completion",
|
|
recipientKey, attemptId: String(i), state: "accepted", completedAt: new Date(1) })));
|
|
const collection = guards(), update = collection.updateOne; let restored = 0;
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$setOnInsert?.state === "recipient_notified" && ++restored === 103) throw new Error("interrupted history backfill");
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
try { await alerts.initializeOwnerAccessAlerts(); throw new Error("Expected migration failure"); }
|
|
catch (error) { expect(error.message).to.equal("interrupted history backfill"); }
|
|
finally { collection.updateOne = update; }
|
|
expect((await guards().findOne({ _id: "recipient-alerts-v2" })).deliveryProgress.id).to.equal("099/completion");
|
|
await guards().updateOne({ _id: "recipient-alerts-v2" }, { $set: { leaseUntil: new Date(0) } });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().countDocuments({ state: "recipient_notified" })).to.equal(105);
|
|
expect((await guards().findOne({ _id: "recipient-alerts-v2" })).deliveryProgress.id).to.equal("104/completion");
|
|
expect((await events.indexes()).some(index => index.name === "owner_access_alert_delivery_history" && index.partialFilterExpression)).to.equal(true);
|
|
expect(sent).to.have.length(0);
|
|
});
|
|
it("runs the v2 history migration on a cold start with only a ready v1 marker", async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v1", state: "ready" });
|
|
await mongoose.connection.collection("owner_access_alert_events").insertOne({ _id: "v1/completion", attemptId: "v1",
|
|
recipientKey: recipient(), state: "accepted", completedAt: new Date(1) });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect((await guards().findOne({ _id: "recipient-alerts-v2" })).state).to.equal("ready");
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recipient_notified");
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(0);
|
|
});
|
|
it("cancels a fresh unsent v1 repeat before restoring its delivery suppression", async () => {
|
|
const attemptId = "v1-unsent-repeat";
|
|
await guards().insertMany([{ _id: "recipient-alerts-v1", state: "ready" }, {
|
|
_id: recipient(), owner: owner._id, resourceKind: resource.kind, resourceId: new mongoose.Types.ObjectId(resource.id),
|
|
attemptId, state: "pending", dispatchPrepared: true, resourceRevision: 0, claimedAt: new Date(),
|
|
}]);
|
|
await mongoose.connection.collection("users").updateOne({ _id: owner._id }, { $set: {
|
|
[`accessAlertDispatches.${attemptId}`]: { state: "prepared", claimedAt: new Date() },
|
|
} });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: {
|
|
accessAlertClaimedAt: new Date(), accessAlertAttemptId: attemptId, accessAlertClaimRevision: 1,
|
|
} });
|
|
await mongoose.connection.collection("owner_access_alert_events").insertOne({ _id: "v1-delivered/completion", attemptId: "v1-delivered",
|
|
recipientKey: recipient(), state: "accepted", completedAt: new Date(1) });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recipient_notified");
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
expect((await mongoose.connection.collection("users").findOne({ _id: owner._id })).accessAlertDispatches[attemptId]).to.equal(undefined);
|
|
await alerts.initializeOwnerAccessAlerts(); await notify(owner.id, "token_expired", resource);
|
|
expect(sent).to.have.length(0);
|
|
});
|
|
const models = () => ({
|
|
repository: require("../src/core/model/anonymizedRepositories/anonymizedRepositories.model").default,
|
|
gist: require("../src/core/model/anonymizedGists/anonymizedGists.model").default,
|
|
"pull-request": require("../src/core/model/anonymizedPullRequests/anonymizedPullRequests.model").default,
|
|
});
|
|
for (const kind of ["repository", "gist", "pull-request"]) {
|
|
it(`fences ${kind} deletion after the sender's last resource check`, async () => {
|
|
const source = await create(owner._id, kind);
|
|
const collection = guards(), update = collection.updateOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$set?.state === "sending") { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const pending = notify(owner.id, "token_expired", source);
|
|
try {
|
|
await gate;
|
|
await models()[kind].updateOne({ _id: source.id }, { $set: { status: "removing" } }).exec();
|
|
} finally { collection.updateOne = update; release(); await pending; }
|
|
expect(sent).to.have.length(0); expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect((await mongoose.connection.collection("users").findOne({ _id: owner._id })).accessAlertDeactivations).to.deep.equal([]);
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
}
|
|
it("does not deactivate a resource reassigned after the lifecycle owner snapshot", async () => {
|
|
const Model = models().repository, users = mongoose.connection.collection("users"), update = users.updateMany;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
users.updateMany = async function (filter, operation, ...args) {
|
|
if (operation.$addToSet?.accessAlertDeactivations) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const pending = Model.updateOne({ _id: resource.id }, { $set: { status: "removing" } }).exec();
|
|
let result;
|
|
try {
|
|
await gate;
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { owner: otherOwner._id } });
|
|
} finally { users.updateMany = update; release(); result = await pending; }
|
|
expect(result.matchedCount).to.equal(0);
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).status).to.equal("ready");
|
|
await notify(otherOwner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("blocks new senders until all concurrent lifecycle mutations finish", async () => {
|
|
const second = await create(owner._id), active = await create(owner._id);
|
|
const Model = models().repository, collection = Model.collection, update = collection.updateOne;
|
|
const releases = new Map(); let entered; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$set?.status === "removing") {
|
|
await new Promise(resolve => { releases.set(String(filter.$and[0]._id), resolve); if (releases.size === 2) entered(); });
|
|
}
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const first = Model.updateOne({ _id: resource.id }, { $set: { status: "removing" } }).exec();
|
|
const last = Model.updateOne({ _id: second.id }, { $set: { status: "removing" } }).exec();
|
|
try {
|
|
await gate;
|
|
expect((await mongoose.connection.collection("users").findOne({ _id: owner._id })).accessAlertDeactivations).to.have.length(2);
|
|
await notify(owner.id, "token_expired", active); expect(sent).to.have.length(0);
|
|
releases.get(second.id)(); await last;
|
|
await notify(owner.id, "token_expired", active); expect(sent).to.have.length(0);
|
|
} finally { collection.updateOne = update; for (const release of releases.values()) release(); await Promise.all([first, last]); }
|
|
await notify(owner.id, "token_expired", active); expect(sent).to.have.length(1);
|
|
});
|
|
it("retains an ambiguous lifecycle reservation until offline reconciliation", async () => {
|
|
const Model = models().repository, collection = Model.collection, update = collection.updateOne;
|
|
collection.updateOne = async () => { throw new Error("lost database acknowledgement"); };
|
|
try { await Model.updateOne({ _id: resource.id }, { $set: { status: "removing" } }).exec(); throw new Error("Expected mutation failure"); }
|
|
catch (error) { expect(error.message).to.equal("lost database acknowledgement"); }
|
|
finally { collection.updateOne = update; }
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(0);
|
|
await alerts.prepareLegacyOwnerAlertMigration(); await alerts.initializeOwnerAccessAlerts();
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
for (const operation of ["updateMany", "findOneAndUpdate", "save"]) it(`fences inactive lifecycle status via ${operation}`, async () => {
|
|
const Model = models().repository;
|
|
const before = await mongoose.connection.collection("users").findOne({ _id: owner._id });
|
|
if (operation === "save") {
|
|
const doc = await Model.findById(resource.id); doc.status = "expired"; await doc.save();
|
|
} else await Model[operation]({ _id: resource.id }, { $set: { status: "expired" } }).exec();
|
|
const after = await mongoose.connection.collection("users").findOne({ _id: owner._id });
|
|
expect(after.accessAlertResetRevision).to.equal((before.accessAlertResetRevision || 0) + 1);
|
|
expect(after.accessAlertDeactivations).to.deep.equal([]);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(0);
|
|
});
|
|
it("does not reopen after reconnect alone, unrelated resource success, or failed recovery", async () => {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id);
|
|
await recover(otherOwner.id, other, async () => {});
|
|
try { await recover(owner.id, resource, async () => { throw new Error("Forbidden"); }); } catch { /* Expected. */ }
|
|
await notify(owner.id, "token_expired", resource); await notify(otherOwner.id, "token_expired", other);
|
|
expect(sent).to.have.length(1);
|
|
});
|
|
it("keeps suppression after recovery but allows a first alert to a new address", async () => {
|
|
await notify(owner.id, "token_expired", resource);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
await Users.updateOne({ _id: owner._id }, { $set: { notificationEmail: "new@example.com" } });
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(2);
|
|
expect(sent[0].headers["Idempotency-Key"]).not.to.equal(sent[1].headers["Idempotency-Key"]);
|
|
expect(await mongoose.connection.collection("owner_access_alert_events").countDocuments()).to.equal(4);
|
|
await Users.updateOne({ _id: owner._id }, { $set: { notificationEmail: "OWNER@example.com" } });
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(2);
|
|
});
|
|
it("does not release a pending send when recovery races with provider delivery", async () => {
|
|
let release, entered; const gate = new Promise(resolve => { entered = resolve; });
|
|
global.fetch = async (_url, options) => { sent.push(options); entered(); await new Promise(resolve => { release = resolve; }); return { ok: true, status: 200 }; };
|
|
const pending = notify(owner.id, "token_expired", resource); await gate;
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
release(); await pending;
|
|
});
|
|
it("rejects stale recovery after another owner action changes the generation", async () => {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
await recover(owner.id, resource, async () => { await reset(owner.id, resource); });
|
|
expect(await guards().findOne({ _id: recipient() })).not.to.equal(null);
|
|
});
|
|
it("cancels an unsent claim before a paused sender can start dispatch", async () => {
|
|
const collection = mongoose.connection.collection("owner_access_alert_events"), insert = collection.insertOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.insertOne = async function (...args) { entered(); await new Promise(resolve => { release = resolve; }); return insert.apply(this, args); };
|
|
try {
|
|
const pending = notify(owner.id, "token_expired", resource); await gate;
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
release(); await pending; expect(sent).to.have.length(0);
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
} finally { collection.insertOne = insert; }
|
|
});
|
|
it("fences a second owner action that races after recovery's final resource check", async () => {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
const collection = guards(), update = collection.updateOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$set?.state === "recovered") { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
try {
|
|
const pending = recover(owner.id, resource, async () => {}); await gate;
|
|
await reset(owner.id, resource); release(); await pending;
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("accepted");
|
|
} finally { collection.updateOne = update; }
|
|
});
|
|
it("does not accept a successful read of a stale source binding", async () => {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { "source.repositoryName": "new/source" } });
|
|
await recover(owner.id, resource, async () => {}, { "source.repositoryName": "old/source" });
|
|
expect(await guards().findOne({ _id: recipient() })).not.to.equal(null);
|
|
});
|
|
for (const failure of ["rejection", "timeout"]) it(`retains the recipient guard after ${failure}`, async () => {
|
|
global.fetch = async (_url, options) => { sent.push(options); if (failure === "timeout") throw new Error("timeout"); return { ok: false, status: 429 }; };
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id);
|
|
await notify(otherOwner.id, "repo_not_found", other); expect(sent).to.have.length(1);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("failed");
|
|
});
|
|
it("keeps a removed resource's recipient guard and delivery history", async () => {
|
|
await notify(owner.id, "token_expired", resource);
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { status: "removed" } });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
expect(await mongoose.connection.collection("owner_access_alert_events").countDocuments()).to.equal(2);
|
|
});
|
|
for (const step of ["resource update", "resource verification", "audit insertion", "dispatch transition"]) it(`releases an unsent guard after ${step} fails`, async () => {
|
|
const collection = step.startsWith("resource") ? coll(resource.kind) : step === "audit insertion" ? mongoose.connection.collection("owner_access_alert_events") : guards();
|
|
const method = step === "resource verification" ? "findOne" : step === "audit insertion" ? "insertOne" : "updateOne";
|
|
const original = collection[method]; let calls = 0;
|
|
collection[method] = async function (...args) {
|
|
calls++;
|
|
if ((step === "resource verification" && calls === 3)
|
|
|| (step === "dispatch transition" && args[1].$set?.state === "sending")
|
|
|| (step === "resource update" && args[1].$set?.accessAlertClaimedAt)
|
|
|| step === "audit insertion") throw new Error("transient database error");
|
|
return original.apply(this, args);
|
|
};
|
|
try { await notify(owner.id, "token_expired", resource); }
|
|
finally { collection[method] = original; }
|
|
expect(sent).to.have.length(0); expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("retains audit evidence while provider delivery is still in flight", async () => {
|
|
let release, entered; const gate = new Promise(resolve => { entered = resolve; });
|
|
global.fetch = async () => { entered(); await new Promise(resolve => { release = resolve; }); return { ok: true, status: 200 }; };
|
|
const pending = notify(owner.id, "token_expired", resource); await gate;
|
|
try {
|
|
const record = await guards().findOne({ _id: recipient() });
|
|
const event = await mongoose.connection.collection("owner_access_alert_events").findOne({ _id: record.attemptId });
|
|
expect(event.state).to.equal("prepared"); expect(record.state).to.equal("sending");
|
|
} finally { release(); await pending; }
|
|
});
|
|
it("reclaims a crashed pre-dispatch attempt without owner action under concurrent failures", async () => {
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "crashed-pending", state: "pending", claimedAt: new Date(0) });
|
|
await Promise.all(Array.from({ length: 40 }, (_, i) => notify(i % 2 ? owner.id : otherOwner.id, "token_expired", i % 2 ? resource : other)));
|
|
expect(sent).to.have.length(1);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("accepted");
|
|
});
|
|
it("refreshes the resource fence when an old claim is reclaimed before guard insertion", async () => {
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "old-pending", state: "pending", claimedAt: new Date(0) });
|
|
const users = mongoose.connection.collection("users"), update = users.updateOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
users.updateOne = async function (filter, operation, ...args) {
|
|
if (Array.isArray(operation)) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try { await gate; await alerts.initializeOwnerAccessAlerts(); }
|
|
finally { users.updateOne = update; release(); await pending; }
|
|
expect(sent).to.have.length(1);
|
|
expect((await guards().findOne({ _id: recipient() })).resourceRevision).to.equal(1);
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimRevision).to.equal(2);
|
|
});
|
|
it("keeps recent pending claims and old sending claims blocked", async () => {
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "active-pending", state: "pending", claimedAt: new Date() });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
await guards().updateOne({ _id: recipient() }, { $set: { state: "sending", claimedAt: new Date(0) } });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("sending");
|
|
});
|
|
it("reaps only expired unsent claims during startup and never reseeds them", async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "ready" });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(0), accessAlertAttemptId: recipient() } });
|
|
for (const [key, state, date] of [[recipient(), "pending", new Date(0)], ["recent", "pending", new Date()],
|
|
["sending", "sending", new Date(0)], ["accepted", "accepted", new Date(0)], ["failed", "failed", new Date(0)]]) {
|
|
await guards().insertOne({ _id: key, owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: key, state, claimedAt: date });
|
|
}
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
for (const key of ["recent", "sending", "accepted", "failed"]) expect(await guards().findOne({ _id: key })).not.to.equal(null);
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("fences a paused sender after another reader reclaims its expired pending claim", async () => {
|
|
const collection = mongoose.connection.collection("owner_access_alert_events"), insert = collection.insertOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
let pausedOnce = false;
|
|
collection.insertOne = async function (...args) {
|
|
if (!pausedOnce) { pausedOnce = true; entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return insert.apply(this, args);
|
|
};
|
|
const paused = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate;
|
|
await guards().updateOne({ _id: recipient() }, { $set: { claimedAt: new Date(0) } });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
} finally { collection.insertOne = insert; release(); await paused; }
|
|
expect(sent).to.have.length(1);
|
|
expect((await guards().findOne({ _id: recipient() })).owner.toString()).to.equal(otherOwner.id);
|
|
});
|
|
it("cannot reclaim a pending attempt that starts dispatch before cancellation", async () => {
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "racing-dispatch", state: "pending", claimedAt: new Date(0) });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(0), accessAlertAttemptId: "racing-dispatch" } });
|
|
const collection = guards(), update = collection.updateOne;
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$set?.state === "cancelled") await update.call(this, { _id: recipient(), state: "pending", attemptId: "racing-dispatch" }, { $set: { state: "sending" } });
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
try { await notify(otherOwner.id, "token_expired", other); }
|
|
finally { collection.updateOne = update; }
|
|
expect(sent).to.have.length(0); expect((await guards().findOne({ _id: recipient() })).state).to.equal("sending");
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertAttemptId).to.equal("racing-dispatch");
|
|
});
|
|
for (const state of ["pending", "cancelled"]) it(`clears a crashed unsent ${state} attempt's legacy fields before migration`, async () => {
|
|
const attemptId = `crashed-${state}`;
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), generation: "old-generation", attemptId, state,
|
|
claimedAt: state === "pending" ? new Date(0) : new Date() });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: {
|
|
accessAlertGeneration: "old-generation", accessAlertClaimedAt: new Date(0), accessAlertAttemptId: attemptId,
|
|
} });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
const row = await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) });
|
|
expect(row.accessAlertClaimedAt).to.equal(undefined); expect(row.accessAlertAttemptId).to.equal(undefined);
|
|
expect(sent).to.have.length(0);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
for (const changed of ["generation", "attempt"]) it(`preserves newer legacy fields when the ${changed} no longer matches the cancelled attempt`, async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "ready" });
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), generation: "old-generation", attemptId: "old-attempt", state: "pending", claimedAt: new Date(0) });
|
|
const generation = changed === "generation" ? "new-generation" : "old-generation";
|
|
const attemptId = changed === "attempt" ? "new-attempt" : "old-attempt";
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: {
|
|
accessAlertGeneration: generation, accessAlertClaimedAt: new Date(1234), accessAlertAttemptId: attemptId,
|
|
} });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
const row = await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) });
|
|
expect(row.accessAlertClaimedAt.getTime()).to.equal(1234); expect(row.accessAlertAttemptId).to.equal(attemptId);
|
|
});
|
|
it("resumes cancelled metadata cleanup after a database failure without creating false legacy evidence", async () => {
|
|
const collection = coll(resource.kind), update = collection.updateOne, read = collection.findOne;
|
|
let reads = 0;
|
|
collection.findOne = async function (...args) { if (++reads === 3) throw new Error("verification failed"); return read.apply(this, args); };
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (Array.isArray(operation)) throw new Error("cleanup unavailable");
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
try { await notify(owner.id, "token_expired", resource); }
|
|
finally { collection.findOne = read; collection.updateOne = update; }
|
|
expect(sent).to.have.length(0); expect((await guards().findOne({ _id: recipient() })).state).to.equal("cancelled");
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect((await collection.findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("cleans recent pending legacy fields once deployment has stopped every writer", async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "ready" });
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "recent-unsent", state: "pending", claimedAt: new Date() });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: {
|
|
accessAlertClaimedAt: new Date(), accessAlertAttemptId: "recent-unsent",
|
|
} });
|
|
await alerts.prepareLegacyOwnerAlertMigration(); await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
});
|
|
it("preserves an untagged legacy delivery written during rollback while cancelling a native pending attempt", async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "ready" });
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "native-unsent", state: "pending", claimedAt: new Date() });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date() } });
|
|
await alerts.prepareLegacyOwnerAlertMigration(); await alerts.initializeOwnerAccessAlerts();
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("legacy");
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(0);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(0);
|
|
});
|
|
it("blocks dispatch while an owner reset has advanced the guard revision but not the resource generation", async () => {
|
|
const events = mongoose.connection.collection("owner_access_alert_events"), insert = events.insertOne;
|
|
const collection = coll(resource.kind), updateMany = collection.updateMany;
|
|
let prepared, releasePrepared, resetting, releaseReset;
|
|
const atPrepared = new Promise(resolve => { prepared = resolve; });
|
|
const atReset = new Promise(resolve => { resetting = resolve; });
|
|
events.insertOne = async function (...args) { prepared(); await new Promise(resolve => { releasePrepared = resolve; }); return insert.apply(this, args); };
|
|
collection.updateMany = async function (...args) { resetting(); await new Promise(resolve => { releaseReset = resolve; }); return updateMany.apply(this, args); };
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
let action;
|
|
try {
|
|
await atPrepared; action = reset(owner.id, resource); await atReset;
|
|
expect((await guards().findOne({ _id: recipient() })).resetRevision).to.equal(1);
|
|
releasePrepared(); await pending;
|
|
expect(sent).to.have.length(0);
|
|
const row = await collection.findOne({ _id: new mongoose.Types.ObjectId(resource.id) });
|
|
expect(row.accessAlertGeneration).to.equal(undefined); expect(row.accessAlertClaimedAt).to.equal(undefined);
|
|
} finally {
|
|
events.insertOne = insert; collection.updateMany = updateMany;
|
|
if (releasePrepared) releasePrepared(); if (releaseReset) releaseReset();
|
|
await pending; if (action) await action;
|
|
}
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("invalidates an old failure when reset starts before its recipient guard exists", async () => {
|
|
const lookup = Users.findOne, collection = coll(resource.kind), updateMany = collection.updateMany;
|
|
let entered, release, resetting, releaseReset;
|
|
const atLookup = new Promise(resolve => { entered = resolve; });
|
|
const atReset = new Promise(resolve => { resetting = resolve; });
|
|
Users.findOne = function (...args) { return { lean: async () => {
|
|
entered(); await new Promise(resolve => { release = resolve; }); return lookup.apply(Users, args).lean();
|
|
} }; };
|
|
collection.updateMany = async function (...args) { resetting(); await new Promise(resolve => { releaseReset = resolve; }); return updateMany.apply(this, args); };
|
|
const pending = notify(owner.id, "token_expired", resource); let action;
|
|
try {
|
|
await atLookup; action = reset(owner.id, resource); await atReset;
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
release(); await pending; expect(sent).to.have.length(0);
|
|
expect((await collection.findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
} finally {
|
|
Users.findOne = lookup; collection.updateMany = updateMany;
|
|
if (release) release(); if (releaseReset) releaseReset(); await pending; if (action) await action;
|
|
}
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("prevents a cancelled sender from recreating legacy fields after its database write resumes", async () => {
|
|
const collection = coll(resource.kind), update = collection.updateOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$set?.accessAlertClaimedAt) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate; await guards().updateOne({ _id: recipient() }, { $set: { claimedAt: new Date(0) } });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
} finally { collection.updateOne = update; release(); await pending; }
|
|
const row = await collection.findOne({ _id: new mongoose.Types.ObjectId(resource.id) });
|
|
expect(row.accessAlertClaimedAt).to.equal(undefined); expect(row.accessAlertClaimRevision).to.equal(1);
|
|
expect(sent).to.have.length(1);
|
|
});
|
|
it("reaps a crashed pre-provider attempt after legacy preparation completed", async () => {
|
|
await guards().insertOne({ _id: recipient(), owner: owner._id, resourceKind: resource.kind,
|
|
resourceId: new mongoose.Types.ObjectId(resource.id), attemptId: "prepared-not-sent", state: "pending", resourceRevision: 0, claimedAt: new Date(0) });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: {
|
|
accessAlertClaimedAt: new Date(0), accessAlertAttemptId: "prepared-not-sent", accessAlertClaimRevision: 1,
|
|
} });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null); expect(sent).to.have.length(0);
|
|
const row = await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) });
|
|
expect(row.accessAlertClaimedAt).to.equal(undefined); expect(row.accessAlertClaimRevision).to.equal(2);
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
for (const accepted of [true, false]) it(`applies recovery recorded during an in-flight ${accepted ? "accepted" : "rejected"} delivery`, async () => {
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
global.fetch = async (_url, options) => {
|
|
sent.push(options); if (sent.length === 1) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return { ok: accepted, status: accepted ? 200 : 403 };
|
|
};
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate; await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
const record = await guards().findOne({ _id: recipient() }); expect(record.state).to.equal("sending");
|
|
expect(record.deferredRecoveries).to.have.length(1);
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
} finally { release(); await pending; }
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("invalidates in-flight recovery evidence after a newer reset", async () => {
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
global.fetch = async (_url, options) => {
|
|
sent.push(options); if (sent.length === 1) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return { ok: true, status: 200 };
|
|
};
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate; await reset(owner.id, resource); await recover(owner.id, resource, async () => {}); await reset(owner.id, resource);
|
|
} finally { release(); await pending; }
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("accepted");
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
await recover(owner.id, resource, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("invalidates deferred recovery if its source binding changes before delivery completes", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { source: { repositoryName: "owner/repo" } } });
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
global.fetch = async (_url, options) => {
|
|
sent.push(options); if (sent.length === 1) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return { ok: true, status: 200 };
|
|
};
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate; await reset(owner.id, resource);
|
|
await recover(owner.id, resource, async () => {}, { "source.repositoryName": "owner/repo" });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { "source.repositoryName": "new/repo" } });
|
|
} finally { release(); await pending; }
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("accepted");
|
|
await recover(owner.id, resource, async () => {}, { "source.repositoryName": "new/repo" });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("rejects dispatch when reset starts after the guard reservation but before the owner commitment", async () => {
|
|
const collection = guards(), update = collection.updateOne, updateMany = collection.updateMany;
|
|
let entered, release, resetting, releaseReset;
|
|
const gate = new Promise(resolve => { entered = resolve; }), resetGate = new Promise(resolve => { resetting = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
const result = await update.call(this, filter, operation, ...args);
|
|
if (operation.$set?.state === "sending") { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return result;
|
|
};
|
|
collection.updateMany = async function (...args) { resetting(); await new Promise(resolve => { releaseReset = resolve; }); return updateMany.apply(this, args); };
|
|
const pending = notify(owner.id, "token_expired", resource); let action;
|
|
try {
|
|
await gate; action = reset(owner.id, resource); await resetGate;
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("sending");
|
|
release(); await pending; expect(sent).to.have.length(0);
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
} finally {
|
|
collection.updateOne = update; collection.updateMany = updateMany;
|
|
if (release) release(); if (releaseReset) releaseReset(); await pending; if (action) await action;
|
|
}
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
});
|
|
it("applies fresh recovery recorded before the reset lease is cleared without another upstream read", async () => {
|
|
await notify(owner.id, "token_expired", resource);
|
|
const users = mongoose.connection.collection("users"), update = users.updateOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
users.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$unset?.accessAlertResetUntil !== undefined) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const action = reset(owner.id, resource);
|
|
try {
|
|
await gate; await recover(owner.id, resource, async () => {});
|
|
expect((await guards().findOne({ _id: recipient() })).deferredRecoveries).to.have.length(1);
|
|
} finally { users.updateOne = update; release(); await action; }
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("retries saved recovery after a crashed reset lease expires", async () => {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
await mongoose.connection.collection("users").updateOne({ _id: owner._id }, { $set: { accessAlertResetUntil: new Date(Date.now() + 150) } });
|
|
await recover(owner.id, resource, async () => {});
|
|
expect(await guards().findOne({ _id: recipient() })).not.to.equal(null);
|
|
for (let i = 0; i < 100 && (await guards().findOne({ _id: recipient() }))?.state !== "recovered"; i++) await new Promise(resolve => global.setTimeout(resolve, 20));
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
for (const state of ["prepared", "cancelled", "committed"]) it(`reconciles interrupted guard reservation with a ${state} owner dispatch record`, async () => {
|
|
const attemptId = `interrupted-${state}`;
|
|
await guards().insertMany([{ _id: "recipient-alerts-v2", state: "ready" }, {
|
|
_id: recipient(), owner: owner._id, resourceKind: resource.kind, resourceId: new mongoose.Types.ObjectId(resource.id),
|
|
attemptId, state: "sending", dispatchPrepared: true, resourceRevision: 0, claimedAt: new Date(0),
|
|
}]);
|
|
await mongoose.connection.collection("users").updateOne({ _id: owner._id }, { $set: {
|
|
[`accessAlertDispatches.${attemptId}`]: { state, claimedAt: new Date(0) },
|
|
} });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: {
|
|
accessAlertClaimedAt: new Date(0), accessAlertAttemptId: attemptId, accessAlertClaimRevision: 1,
|
|
} });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
const record = await guards().findOne({ _id: recipient() });
|
|
const account = await mongoose.connection.collection("users").findOne({ _id: owner._id });
|
|
if (state === "committed") {
|
|
expect(record.state).to.equal("sending"); expect(account.accessAlertDispatches[attemptId].state).to.equal(state);
|
|
} else {
|
|
expect(record).to.equal(null); expect(account.accessAlertDispatches[attemptId]).to.equal(undefined);
|
|
expect((await coll(resource.kind).findOne({ _id: new mongoose.Types.ObjectId(resource.id) })).accessAlertClaimedAt).to.equal(undefined);
|
|
}
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(state === "committed" ? 0 : 1);
|
|
});
|
|
it("prunes expired uncommitted owner dispatch records and cleans completed attempts", async () => {
|
|
await mongoose.connection.collection("users").updateOne({ _id: owner._id }, { $set: { accessAlertDispatches: {
|
|
abandoned: { state: "prepared", claimedAt: new Date(0) }, cancelled: { state: "cancelled", claimedAt: new Date(0) },
|
|
unknown: { state: "committed", claimedAt: new Date(0) },
|
|
} } });
|
|
await notify(owner.id, "token_expired", resource); expect(sent).to.have.length(1);
|
|
const account = await mongoose.connection.collection("users").findOne({ _id: owner._id });
|
|
expect(Object.keys(account.accessAlertDispatches)).to.deep.equal(["unknown"]);
|
|
});
|
|
it("keeps shared-address suppression when the original owner is banned", async () => {
|
|
await notify(owner.id, "token_expired", resource);
|
|
await Users.updateOne({ _id: owner._id }, { $set: { status: "banned" } });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("resumes an indexed migration from its last completed checkpoint", async () => {
|
|
await Users.updateOne({ _id: otherOwner._id }, { $set: { notificationEmail: "second@example.com" } });
|
|
const second = await create(otherOwner._id);
|
|
await coll("repository").updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await coll("repository").updateOne({ _id: new mongoose.Types.ObjectId(second.id) }, { $set: { accessAlertClaimedAt: new Date(2) } });
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "new", leaseUntil: new Date(0),
|
|
progress: { repository: { claimedAt: new Date(1), id: new mongoose.Types.ObjectId(resource.id) } } });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect(await guards().findOne({ _id: recipient() })).to.equal(null);
|
|
expect(await guards().findOne({ _id: alerts.recipientAlertKey("second@example.com") })).not.to.equal(null);
|
|
expect((await guards().findOne({ _id: "recipient-alerts-v2" })).progress.repository.claimedAt.getTime()).to.equal(2);
|
|
expect((await coll("repository").indexes()).some(i => i.name === "owner_access_alert_backfill" && i.partialFilterExpression)).to.equal(true);
|
|
});
|
|
it("confirms recovery on a fresh uncached file read but never on a cache hit", async () => {
|
|
const database = require("../src/server/database");
|
|
const GitHubStream = require("../src/core/source/GitHubStream").default;
|
|
const storage = require("../src/core/storage").default;
|
|
const { Readable } = require("stream");
|
|
const originals = { isConnected: database.isConnected, fileInfo: storage.fileInfo, write: storage.write, read: storage.read };
|
|
await coll("repository").updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { repoId: resource.id, source: { repositoryName: "owner/repo", commit: "pinned" } } });
|
|
let cached = false;
|
|
database.isConnected = true;
|
|
storage.fileInfo = async () => { if (!cached) throw Object.assign(new Error("miss"), { code: "ENOENT" }); return { size: 11, contentType: "text/plain" }; };
|
|
storage.write = async (_repo, _path, input) => { for await (const _chunk of input) { /* Consume the upstream stream. */ } cached = true; };
|
|
storage.read = async () => Readable.from(["fresh-bytes"]);
|
|
try {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
const source = new GitHubStream({ repoId: resource.id, organization: "owner", repoName: "repo", commit: "pinned", getToken: async () => "mock-token" });
|
|
source.downloadWithFallback = async () => Readable.from(["fresh-bytes"]);
|
|
const read = () => source.getFileContentCache("a.txt", resource.id, () => ({ sha: "a".repeat(40), size: 11 }));
|
|
(await read()).destroy(); expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
const secondKey = alerts.recipientAlertKey("second@example.com");
|
|
await Users.updateOne({ _id: owner._id }, { $set: { notificationEmail: "second@example.com" } });
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
(await read()).destroy(); expect((await guards().findOne({ _id: secondKey })).state).to.equal("accepted");
|
|
} finally { database.isConnected = originals.isConnected; storage.fileInfo = originals.fileInfo; storage.write = originals.write; storage.read = originals.read; }
|
|
});
|
|
it("confirms recovery when a successful scheduled refresh finds the same commit", async () => {
|
|
const Repository = require("../src/core/Repository").default;
|
|
const Model = require("../src/core/model/anonymizedRepositories/anonymizedRepositories.model").default;
|
|
const github = require("../src/core/source/GitHubRepository");
|
|
const original = github.getRepositoryFromGitHub;
|
|
const source = { repositoryName: "owner/repo", commit: "pinned", branch: "main" };
|
|
await coll("repository").updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { source } });
|
|
const repo = new Repository(new Model({ _id: resource.id, owner: owner._id, repoId: resource.id, status: "ready",
|
|
source, lastView: new Date(0), options: { update: true, expirationMode: "never" } }));
|
|
repo.getToken = async () => "mock-token";
|
|
github.getRepositoryFromGitHub = async () => ({ fullName: "owner/repo", model: { defaultBranch: "main" },
|
|
branches: async () => [{ name: "main", commit: "pinned" }], getCommitInfo: async () => { throw new Error("Unexpected commit fetch"); } });
|
|
try {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
await repo.updateIfNeeded({ force: false });
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
} finally { github.getRepositoryFromGitHub = original; }
|
|
});
|
|
it("seeds old claims once while five instances start concurrently", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date() } });
|
|
await Promise.all(Array.from({ length: 5 }, () => alerts.initializeOwnerAccessAlerts()));
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("legacy");
|
|
await notify(otherOwner.id, "repo_not_found", other); expect(sent).to.have.length(0);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await alerts.initializeOwnerAccessAlerts(); expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
});
|
|
it("preserves every legacy claim for a shared address until each triggering resource recovers", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await coll(other.kind).updateOne({ _id: new mongoose.Types.ObjectId(other.id) }, { $set: { accessAlertClaimedAt: new Date(2) } });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
expect((await guards().findOne({ _id: recipient() })).legacyClaims).to.have.length(2);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
expect((await guards().findOne({ _id: recipient() })).legacyClaims.filter(claim => !claim.recovered)).to.have.length(1);
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
await reset(otherOwner.id, other); await recover(otherOwner.id, other, async () => {});
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
});
|
|
it("does not retire a legacy group when only its first triggering resource is removed", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await coll(other.kind).updateOne({ _id: new mongoose.Types.ObjectId(other.id) }, { $set: { accessAlertClaimedAt: new Date(2) } });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { status: "removed" } });
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
await reset(otherOwner.id, other); await recover(otherOwner.id, other, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
});
|
|
it("atomically marks concurrent legacy recoveries without releasing suppression", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await coll(other.kind).updateOne({ _id: new mongoose.Types.ObjectId(other.id) }, { $set: { accessAlertClaimedAt: new Date(2) } });
|
|
await alerts.initializeOwnerAccessAlerts(); await reset(owner.id, resource); await reset(otherOwner.id, other);
|
|
await Promise.all([recover(owner.id, resource, async () => {}), recover(otherOwner.id, other, async () => {})]);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
});
|
|
it("requires a fresh read after a newer reset of a partially recovered legacy group", async () => {
|
|
for (const item of [resource, other]) await coll(item.kind).updateOne({ _id: new mongoose.Types.ObjectId(item.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await reset(owner.id, resource);
|
|
await reset(otherOwner.id, other); await recover(otherOwner.id, other, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("legacy");
|
|
await recover(owner.id, resource, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
});
|
|
it("fences legacy claim mutation when reset races after the final resource check", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await alerts.initializeOwnerAccessAlerts(); await reset(owner.id, resource);
|
|
const collection = guards(), update = collection.updateOne;
|
|
let entered, release; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (filter.attemptId && Array.isArray(operation)) { entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const pending = recover(owner.id, resource, async () => {});
|
|
try { await gate; await reset(owner.id, resource); }
|
|
finally { collection.updateOne = update; release(); await pending; }
|
|
const record = await guards().findOne({ _id: recipient() });
|
|
expect(record.state).to.equal("legacy"); expect(record.legacyClaims[0].recovered).to.equal(false);
|
|
});
|
|
it("reconciles claims written by legacy instances after rollback without reseeding ordinary restarts", async () => {
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { accessAlertClaimedAt: new Date(1) } });
|
|
await alerts.initializeOwnerAccessAlerts(); await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await alerts.initializeOwnerAccessAlerts(); expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
await coll(other.kind).updateOne({ _id: new mongoose.Types.ObjectId(other.id) }, { $set: { accessAlertClaimedAt: new Date() } });
|
|
await alerts.prepareLegacyOwnerAlertMigration();
|
|
const marker = await guards().findOne({ _id: "recipient-alerts-v2" }); expect(marker.state).to.equal("new"); expect(marker.progress).to.equal(undefined);
|
|
await alerts.initializeOwnerAccessAlerts(); await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
expect((await guards().findOne({ _id: recipient() })).legacyClaims.filter(claim => !claim.recovered)).to.have.length(1);
|
|
await reset(otherOwner.id, other); await recover(otherOwner.id, other, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(0);
|
|
});
|
|
it("preserves a native guard's origin when rollback reconciliation adds another legacy claim", async () => {
|
|
await alerts.initializeOwnerAccessAlerts(); await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
await coll(other.kind).updateOne({ _id: new mongoose.Types.ObjectId(other.id) }, { $set: { accessAlertClaimedAt: new Date() } });
|
|
await alerts.prepareLegacyOwnerAlertMigration(); await alerts.initializeOwnerAccessAlerts();
|
|
expect((await guards().findOne({ _id: recipient() })).legacyClaims.filter(claim => !claim.recovered)).to.have.length(2);
|
|
await reset(otherOwner.id, other); await recover(otherOwner.id, other, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
await recover(owner.id, resource, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("keeps suppression through removal, reactivation, reset and recovery", async () => {
|
|
await notify(owner.id, "token_expired", resource);
|
|
const original = await guards().findOne({ _id: recipient() });
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { status: "removed" } });
|
|
await notify(otherOwner.id, "token_expired", other);
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { status: "ready" } });
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
expect((await guards().findOne({ _id: recipient() })).attemptId).to.equal(original.attemptId);
|
|
});
|
|
for (const accepted of [true, false]) it(`retries the guard update after a definite ${accepted ? "accepted" : "rejected"} provider response`, async () => {
|
|
const collection = guards(), update = collection.updateOne; let writes = 0;
|
|
global.fetch = async (_url, options) => { sent.push(options); return { ok: accepted, status: accepted ? 200 : 403 }; };
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (["accepted", "failed"].includes(operation.$set?.state)) {
|
|
expect(await mongoose.connection.collection("owner_access_alert_events").findOne({ _id: `${filter.attemptId}/completion` })).not.to.equal(null);
|
|
if (++writes === 1) throw new Error("transient result write failure");
|
|
}
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
try { await notify(owner.id, "token_expired", resource); }
|
|
finally { collection.updateOne = update; }
|
|
expect(writes).to.equal(2); expect(sent).to.have.length(1);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal(accepted ? "accepted" : "failed");
|
|
});
|
|
for (const startup of [true, false]) it(`reconciles a durable provider result during ${startup ? "startup" : "the next notification"}`, async () => {
|
|
await alerts.initializeOwnerAccessAlerts();
|
|
const collection = guards(), update = collection.updateOne; let writes = 0;
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (["accepted", "failed"].includes(operation.$set?.state)) { writes++; throw new Error("database unavailable"); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
try { await notify(owner.id, "token_expired", resource); }
|
|
finally { collection.updateOne = update; }
|
|
expect(writes).to.equal(3); expect((await guards().findOne({ _id: recipient() })).state).to.equal("sending");
|
|
if (startup) await alerts.initializeOwnerAccessAlerts(); else await notify(otherOwner.id, "token_expired", other);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("accepted"); expect(sent).to.have.length(1);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
await notify(otherOwner.id, "token_expired", other); expect(sent).to.have.length(1);
|
|
});
|
|
it("records a provider completion once after its insertion acknowledgement is lost", async () => {
|
|
const collection = mongoose.connection.collection("owner_access_alert_events"), insert = collection.insertOne;
|
|
let lost = false;
|
|
collection.insertOne = async function (record, ...args) {
|
|
const result = await insert.call(this, record, ...args);
|
|
if (record.state === "accepted" && !lost) { lost = true; throw new Error("lost acknowledgement"); }
|
|
return result;
|
|
};
|
|
try { await notify(owner.id, "token_expired", resource); }
|
|
finally { collection.insertOne = insert; }
|
|
expect(lost).to.equal(true); expect(sent).to.have.length(1);
|
|
expect(await collection.countDocuments({ state: "accepted" })).to.equal(1);
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("accepted");
|
|
});
|
|
it("does not let a late provider-result write resurrect a recovered guard", async () => {
|
|
const collection = guards(), update = collection.updateOne;
|
|
let entered, release, paused = false; const gate = new Promise(resolve => { entered = resolve; });
|
|
collection.updateOne = async function (filter, operation, ...args) {
|
|
if (operation.$set?.state === "accepted" && !paused) { paused = true; entered(); await new Promise(resolve => { release = resolve; }); }
|
|
return update.call(this, filter, operation, ...args);
|
|
};
|
|
const pending = notify(owner.id, "token_expired", resource);
|
|
try {
|
|
await gate; await notify(otherOwner.id, "token_expired", other);
|
|
await reset(owner.id, resource); await recover(owner.id, resource, async () => {});
|
|
} finally { collection.updateOne = update; release(); await pending; }
|
|
expect(sent).to.have.length(1); expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
});
|
|
it("confirms truncated-file metadata recovery only on a successful fresh matching-source probe", async () => {
|
|
const database = require("../src/server/database");
|
|
const GitHubStream = require("../src/core/source/GitHubStream").default;
|
|
const utils = require("../src/core/GitHubUtils");
|
|
const originals = { isConnected: database.isConnected, octokit: utils.octokit };
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { repoId: resource.id, source: { repositoryName: "owner/repo", commit: "pinned" } } });
|
|
let response = { type: "file", sha: "a".repeat(40), size: 11 };
|
|
database.isConnected = true;
|
|
utils.octokit = () => ({ repos: { getContent: async () => { if (response instanceof Error) throw response; return { data: response }; } } });
|
|
const source = new GitHubStream({ repoId: resource.id, organization: "owner", repoName: "repo", commit: "pinned", getToken: async () => "mock-token" });
|
|
try {
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
expect((await source.fetchFileInfoFromPath("a.txt")).sha).to.equal("a".repeat(40));
|
|
expect((await guards().findOne({ _id: recipient() })).state).to.equal("recovered");
|
|
const secondKey = alerts.recipientAlertKey("second@example.com");
|
|
await Users.updateOne({ _id: owner._id }, { $set: { notificationEmail: "second@example.com" } });
|
|
await notify(owner.id, "token_expired", resource); await reset(owner.id, resource);
|
|
response = { type: "dir" }; expect(await source.fetchFileInfoFromPath("a.txt")).to.equal(null);
|
|
expect((await guards().findOne({ _id: secondKey })).state).to.equal("accepted");
|
|
response = Object.assign(new Error("Forbidden"), { status: 403 });
|
|
try { await source.fetchFileInfoFromPath("a.txt"); throw new Error("Expected a failed probe"); }
|
|
catch (error) { expect(error.message).to.equal("Forbidden"); }
|
|
expect((await guards().findOne({ _id: secondKey })).state).to.equal("accepted");
|
|
response = { type: "file", sha: "a".repeat(40), size: 11 };
|
|
await coll(resource.kind).updateOne({ _id: new mongoose.Types.ObjectId(resource.id) }, { $set: { "source.repositoryName": "different/source" } });
|
|
await source.fetchFileInfoFromPath("a.txt");
|
|
expect((await guards().findOne({ _id: secondKey })).state).to.equal("accepted");
|
|
} finally { database.isConnected = originals.isConnected; utils.octokit = originals.octokit; }
|
|
});
|
|
it("resumes a migration after a startup crashed and its lease expired", async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "new", lease: "crashed", leaseUntil: new Date(0) });
|
|
await alerts.initializeOwnerAccessAlerts(); expect((await guards().findOne({ _id: "recipient-alerts-v2" })).state).to.equal("ready");
|
|
});
|
|
it("continues waiting beyond 300 polls while another migration holds a healthy lease", async () => {
|
|
await guards().insertOne({ _id: "recipient-alerts-v2", state: "new", lease: "active-migration", leaseUntil: new Date(Date.now() + 600000) });
|
|
const collection = guards(), update = collection.updateOne, timeout = global.setTimeout;
|
|
let polls = 0;
|
|
collection.updateOne = async function (filter, ...args) {
|
|
if (filter._id === "recipient-alerts-v2" && filter.$or && ++polls === 305) {
|
|
await update.call(this, { _id: "recipient-alerts-v2" }, { $set: { state: "ready" } });
|
|
}
|
|
return update.call(this, filter, ...args);
|
|
};
|
|
global.setTimeout = (callback, delay, ...args) => delay === 250 ? timeout(callback, 0, ...args) : timeout(callback, delay, ...args);
|
|
try { await alerts.initializeOwnerAccessAlerts(); }
|
|
finally { collection.updateOne = update; global.setTimeout = timeout; }
|
|
expect(polls).to.equal(305);
|
|
expect((await guards().findOne({ _id: "recipient-alerts-v2" })).state).to.equal("ready");
|
|
});
|
|
});
|