Files
bot-hub/server/og/skillOgSvg.test.ts
zeekay cacc1f4d92 rebrand clawhub → bothub + add self-hosted API backend
Migration from Convex/Vercel to self-hosted infrastructure:

API Backend (api/):
- Hono server with PostgreSQL (Drizzle ORM) + pgvector
- Skills CRUD, search (hybrid vector + lexical), auth (hanzo.id OAuth)
- Upload (MinIO S3), stars, comments, API tokens
- Rate limiting middleware, session management

Frontend Adapter (src/lib/):
- api.ts: REST API client replacing Convex SDK
- hooks.ts: React hooks replacing convex/react useQuery/useMutation
- AuthContext.tsx: Auth provider replacing ConvexAuthProvider

Deployment:
- Dockerfile (multi-stage: API build + web build + production)
- K8s manifests for hub.hanzo.bot on hanzo-k8s
- GitHub Actions CI/CD (build → GHCR → deploy)

Rebrand:
- clawhub/clawdhub/ClawHub → bothub/Bot Hub
- Domain: clawhub.ai → hub.hanzo.bot
- CLI: clawhub → bothub
- Package: @hanzo/bot-hub-api
2026-02-16 19:36:19 -08:00

60 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { buildSkillOgSvg } from './skillOgSvg'
describe('skill OG SVG', () => {
it('includes title, description, and labels', () => {
const svg = buildSkillOgSvg({
markDataUrl: 'data:image/png;base64,AAA=',
title: 'Discord Doctor',
description: 'Quick diagnosis and repair for Discord bot.',
ownerLabel: '@jhillock',
versionLabel: 'v1.2.3',
footer: 'hub.hanzo.bot/jhillock/discord-doctor',
})
expect(svg).toContain('Discord Doctor')
expect(svg).toContain('Quick diagnosis and repair')
expect(svg).toContain('@jhillock')
expect(svg).toContain('v1.2.3')
expect(svg).toContain('hub.hanzo.bot/jhillock/discord-doctor')
})
it('wraps long titles to avoid clipping', () => {
const svg = buildSkillOgSvg({
markDataUrl: 'data:image/png;base64,AAA=',
title: 'Excalidraw Flowchart',
description: 'Create Excalidraw flowcharts from descriptions.',
ownerLabel: '@swiftlysisngh',
versionLabel: 'v1.0.2',
footer: 'hub.hanzo.bot/swiftlysisngh/excalidraw-flowchart',
})
const titleBlock = svg.match(/<text[^>]*font-weight="800"[\s\S]*?<\/text>/)?.[0] ?? ''
const titleTspans = titleBlock.match(/<tspan /g) ?? []
expect(titleTspans.length).toBe(2)
expect(svg).toContain('Excalidraw')
expect(svg).toContain('Flowchart')
})
it('clips and wraps long descriptions', () => {
const longWord = 'a'.repeat(200)
const svg = buildSkillOgSvg({
markDataUrl: 'data:image/png;base64,AAA=',
title: 'Gurkerlcli',
description: `Prefix ${longWord} suffix`,
ownerLabel: '@pasogott',
versionLabel: 'v0.1.0',
footer: 'hub.hanzo.bot/pasogott/gurkerlcli',
})
expect(svg).toContain('<clipPath id="cardClip">')
expect(svg).toContain('clip-path="url(#cardClip)"')
expect(svg).not.toContain(longWord)
expect(svg).toContain('…')
const descBlock = svg.match(/<text[^>]*font-size="26"[\s\S]*?<\/text>/)?.[0] ?? ''
const descTspans = descBlock.match(/<tspan /g) ?? []
expect(descTspans.length).toBeLessThanOrEqual(3)
})
})