test: add auth routes test suite with 13 cases

Tests check-name validation, login pubkey validation, register name/pubkey
validation, register-human validation, NIP-98 session (valid + expired).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-12 23:43:57 +00:00
co-authored by Claude Opus 4.6
parent 6144fa7910
commit 34a83fb0fc
+159
View File
@@ -0,0 +1,159 @@
import { describe, it, expect, vi } from 'vitest'
import { Hono } from 'hono'
import { authRouter } from './auth.js'
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools'
const app = new Hono()
app.route('/api/auth', authRouter)
function makeNip98Header(sk: Uint8Array, url: string, method: string) {
const event = finalizeEvent({
kind: 27235,
created_at: Math.floor(Date.now() / 1000),
tags: [['u', url], ['method', method]],
content: '',
}, sk)
return `Nostr ${Buffer.from(JSON.stringify(event)).toString('base64')}`
}
describe('auth routes', () => {
// --- check-name ---
it('check-name: rejects short name', async () => {
const res = await app.request('/api/auth/check-name/x')
const body = await res.json() as { available: boolean; error?: string }
expect(body.available).toBe(false)
expect(body.error).toContain('2-12')
})
it('check-name: rejects long name (>12)', async () => {
const res = await app.request('/api/auth/check-name/verylongbotnamehere')
const body = await res.json() as { available: boolean; error?: string }
expect(body.available).toBe(false)
})
it('check-name: available name returns true', async () => {
const name = `t${Date.now().toString(36).slice(-6)}`
const res = await app.request(`/api/auth/check-name/${name}`)
const body = await res.json() as { available: boolean }
expect(body.available).toBe(true)
})
// --- login ---
it('login: rejects invalid pubkey (too short)', async () => {
const res = await app.request('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pubkey: 'tooshort' }),
})
expect(res.status).toBe(400)
const body = await res.json() as { error: string }
expect(body.error).toContain('Invalid pubkey')
})
it('login: rejects missing pubkey', async () => {
const res = await app.request('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
})
expect(res.status).toBe(400)
})
it('login: returns exists=false for unknown pubkey', async () => {
const pk = '0'.repeat(64)
const res = await app.request('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pubkey: pk }),
})
expect(res.status).toBe(200)
const body = await res.json() as { exists: boolean; pubkey?: string }
expect(body.exists).toBe(false)
expect(body.pubkey).toBe(pk)
})
// --- register ---
it('register: rejects invalid pubkey', async () => {
const res = await app.request('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pubkey: 'bad', name: 'test-bot' }),
})
expect(res.status).toBe(400)
const body = await res.json() as { error: string }
expect(body.error).toContain('Invalid pubkey')
})
it('register: rejects invalid name (special chars)', async () => {
const res = await app.request('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pubkey: 'a'.repeat(64), name: 'hello world!' }),
})
expect(res.status).toBe(400)
const body = await res.json() as { error: string }
expect(body.error).toContain('alphanumeric')
})
it('register: rejects name too short', async () => {
const res = await app.request('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pubkey: 'b'.repeat(64), name: 'x' }),
})
expect(res.status).toBe(400)
const body = await res.json() as { error: string }
expect(body.error).toContain('2-12')
})
// --- register-human ---
it('register-human: rejects invalid pubkey', async () => {
const res = await app.request('/api/auth/register-human', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pubkey: 'short', name: 'human1' }),
})
expect(res.status).toBe(400)
})
// --- nostr/session ---
it('nostr/session: rejects missing auth header', async () => {
const res = await app.request('/api/auth/nostr/session', {
method: 'POST',
})
expect(res.status).toBe(401)
})
it('nostr/session: accepts valid NIP-98 token', async () => {
const sk = generateSecretKey()
const pk = getPublicKey(sk)
const header = makeNip98Header(sk, 'https://localhost/api/auth/nostr/session', 'POST')
const res = await app.request('/api/auth/nostr/session', {
method: 'POST',
headers: { Authorization: header },
})
expect(res.status).toBe(200)
const body = await res.json() as { token: string; pubkey: string; exists: boolean }
expect(body.token).toBeDefined()
expect(body.pubkey).toBe(pk)
expect(body.exists).toBe(false)
})
it('nostr/session: rejects expired NIP-98 token', async () => {
const sk = generateSecretKey()
const event = finalizeEvent({
kind: 27235,
created_at: Math.floor(Date.now() / 1000) - 300,
tags: [['u', 'https://localhost/api/auth/nostr/session'], ['method', 'POST']],
content: '',
}, sk)
const header = `Nostr ${Buffer.from(JSON.stringify(event)).toString('base64')}`
const res = await app.request('/api/auth/nostr/session', {
method: 'POST',
headers: { Authorization: header },
})
expect(res.status).toBe(401)
})
})