Files
videogen/scripts/test-mandanten.mjs
2026-08-15 13:43:52 +02:00

120 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* E2-Abnahme: Mandantentrennung geprüft über die API, nicht über das UI.
*
* Legt zwei echte Konten an und baut für beide Team + brands-Zeile genau so wie
* client/src/lib/auth.ts. Danach: sieht Konto B irgendetwas von Konto A?
*
* Kein SDK, nur fetch mit eigenem Cookie-Speicher so verhält sich der Aufruf
* wie ein Browser, und der Test hängt nicht an SDK-Eigenheiten.
*/
const ENDPOINT = 'https://appwrite.webklar.com/v1';
const PROJECT = '6a5cee34002bb8360c34';
const DB = 'brandloop';
const KEY = process.env.APPWRITE_API_KEY;
if (!KEY) { console.error('APPWRITE_API_KEY fehlt'); process.exit(1); }
const stamp = Date.now();
let fehler = 0;
const ok = (m) => console.log(` ok ${m}`);
const bad = (m) => { fehler++; console.log(` FEHL ${m}`); };
const limit = encodeURIComponent(JSON.stringify({ method: 'limit', values: [100] }));
/** Ein Konto = ein Cookie-Speicher, so wie ein Browser-Profil. */
function neueSitzung() {
const jar = new Map();
return async function call(method, path, body) {
const headers = { 'X-Appwrite-Project': PROJECT, 'Content-Type': 'application/json' };
if (jar.size) headers.Cookie = [...jar].map(([k, v]) => `${k}=${v}`).join('; ');
const res = await fetch(`${ENDPOINT}${path}`, {
method, headers, body: body ? JSON.stringify(body) : undefined, redirect: 'manual',
});
for (const c of res.headers.getSetCookie?.() ?? []) {
const [pair] = c.split(';');
const i = pair.indexOf('=');
if (i > 0) jar.set(pair.slice(0, i).trim(), pair.slice(i + 1).trim());
}
const text = await res.text();
let json; try { json = JSON.parse(text); } catch { json = { message: text }; }
return { status: res.status, json };
};
}
const admin = async (method, path, body) => {
const res = await fetch(`${ENDPOINT}${path}`, {
method,
headers: { 'X-Appwrite-Project': PROJECT, 'X-Appwrite-Key': KEY, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let json; try { json = JSON.parse(text); } catch { json = { message: text }; }
return { status: res.status, json };
};
function muss(r, was) {
if (r.status >= 200 && r.status < 300) return r.json;
throw new Error(`${was}: HTTP ${r.status} ${r.json?.message}`);
}
/** Spiegelt client/src/lib/auth.ts: Konto → Session → Team → brands-Zeile. */
async function kontoAnlegen(kennung) {
const call = neueSitzung();
const email = `e2-${kennung}-${stamp}@brandloop.test`;
const password = `Test-${stamp}-${kennung}!`;
const label = `Marke ${kennung.toUpperCase()}`;
const user = muss(await call('POST', '/account', { userId: 'unique()', email, password, name: label }), 'account');
muss(await call('POST', '/account/sessions/email', { email, password }), 'session');
const team = muss(await call('POST', '/teams', { teamId: 'unique()', name: label }), 'team');
const rolle = `team:${team.$id}`;
const brand = muss(await call('POST', `/tablesdb/${DB}/tables/brands/rows`, {
rowId: 'unique()',
data: { team_id: team.$id, label_name: label, status: 'trial', plan: 'trial' },
permissions: [`read("${rolle}")`, `update("${rolle}")`, `delete("${rolle}")`],
}), 'brands-Zeile');
return { call, userId: user.$id, teamId: team.$id, brandId: brand.$id, label };
}
console.log('Konten anlegen (wie der Client es tut):');
const A = await kontoAnlegen('a');
console.log(` A: user=${A.userId} team=${A.teamId} brand=${A.brandId}`);
const B = await kontoAnlegen('b');
console.log(` B: user=${B.userId} team=${B.teamId} brand=${B.brandId}`);
console.log('\nMandantentrennung:');
const liste = await B.call('GET', `/tablesdb/${DB}/tables/brands/rows?queries[]=${limit}`);
const ids = (liste.json.rows ?? []).map((r) => r.$id);
if (ids.includes(A.brandId)) bad(`B sieht A's Zeile in der Liste`);
else ok(`B listet ${ids.length} brands-Zeile(n), A's ist nicht dabei`);
if (!ids.includes(B.brandId)) bad('B sieht die EIGENE Zeile nicht Rechte zu streng');
else ok('B sieht die eigene Zeile');
const direkt = await B.call('GET', `/tablesdb/${DB}/tables/brands/rows/${A.brandId}`);
if (direkt.status < 300) bad(`B konnte A's Zeile direkt lesen (HTTP ${direkt.status})`);
else ok(`Direktzugriff B→A abgewiesen (HTTP ${direkt.status} ${direkt.json?.type ?? ''})`);
const schreib = await B.call('PATCH', `/tablesdb/${DB}/tables/brands/rows/${A.brandId}`, { data: { label_name: 'gekapert' } });
if (schreib.status < 300) bad(`B konnte A's Zeile ÄNDERN (HTTP ${schreib.status})`);
else ok(`Schreibzugriff B→A abgewiesen (HTTP ${schreib.status} ${schreib.json?.type ?? ''})`);
// Gegenprobe: sieht der Server-Key beide? Sonst wäre der erste Test wertlos,
// weil dann vielleicht schlicht nichts in der Tabelle steht.
const alle = await admin('GET', `/tablesdb/${DB}/tables/brands/rows?queries[]=${limit}`);
const alleIds = (alle.json.rows ?? []).map((r) => r.$id);
if (alleIds.includes(A.brandId) && alleIds.includes(B.brandId)) ok(`Server-Key sieht beide Zeilen (${alle.json.total} gesamt) der erste Test ist damit aussagekräftig`);
else bad(`Server-Key sieht nicht beide: ${JSON.stringify(alleIds)}`);
console.log('\nAufräumen:');
for (const k of [A, B]) {
await admin('DELETE', `/tablesdb/${DB}/tables/brands/rows/${k.brandId}`);
await admin('DELETE', `/teams/${k.teamId}`);
await admin('DELETE', `/users/${k.userId}`);
}
const rest = await admin('GET', `/tablesdb/${DB}/tables/brands/rows?queries[]=${limit}`);
console.log(` Testkonten entfernt, brands enthält jetzt ${rest.json.total} Zeile(n).`);
console.log(fehler ? `\n${fehler} FEHLER Mandantentrennung nicht dicht.` : '\nMandantentrennung hält.');
process.exit(fehler ? 1 : 0);