import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:words999/services/settings_repository.dart'; import 'package:words999/services/true_random_service.dart'; void main() { group('parseRandomOrgPlain', () { test('reads three d10 faces', () { expect(parseRandomOrgPlain('7\n4\n2\n', 3), [7, 4, 2]); }); test('rejects quota / error pages', () { expect( () => parseRandomOrgPlain('Error: Quota exceeded', 3), throwsFormatException, ); }); }); group('parseRandomOrgSequence', () { test('reads a permutation', () { expect(parseRandomOrgSequence('2\n0\n1\n', 0, 2), [2, 0, 1]); }); test('rejects duplicates', () { expect( () => parseRandomOrgSequence('0\n0\n1\n', 0, 2), throwsFormatException, ); }); }); group('parseRandomOrgJsonRpc', () { test('reads generateIntegers payload', () { const body = ''' {"jsonrpc":"2.0","result":{"random":{"data":[0,9,5]},"bitsUsed":12},"id":1} '''; expect(parseRandomOrgJsonRpc(body, 3), [0, 9, 5]); }); test('rejects API error objects', () { expect( () => parseRandomOrgJsonRpc( '{"jsonrpc":"2.0","error":{"code":401,"message":"bad key"},"id":1}', 3, ), throwsFormatException, ); }); }); group('TrueRandomService', () { test('uses RANDOM.ORG HTTP when the client succeeds', () async { final client = MockClient((request) async { expect(request.url.host, 'www.random.org'); expect(request.url.path, '/integers/'); expect(request.url.queryParameters['num'], '3'); expect(request.url.queryParameters['min'], '0'); expect(request.url.queryParameters['max'], '9'); return http.Response('1\n8\n3\n', 200); }); final service = TrueRandomService( client: client, apiKey: '', minInterval: Duration.zero, ); final roll = await service.take(3); expect(roll.digits, [1, 8, 3]); expect(roll.source, RandomSource.randomOrg); expect(roll.asNumber, 183); }); test('falls back to local CSPRNG when RANDOM.ORG fails', () async { final client = MockClient((request) async { return http.Response('Error: Service unavailable', 503); }); final service = TrueRandomService( client: client, apiKey: '', timeout: const Duration(milliseconds: 200), minInterval: Duration.zero, ); final roll = await service.take(3); expect(roll.digits.length, 3); expect(roll.digits.every((d) => d >= 0 && d <= 9), isTrue); expect(roll.source, RandomSource.local); }); test('uses JSON-RPC when an API key is set', () async { final client = MockClient((request) async { expect(request.url.host, 'api.random.org'); expect(request.method, 'POST'); return http.Response( '{"jsonrpc":"2.0","result":{"random":{"data":[9,0,1]}},"id":1}', 200, ); }); final service = TrueRandomService( client: client, apiKey: 'dev-key', minInterval: Duration.zero, ); final roll = await service.take(3); expect(roll.digits, [9, 0, 1]); expect(roll.source, RandomSource.randomOrg); }); test('production instance reads API key from settings, not the hashed dashboard value', () async { SharedPreferences.setMockInitialValues({}); await SettingsRepository().init(); await SettingsRepository().setRandomOrgApiKey( '11111111-2222-4333-8444-555555555555', ); String? sentBody; final client = MockClient((request) async { sentBody = request.body; expect(request.url.host, 'api.random.org'); return http.Response( '{"jsonrpc":"2.0","result":{"random":{"data":[2,2,8]}},"id":1}', 200, ); }); final service = TrueRandomService( client: client, minInterval: Duration.zero, ); final roll = await service.take(3); expect(roll.digits, [2, 2, 8]); expect(sentBody, contains('11111111-2222-4333-8444-555555555555')); expect(sentBody, isNot(contains('hashedApiKey'))); }); test('permutation uses generateIntegers without replacement', () async { String? body; final client = MockClient((request) async { expect(request.url.host, 'api.random.org'); body = request.body; return http.Response( '{"jsonrpc":"2.0","result":{"random":{"data":[3,0,2,1]}},"id":1}', 200, ); }); final service = TrueRandomService( client: client, apiKey: 'dev-key', minInterval: Duration.zero, ); final shuffle = await service.permutation(min: 0, max: 3); expect(body, contains('"replacement":false')); expect(body, contains('"n":4')); expect(shuffle.source, RandomSource.randomOrg); expect(shuffle.order, [3, 0, 2, 1]); expect(isPermutation(shuffle.order, 0, 3), isTrue); }); test('permutation falls back locally when RANDOM.ORG fails', () async { final client = MockClient((request) async { return http.Response('Error: Service unavailable', 503); }); final service = TrueRandomService( client: client, apiKey: 'dev-key', minInterval: Duration.zero, ); final shuffle = await service.permutation(min: 0, max: 9); expect(shuffle.source, RandomSource.local); expect(isPermutation(shuffle.order, 0, 9), isTrue); }); }); }