Files
enaklo-flutter/test/customer_integration_test.dart
efrilm 38850b281f
Build & Deploy iOS to TestFlight / build-and-deploy (push) Waiting to run
feat: integrate enakgame and voucher
2026-10-10 02:33:16 +07:00

188 lines
5.9 KiB
Dart

import 'package:dio/dio.dart';
import 'package:enaklo/common/api/interceptors/unauthorized_interceptor.dart';
import 'package:enaklo/domain/wallet/wallet.dart';
import 'package:enaklo/infrastructure/wallet/wallet_dtos.dart';
import 'package:enaklo/infrastructure/wallet/wallet_response.dart';
import 'package:enaklo/presentation/pages/reward/reward_ui.dart';
import 'package:flutter_test/flutter_test.dart';
Response<dynamic> _response(int status, Object? data) => Response(
requestOptions: RequestOptions(path: '/'),
statusCode: status,
data: data,
);
Map<String, dynamic> _error(String code, String entity, String cause) => {
'success': false,
'data': null,
'errors': [
{'code': code, 'entity': entity, 'cause': cause},
],
};
void main() {
group('token ditolak (§2.2)', () {
test('401 dan 304 dari auth_handler berarti login ulang', () {
expect(isTokenRejected(_response(401, null)), isTrue);
expect(
isTokenRejected(_response(400, _error('304', 'auth_handler', 'x'))),
isTrue,
);
});
test('304 aturan bisnis dan 403 PIN tidak dianggap logout', () {
expect(
isTokenRejected(
_response(400, _error('304', 'wallet_service', 'not enough')),
),
isFalse,
);
expect(
isTokenRejected(
_response(403, {
'success': false,
'data': {'code': 'PIN_NOT_SET'},
'errors': [
{'code': 'PIN_NOT_SET'},
],
}),
),
isFalse,
);
});
test('parseWalletFailure memetakan auth_handler ke unauthorized', () {
final failure = parseWalletFailure(
_response(400, _error('304', 'auth_handler', 'token expired')),
);
expect(failure, const WalletFailure.unauthorized());
});
test('parseWalletFailure tetap membaca error PIN dari data', () {
final failure = parseWalletFailure(
_response(400, {
'success': false,
'data': {'code': 'PIN_INVALID', 'remaining_attempts': 3},
'errors': [
{'code': 'PIN_INVALID'},
],
}),
);
expect(failure, const WalletFailure.pinInvalid(3));
});
});
group('tipe mutasi (§4.2)', () {
test('tipe game dan voucher dikenali, PAYMENT tidak lagi', () {
expect(
WalletTransactionType.fromApi('GAME_REWARD'),
WalletTransactionType.gameReward,
);
expect(
WalletTransactionType.fromApi('REWARD_REDEEM_REFUND'),
WalletTransactionType.rewardRedeemRefund,
);
expect(
WalletTransactionType.fromApi('PAYMENT'),
WalletTransactionType.unknown,
);
});
test('filter per tab hanya menawarkan tipe milik mata uang itu', () {
final point = WalletTransactionType.filterableFor(WalletCurrency.point);
final coin = WalletTransactionType.filterableFor(WalletCurrency.coin);
expect(point, contains(WalletTransactionType.rewardRedeem));
expect(point, isNot(contains(WalletTransactionType.gameSpend)));
expect(coin, contains(WalletTransactionType.gameSpendRefund));
expect(coin, isNot(contains(WalletTransactionType.exchangeIn)));
expect(point, isNot(contains(WalletTransactionType.unknown)));
});
});
group('penolakan tukar voucher (§9.2)', () {
({String message, bool reloadCatalog}) describe(String cause) =>
describeVoucherRedeemFailure(WalletFailure.rejected(cause));
test('setiap cause punya pesan ramah', () {
expect(
describe('not enough EnakPoint').message,
'EnakPoint kamu kurang.',
);
expect(
describe(
'this voucher can be redeemed at most 2 times per customer',
).message,
'Kamu sudah mencapai batas penukaran voucher ini.',
);
expect(
describe('the customer is not active').message,
'Akun tidak aktif.',
);
});
test('voucher habis / tidak tersedia memuat ulang katalog', () {
for (final cause in [
'the voucher is out of stock',
'the voucher is not available',
'the voucher cannot be redeemed yet',
'the voucher has ended',
'the voucher is not available yet',
]) {
expect(describe(cause).reloadCatalog, isTrue, reason: cause);
}
expect(describe('not enough EnakPoint').reloadCatalog, isFalse);
});
test('cause tidak pernah ditampilkan mentah', () {
const cause =
'this Idempotency-Key was already used to redeem another voucher';
expect(describe(cause).message, isNot(contains('Idempotency')));
});
});
group('DTO dari contoh dokumen', () {
test(
'voucher katalog (§9.1): stok null tidak dihitung, 0 berarti habis',
() {
Voucher voucher(int? available) => VoucherDto.fromJson({
'id': 'v1',
'name': 'Kopi Susu Gratis',
'voucher_type': 'FREE_ITEM',
'face_value': 20000,
'point_cost': 15000,
'max_per_customer': null,
'valid_until': '2026-12-31T16:59:59Z',
'terms': {'a': 'b'},
'available': available,
}).toDomain();
expect(voucher(null).isOutOfStock, isFalse);
expect(voucher(0).isOutOfStock, isTrue);
expect(voucher(120).type, VoucherType.freeItem);
},
);
test('hasil tukar voucher (§9.2)', () {
final result = VoucherRedemptionDto.fromJson({
'id': 'r1',
'voucher_id': 'v1',
'voucher_name': 'Kopi Susu Gratis',
'voucher_type': 'FREE_ITEM',
'status': 'COMPLETED',
'face_value': 20000,
'point_cost': 15000,
'code': 'KOPI-7F3C-2291',
'code_expires_at': '2026-12-31T16:59:59Z',
'point_balance': 2500,
'replayed': true,
}).toRedeemResult();
expect(result.pointBalance, 2500);
expect(result.replayed, isTrue);
expect(result.redemption.code, 'KOPI-7F3C-2291');
expect(result.redemption.status, VoucherRedemptionStatus.completed);
});
});
}