| … | … | @@ -0,0 +1,70 @@ |
| 1 | // upload-blob over the UDS accepts a PATH as well as hex bytes. |
| 2 | // |
| 3 | // favela-bridge (lib/bsky.js uploadBlob) hands hpp a picture BY PATH — both ends share one machine, and |
| 4 | // hex-encoding it doubles the transfer and needs a buffer library the client may not have. Until this |
| 5 | // handler took `path`, every such upload failed with "bytes (hex) required" and the bridge logged |
| 6 | // blob-upload-failed and posted without the image. |
| 7 | // |
| 8 | // The socket is 0775 fleeky:fleeky: only the daemon's own user can connect, so reading a path grants no |
| 9 | // access that caller does not already have. |
| 10 | // |
| 11 | // Run: bare test/upload-blob-path-uds.test.mjs |
| 12 | import { serveDeviceUds } from '../lib/serve-device-uds.js' |
| 13 | import { Client } from 'hyper-svc' |
| 14 | import b4a from 'b4a' |
| 15 | import fs from 'bare-fs' |
| 16 | import process from 'bare-process' |
| 17 | import { join } from 'bare-path' |
| 18 | |
| 19 | const assert = (cond, msg) => { if (!cond) { console.error('FAIL:', msg); process.exit(1) } console.log(' ok -', msg) } |
| 20 | const tmp = join(process.env.TMPDIR || '/tmp', 'hpp-upload-path-' + process.pid) |
| 21 | fs.mkdirSync(tmp, { recursive: true }) |
| 22 | |
| 23 | // The handler is only registered when a blob store is passed; a stub records exactly what it was given. |
| 24 | const stored = [] |
| 25 | const blobs = { |
| 26 | async put (buf, mimeType) { stored.push({ buf, mimeType }); return { cid: 'bafystub' + stored.length, size: buf.length } } |
| 27 | } |
| 28 | const device = { state: () => ({ ok: true }), membersDiag: async () => ({}), did: 'did:plc:test' } |
| 29 | |
| 30 | const sock = join(tmp, 'u.sock') |
| 31 | const served = await serveDeviceUds(device, { uds: sock, blobs }) |
| 32 | const c = await Client.connectUds(sock) |
| 33 | const rejects = async (params) => { try { await c.call('upload-blob', params); return null } catch (e) { return e.message } } |
| 34 | |
| 35 | console.log('# a path uploads the file\'s exact bytes') |
| 36 | { |
| 37 | const file = join(tmp, 'pic.png') |
| 38 | const bytes = b4a.from('not really a png, but exact bytes are the point') |
| 39 | fs.writeFileSync(file, bytes) |
| 40 | const r = await c.call('upload-blob', { path: file, mimeType: 'image/png' }) |
| 41 | assert(r && r.cid === 'bafystub1', 'upload-blob by path returns the store\'s CID') |
| 42 | assert(r.size === bytes.length && r.mimeType === 'image/png', 'size and mimeType are what was stored') |
| 43 | assert(b4a.equals(stored[0].buf, bytes), 'the store received the file\'s bytes, unchanged') |
| 44 | } |
| 45 | |
| 46 | console.log('\n# hex bytes still work — a remote client has no path to offer') |
| 47 | { |
| 48 | const bytes = b4a.from('bytes over the wire') |
| 49 | const r = await c.call('upload-blob', { bytes: b4a.toString(bytes, 'hex'), mimeType: 'image/jpeg' }) |
| 50 | assert(r && r.size === bytes.length, 'upload-blob by bytes still stores them') |
| 51 | assert(b4a.equals(stored[1].buf, bytes), 'and they decode exactly') |
| 52 | } |
| 53 | |
| 54 | console.log('\n# failures say what is wrong, and store nothing') |
| 55 | { |
| 56 | const before = stored.length |
| 57 | const missing = await rejects({ path: join(tmp, 'nope.png') }) |
| 58 | assert(missing && /no file at/.test(missing), `a missing path is named (${missing})`) |
| 59 | const empty = join(tmp, 'empty.png'); fs.writeFileSync(empty, b4a.alloc(0)) |
| 60 | const e2 = await rejects({ path: empty }) |
| 61 | assert(e2 && /empty/.test(e2), `an empty file is refused (${e2})`) |
| 62 | const e3 = await rejects({}) |
| 63 | assert(e3 && /bytes \(hex\) or path required/.test(e3), `neither bytes nor path is refused (${e3})`) |
| 64 | assert(stored.length === before, 'no failed call reached the store') |
| 65 | } |
| 66 | |
| 67 | try { await c.close() } catch {} |
| 68 | try { await served.close() } catch {} |
| 69 | fs.rmSync(tmp, { recursive: true, force: true }) |
| 70 | console.log('\nupload-blob path: all assertions passed') |