realtimeReceiptRoutes.test.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536
  1. // @vitest-environment node
  2. import { afterEach, expect, it } from 'vitest'
  3. import { mkdtempSync, mkdirSync, rmSync } from 'node:fs'
  4. import { tmpdir } from 'node:os'
  5. import { join } from 'node:path'
  6. import { createServer, type Server } from 'node:http'
  7. import request from 'supertest'
  8. import { createApplication, type ApplicationRuntime } from '../../src/server/app'
  9. import type { RealtimeReceipts } from '../../src/server/realtimeReceipts'
  10. let runtime: ApplicationRuntime | undefined, home: string, server: Server | undefined
  11. afterEach(async () => { runtime?.close(); if (server) { server.closeAllConnections(); await new Promise<void>(done => server!.close(() => done())) }
  12. if (home) rmSync(home, { recursive: true, force: true }) })
  13. it('recovers encoded mobile receipt IDs while preserving authentication and strict ID validation', async () => {
  14. home = mkdtempSync(join(tmpdir(), 'yaoyao-receipt-route-'))
  15. for (const name of ['media', 'attachments', 'images']) mkdirSync(join(home, name))
  16. runtime = createApplication({ deferBackground: true, fetchImpl: async () => { throw new Error('No upstream network in this test') },
  17. config: { host: '127.0.0.1', port: 15300, upstream: new URL('http://127.0.0.1:1'), home, allowedHosts: new Set(),
  18. mediaRoot: join(home, 'media'), attachmentsRoot: join(home, 'attachments'), imagesRoot: join(home, 'images'),
  19. mediaOwner: 'fixture', production: false, allowInsecureLan: false, insecureLan: false } })
  20. server = createServer(runtime.app.callback()); await new Promise<void>(done => server!.listen(0, '127.0.0.1', done))
  21. const agent = request.agent(server), headers = { Host: '127.0.0.1:15300', Origin: 'http://127.0.0.1:15300' }
  22. const bootstrap = await agent.get('/api/app/bootstrap?inspectOnly=1').set(headers).expect(200)
  23. const setup = await agent.post('/api/app/setup').set(headers).set('X-CSRF-Token', bootstrap.body.csrfToken)
  24. .send({ username: 'receipt-fixture', password: 'fixture-password' }).expect(200)
  25. const id = 'ios:attachment:request:file', owner = `user:${setup.body.user.id}:${runtime.auth.pushAuthorizationVersion(setup.body.user.id)}`
  26. const receipts = (runtime.realtime.broker as unknown as { receipts: RealtimeReceipts }).receipts
  27. receipts.reserve(owner, id, '{}'); receipts.finish(owner, id, 'confirmed', { result: { ref_text: '@file:/fixture/file' } }, { method: 'file.attach' })
  28. for (const path of [id, encodeURIComponent(id)]) {
  29. const result = await agent.get(`/api/realtime/commands/${path}`).set(headers).expect(200)
  30. expect(result.body).toMatchObject({ requestId: id, state: 'confirmed', response: { result: { ref_text: '@file:/fixture/file' } } })
  31. }
  32. await request(server).get(`/api/realtime/commands/${encodeURIComponent(id)}`).set(headers).expect(401)
  33. for (const invalid of ['ios%2Fforeign', '%ZZ', 'bad%20id'])
  34. await agent.get(`/api/realtime/commands/${invalid}`).set(headers).expect(400)
  35. })