122 lines
3.8 KiB
Dart
122 lines
3.8 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:cyberhybridhub_server/question_source_preferences.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('QuestionSourcePreferences.effectiveMix', () {
|
|
test('matches client-style normalization for enabled sources', () {
|
|
final List<ResolvedQuestionSourcePreference> prefs =
|
|
QuestionSourcePreferences.mergeStored(<Map<String, dynamic>>[
|
|
<String, dynamic>{'id': 'market_history', 'enabled': true, 'ratio': 70},
|
|
<String, dynamic>{'id': 'spelling', 'enabled': true, 'ratio': 30},
|
|
]);
|
|
|
|
final Map<String, int> mix = QuestionSourcePreferences.effectiveMix(prefs);
|
|
|
|
expect(mix['market_history'], 70);
|
|
expect(mix['spelling'], 30);
|
|
});
|
|
|
|
test('excludes disabled sources', () {
|
|
final List<ResolvedQuestionSourcePreference> prefs =
|
|
QuestionSourcePreferences.mergeStored(<Map<String, dynamic>>[
|
|
<String, dynamic>{'id': 'market_history', 'enabled': true, 'ratio': 50},
|
|
<String, dynamic>{'id': 'spelling', 'enabled': false, 'ratio': 50},
|
|
]);
|
|
|
|
final Map<String, int> mix = QuestionSourcePreferences.effectiveMix(prefs);
|
|
|
|
expect(mix.keys, <String>{'market_history'});
|
|
expect(mix['market_history'], 100);
|
|
});
|
|
});
|
|
|
|
group('_sourceTryOrder (via dispatcher selection)', () {
|
|
test('weighted random favors configured ratio over many rolls', () {
|
|
final List<ResolvedQuestionSourcePreference> prefs =
|
|
QuestionSourcePreferences.mergeStored(<Map<String, dynamic>>[
|
|
<String, dynamic>{'id': 'market_history', 'enabled': true, 'ratio': 90},
|
|
<String, dynamic>{'id': 'spelling', 'enabled': true, 'ratio': 10},
|
|
]);
|
|
|
|
final Random random = Random(42);
|
|
var marketFirst = 0;
|
|
for (var i = 0; i < 200; i++) {
|
|
final List<String> order = _sourceTryOrderForTest(prefs, random);
|
|
if (order.first == 'market_history') {
|
|
marketFirst++;
|
|
}
|
|
}
|
|
|
|
expect(marketFirst, greaterThan(140));
|
|
});
|
|
|
|
test('only enabled source is always first', () {
|
|
final List<ResolvedQuestionSourcePreference> prefs =
|
|
QuestionSourcePreferences.mergeStored(<Map<String, dynamic>>[
|
|
<String, dynamic>{'id': 'market_history', 'enabled': false, 'ratio': 0},
|
|
<String, dynamic>{'id': 'spelling', 'enabled': true, 'ratio': 100},
|
|
]);
|
|
|
|
final List<String> order =
|
|
_sourceTryOrderForTest(prefs, Random(1));
|
|
|
|
expect(order, <String>['spelling']);
|
|
});
|
|
});
|
|
}
|
|
|
|
/// Mirrors [QuestionDispatcher._sourceTryOrder] for deterministic unit tests.
|
|
List<String> _sourceTryOrderForTest(
|
|
List<ResolvedQuestionSourcePreference> prefs,
|
|
Random random,
|
|
) {
|
|
final List<ResolvedQuestionSourcePreference> enabled = prefs
|
|
.where(
|
|
(ResolvedQuestionSourcePreference s) => s.enabled && !s.comingSoon,
|
|
)
|
|
.toList(growable: false);
|
|
if (enabled.isEmpty) {
|
|
return <String>[];
|
|
}
|
|
|
|
final int totalWeight = enabled.fold<int>(
|
|
0,
|
|
(int sum, ResolvedQuestionSourcePreference s) => sum + s.ratio,
|
|
);
|
|
if (totalWeight <= 0) {
|
|
final List<String> ids =
|
|
enabled.map((ResolvedQuestionSourcePreference s) => s.id).toList();
|
|
ids.shuffle(random);
|
|
return ids;
|
|
}
|
|
|
|
var roll = random.nextInt(totalWeight);
|
|
String? primary;
|
|
for (final ResolvedQuestionSourcePreference source in enabled) {
|
|
roll -= source.ratio;
|
|
if (roll < 0) {
|
|
primary = source.id;
|
|
break;
|
|
}
|
|
}
|
|
primary ??= enabled.first.id;
|
|
|
|
final List<ResolvedQuestionSourcePreference> rest = enabled
|
|
.where((ResolvedQuestionSourcePreference s) => s.id != primary)
|
|
.toList()
|
|
..sort(
|
|
(
|
|
ResolvedQuestionSourcePreference a,
|
|
ResolvedQuestionSourcePreference b,
|
|
) =>
|
|
b.ratio.compareTo(a.ratio),
|
|
);
|
|
|
|
return <String>[
|
|
primary,
|
|
...rest.map((ResolvedQuestionSourcePreference s) => s.id),
|
|
];
|
|
}
|