From d1cae93c1a584d14abc1563132892f9f953e8c1c Mon Sep 17 00:00:00 2001 From: Tom Brow Date: Thu, 17 Sep 2026 09:30:20 -0700 Subject: [PATCH] fix(mobile): temporarily disable age gating (#7708) Disable the mobile launch age gate to restore access for users locked out after updating. The production provider now allows access immediately without calling the platform age API, while preserving the existing push snapshot restoration path. This is a temporary disable of age enforcement on both mobile platforms. The original gate implementation remains available for a separate follow-up. Signed-off-by: Tom Brow --- .../age_gate/age_signal_provider.dart | 12 +- .../features/age_gate/age_gate_app_test.dart | 124 ++++++++---------- .../age_gate/age_signal_provider_test.dart | 29 +++- 3 files changed, 94 insertions(+), 71 deletions(-) diff --git a/mobile/lib/features/age_gate/age_signal_provider.dart b/mobile/lib/features/age_gate/age_signal_provider.dart index 2577e342ce..d3c9b408e6 100644 --- a/mobile/lib/features/age_gate/age_signal_provider.dart +++ b/mobile/lib/features/age_gate/age_signal_provider.dart @@ -252,5 +252,15 @@ class AgeSignalNotifier extends Notifier { } final ageSignalProvider = NotifierProvider( - 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 request() async {} +} diff --git a/mobile/test/features/age_gate/age_gate_app_test.dart b/mobile/test/features/age_gate/age_gate_app_test.dart index 728505a512..f52aa680dd 100644 --- a/mobile/test/features/age_gate/age_gate_app_test.dart +++ b/mobile/test/features/age_gate/age_gate_app_test.dart @@ -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'; @@ -98,75 +96,65 @@ void main() { expect(badgeCounts.last, 0); }); - testWidgets('keeps app content unmounted until the signal resolves', ( - tester, - ) async { - final response = Completer(); - 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(); + 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(); - 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()), - ), - ); + 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()), + ), + ); - 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.pump(); + await tester.pump(); + expect(snapshotSuspensions, 0); - response.complete({'status': 'noSignal', 'ageUpper': null}); - await tester.pump(); - await tester.pump(); - - 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, diff --git a/mobile/test/features/age_gate/age_signal_provider_test.dart b/mobile/test/features/age_gate/age_signal_provider_test.dart index 3846e651a1..65df38acc1 100644 --- a/mobile/test/features/age_gate/age_signal_provider_test.dart +++ b/mobile/test/features/age_gate/age_signal_provider_test.dart @@ -13,6 +13,27 @@ 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 requestWithResponse(Object? response) async { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(ageSignalChannel, (call) async { @@ -20,7 +41,9 @@ void main() { 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(); @@ -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);