feat(api): Logo-Endpunkte, Fehlercodes und gemeinsame Helfer
- common.js (aus app.js herausgelöst, Verhalten gleich): MSG, HttpError, Prüfungen, Rechte-Strings, Melder-Hash,
Bildmaße und die Dateiprüfungen checkFileExists/checkFileOwnership. Neu: die Logo-Texte in MSG, codeFor,
likeRowPermissions. app.js exportiert weiter alles wie bisher.
- Fehlerantworten sind jetzt {error, code}: code = MSG-Schlüssel des Textes, dynamische Prüftexte badRequest,
Appwrite-Fehler unavailable (502) bzw. internal (500).
- logos.js: POST /api/logos/hochladen, /herz, /melden, /loeschen und /api/admin/logo-sichtbarkeit nach dem Vorbild der
Karten-Endpunkte (Datei prüfen, Zeile anlegen, Datei auf read("any") festlegen, sonst Rücknahme; likeCount und
reportCount immer aus countRows; ein gemeinsames Rate-Limit für Karten- und Logo-Meldungen).
- appwrite.js: countRows. fake-appwrite.js: Tabellen logos, logo_likes, logo_reports mit Spaltenlängen, Bucket
logo-images und total in den Listen-Antworten.
- Tests: logos.test.js (59), code-Fälle und MSG-Eindeutigkeit in app.test.js (+7), countRows in appwrite.test.js (+2);
alle 111 bisherigen Tests unverändert grün (179 gesamt).
- Doku: api/README.md (Endpunkte, Fehlercodes, Logo-Regeln), deploy/README.md (Betrieb kopiert fünf Dateien).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
88abc2061d
commit
dc5de99611
10 files changed
+2181
-286
No files matched your search
@@ -129,6 +129,47 @@ test('listRows blättert mit cursorAfter', async () => {
|
||||
assert.deepEqual(last.map((row) => row.$id), ['e']);
|
||||
});
|
||||
|
||||
test('countRows: liefert das total der Abfrage (nicht die Länge der Seite) und schickt limit(1) mit', async () => {
|
||||
const { fake, appwrite } = await setup();
|
||||
for (const id of ['a', 'b', 'c', 'd']) {
|
||||
await appwrite.createRow('reports', id, { galleryId: id === 'd' ? 'other' : 'g1', reason: 'spam', reporterHash: id }, []);
|
||||
}
|
||||
assert.equal(await appwrite.countRows('reports', [queryEqual('galleryId', ['g1'])]), 3);
|
||||
const call = lastCall(fake);
|
||||
assert.equal(call.method, 'GET');
|
||||
assert.equal(call.path, '/v1/tablesdb/aircard/tables/reports/rows');
|
||||
assert.deepEqual(call.search.split('&').map((part) => decodeURIComponent(part)), [
|
||||
'queries[]={"method":"equal","attribute":"galleryId","values":["g1"]}',
|
||||
'queries[]={"method":"limit","values":[1]}',
|
||||
]);
|
||||
assert.equal(call.headers['x-appwrite-key'], fake.key);
|
||||
assert.equal(await appwrite.countRows('reports', [queryEqual('galleryId', ['nix'])]), 0);
|
||||
assert.equal(await appwrite.countRows('reports'), 4, 'ohne Abfragen zählt es die ganze Tabelle');
|
||||
});
|
||||
|
||||
test('countRows: fehlendes oder ungültiges total ist ein AppwriteError (502, invalid_response), Appwrite-Fehler werden weitergereicht', async () => {
|
||||
for (const payload of [{ rows: [] }, { total: '3', rows: [] }, { total: null, rows: [] }, { total: -1, rows: [] }, { total: 1.5, rows: [] }, [], 'text', null]) {
|
||||
const appwrite = createAppwrite({
|
||||
endpoint: 'http://appwrite.invalid/v1',
|
||||
project: 'p',
|
||||
key: 'k',
|
||||
fetchImpl: async () => new Response(JSON.stringify(payload), { status: 200, headers: { 'Content-Type': 'application/json' } }),
|
||||
});
|
||||
await assert.rejects(
|
||||
() => appwrite.countRows('reports', []),
|
||||
(err) => err instanceof AppwriteError && err.type === 'invalid_response' && err.status === 502,
|
||||
JSON.stringify(payload),
|
||||
);
|
||||
}
|
||||
const zero = createAppwrite({ endpoint: 'http://appwrite.invalid/v1', project: 'p', key: 'k', fetchImpl: async () => new Response(JSON.stringify({ total: 0, rows: [] }), { status: 200 }) });
|
||||
assert.equal(await zero.countRows('reports', []), 0, 'total 0 ist eine gültige Zahl');
|
||||
|
||||
const { fake, appwrite } = await setup();
|
||||
fake.failNext((call) => call.method === 'GET', { status: 500, type: 'general_server_error' });
|
||||
await assert.rejects(() => appwrite.countRows('reports', []), (err) => err instanceof AppwriteError && err.status === 500);
|
||||
await assert.rejects(() => appwrite.countRows('keine-tabelle', []), (err) => err instanceof AppwriteError && err.status === 404 && err.type === 'table_not_found');
|
||||
});
|
||||
|
||||
test('Dateien: lesen, Rechte setzen, löschen', async () => {
|
||||
const { fake, appwrite } = await setup();
|
||||
fake.addFile({ id: 'f1', owner: 'u1', mimeType: 'image/webp', size: 1234 });
|
||||
@@ -335,11 +376,12 @@ test('Alle Appwrite-Aufrufe verwenden redirect: "error" (der Key folgt keiner We
|
||||
key: 'k',
|
||||
fetchImpl: async (url, init) => {
|
||||
seen.push(init.redirect);
|
||||
return new Response(JSON.stringify({ rows: [], $id: 'x' }), { status: 200 });
|
||||
return new Response(JSON.stringify({ total: 0, rows: [], $id: 'x' }), { status: 200 });
|
||||
},
|
||||
});
|
||||
await appwrite.getRow('gallery', 'x');
|
||||
await appwrite.listRows('gallery', []);
|
||||
await appwrite.countRows('gallery', []);
|
||||
await appwrite.createRow('gallery', 'x', {}, []);
|
||||
await appwrite.updateRow('gallery', 'x', {});
|
||||
await appwrite.deleteRow('gallery', 'x');
|
||||
@@ -347,7 +389,7 @@ test('Alle Appwrite-Aufrufe verwenden redirect: "error" (der Key folgt keiner We
|
||||
await appwrite.updateFilePermissions('gallery-images', 'x', []);
|
||||
await appwrite.deleteFile('gallery-images', 'x');
|
||||
await appwrite.accountFromJwt('a.b.c');
|
||||
assert.equal(seen.length, 9);
|
||||
assert.equal(seen.length, 10);
|
||||
assert.ok(seen.every((value) => value === 'error'));
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user