150 lines
5.9 KiB
TypeScript
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);
|
|
});
|
|
});
|