/** * In-memory fake of the Mongo collections used by the service. Production runs * MongoDB 3.2, for which no mongod binary is downloadable/runnable on modern * dev/CI hosts (glibc/libssl). So the DB integration tests run against this * fake, which faithfully emulates the subset of behaviour the code relies on: * the query operators used ($ne/$in/$gte/$exists/equality), $set/$unset updates, * projection/limit, and — crucially — unique-index duplicate-key errors (11000) * that back the idempotency guarantee. */ import type { Repos } from '../src/db.js'; type Doc = Record; function isObjectId(v: any): boolean { return v != null && typeof v === 'object' && typeof v.equals === 'function' && v._bsontype === 'ObjectID'; } function valEq(a: any, b: any): boolean { if (a === b) return true; if (a == null || b == null) return a === b; if (isObjectId(a)) { try { return a.equals(b); } catch { return false; } } if (isObjectId(b)) return b.equals(a); if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime(); return false; } function isOperatorObject(cond: any): boolean { if (cond == null || typeof cond !== 'object') return false; if (cond instanceof Date || isObjectId(cond)) return false; const keys = Object.keys(cond); return keys.length > 0 && keys.every((k) => k.startsWith('$')); } function matchField(docVal: any, cond: any): boolean { if (isOperatorObject(cond)) { return Object.entries(cond).every(([op, v]) => { switch (op) { case '$ne': return !valEq(docVal, v); case '$in': return Array.isArray(v) && v.some((x) => valEq(docVal, x)); case '$gte': return docVal != null && docVal >= (v as any); case '$gt': return docVal != null && docVal > (v as any); case '$lte': return docVal != null && docVal <= (v as any); case '$lt': return docVal != null && docVal < (v as any); case '$exists': return (docVal !== undefined) === v; default: throw new Error(`fake-repo: unsupported operator ${op}`); } }); } return valEq(docVal, cond); } function matches(doc: Doc, filter: Doc): boolean { return Object.entries(filter).every(([k, cond]) => matchField(doc[k], cond)); } function applyUpdate(doc: Doc, update: Doc): void { if (update.$set) Object.assign(doc, update.$set); if (update.$unset) for (const k of Object.keys(update.$unset)) delete doc[k]; } class FakeCollection { docs: Doc[] = []; collectionName = 'fake'; private uniqueFields: string[] | null = null; async createIndex(spec: Doc, opts?: { unique?: boolean }): Promise { if (opts?.unique) this.uniqueFields = Object.keys(spec); return 'idx'; } private violatesUnique(doc: Doc): boolean { if (!this.uniqueFields) return false; return this.docs.some((d) => this.uniqueFields!.every((f) => valEq(d[f], doc[f]))); } async insertOne(doc: Doc): Promise<{ insertedId: any }> { if (this.violatesUnique(doc)) { const err: any = new Error('E11000 duplicate key error'); err.code = 11000; throw err; } const copy = { ...doc }; this.docs.push(copy); return { insertedId: copy._id }; } async insertMany(docs: Doc[]): Promise<{ insertedCount: number }> { for (const d of docs) await this.insertOne(d); return { insertedCount: docs.length }; } async findOne(filter: Doc = {}): Promise { return this.docs.find((d) => matches(d, filter)) ?? null; } async updateOne(filter: Doc, update: Doc, opts?: { upsert?: boolean }): Promise<{ modifiedCount: number; upsertedCount: number }> { const d = this.docs.find((x) => matches(x, filter)); if (!d) { if (opts?.upsert) { const created: Doc = { ...filter }; applyUpdate(created, update); this.docs.push(created); return { modifiedCount: 0, upsertedCount: 1 }; } return { modifiedCount: 0, upsertedCount: 0 }; } applyUpdate(d, update); return { modifiedCount: 1, upsertedCount: 0 }; } async updateMany(filter: Doc, update: Doc): Promise<{ modifiedCount: number }> { const ds = this.docs.filter((x) => matches(x, filter)); for (const d of ds) applyUpdate(d, update); return { modifiedCount: ds.length }; } async deleteOne(filter: Doc): Promise<{ deletedCount: number }> { const i = this.docs.findIndex((d) => matches(d, filter)); if (i === -1) return { deletedCount: 0 }; this.docs.splice(i, 1); return { deletedCount: 1 }; } async deleteMany(filter: Doc = {}): Promise<{ deletedCount: number }> { const before = this.docs.length; this.docs = this.docs.filter((d) => !matches(d, filter)); return { deletedCount: before - this.docs.length }; } async countDocuments(filter: Doc = {}): Promise { return this.docs.filter((d) => matches(d, filter)).length; } find(filter: Doc = {}, _opts?: Doc) { let result = this.docs.filter((d) => matches(d, filter)); const cursor = { project() { return cursor; }, sort(spec: Doc) { const [field, dir] = Object.entries(spec)[0] as [string, number]; result = [...result].sort((a, b) => { const av = a[field]; const bv = b[field]; if (av === bv) return 0; return (av < bv ? -1 : 1) * (dir < 0 ? -1 : 1); }); return cursor; }, limit(n: number) { result = result.slice(0, n); return cursor; }, async toArray() { return result.map((d) => ({ ...d })); }, }; return cursor; } } export function makeRepos(): Repos { return { client: {} as any, db: {} as any, notifications: new FakeCollection() as any, users: new FakeCollection() as any, sends: new FakeCollection() as any, subscriptions: new FakeCollection() as any, activefires: new FakeCollection() as any, notificationsShadow: new FakeCollection() as any, matcherState: new FakeCollection() as any, close: async () => {}, }; }