Patterns
Паттерны тестирования: HMAC, TB account ID, PII round-trip.
HMAC Pinned Test¶
Тест проверяет что Dart-реализация PspHmacClient.computeSignature даёт точно тот же результат, что и Node.js реализация в payment-manager/src/auth/hmac.ts.
Canonical string: {timestamp}\n{METHOD}\n{path}\n{sha256hex(body)}
// test/integration/payment/psp_hmac_client_test.dart
test('matches PM Node crypto reference for POST with body', () {
final sig = PspHmacClient.computeSignature(
method: 'POST',
path: '/intents',
body: '{"a":1}',
timestampSeconds: 1700000000,
secret: 'test-secret',
);
expect(sig,
equals('a16f8acc3d682ab80a904f45da0c1c7ef7f1039073f8086e6a2a26051ead2e8a'));
});
test('matches PM Node crypto reference for GET with empty body', () {
final sig = PspHmacClient.computeSignature(
method: 'GET',
path: '/intents/aaa',
body: '',
timestampSeconds: 1700000000,
secret: 'test-secret',
);
expect(sig,
equals('13f22c510b489472755edd13865d4dfbfdee79e4b54bff415e7df2aa18bd2f25'));
});
Pinned значения получены запуском PM функции accountId(name) — не менять без обновления соответствующего теста в PM.
TB Account ID Parity¶
TigerBeetle account ID детерминирован: UUIDv5(name, TB_NS), где TB_NS = '3e7b4a1c-9f2d-5e8a-b6c3-4d1f07e2a9b5'.
// test/integration/payment/tb_account_id_deriver_test.dart
// Pinned values — DO NOT REGENERATE unless TB_NS changes
const expectations = <String, String>{
'user.1.THB': 'b509494c-a6f9-578f-a9c4-e9f0ac7bcaea',
'user.42.THB': '4a539b8b-e623-57cd-9b08-0ff8c25807f9',
'system.transit.INTERNAL.THB': 'd3b8074e-85ab-5f6e-b98c-e21270214885',
'system.revenue.THB': 'a33fa48f-09a0-5718-a6f0-4e0d4824b2f7',
'system.nostro.ipps.THB': '413fcb53-ad21-5bdf-9303-4c9b2a52121a',
'merchant.7.available.THB': 'cc95facd-e988-542f-a4d3-ba6e7486db23',
};
Цель: если TB_NS или алгоритм деривации изменится — все pinned значения сломаются, и это сразу заметно. Изменение TB_NS = нарушение всех существующих account ID в production.
FakeAdapter Паттерн¶
Подмена HTTP-адаптера для unit-тестирования PM вызовов без реального сервера:
// test/integration/payment/psp_hmac_client_test.dart
class _FakeAdapter implements HttpClientAdapter {
final Future<ResponseBody> Function(RequestOptions options) handler;
_FakeAdapter(this.handler);
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
) => handler(options);
@override
void close({bool force = false}) {}
}
// Использование в тесте:
PspHmacClient.dio.httpClientAdapter = _FakeAdapter((options) async {
// перехватить options.headers['X-Signature'] и проверить
return ResponseBody.fromString('{"id":"test"}', 200);
});
Этот паттерн позволяет:
- проверять заголовки X-Service-Id, X-Timestamp, X-Signature без PM
- тестировать обработку HTTP-ошибок (4xx, 5xx) без реального сервера
- тестировать двухключевую схему (auth-center key vs merchant key)
Правило: Независимость тестов¶
Каждый тест должен быть самодостаточен:
- Данные, созданные тестом, не должны влиять на другие тесты
- rollbackDatabase: RollbackDatabase.afterEach гарантирует это для DB тестов
- Unit тесты (без DB) изолированы по умолчанию
- Seed-данные создаются внутри setUp() или непосредственно в теле теста