Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion mobile/lib/features/age_gate/age_signal_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,5 +252,15 @@ class AgeSignalNotifier extends Notifier<AgeSignalState> {
}

final ageSignalProvider = NotifierProvider<AgeSignalNotifier, AgeSignalState>(
AgeSignalNotifier.new,
_DisabledAgeSignalNotifier.new,
);

// Temporarily disable age blocking: platform age checks can lock users out of
// the app. Keep the allowed transition so push snapshots are restored normally.
class _DisabledAgeSignalNotifier extends AgeSignalNotifier {
@override
AgeSignalState build() => AgeSignalState.allowed;

@override
Future<void> request() async {}
}
124 changes: 56 additions & 68 deletions mobile/test/features/age_gate/age_gate_app_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:async';

import 'package:buzz/app.dart';
import 'package:buzz/features/age_gate/age_restriction_page.dart';
import 'package:buzz/features/age_gate/age_signal_push_bootstrap.dart';
Expand Down Expand Up @@ -98,75 +96,65 @@ void main() {
expect(badgeCounts.last, 0);
});

testWidgets('keeps app content unmounted until the signal resolves', (
tester,
) async {
final response = Completer<Object?>();
final relaySession = _CountingRelaySessionNotifier();
var requests = 0;
var snapshotSuspensions = 0;
var snapshotRestorations = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ageSignalChannel, (call) {
requests += 1;
return response.future;
});
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();

await tester.pumpWidget(
ProviderScope(
overrides: [
authProvider.overrideWith(() => _AuthenticatedAuthNotifier()),
relaySessionProvider.overrideWith(() => relaySession),
suspendCommunitySnapshotForAgeCheckProvider.overrideWithValue(
() async {
snapshotSuspensions += 1;
if (snapshotSuspensions == 1) {
throw StateError('injected suspension failure');
}
},
),
resumeCommunitySnapshotAfterAgeCheckProvider.overrideWithValue(
() async {
snapshotRestorations += 1;
if (snapshotRestorations == 1) {
throw StateError('injected restoration failure');
}
},
),
ageSignalPushSnapshotRetryWaitProvider.overrideWithValue(
(_) async {},
),
savedPrefsProvider.overrideWithValue(prefs),
],
child: const AgeSignalPushBootstrap(child: App()),
),
);
testWidgets(
'opens app and restores push snapshots without a native age check',
(tester) async {
final relaySession = _CountingRelaySessionNotifier();
var requests = 0;
var snapshotSuspensions = 0;
var snapshotRestorations = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ageSignalChannel, (call) {
requests += 1;
throw PlatformException(code: 'unavailable');
});
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();

expect(requests, 1);
expect(find.bySemanticsLabel('Checking age eligibility'), findsOneWidget);
expect(find.byType(HomePage), findsNothing);
expect(find.byType(Navigator), findsNothing);
expect(relaySession.builds, 0);
await tester.pump();
await tester.pump();
expect(snapshotSuspensions, 2);
expect(snapshotRestorations, 0);
await tester.pumpWidget(
ProviderScope(
overrides: [
authProvider.overrideWith(() => _AuthenticatedAuthNotifier()),
relaySessionProvider.overrideWith(() => relaySession),
suspendCommunitySnapshotForAgeCheckProvider.overrideWithValue(
() async {
snapshotSuspensions += 1;
if (snapshotSuspensions == 1) {
throw StateError('injected suspension failure');
}
},
),
resumeCommunitySnapshotAfterAgeCheckProvider.overrideWithValue(
() async {
snapshotRestorations += 1;
if (snapshotRestorations == 1) {
throw StateError('injected restoration failure');
}
},
),
ageSignalPushSnapshotRetryWaitProvider.overrideWithValue(
(_) async {},
),
savedPrefsProvider.overrideWithValue(prefs),
],
child: const AgeSignalPushBootstrap(child: App()),
),
);

response.complete({'status': 'noSignal', 'ageUpper': null});
await tester.pump();
await tester.pump();
await tester.pump();
await tester.pump();
expect(snapshotSuspensions, 0);

expect(find.bySemanticsLabel('Checking age eligibility'), findsNothing);
expect(find.byType(HomePage), findsOneWidget);
expect(find.byType(Navigator), findsOneWidget);
expect(relaySession.builds, 1);
expect(requests, 1);
await tester.pump();
await tester.pump();
expect(snapshotRestorations, 2);
});
expect(find.bySemanticsLabel('Checking age eligibility'), findsNothing);
expect(find.byType(HomePage), findsOneWidget);
expect(find.byType(Navigator), findsOneWidget);
expect(relaySession.builds, 1);
expect(requests, 0);
await tester.pump();
await tester.pump();
expect(snapshotRestorations, 2);
},
);

testWidgets('offers a retry after the native age check fails', (
tester,
Expand Down
29 changes: 27 additions & 2 deletions mobile/test/features/age_gate/age_signal_provider_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,37 @@ void main() {
.setMockMethodCallHandler(ageSignalChannel, null);
});

test(
'production launch allows access without requesting a native age signal',
() async {
var nativeCalls = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ageSignalChannel, (_) async {
nativeCalls += 1;
throw PlatformException(code: 'unavailable');
});
final container = ProviderContainer();
addTearDown(container.dispose);

expect(container.read(ageSignalProvider), AgeSignalState.allowed);
await container.read(ageSignalProvider.notifier).request();
await container.read(ageSignalProvider.notifier).request();

expect(container.read(ageSignalProvider), AgeSignalState.allowed);
expect(nativeCalls, 0);
},
);

Future<AgeSignalState> requestWithResponse(Object? response) async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(ageSignalChannel, (call) async {
expect(call.method, 'requestAgeSignal');
expect(call.arguments, isNull);
return response;
});
final container = ProviderContainer();
final container = ProviderContainer(
overrides: [ageSignalProvider.overrideWith(AgeSignalNotifier.new)],
);
addTearDown(container.dispose);

await container.read(ageSignalProvider.notifier).request();
Expand Down Expand Up @@ -506,7 +529,9 @@ void main() {
requests += 1;
return {'status': 'noSignal', 'ageUpper': null};
});
final container = ProviderContainer();
final container = ProviderContainer(
overrides: [ageSignalProvider.overrideWith(AgeSignalNotifier.new)],
);
addTearDown(container.dispose);
final notifier = container.read(ageSignalProvider.notifier);

Expand Down