import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import LibDashStore from '../src/index.js'; import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; async function open(tempDir) { const provider = new BetterSqlite3Provider({ filename: join(tempDir, 'test.db'), storageDir: join(tempDir, 'storage') }); await provider.initialize(); return { provider, store: await new LibDashStore(provider) }; } const tempDir = mkdtempSync(join(tmpdir(), 'test-cu-roles-')); try { console.log('--- Testing companyuser roles + autoinvite ---'); let { provider, store } = await open(tempDir); const adminId = await store.google_auth.add('admin@example.com'); const userId = await store.google_auth.add('user@example.com'); const company = await store.company.create({ name: 'Roles Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId: adminId }); await store.google_auth.setSelectedCompany(adminId, company.id); // creator is admin let members = await store.companyuser.list(adminId); assert.deepEqual(members.map((m) => [m.email, m.role]), [['admin@example.com', 'admin']]); assert.equal((await store.company.list(adminId))[0].role, 'admin'); // non-member cannot select the company await assert.rejects(store.google_auth.setSelectedCompany(userId, company.id), /Not authorized/); // admin adds existing user await assert.rejects(store.companyuser.addByEmail(adminId, { email: 'nobody@example.com' }), /User not found/); await assert.rejects(store.companyuser.addByEmail(adminId, { email: 'user@example.com', role: 'root' }), /Invalid role/); const added = await store.companyuser.addByEmail(adminId, { email: 'User@Example.com' }); assert.equal(added.role, 'user'); await assert.rejects(store.companyuser.addByEmail(adminId, { email: 'user@example.com' }), /already in company/); // user can now select it, but is not admin await store.google_auth.setSelectedCompany(userId, company.id); await assert.rejects(store.companyuser.list(userId), /Not authorized/); await assert.rejects(store.companyuser.addByEmail(userId, { email: 'admin@example.com' }), /Not authorized/); await assert.rejects(store.companyuser.setRole(userId, added.id, 'admin'), /Not authorized/); await assert.rejects(store.companyuser.remove(userId, added.id), /Not authorized/); await assert.rejects(store.autoinvite.add(userId, { email: 'x@example.com' }), /Not authorized/); // last admin guard const adminCu = members[0].id; await assert.rejects(store.companyuser.setRole(adminId, adminCu, 'user'), /last admin/); await assert.rejects(store.companyuser.remove(adminId, adminCu), /last admin/); await store.companyuser.setRole(adminId, added.id, 'admin'); await store.companyuser.setRole(adminId, adminCu, 'user'); await store.companyuser.setRole(userId, adminCu, 'admin'); await store.companyuser.setRole(userId, added.id, 'user'); await assert.rejects(store.companyuser.setRole(adminId, 99999, 'user'), /Company user not found/); // autoinvite await assert.rejects(store.autoinvite.add(adminId, { email: 'user@example.com' }), /already registered/); await assert.rejects(store.autoinvite.add(adminId, { email: 'bad' }), /Invalid email/); await store.autoinvite.add(adminId, { email: 'New@Example.com', role: 'admin' }); await assert.rejects(store.autoinvite.add(adminId, { email: 'new@example.com' }), /already exists/); await store.autoinvite.add(adminId, { email: 'gone@example.com' }); assert.equal((await store.autoinvite.list(adminId)).length, 2); await store.autoinvite.remove(adminId, (await store.autoinvite.list(adminId)).find((i) => i.email === 'gone@example.com').id); await assert.rejects(store.autoinvite.remove(adminId, 99999), /Invitation not found/); const newId = await store.google_auth.add('new@example.com'); assert.deepEqual(await store.autoinvite.list(adminId), []); await store.google_auth.setSelectedCompany(newId, company.id); members = await store.companyuser.list(adminId); assert.equal(members.find((m) => m.email === 'new@example.com').role, 'admin'); // remove clears selection const userCu = members.find((m) => m.email === 'user@example.com'); await store.companyuser.remove(adminId, userCu.id); assert.equal((await store.google_auth.getById(userId)).selectedCompanyId, null); // downgrade → upgrade through the migration engine await provider.close?.(); const env = { ...process.env, DB_PROVIDER: 'sqlite', SQLITE_PATH: join(tempDir, 'test.db') }; execFileSync('node', ['scripts/downgrade.js', '5'], { env, stdio: 'inherit' }); ({ provider, store } = await open(tempDir).catch((e) => { throw e; })); // opening upgrades to latest again; roles reset to admin for all members const rows = (await provider.query('SELECT role FROM companyuser')).rows; assert.ok(rows.length > 0 && rows.every((r) => r.role === 'admin')); assert.deepEqual((await provider.query('SELECT * FROM autoinvite')).rows, []); await provider.close?.(); console.log('OK'); } finally { rmSync(tempDir, { recursive: true, force: true }); }