188 lines
5.9 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|