test: add opt-in e2e suite

This commit is contained in:
Peter Steinberger 2026-01-04 07:28:47 +01:00
parent 5d96853cff
commit de907c9dbe
5 changed files with 146 additions and 2 deletions

122
e2e/clawdhub.e2e.test.ts Normal file
View File

@ -0,0 +1,122 @@
/* @vitest-environment node */
import { spawnSync } from 'node:child_process'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
ApiCliWhoamiResponseSchema,
ApiRoutes,
ApiSearchResponseSchema,
parseArk,
} from '@clawdhub/schema'
import { describe, expect, it } from 'vitest'
import { readGlobalConfig } from '../packages/clawdhub/src/config'
function mustGetToken() {
const fromEnv = process.env.CLAWDHUB_E2E_TOKEN?.trim()
if (fromEnv) return fromEnv
return null
}
async function makeTempConfig(registry: string, token: string | null) {
const dir = await mkdtemp(join(tmpdir(), 'clawdhub-e2e-'))
const path = join(dir, 'config.json')
await writeFile(
path,
`${JSON.stringify({ registry, token: token || undefined }, null, 2)}\n`,
'utf8',
)
return { dir, path }
}
describe('clawdhub e2e', () => {
it('search endpoint returns a results array (schema parse)', async () => {
const registry = process.env.CLAWDHUB_REGISTRY?.trim() || 'https://clawdhub.com'
const url = new URL(ApiRoutes.search, registry)
url.searchParams.set('q', 'gif')
url.searchParams.set('limit', '5')
const response = await fetch(url.toString(), { headers: { Accept: 'application/json' } })
expect(response.ok).toBe(true)
const json = (await response.json()) as unknown
const parsed = parseArk(ApiSearchResponseSchema, json, 'API response')
expect(Array.isArray(parsed.results)).toBe(true)
})
it('cli search does not error on multi-result responses', async () => {
const registry = process.env.CLAWDHUB_REGISTRY?.trim() || 'https://clawdhub.com'
const site = process.env.CLAWDHUB_SITE?.trim() || 'https://clawdhub.com'
const token = mustGetToken() ?? (await readGlobalConfig())?.token ?? null
const cfg = await makeTempConfig(registry, token)
try {
const workdir = await mkdtemp(join(tmpdir(), 'clawdhub-e2e-workdir-'))
const result = spawnSync(
'bun',
[
'clawdhub',
'search',
'gif',
'--limit',
'5',
'--site',
site,
'--registry',
registry,
'--workdir',
workdir,
],
{
cwd: process.cwd(),
env: { ...process.env, CLAWDHUB_CONFIG_PATH: cfg.path },
encoding: 'utf8',
},
)
await rm(workdir, { recursive: true, force: true })
expect(result.status).toBe(0)
expect(result.stderr).not.toMatch(/API response:/)
} finally {
await rm(cfg.dir, { recursive: true, force: true })
}
})
it('assumes a logged-in user (whoami succeeds)', async () => {
const registry = process.env.CLAWDHUB_REGISTRY?.trim() || 'https://clawdhub.com'
const site = process.env.CLAWDHUB_SITE?.trim() || 'https://clawdhub.com'
const token = mustGetToken() ?? (await readGlobalConfig())?.token ?? null
if (!token) {
throw new Error('Missing token. Set CLAWDHUB_E2E_TOKEN or run: bun clawdhub auth login')
}
const cfg = await makeTempConfig(registry, token)
try {
const whoamiUrl = new URL(ApiRoutes.cliWhoami, registry)
const whoamiRes = await fetch(whoamiUrl.toString(), {
headers: { Accept: 'application/json', Authorization: `Bearer ${token}` },
})
expect(whoamiRes.ok).toBe(true)
const whoami = parseArk(
ApiCliWhoamiResponseSchema,
(await whoamiRes.json()) as unknown,
'Whoami',
)
expect(whoami.user).toBeTruthy()
const result = spawnSync(
'bun',
['clawdhub', 'whoami', '--site', site, '--registry', registry],
{
cwd: process.cwd(),
env: { ...process.env, CLAWDHUB_CONFIG_PATH: cfg.path },
encoding: 'utf8',
},
)
expect(result.status).toBe(0)
expect(result.stderr).not.toMatch(/not logged in|unauthorized|error:/i)
} finally {
await rm(cfg.dir, { recursive: true, force: true })
}
})
})

View File

@ -11,6 +11,7 @@
"preview": "bun --bun vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "vitest run -c vitest.e2e.config.ts",
"coverage": "vitest run --coverage",
"convex:deploy": "bunx convex deploy --typecheck=disable --yes",
"lint": "bun run lint:biome && bun run lint:oxlint",

View File

@ -1,9 +1,11 @@
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
import { dirname, join, resolve } from 'node:path'
import { type GlobalConfig, GlobalConfigSchema, parseArk } from '@clawdhub/schema'
export function getGlobalConfigPath() {
const override = process.env.CLAWDHUB_CONFIG_PATH?.trim()
if (override) return resolve(override)
const home = homedir()
if (process.platform === 'darwin') {
return join(home, 'Library', 'Application Support', 'clawdhub', 'config.json')

View File

@ -5,7 +5,14 @@ export default defineConfig({
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
exclude: ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/convex/_generated/**'],
exclude: [
'**/node_modules/**',
'**/dist/**',
'**/coverage/**',
'**/convex/_generated/**',
'e2e/**',
'**/*.e2e.test.ts',
],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
@ -33,6 +40,7 @@ export default defineConfig({
'packages/clawdhub/src/config.ts',
'packages/clawdhub/src/types.ts',
'packages/schema/dist/',
'e2e/**',
],
},
},

11
vitest.e2e.config.ts Normal file
View File

@ -0,0 +1,11 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node',
include: ['e2e/**/*.e2e.test.ts'],
exclude: ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/convex/_generated/**'],
testTimeout: 60_000,
hookTimeout: 60_000,
},
})