fix(relay): secure reconnect and room ownership

This commit is contained in:
edde746
2026-07-24 03:46:50 +02:00
parent e0bf66eea8
commit 43a8fe020d
82 changed files with 12341 additions and 1382 deletions
+80
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/models/audio_quality_preset.dart';
@@ -226,6 +228,40 @@ void main() {
});
});
group('SettingsService Watch Together relay', () {
test('typed writes canonicalize valid bases and reject invalid replacement', () async {
final settings = await SettingsService.getInstance();
await settings.write(SettingsService.customRelayUrl, ' HTTPS://Relay.Example.Test/path/// ');
expect(settings.read(SettingsService.customRelayUrl), 'https://relay.example.test/path');
await expectLater(
settings.write(SettingsService.customRelayUrl, 'ws://relay.example.test'),
throwsFormatException,
);
expect(settings.read(SettingsService.customRelayUrl), 'https://relay.example.test/path');
await settings.write(SettingsService.customRelayUrl, ' ');
expect(settings.read(SettingsService.customRelayUrl), isNull);
});
test('startup canonicalizes valid history and removes invalid history', () async {
resetSharedPreferencesForTest(
initialAsync: {SettingsService.customRelayUrl.key: ' http://Relay.Example.Test:8080/prefix// '},
);
SettingsService.resetForTesting();
var settings = await SettingsService.getInstance();
expect(settings.read(SettingsService.customRelayUrl), 'http://relay.example.test:8080/prefix');
resetSharedPreferencesForTest(
initialAsync: {SettingsService.customRelayUrl.key: 'https://relay.example.test/path?wrong=route'},
);
SettingsService.resetForTesting();
settings = await SettingsService.getInstance();
expect(settings.read(SettingsService.customRelayUrl), isNull);
});
});
group('SettingsService listenables', () {
test('refreshListenables updates active prefs outside the resettable surface', () async {
final settings = await SettingsService.getInstance();
@@ -276,4 +312,48 @@ void main() {
expect(settings.isLibraryAllowedForTracker(TrackerService.trakt, 'server:allowed'), isTrue);
});
});
group('BaseSharedPreferencesService initialization generations', () {
test('a reset-raced initialization resolves to the replacement backend', () async {
resetSharedPreferencesForTest(initialAsync: const {'generation_marker': 'old'});
final firstOnInit = Completer<void>();
final firstStarted = Completer<void>();
var constructions = 0;
final raced = BaseSharedPreferencesService.initializeInstance<_GenerationTestPreferences>(() {
final construction = constructions++;
return _GenerationTestPreferences(
construction,
onInitStarted: construction == 0 ? firstStarted : null,
onInitGate: construction == 0 ? firstOnInit : null,
);
});
await firstStarted.future;
resetSharedPreferencesForTest(initialAsync: const {'generation_marker': 'new'});
final replacement = await BaseSharedPreferencesService.initializeInstance<_GenerationTestPreferences>(
() => _GenerationTestPreferences(constructions++),
);
firstOnInit.complete();
final recovered = await raced;
expect(recovered, same(replacement));
expect(recovered.construction, 1);
expect(recovered.readString('generation_marker'), 'new');
expect(constructions, 2);
});
});
}
class _GenerationTestPreferences extends BaseSharedPreferencesService {
_GenerationTestPreferences(this.construction, {this.onInitStarted, this.onInitGate});
final int construction;
final Completer<void>? onInitStarted;
final Completer<void>? onInitGate;
@override
Future<void> onInit() async {
onInitStarted?.complete();
await onInitGate?.future;
}
}