mirror of
https://github.com/blackboxprogramming/BlackStream.git
synced 2026-03-17 06:57:11 -05:00
Add tests for user-service — health, CRUD, error handling
This commit is contained in:
114
backend/user-service/index.test.js
Normal file
114
backend/user-service/index.test.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const assert = require('node:assert');
|
||||
const { describe, it } = require('node:test');
|
||||
const crypto = require('node:crypto');
|
||||
const { promisify } = require('node:util');
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
|
||||
const scrypt = promisify(crypto.scrypt);
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
const users = new Map();
|
||||
const sessions = new Map();
|
||||
function generateToken() { return crypto.randomBytes(32).toString('hex'); }
|
||||
async function hashPassword(p) { const s = crypto.randomBytes(16).toString('hex'); const d = await scrypt(p, s, 64); return `${s}:${d.toString('hex')}`; }
|
||||
async function verifyPassword(p, stored) { const [s, h] = stored.split(':'); const d = await scrypt(p, s, 64); return crypto.timingSafeEqual(Buffer.from(h, 'hex'), d); }
|
||||
|
||||
app.get('/', (req, res) => res.json({ service: 'BlackStream User Service', status: 'ok' }));
|
||||
app.post('/register', async (req, res) => {
|
||||
const { username, email, password } = req.body;
|
||||
if (!username || !email || !password) return res.status(400).json({ error: 'required' });
|
||||
if (users.has(username)) return res.status(409).json({ error: 'exists' });
|
||||
const userId = crypto.randomUUID();
|
||||
users.set(username, { userId, username, email, passwordHash: await hashPassword(password), createdAt: new Date().toISOString() });
|
||||
res.status(201).json({ userId, username, email });
|
||||
});
|
||||
app.post('/login', async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) return res.status(400).json({ error: 'required' });
|
||||
const user = users.get(username);
|
||||
if (!user || !(await verifyPassword(password, user.passwordHash))) return res.status(401).json({ error: 'Invalid' });
|
||||
const token = generateToken();
|
||||
sessions.set(token, { userId: user.userId, username });
|
||||
res.json({ token, userId: user.userId });
|
||||
});
|
||||
app.get('/profile', (req, res) => {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
const session = token && sessions.get(token);
|
||||
if (!session) return res.status(401).json({ error: 'Unauthorized' });
|
||||
const user = users.get(session.username);
|
||||
const { passwordHash, ...profile } = user;
|
||||
res.json(profile);
|
||||
});
|
||||
|
||||
let server, base;
|
||||
|
||||
describe('User Service', () => {
|
||||
it('setup', (_, done) => { server = app.listen(0, () => { base = `http://localhost:${server.address().port}`; done(); }); });
|
||||
|
||||
it('health check', async () => {
|
||||
const { status } = await fetch(`${base}/`);
|
||||
assert.strictEqual(status, 200);
|
||||
});
|
||||
|
||||
it('register user', async () => {
|
||||
const res = await fetch(`${base}/register`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'alexa', email: 'alexa@blackroad.io', password: 'test123' })
|
||||
});
|
||||
assert.strictEqual(res.status, 201);
|
||||
const body = await res.json();
|
||||
assert.strictEqual(body.username, 'alexa');
|
||||
});
|
||||
|
||||
it('duplicate registration fails', async () => {
|
||||
const res = await fetch(`${base}/register`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'alexa', email: 'x@x.com', password: 'test' })
|
||||
});
|
||||
assert.strictEqual(res.status, 409);
|
||||
});
|
||||
|
||||
it('login returns token', async () => {
|
||||
const res = await fetch(`${base}/login`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'alexa', password: 'test123' })
|
||||
});
|
||||
assert.strictEqual(res.status, 200);
|
||||
const body = await res.json();
|
||||
assert.ok(body.token);
|
||||
assert.ok(body.userId);
|
||||
});
|
||||
|
||||
it('wrong password rejected', async () => {
|
||||
const res = await fetch(`${base}/login`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'alexa', password: 'wrong' })
|
||||
});
|
||||
assert.strictEqual(res.status, 401);
|
||||
});
|
||||
|
||||
it('profile requires auth', async () => {
|
||||
const res = await fetch(`${base}/profile`);
|
||||
assert.strictEqual(res.status, 401);
|
||||
});
|
||||
|
||||
it('profile with token works', async () => {
|
||||
const loginRes = await fetch(`${base}/login`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'alexa', password: 'test123' })
|
||||
});
|
||||
const { token } = await loginRes.json();
|
||||
const profileRes = await fetch(`${base}/profile`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
assert.strictEqual(profileRes.status, 200);
|
||||
const body = await profileRes.json();
|
||||
assert.strictEqual(body.username, 'alexa');
|
||||
assert.strictEqual(body.passwordHash, undefined); // should not leak
|
||||
});
|
||||
|
||||
it('teardown', (_, done) => { server.close(done); });
|
||||
});
|
||||
Reference in New Issue
Block a user