hoppscotch-backend-git/tests/rest.axios.test.ts
tlemesle 0cc7276c23 Auto-generate secrets in per-user state directory
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 16:24:27 +02:00

150 lines
5.9 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import axios from 'axios';
import { createSampleCollection, createSampleEnvironment } from '../fixtures/sample.ts';
import { createApp } from '../src/app.ts';
import { loadConfig } from '../src/config.ts';
import { userFromOsIdentity } from '../src/os-user.ts';
import { settle } from '../src/settle.ts';
const jwtSecret = 'test-jwt-secret-16chars';
const testUser = await userFromOsIdentity({
recordName: 'rest-test',
email: 'rest-test@example.com',
displayName: 'REST Test',
});
describe('REST API (axios)', () => {
let gitRepoPath = '';
let metaPath = '';
let baseURL = '';
let server: ReturnType<typeof Bun.serve> | null = null;
let echoServer: ReturnType<typeof Bun.serve> | null = null;
let echoUrl = '';
beforeAll(async () => {
gitRepoPath = await mkdtemp(join(tmpdir(), 'hbg-git-'));
metaPath = join(await mkdtemp(join(tmpdir(), 'hbg-meta-')), 'meta.json');
echoServer = Bun.serve({
port: 0,
fetch: () => Response.json({ ok: true, method: 'GET' }),
});
echoUrl = `http://127.0.0.1:${echoServer.port}/`;
const config = await loadConfig({
PORT: '0',
GIT_REPO_PATH: gitRepoPath,
META_PATH: metaPath,
JWT_SECRET: jwtSecret,
WEBAPP_FRONTEND_PATH: join(import.meta.dir, '../fixtures/webapp-frontend'),
WEBAPP_SERVER_SIGNING_SECRET: 'test-webapp-signing-secret',
HOPP_STATE_DIR: await mkdtemp(join(tmpdir(), 'hbg-rest-state-')),
});
const app = await createApp(config, { localUser: testUser });
server = Bun.serve({
port: 0,
fetch: app.fetch,
});
baseURL = `http://127.0.0.1:${server.port}`;
});
afterAll(async () => {
server?.stop(true);
echoServer?.stop(true);
await rm(gitRepoPath, { recursive: true, force: true });
await rm(join(metaPath, '..'), { recursive: true, force: true });
});
test('CRUD collections and environments', async () => {
const collection = createSampleCollection(echoUrl);
const createRes = await axios.post(`${baseURL}/v1/collections`, {
id: collection.id,
collection,
});
expect(createRes.status).toBe(201);
expect(createRes.data.id).toBe(collection.id);
const listRes = await axios.get(`${baseURL}/v1/collections`);
expect(listRes.data).toHaveLength(1);
const getRes = await axios.get(`${baseURL}/v1/collections/${collection.id}`);
expect(getRes.data.name).toBe('Sample Echo');
const environment = createSampleEnvironment();
const envRes = await axios.post(`${baseURL}/v1/environments`, {
environment,
});
expect(envRes.status).toBe(201);
expect(envRes.data.id).toBe('sample-env');
const envGet = await axios.get(`${baseURL}/v1/environments/sample-env`);
expect(envGet.data.name).toBe('Sample');
});
test('PAT create and CLI access-token fetch shapes', async () => {
const patRes = await axios.post(
`${baseURL}/v1/access-tokens/create`,
{ label: 'cli', expiryInDays: 7 },
);
expect(patRes.data.token).toMatch(/^pat-/);
const token = patRes.data.token as string;
const collRes = await axios.get(
`${baseURL}/v1/access-tokens/collection/sample-echo`,
{ headers: { Authorization: `Bearer ${token}` } },
);
expect(collRes.data.title).toBe('Sample Echo');
expect(collRes.data.parentID).toBeNull();
expect(Array.isArray(collRes.data.folders)).toBe(true);
expect(Array.isArray(collRes.data.requests)).toBe(true);
expect(collRes.data.requests[0].request).toBeTypeOf('string');
expect(JSON.parse(collRes.data.requests[0].request).endpoint).toBe(echoUrl);
const envRes = await axios.get(
`${baseURL}/v1/access-tokens/environment/sample-env`,
{ headers: { Authorization: `Bearer ${token}` } },
);
expect(envRes.data.name).toBe('Sample');
expect(envRes.data.teamID).toBe('personal');
expect(envRes.data.variables[0].key).toBe('baseURL');
});
test('invalid PAT returns TOKEN_INVALID', async () => {
const { reason, value } = await settle(
axios.get(`${baseURL}/v1/access-tokens/collection/sample-echo`, {
headers: { Authorization: 'Bearer pat-does-not-exist' },
validateStatus: () => true,
}),
);
expect(reason).toBeUndefined();
expect(value?.status).toBe(400);
expect(value?.data.reason).toBe('TOKEN_INVALID');
});
test('desktop webapp endpoints serve signed bundle', async () => {
const keyRes = await axios.get(`${baseURL}/api/v1/key`);
expect(keyRes.data.success).toBe(true);
expect(keyRes.data.data.key).toBeTypeOf('string');
expect(Buffer.from(keyRes.data.data.key, 'base64').length).toBe(32);
const manifestRes = await axios.get(`${baseURL}/api/v1/manifest`);
expect(manifestRes.data.success).toBe(true);
expect(manifestRes.data.data.manifest.files.length).toBeGreaterThan(0);
expect(manifestRes.data.data.signature).toBeTypeOf('string');
const bundleRes = await axios.get(`${baseURL}/api/v1/bundle`, {
responseType: 'arraybuffer',
});
expect(bundleRes.status).toBe(200);
expect(bundleRes.headers['content-type']).toContain('application/zip');
expect(bundleRes.data.byteLength).toBeGreaterThan(0);
const compat = await axios.get(`${baseURL}/desktop-app-server/api/v1/key`);
expect(compat.data.data.key).toBe(keyRes.data.data.key);
});
});