From fc85b0d1547d29a389a9eb9fdf8abcf933a239e8 Mon Sep 17 00:00:00 2001 From: Tom Brow Date: Thu, 5 Nov 2026 16:51:20 -0800 Subject: [PATCH] test(mobile): check posterless video previews renew the relay assertion (#9144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to https://github.com/block/buzz/pull/9134, which made posterless video previews and voice notes renew an expired relay assertion before handing headers to the native player. The voice-note path got a test there, but nothing failed if the video preview went back to the cached-only headers. This extends the expired-assertion journey in `native_media_headers_test` with one step: the real `videoPreviewFrameLoaderProvider` loads a relay video through a fake native video player, which records the headers it is created with. The player must receive both the media `Authorization` proof and the renewed `Nostr-Federated-Identity`. ### Validation - Added tests: the video-preview step above. Switching the loader back to the synchronous cached headers fails it. - `flutter analyze` is clean and the full mobile suite passes (4508 tests). - Test-only change, so no app run. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow Co-authored-by: Tom Brow --- .../hosted/native_media_headers_test.dart | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/mobile/test/shared/hosted/native_media_headers_test.dart b/mobile/test/shared/hosted/native_media_headers_test.dart index ad88133c01..b864c90ab6 100644 --- a/mobile/test/shared/hosted/native_media_headers_test.dart +++ b/mobile/test/shared/hosted/native_media_headers_test.dart @@ -1,10 +1,13 @@ +import 'package:buzz/features/channels/message_content.dart'; import 'package:buzz/shared/hosted/builderlab_account.dart'; import 'package:buzz/shared/hosted/hosted_relay_auth.dart'; import 'package:buzz/shared/hosted/native_media_headers.dart'; import 'package:buzz/shared/relay/relay.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nostr/nostr.dart' as nostr; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; final _nsec = nostr.Keys.generate().nsec; @@ -55,6 +58,7 @@ ProviderContainer _container(HostedRelayAuth hosted) => ProviderContainer( ); void main() { + TestWidgetsFlutterBinding.ensureInitialized(); test('native media headers add the relay assertion only for relay media', () { final hosted = _CachedHostedRelayAuth(); addTearDown(hosted.dispose); @@ -104,6 +108,24 @@ void main() { expect(headers['Nostr-Federated-Identity'], 'Bearer fresh-token'); expect(hosted.requested, [Uri.parse(url)]); + // A posterless video's first-frame preview hands the native player the + // renewed assertion too. + const videoUrl = 'https://relay.example/media/clip.mp4'; + final player = _HeaderRecordingVideoPlayer(); + final previousPlayer = VideoPlayerPlatform.instance; + VideoPlayerPlatform.instance = player; + addTearDown(() => VideoPlayerPlatform.instance = previousPlayer); + expect( + await container.read(videoPreviewFrameLoaderProvider)(videoUrl), + isNull, + ); + expect(player.headers.single['Authorization'], startsWith('Nostr ')); + expect( + player.headers.single['Nostr-Federated-Identity'], + 'Bearer fresh-token', + ); + expect(hosted.requested.last, Uri.parse(videoUrl)); + // A failed renewal still sends the media proof, and the relay decides. final failing = _ExpiredHostedRelayAuth(fail: true); addTearDown(failing.dispose); @@ -116,3 +138,29 @@ void main() { expect(fallback, isNot(contains('Nostr-Federated-Identity'))); }); } + +/// Records the headers each native player is created with, then fails its +/// initialization so the preview loader gives up. +class _HeaderRecordingVideoPlayer extends VideoPlayerPlatform { + final headers = >[]; + + @override + Future init() async {} + + @override + Future setMixWithOthers(bool mixWithOthers) async {} + + @override + Future createWithOptions(VideoCreationOptions options) async { + headers.add(options.dataSource.httpHeaders); + return headers.length; + } + + @override + Stream videoEventsFor(int playerId) => Stream.error( + PlatformException(code: 'VideoError', message: 'Fake init failure'), + ); + + @override + Future dispose(int playerId) async {} +}