aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-29 08:33:28 -0700
committerAdam Soltys <[email protected]>2026-09-29 08:33:28 -0700
commitbdb8c3878df0439e3ebbcede5e2555f949a0cd7a (patch)
treef3f4632643dfcbc5cd6d4cf4906b1df565449e9b /tools
parentb9c04f573567d69d94fb120aa8feed89cf75585c (diff)
downloadcoinosv3-bdb8c3878df0439e3ebbcede5e2555f949a0cd7a.tar.gz
coinosv3-bdb8c3878df0439e3ebbcede5e2555f949a0cd7a.zip
Fix public media uploads and feed tab navigation
Diffstat (limited to 'tools')
-rw-r--r--tools/feed-nav-destination-test.js18
-rw-r--r--tools/media-upload-test.js43
2 files changed, 61 insertions, 0 deletions
diff --git a/tools/feed-nav-destination-test.js b/tools/feed-nav-destination-test.js
new file mode 100644
index 0000000..b14a124
--- /dev/null
+++ b/tools/feed-nav-destination-test.js
@@ -0,0 +1,18 @@
+// The Feed destination and the new-posts pill deliberately have different
+// navigation semantics. Keep this small source-level guard beside the fuller
+// browser test for the pill's landing position.
+import assert from 'node:assert/strict';
+
+const source = await Bun.file('src/features/messages.js').text();
+const navStart = source.indexOf("button('feed', t('feedTitle')");
+const navEnd = source.indexOf("button('messages',", navStart);
+const feedButton = source.slice(navStart, navEnd);
+assert(navStart > 0 && navEnd > navStart, 'Feed bottom-nav handler exists');
+assert(feedButton.includes('glideToTop()'), 'Feed bottom-nav button returns to the top');
+assert(!feedButton.includes('jumpToNew()'), 'Feed bottom-nav button does not jump to unread posts');
+
+const pillStart = source.indexOf("class: 'feed-new-pill'");
+const pill = source.slice(pillStart, source.indexOf('})', pillStart) + 2);
+assert(pillStart > 0 && pill.includes('jumpToNew()'), 'new-posts pill jumps to the first unread arrival');
+
+console.log('✓ Feed tab goes to the top; only the new-posts pill jumps to unread posts');
diff --git a/tools/media-upload-test.js b/tools/media-upload-test.js
new file mode 100644
index 0000000..f9cc56a
--- /dev/null
+++ b/tools/media-upload-test.js
@@ -0,0 +1,43 @@
+import { uploadPublicMedia, MediaUploadError } from '../src/media-upload.js';
+import { sha256 } from '@noble/hashes/sha256';
+import { bytesToHex } from '@noble/hashes/utils';
+
+let ok = true;
+const check = (name, pass) => { console.log(`${pass ? '✓' : '✗'} ${name}`); if (!pass) ok = false; };
+const bytes = new TextEncoder().encode('phone video bytes');
+const file = new File([bytes], 'clip.mov', { type: 'video/quicktime' });
+let signed = null;
+const sign = async (event) => (signed = { ...event, id: '1'.repeat(64), pubkey: '2'.repeat(64), sig: '3'.repeat(128) });
+const calls = [];
+const fetcher = async (url, init) => {
+ calls.push({ url, init });
+ if (url.startsWith('https://first')) return new Response(JSON.stringify({ message: 'temporarily unavailable' }), { status: 503 });
+ return new Response(JSON.stringify({ url: 'https://cdn.example/clip.mov' }), { status: 201, headers: { 'content-type': 'application/json' } });
+};
+
+const url = await uploadPublicMedia(file, sign, { servers: ['https://first', 'https://second/'], fetcher, timeoutMs: 1000 });
+check('falls back to a second independent media host', url === 'https://cdn.example/clip.mov' && calls.length === 2);
+check('uploads the original video type and bytes', calls[1].init.method === 'PUT' && calls[1].init.body === file
+ && calls[1].init.headers['content-type'] === 'video/quicktime');
+check('hashes the file and binds the hash into Blossom auth', calls[1].init.headers['x-sha-256'] === bytesToHex(sha256(bytes))
+ && signed.tags.some((tag) => tag[0] === 'x' && tag[1] === calls[1].init.headers['x-sha-256']));
+check('sends signed Blossom authorization', calls[1].init.headers.authorization.startsWith('Nostr '));
+
+const bareHashUrl = await uploadPublicMedia(file, sign, {
+ servers: ['https://media.example'], timeoutMs: 1000,
+ fetcher: async () => new Response(JSON.stringify({ url: '/' + 'a'.repeat(64) }), { status: 201 }),
+});
+check('adds the video extension when a Blossom server returns a bare hash URL', bareHashUrl.endsWith('.mov'));
+
+try {
+ await uploadPublicMedia(file, sign, {
+ servers: ['https://one', 'https://two'], timeoutMs: 1000,
+ fetcher: async () => new Response('', { status: 413 }),
+ });
+ check('reports provider size rejection', false);
+} catch (error) {
+ check('reports provider size rejection', error instanceof MediaUploadError && error.code === 'too-big');
+}
+
+console.log(ok ? '\n✅ public media upload works' : '\n❌ public media upload failed');
+process.exit(ok ? 0 : 1);