120 lines
5.6 KiB
JavaScript
120 lines
5.6 KiB
JavaScript
/**
|
||
* 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);
|