Files
wursor/api/__tests__/routes/sites-pair.test.ts
T
SinachPat 9801b9475b
Mirror to GitHub / mirror (push) Canceled after 0s
feat: Sprint 2 — Wursor-side pairing flow + signed plugin client
- session store + requireSession middleware (signup persists session)
- PairingService (8-char code, 5-min TTL, 5-attempt lockout, single-use, https check)
- POST /sites/pair, /sites/redeem, /sites/:id/confirm, GET /sites/:id
- PluginClient signs HMAC (timestamp+method+path+body-hash), token in Authorization
- ADR 0016; 58 api tests green
2026-08-15 23:10:19 +01:00

93 lines
2.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import type { FastifyInstance } from 'fastify';
import { buildApp } from '../../src/app.ts';
async function signup(app: FastifyInstance): Promise<string> {
const res = await app.inject({
method: 'POST',
url: '/auth/signup',
payload: { email: `a-${Date.now()}@example.com`, password: 'password123' },
});
return res.json().sessionToken as string;
}
describe('sites pairing flow', () => {
it('requires a session to pair', async () => {
const app = await buildApp();
const res = await app.inject({ method: 'POST', url: '/sites/pair' });
expect(res.statusCode).toBe(401);
});
it('redeems a code once, binds site_url, and returns tokens', async () => {
const app = await buildApp();
const token = await signup(app);
const pair = await app.inject({
method: 'POST',
url: '/sites/pair',
headers: { authorization: `Bearer ${token}` },
});
expect(pair.statusCode).toBe(201);
const { code } = pair.json();
const redeem = await app.inject({
method: 'POST',
url: '/sites/redeem',
payload: { code, siteUrl: 'https://example.com' },
});
expect(redeem.statusCode).toBe(201);
const body = redeem.json();
expect(body.siteId).toBeTruthy();
expect(body.readToken).toBeTruthy();
expect(body.deployToken).toBeTruthy();
expect(body.hmacSecret).toBeTruthy();
const second = await app.inject({
method: 'POST',
url: '/sites/redeem',
payload: { code, siteUrl: 'https://example.com' },
});
expect(second.statusCode).toBe(400);
});
it('does not mark the site connected until confirmed', async () => {
const app = await buildApp();
const token = await signup(app);
const pair = await app.inject({
method: 'POST',
url: '/sites/pair',
headers: { authorization: `Bearer ${token}` },
});
const { code } = pair.json();
const redeem = await app.inject({
method: 'POST',
url: '/sites/redeem',
payload: { code, siteUrl: 'https://example.com' },
});
const { siteId } = redeem.json();
const before = await app.inject({
method: 'GET',
url: `/sites/${siteId}`,
headers: { authorization: `Bearer ${token}` },
});
expect(before.json().connected).toBe(false);
const confirm = await app.inject({
method: 'POST',
url: `/sites/${siteId}/confirm`,
headers: { authorization: `Bearer ${token}` },
});
expect(confirm.statusCode).toBe(200);
const after = await app.inject({
method: 'GET',
url: `/sites/${siteId}`,
headers: { authorization: `Bearer ${token}` },
});
expect(after.json().connected).toBe(true);
});
});