"use strict"; /** A DokployClient stand-in that records calls and replays canned answers. */ class FakeApi { /** * @param {object} handlers Procedure name -> value, or (input, call) => value. * A value may be a function returning a promise, or an array consumed one * entry per call so a poller can see a status change. */ constructor(handlers = {}) { this.handlers = handlers; this.calls = []; this.counts = {}; } #dispatch(kind, procedure, input) { this.calls.push({ kind, procedure, input }); const index = (this.counts[procedure] = (this.counts[procedure] ?? 0) + 1) - 1; if (!(procedure in this.handlers)) { throw new Error(`FakeApi: unexpected ${kind} ${procedure}`); } const handler = this.handlers[procedure]; if (typeof handler === "function") return Promise.resolve(handler(input, index)); if (Array.isArray(handler) && handler.__sequence) { return Promise.resolve(handler[Math.min(index, handler.length - 1)]); } return Promise.resolve(handler); } query(procedure, input) { return this.#dispatch("query", procedure, input); } mutate(procedure, input) { return this.#dispatch("mutate", procedure, input); } procedures() { return this.calls.map((call) => call.procedure); } find(procedure) { return this.calls.find((call) => call.procedure === procedure); } } /** Marks an array as "one entry per call" rather than a literal return value. */ function sequence(values) { const copy = [...values]; copy.__sequence = true; return copy; } /** A clock that only moves when something sleeps, so tests never really wait. */ function fakeClock(start = 0) { let current = start; return { now: () => current, sleep: async (ms) => { current += Math.max(ms, 1); }, advance: (ms) => { current += ms; }, }; } function collectingLog() { const lines = []; return { lines, info: (message) => lines.push(`info: ${message}`), warning: (message) => lines.push(`warning: ${message}`), }; } module.exports = { FakeApi, sequence, fakeClock, collectingLog };