Umzug: Webhook-Adresse aus config.url, Aufräumen auch nach Selbstheilung
Fix round 1 (Review C1/I1/I2/I3, Controller-Ruling R11): - C1 (kritisch): Hooks haben in echtem Gitea kein Top-Level-url, nur config.url - isInternalRepo/deletePortalHooks lasen bisher ein Feld, das es in Produktion nicht gibt. - I1: Schritt-2-Namensvergleich jetzt ohne Ruecksicht auf Gross-/Kleinschreibung. - I2: fs.rm in Schritt 9 gegen EACCES/EBUSY/ENOTEMPTY & Co. abgesichert. - I3: Aufraeumen (Cache, alter Einzel-Webhook, Preview-Verzeichnis) laeuft jetzt ueber den neuen idempotenten finishMove()-Baustein in jedem Fall, in dem das Repo tatsaechlich in WEBklar liegt - auch nach einem Appwrite-Fehler, bei Selbstheilung und bei einem schon dort liegenden Repo (WARN.hookNotRemoved bei fehlgeschlagenem Loeschen). - Fake Gitea: Hook-Form ohne Top-Level-url, 301 mit relativer Location (auch fuer Unterpfade) wie im echten Gitea 1.25.2, failNext() fuer gezielte Fehler in Tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e6c9a4c24
commit
405b2a0485
4 files changed
+277
-88
No files matched your search
@@ -17,12 +17,17 @@ import { randomUUID } from 'node:crypto'
|
||||
* all: (collection: string) => object[],
|
||||
* get: (collection: string, id: string) => (object|undefined),
|
||||
* calls: Array<{method: string, path: string, query: Record<string, string>, headers: Record<string, string>, body: any}>,
|
||||
* failNext: (spec: {method: string, pathIncludes: string, status: number}) => void,
|
||||
* close: () => Promise<void>,
|
||||
* }>} `all`/`get` lesen den LIVE-Stand (nach create/patch/delete ueber HTTP). Die
|
||||
* Datenbank-ID im Pfad wird nicht geprueft - jede wird akzeptiert.
|
||||
* `failNext({method, pathIncludes, status})` laesst GENAU die naechste Anfrage, deren Methode
|
||||
* passt und deren Pfad `pathIncludes` enthaelt, mit diesem Status und einer generischen
|
||||
* JSON-Fehlermeldung scheitern (danach wieder normales Verhalten).
|
||||
*/
|
||||
export async function startFakeAppwrite(options = {}) {
|
||||
const calls = []
|
||||
const pendingFailures = []
|
||||
/** @type {Map<string, Map<string, object>>} Sammlungsname -> (Dokument-ID -> Dokument). */
|
||||
const store = new Map()
|
||||
|
||||
@@ -196,6 +201,14 @@ export async function startFakeAppwrite(options = {}) {
|
||||
body: parsedBody,
|
||||
})
|
||||
|
||||
const failIdx = pendingFailures.findIndex(
|
||||
(f) => f.method === req.method && url.pathname.includes(f.pathIncludes)
|
||||
)
|
||||
if (failIdx !== -1) {
|
||||
const [f] = pendingFailures.splice(failIdx, 1)
|
||||
return send(res, f.status, { message: `fakeAppwrite: erzwungener Fehler (${f.status})` })
|
||||
}
|
||||
|
||||
const route = ROUTES.find((r) => r.method === req.method && r.re.test(url.pathname))
|
||||
if (!route) return send(res, 404, { message: 'Not Found' })
|
||||
const match = route.re.exec(url.pathname)
|
||||
@@ -228,10 +241,14 @@ export async function startFakeAppwrite(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function failNext({ method, pathIncludes, status }) {
|
||||
pendingFailures.push({ method, pathIncludes, status })
|
||||
}
|
||||
|
||||
function close() {
|
||||
for (const socket of sockets) socket.destroy()
|
||||
return new Promise((resolve) => server.close(() => resolve()))
|
||||
}
|
||||
|
||||
return { endpoint, all, get, calls, close }
|
||||
return { endpoint, all, get, calls, failNext, close }
|
||||
}
|
||||
Reference in new issue
Block a user