bf7be65b03
Jenkins Production Deployment
Reviewed-on: #12 Co-authored-by: Patrick Müller <mail@pmueller.me> Co-committed-by: Patrick Müller <mail@pmueller.me>
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
import {expect} from 'vitest';
|
|
import type {Application} from 'express';
|
|
import request from 'supertest';
|
|
import {NachklangAdminDB} from '../../src/models/admin/Admin.db.js';
|
|
import * as InvitationsService from '../../src/models/admin/invitations/invitations.service.js';
|
|
import {ACCESS_ROLE, AppName, AppPermission, toPermissions} from '../../src/models/admin/admin.schema.js';
|
|
|
|
const db = NachklangAdminDB.db;
|
|
|
|
// Dev/test cookie name: advanced.cookiePrefix is 'nachklang', and the __Secure-
|
|
// prefix is only added over https.
|
|
export const SESSION_COOKIE = 'nachklang.session_token';
|
|
|
|
/**
|
|
* Wipes every table between test files. Child tables first - the FKs to `user`
|
|
* are ON DELETE CASCADE, but the rest are not.
|
|
*
|
|
* `rateLimit` matters more than it looks: the limiter is enabled during the
|
|
* suite, and better-auth caps /sign-in/* at 3 requests per 10 seconds. All
|
|
* tests resolve to the same client IP, so they share one bucket - without this
|
|
* reset the suite would start failing with 429s that look like auth bugs as
|
|
* soon as a third sign-in assertion is added.
|
|
*/
|
|
export const resetDatabase = async (): Promise<void> => {
|
|
await db.deleteFrom('session').execute();
|
|
await db.deleteFrom('user_app_permissions').execute();
|
|
await db.deleteFrom('passkey').execute();
|
|
await db.deleteFrom('invitations').execute();
|
|
await db.deleteFrom('verification').execute();
|
|
await db.deleteFrom('rateLimit').execute();
|
|
await db.deleteFrom('user').execute();
|
|
};
|
|
|
|
export const closeDatabase = async (): Promise<void> => {
|
|
await db.destroy();
|
|
};
|
|
|
|
/**
|
|
* Creates an invitation straight through the service (so the test gets the raw
|
|
* token, which the API deliberately never returns) and redeems it through the
|
|
* public endpoint. Returns an agent that carries the resulting session cookie.
|
|
*/
|
|
export const createAndAcceptInvitation = async (
|
|
app: Application,
|
|
email: string,
|
|
name: string,
|
|
// Takes the shorthand as well as the full form: most tests only care that
|
|
// someone can open an app, and `['tickets']` says that with less noise.
|
|
grants: (AppName | AppPermission)[],
|
|
password = 'devpassword123'
|
|
) => {
|
|
const permissions = toPermissions(grants) ?? [];
|
|
const invitation = await InvitationsService.createInvitation(email, name, permissions, null);
|
|
|
|
const agent = request.agent(app);
|
|
const res = await agent
|
|
.post('/admin/auth/invitations/accept')
|
|
.send({token: invitation.token, password});
|
|
|
|
expect(res.status).toBe(200);
|
|
return {agent, userId: res.body.user.id, token: invitation.token};
|
|
};
|
|
|
|
export const cookieHeader = (res: request.Response): string[] => {
|
|
const raw = res.headers['set-cookie'];
|
|
return Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
};
|
|
|
|
export const sessionCookieFrom = (res: request.Response): string | undefined => {
|
|
return cookieHeader(res).find(cookie => cookie.startsWith(SESSION_COOKIE));
|
|
};
|
|
|
|
/** `accessTo('feedback')` reads better than the (app, role) literal in tests
|
|
* that only care that someone can open an app. */
|
|
export const accessTo = (...apps: AppName[]): AppPermission[] => {
|
|
return apps.map(app => ({app, role: ACCESS_ROLE}));
|
|
};
|