test(mobile): check posterless video previews renew the relay assertion (#9144)

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 <tomb@squareup.com>
Co-authored-by: Tom Brow <tomb@squareup.com>
This commit is contained in:
Tom Brow
2026-11-05 16:51:20 -08:00
committed by GitHub
parent fba8a7a94f
commit fc85b0d154

View File

@@ -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 = <Map<String, String>>[];
@override
Future<void> init() async {}
@override
Future<void> setMixWithOthers(bool mixWithOthers) async {}
@override
Future<int?> createWithOptions(VideoCreationOptions options) async {
headers.add(options.dataSource.httpHeaders);
return headers.length;
}
@override
Stream<VideoEvent> videoEventsFor(int playerId) => Stream.error(
PlatformException(code: 'VideoError', message: 'Fake init failure'),
);
@override
Future<void> dispose(int playerId) async {}
}