Files
vendoo/tools/verify-controlled-invitations-1.42.0.mjs
Masterluke77andGitHub 40071f718e Vendoo 1.43.0 – Production Update
Manifestbasierter Production-Updater 3.3, Modulnavigation, optionales Produktbild Studio, Listing-Studio-Aktionsleiste sowie plattformübergreifende Windows-/Linux-Release-Gates.
2026-07-10 13:16:21 +02:00

58 lines
3.4 KiB
JavaScript

import { mkdtempSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { readFileSync } from 'node:fs';
import { closeVendooDatabase, removeTempDirectory } from './test-runtime-cleanup.mjs';
const tempPrefix = process.env.VENDOO_TEST_TEMP_PREFIX || 'vendoo-invitations-';
const temp = mkdtempSync(join(tmpdir(), tempPrefix));
process.env.NODE_ENV = 'test';
process.env.VENDOO_DATA_DIR = temp;
process.env.VENDOO_DB_PATH = join(temp, 'db', 'vendoo.db');
process.env.VENDOO_CONFIG_PATH = join(temp, 'config', 'vendoo.env');
const failures = [];
const assert = (condition, message) => { if (!condition) failures.push(message); };
try {
const identity = await import('../app/core/identity/identity-store.mjs');
const { createUsersService } = await import('../app/modules/users/service.mjs');
const first = identity.createAuthToken('tester@example.invalid', 'invite', 'viewer', null);
assert(first.token && first.expiresAt, 'Einladungstoken oder Ablauf fehlt');
let invitations = identity.listInvitations();
assert(invitations.length === 1, `Erwartet eine Einladung, erhalten ${invitations.length}`);
assert(invitations[0].status === 'pending', `Neue Einladung ist ${invitations[0].status}`);
assert(!Object.hasOwn(invitations[0], 'token_hash'), 'Token-Hash wird in der Einladungsliste ausgegeben');
assert(invitations[0].role === 'viewer', `Minimale Testerrolle ist ${invitations[0].role}`);
const second = identity.createAuthToken('tester@example.invalid', 'invite', 'viewer', null);
invitations = identity.listInvitations();
assert(invitations.filter(item => item.status === 'pending').length === 1, 'Erneutes Senden lässt mehrere gültige Einladungen offen');
const pending = invitations.find(item => item.status === 'pending');
const revoked = identity.revokeInvitation(pending.id);
assert(revoked?.status === 'revoked', 'Einladung wurde nicht widerrufen');
assert(identity.validateAuthToken(second.token) === null, 'Widerrufener Link bleibt gültig');
const service = createUsersService();
const prepared = service.prepareInvite({ email: 'minimal@example.invalid', name: 'Minimal', createdBy: null, method: 'magic_link' });
assert(prepared.role === 'viewer', `Service-Standardrolle ist ${prepared.role}`);
const routes = readFileSync(new URL('../app/modules/users/routes.mjs', import.meta.url), 'utf8');
for (const marker of ['/api/admin/users/invitations', '/api/admin/users/invitations/:id/resend', "'user.invitation_revoked'", "policy: 'users.manage'", 'csrf: true']) {
assert(routes.includes(marker), `Einladungs-Routenvertrag fehlt: ${marker}`);
}
const frontend = readFileSync(new URL('../public/app.js', import.meta.url), 'utf8');
assert(frontend.includes('loadAdminInvitations'), 'Einladungsübersicht fehlt im Frontend');
assert(frontend.includes("invite-role').value = 'viewer'"), 'Einladungsdialog startet nicht mit Leserrolle');
} finally {
await closeVendooDatabase({ failures, label: 'Einladungs-Testdatenbank' });
await removeTempDirectory(temp, { failures, label: 'Einladungs-Testordner' });
}
if (failures.length) {
console.error(`Controlled-Invitations-Gate 1.42.0 fehlgeschlagen (${failures.length}):`);
failures.forEach(failure => console.error(`- ${failure}`));
process.exit(1);
}
console.log('Controlled-Invitations-Gate 1.42.0 erfolgreich: Leser-Standardrolle, 48h-Ablauf, Einmalgültigkeit, erneutes Senden, Widerruf, Secret-Grenze, RBAC und UI geprüft.');
process.exit(0);