Initial import of zappier-edge from zapier monorepo
This commit is contained in:
commit
48cd80ce5f
120 changed files with 19867 additions and 0 deletions
115
tests/admin-users.test.ts
Normal file
115
tests/admin-users.test.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import Database from 'better-sqlite3';
|
||||
import request from 'supertest';
|
||||
import { hashPassword } from '../src/accounts';
|
||||
import { InMemoryAdminUserRepo, seedAdminUsersFromEnv } from '../src/admin-users';
|
||||
import { buildApp } from '../src/app';
|
||||
import { SqliteAdminUserRepo } from '../src/db/admin-user-repo';
|
||||
|
||||
const ADMIN = { 'x-admin-key': 'admin-dev-key' };
|
||||
|
||||
describe('admin user repos', () => {
|
||||
it('seeds from env defaults (in-memory)', () => {
|
||||
const repo = InMemoryAdminUserRepo.seeded(seedAdminUsersFromEnv());
|
||||
expect(repo.findByUsername('admin')?.active).toBe(true);
|
||||
expect(repo.findByUsername('demo')?.active).toBe(true);
|
||||
expect(repo.findByUsername('admin')?.passwordHash).toMatch(/^scrypt:/);
|
||||
});
|
||||
|
||||
it('seeds from env defaults (sqlite) and persists', () => {
|
||||
const db = new Database(':memory:');
|
||||
const first = SqliteAdminUserRepo.seeded(db, seedAdminUsersFromEnv());
|
||||
first.save({
|
||||
id: 'usr_extra',
|
||||
username: 'ops',
|
||||
passwordHash: hashPassword('ops-password-1'),
|
||||
active: true,
|
||||
createdMs: Date.now(),
|
||||
});
|
||||
const second = SqliteAdminUserRepo.seeded(db, seedAdminUsersFromEnv());
|
||||
expect(second.findByUsername('ops')?.active).toBe(true);
|
||||
// re-seeding must not duplicate the env accounts
|
||||
expect(second.list().filter((u) => u.username === 'admin')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('deactivation persists in sqlite', () => {
|
||||
const repo = SqliteAdminUserRepo.seeded(new Database(':memory:'), seedAdminUsersFromEnv());
|
||||
const demo = repo.findByUsername('demo')!;
|
||||
repo.save({ ...demo, active: false });
|
||||
expect(repo.findByUsername('demo')?.active).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('admin users API', () => {
|
||||
it('lists users without password hashes', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app).get('/admin/api/users').set(ADMIN);
|
||||
expect(res.status).toBe(200);
|
||||
const usernames = res.body.users.map((u: { username: string }) => u.username);
|
||||
expect(usernames).toEqual(expect.arrayContaining(['admin', 'demo']));
|
||||
expect(res.body.users[0].passwordHash).toBeUndefined();
|
||||
});
|
||||
|
||||
it('creates a user who can then log in', async () => {
|
||||
const { app } = buildApp();
|
||||
const created = await request(app)
|
||||
.post('/admin/api/users')
|
||||
.set(ADMIN)
|
||||
.send({ username: 'finance', password: 'finance-pass-1' });
|
||||
expect(created.status).toBe(201);
|
||||
const login = await request(app)
|
||||
.post('/admin/api/login')
|
||||
.send({ username: 'finance', password: 'finance-pass-1' });
|
||||
expect(login.status).toBe(200);
|
||||
expect(login.body.token).toMatch(/^[a-f0-9]{48}$/);
|
||||
});
|
||||
|
||||
it('rejects duplicates, weak passwords, and bad usernames', async () => {
|
||||
const { app } = buildApp();
|
||||
const dup = await request(app)
|
||||
.post('/admin/api/users')
|
||||
.set(ADMIN)
|
||||
.send({ username: 'demo', password: 'whatever-pass-1' });
|
||||
expect(dup.status).toBe(409);
|
||||
const weak = await request(app)
|
||||
.post('/admin/api/users')
|
||||
.set(ADMIN)
|
||||
.send({ username: 'x1', password: 'short' });
|
||||
expect(weak.status).toBe(400);
|
||||
const bad = await request(app)
|
||||
.post('/admin/api/users')
|
||||
.set(ADMIN)
|
||||
.send({ username: 'bad name!', password: 'fine-password-1' });
|
||||
expect(bad.status).toBe(400);
|
||||
});
|
||||
|
||||
it('deactivation blocks login; reactivation restores it', async () => {
|
||||
const { app } = buildApp();
|
||||
const off = await request(app).post('/admin/api/users/demo/deactivate').set(ADMIN);
|
||||
expect(off.status).toBe(200);
|
||||
const login = await request(app)
|
||||
.post('/admin/api/login')
|
||||
.send({ username: 'demo', password: '$$$Adm1n###' });
|
||||
expect(login.status).toBe(401);
|
||||
|
||||
const on = await request(app).post('/admin/api/users/demo/activate').set(ADMIN);
|
||||
expect(on.status).toBe(200);
|
||||
const again = await request(app)
|
||||
.post('/admin/api/login')
|
||||
.send({ username: 'demo', password: '$$$Adm1n###' });
|
||||
expect(again.status).toBe(200);
|
||||
});
|
||||
|
||||
it('refuses to deactivate the last active admin', async () => {
|
||||
const { app } = buildApp();
|
||||
await request(app).post('/admin/api/users/demo/deactivate').set(ADMIN);
|
||||
const last = await request(app).post('/admin/api/users/admin/deactivate').set(ADMIN);
|
||||
expect(last.status).toBe(400);
|
||||
expect(last.body.error).toContain('last active');
|
||||
});
|
||||
|
||||
it('404s unknown usernames on activate/deactivate', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app).post('/admin/api/users/nobody/deactivate').set(ADMIN);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue