Files
999/test/true_random_service_test.dart
Alexyz 7f91366201 feat: Random.org dice and daily word matrix, interpreter voices, curated lexicon
Wire cleromancy rolls and the once-per-UTC-day 000-999 word shuffle to RANDOM.ORG JSON-RPC, with a local CSPRNG fallback. Add settings for interpreter personas and a cleaner default oracle prompt. Replace proper names and bureaucratic filler in the 1000-word lexicon with sacred symbols.
2026-09-08 13:46:11 +05:00

174 lines
5.6 KiB
Dart

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);
});
});
}