cowrite-dev / test /prod.js
lvwerra's picture
lvwerra HF Staff
Upload folder using huggingface_hub
b1c90fd verified
Raw
History Blame Contribute Delete
8.13 kB
// Production check against the deployed Space. Uses the real HF token.
// PROD_URL=https://lvwerra-interactive-editor.hf.space HF_TOKEN=hf_xxx node test/prod.js
import assert from 'node:assert'
import * as Y from 'yjs'
import { HocuspocusProvider, HocuspocusProviderWebsocket } from '@hocuspocus/provider'
import WS from 'ws'
const BASE = process.env.PROD_URL || 'https://lvwerra-interactive-editor.hf.space'
const TOKEN = process.env.HF_TOKEN
if (!TOKEN) throw new Error('HF_TOKEN required')
const HANDLE = 'prod-smoke-agent'
// The Space is private: the ws upgrade must carry the HF token for the proxy.
class AuthedWS extends WS {
constructor(url, protocols) {
super(url, protocols, { headers: { authorization: `Bearer ${TOKEN}` } })
}
}
const api = (path, { method = 'GET', body } = {}) =>
fetch(BASE + path, {
method,
headers: { authorization: `Bearer ${TOKEN}`, ...(body ? { 'content-type': 'application/json' } : {}) },
body: body ? JSON.stringify(body) : undefined,
}).then(r => r.json())
async function waitFor(fn, what, ms = 20000) {
const t0 = Date.now()
while (Date.now() - t0 < ms) {
try {
if (await fn()) return
} catch {}
await new Promise(r => setTimeout(r, 250))
}
throw new Error(`timeout waiting for ${what}`)
}
async function main() {
const me = await api('/api/me')
assert.ok(me.user?.username, 'authenticated: ' + JSON.stringify(me))
console.log('✓ authenticated as', me.user.username)
const { id: docId } = await api('/api/docs', { method: 'POST', body: { title: 'Prod Smoke' } })
assert.ok(docId)
console.log('✓ doc created:', docId)
const doc = new Y.Doc()
const socket = new HocuspocusProviderWebsocket({
url: BASE.replace('https', 'wss') + '/collab',
WebSocketPolyfill: AuthedWS,
})
const provider = new HocuspocusProvider({ websocketProvider: socket, name: docId, document: doc, token: TOKEN })
provider.attach()
const frag = doc.getXmlFragment('default')
await waitFor(() => frag.length >= 2, 'ws sync')
console.log('✓ websocket collab sync through the Space proxy')
const reg = await api('/api/agents', { method: 'POST', body: { handle: HANDLE } })
assert.equal(reg.ok, true, JSON.stringify(reg))
assert.ok(reg.key?.startsWith('ak_'), 'agent key issued')
const AGENT = reg.key
const agentApi = (path, opts = {}) =>
fetch(BASE + path, {
method: opts.method || 'GET',
headers: { authorization: `Bearer ${AGENT}`, ...(opts.body ? { 'content-type': 'application/json' } : {}) },
body: opts.body ? JSON.stringify(opts.body) : undefined,
}).then(r => r.json())
const threadId = 'prodth' + Math.random().toString(36).slice(2, 8)
doc.transact(() => {
const messages = new Y.Array()
messages.push([{ id: 'pm1', author: me.user.username, authorType: 'user', text: `@${HANDLE} check this`, ts: Date.now() }])
const t = new Y.Map()
t.set('id', threadId)
t.set('anchorStart', b64(Y.createRelativePositionFromTypeIndex(frag, 1)))
t.set('anchorEnd', b64(Y.createRelativePositionFromTypeIndex(frag, 2)))
t.set('excerpt', 'prod excerpt')
t.set('resolved', false)
t.set('createdBy', me.user.username)
t.set('createdAt', Date.now())
t.set('messages', messages)
doc.getMap('threads').set(threadId, t)
})
const poll = await (async () => {
const resp = await fetch(`${BASE}/api/mentions/stream?wait=15`, { headers: { authorization: `Bearer ${AGENT}` } })
let text = ''
for await (const chunk of resp.body) text += Buffer.from(chunk).toString()
return JSON.parse(text.trim().split('\n').filter(l => !l.startsWith(':')).pop())
})()
assert.equal(poll.mentions?.length, 1, JSON.stringify(poll))
console.log('✓ mention delivered via long-poll')
const snap = await api(`/api/docs/${docId}`)
assert.ok(snap.blocks?.length >= 2)
const sugg = await agentApi(`/api/docs/${docId}/suggestions`, {
method: 'POST',
body: {
block_index: 1,
replacement_markdown: 'Rewritten by the **prod** smoke agent.',
rationale: 'prod test',
mention_id: poll.mentions[0].mention_id,
thread_id: threadId,
},
})
assert.equal(sugg.ok, true, JSON.stringify(sugg))
const reply = await agentApi(`/api/docs/${docId}/threads/${threadId}/reply`, {
method: 'POST',
body: { text: 'done — see suggestion', mention_id: poll.mentions[0].mention_id },
})
assert.equal(reply.ok, true)
console.log('✓ agent reply + suggestion')
await waitFor(() => {
const msgs = doc.getMap('threads').get(threadId)?.get('messages')?.toArray() || []
return msgs[0]?.mentions?.[0]?.status === 'done' && msgs.some(m => m.authorType === 'agent')
}, 'chip done + agent msg synced to client')
console.log('✓ status chip ✅ and agent reply visible in live doc')
const acc = await api(`/api/docs/${docId}/suggestions/${sugg.suggestion_id}/accept`, { method: 'POST' })
assert.equal(acc.ok, true, JSON.stringify(acc))
await waitFor(async () => (await api(`/api/docs/${docId}`)).markdown.includes('prod'), 'accept applied')
console.log('✓ suggestion accepted and applied')
// agent adds a figure: upload bytes, reference in a suggestion, accept
const pngBytes = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAF0lEQVR4nGP8z8Dwn4EIwESMolGFtFEIAK5+AxGmizXcAAAAAElFTkSuQmCC',
'base64'
)
const up = await fetch(`${BASE}/api/docs/${docId}/upload`, {
method: 'POST',
headers: { authorization: `Bearer ${AGENT}`, 'content-type': 'image/png' },
body: pngBytes,
}).then(r => r.json())
assert.ok(up.url?.startsWith('/files/'), 'agent upload: ' + JSON.stringify(up))
const figSugg = await agentApi(`/api/docs/${docId}/suggestions`, {
method: 'POST',
body: {
block_index: 1,
replacement_markdown: `A paragraph with a figure.\n\n![test figure](${up.url})`,
},
})
assert.equal(figSugg.ok, true, JSON.stringify(figSugg))
const figAcc = await api(`/api/docs/${docId}/suggestions/${figSugg.suggestion_id}/accept`, { method: 'POST' })
assert.equal(figAcc.ok, true, JSON.stringify(figAcc))
await waitFor(async () => (await api(`/api/docs/${docId}`)).markdown.includes('![test figure]'), 'figure in doc')
const fig = await fetch(`${BASE}${up.url}`, { headers: { authorization: `Bearer ${TOKEN}` } })
assert.equal(fig.status, 200, 'figure served')
console.log('✓ agent figure: upload -> suggestion -> accept -> served')
// streaming long-poll must survive the proxy well past 60s (heartbeats)
console.log('… streaming long-poll test (~70s: mention arrives 65s into the stream)')
const streamP = (async () => {
const resp = await fetch(`${BASE}/api/mentions/stream?wait=90`, { headers: { authorization: `Bearer ${AGENT}` } })
let text = ''
for await (const chunk of resp.body) text += Buffer.from(chunk).toString()
return text
})()
await new Promise(r => setTimeout(r, 65000))
doc.transact(() => {
doc.getMap('threads').get(threadId).get('messages').push([
{ id: 'pm-stream', author: me.user.username, authorType: 'user', text: `@${HANDLE} still there?`, ts: Date.now() },
])
})
const sText = await streamP
const sLast = sText.trim().split('\n').filter(l => !l.startsWith(':')).pop()
const sRes = JSON.parse(sLast)
assert.ok(sRes.mentions?.length >= 1, 'stream delivered after 65s: ' + String(sLast).slice(0, 200))
assert.ok(sText.split(':hb').length > 2, 'heartbeats flowed through the proxy')
console.log('✓ streaming long-poll: mention delivered 65s in, through the Space proxy')
provider.destroy()
socket.destroy()
try {
await api(`/api/docs/${docId}`, { method: 'DELETE' })
await api(`/api/agents/${HANDLE}`, { method: 'DELETE' })
console.log('✓ cleanup (doc + handle deleted)')
} catch {
console.log('~ cleanup skipped (delete endpoint not in running image yet)')
}
console.log('\nPROD CHECK PASSED')
process.exit(0)
}
function b64(rel) {
return Buffer.from(Y.encodeRelativePosition(rel)).toString('base64url')
}
main().catch(err => {
console.error('PROD CHECK FAILED:', err)
process.exit(1)
})