Add Feedback domain module: public submission flow, admin CRUD, reporting (#7)
Jenkins Production Deployment

Co-authored-by: Patrick Müller <mail@pmueller.me>
Reviewed-on: #7
Co-authored-by: Patrick Mueller <patrick@mueller-patrick.tech>
Co-committed-by: Patrick Mueller <patrick@mueller-patrick.tech>
This commit was merged in pull request #7.
This commit is contained in:
2026-08-23 09:39:02 +00:00
committed by Patrick Müller
parent e7621b8290
commit b05f6b9da0
38 changed files with 4115 additions and 2 deletions
+87
View File
@@ -0,0 +1,87 @@
import {Request, Response} from 'express';
jest.mock('../../src/models/calendar/users/users.service', () => ({
checkSession: jest.fn()
}));
import * as UserService from '../../src/models/calendar/users/users.service';
import {requireAdminAuth, sessionHeaderAuthenticator} from '../../src/models/feedback/feedback.auth';
const mockCheckSession = UserService.checkSession as jest.Mock;
const makeReq = (headers: Record<string, string>): Request => {
return {
header: (name: string) => headers[name],
ip: '203.0.113.42'
} as unknown as Request;
};
const makeRes = (): Response => {
const res: any = {};
res.status = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
res.locals = {};
return res as Response;
};
describe('sessionHeaderAuthenticator', () => {
beforeEach(() => mockCheckSession.mockReset());
it('returns null when headers are missing', async () => {
const identity = await sessionHeaderAuthenticator(makeReq({}));
expect(identity).toBeNull();
expect(mockCheckSession).not.toHaveBeenCalled();
});
it('returns null when checkSession finds no user', async () => {
mockCheckSession.mockResolvedValue(null);
const identity = await sessionHeaderAuthenticator(makeReq({'X-Session-Id': '1', 'X-Session-Key': 'k'}));
expect(identity).toBeNull();
});
it('returns null for a valid session on an inactive account', async () => {
mockCheckSession.mockResolvedValue({userId: 1, email: 'a@nachklang.art', fullName: 'A', isActive: false});
const identity = await sessionHeaderAuthenticator(makeReq({'X-Session-Id': '1', 'X-Session-Key': 'k'}));
expect(identity).toBeNull();
});
it('returns the identity for a valid session on an active account', async () => {
mockCheckSession.mockResolvedValue({userId: 1, email: 'a@nachklang.art', fullName: 'Anna Admin', isActive: true});
const identity = await sessionHeaderAuthenticator(makeReq({'X-Session-Id': '1', 'X-Session-Key': 'k'}));
expect(identity).toEqual({id: '1', email: 'a@nachklang.art', displayName: 'Anna Admin'});
});
it('passes the session id and key from headers through to checkSession, never from query params', async () => {
mockCheckSession.mockResolvedValue({userId: 1, email: 'a@nachklang.art', fullName: 'A', isActive: true});
await sessionHeaderAuthenticator(makeReq({'X-Session-Id': '42', 'X-Session-Key': 'sekret'}));
expect(mockCheckSession).toHaveBeenCalledWith('42', 'sekret', '203.0.113.42');
});
});
describe('requireAdminAuth', () => {
beforeEach(() => mockCheckSession.mockReset());
it('responds 401 and does not call next() when unauthenticated', async () => {
mockCheckSession.mockResolvedValue(null);
const req = makeReq({});
const res = makeRes();
const next = jest.fn();
await requireAdminAuth(req, res, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(next).not.toHaveBeenCalled();
});
it('sets res.locals.admin and calls next() when authenticated', async () => {
mockCheckSession.mockResolvedValue({userId: 1, email: 'a@nachklang.art', fullName: 'Anna Admin', isActive: true});
const req = makeReq({'X-Session-Id': '1', 'X-Session-Key': 'k'});
const res = makeRes();
const next = jest.fn();
await requireAdminAuth(req, res, next);
expect(next).toHaveBeenCalled();
expect(res.locals.admin).toEqual({id: '1', email: 'a@nachklang.art', displayName: 'Anna Admin'});
});
});