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.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user