from datetime import timedelta from django.test import TestCase from django.utils import timezone from django.core.exceptions import PermissionDenied from labs.models import ( User, UserCreditSummary, UserCreditDetail, CreditVoucher, CreditTransaction, EventVoucher, UserLabWorkshop, LabInstance, ) from labs.billing.voucher import ( redeem_voucher_for_credits, redeem_voucher_for_workshop, InvalidVoucherCodeError, redeem_voucher, bulk_generate_credits_voucher, VOUCHER_FREE_CREDITS_EXPIRES_IN_DAYS, ) from labs.billing.utils import round_to_end_of_day, round_to_start_of_day from labs.lab_manager_v2.lab_instance import check_user_can_start_lab from labs.lab_manager_v2.lab_spec import load_lab_spec class CreditVoucherTests(TestCase): fixtures = [ "labs/tests/fixtures/user0.json", ] def setUp(self): self.user = User.objects.get(email="test1@example.com") self.non_staff_user = User.objects.filter(is_staff=False).first() def test_redeem_voucher_success(self): voucher_codes = bulk_generate_credits_voucher( user=self.user, lark_user_union_id=None, redeemable_days=30, campaign_code="mycampaign", credits_per_voucher=100, voucher_count=1000, voucher_category="test", customer_name="test", ) self.assertEqual(len(voucher_codes), 1000) voucher_code = voucher_codes[100] voucher = CreditVoucher.objects.get(unique_code=voucher_code) self.assertEqual(voucher.used, CreditVoucher.UsedChoices.N) self.assertIsNone(voucher.redeem_at) self.assertIsNone(voucher.redeem_by) user_credit_summary = UserCreditSummary.objects.get(user=self.user) self.assertEqual(user_credit_summary.free_credits, 160) self.assertEqual(user_credit_summary.paid_credits, 0) self.assertEqual(UserCreditDetail.objects.filter(user=self.user).count(), 3) self.assertEqual(CreditTransaction.objects.filter(user=self.user).count(), 0) redeem_voucher_for_credits(self.user, voucher_code) user_credit_summary.refresh_from_db() voucher.refresh_from_db() transaction = CreditTransaction.objects.get(user=self.user) self.assertEqual(voucher.used, CreditVoucher.UsedChoices.Y) self.assertIsNotNone(voucher.redeem_at) self.assertEqual(voucher.redeem_by, self.user) self.assertEqual(user_credit_summary.free_credits, 260) self.assertEqual(user_credit_summary.paid_credits, 0) self.assertEqual(UserCreditDetail.objects.filter(user=self.user).count(), 4) self.assertEqual(CreditTransaction.objects.filter(user=self.user).count(), 1) self.assertEqual( transaction.transaction_type, CreditTransaction.TransactionType.BONUS ) self.assertEqual(transaction.free_credits, 100) self.assertEqual(transaction.paid_credits, 0) voucher_credit_detail = UserCreditDetail.objects.filter( user=self.user, credit_type=UserCreditDetail.CreditType.FREE, remark=f"Redeem voucher {voucher_code}", ).get() expected_expires_at = round_to_end_of_day( voucher.redeem_at + timedelta(days=VOUCHER_FREE_CREDITS_EXPIRES_IN_DAYS) ) self.assertEqual(voucher_credit_detail.expires_at, expected_expires_at) # Credit lifetime is independent of voucher redeemability window. self.assertNotEqual( voucher_credit_detail.expires_at.date(), voucher.valid_before, ) with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_credits(self.user, voucher_code) user_credit_summary.refresh_from_db() self.assertEqual(user_credit_summary.free_credits, 260) self.assertEqual(user_credit_summary.paid_credits, 0) def _assert_user_credit_not_changed(self): user_credit_summary = UserCreditSummary.objects.get(user=self.user) self.assertEqual(user_credit_summary.free_credits, 160) self.assertEqual(user_credit_summary.paid_credits, 0) self.assertEqual(UserCreditDetail.objects.filter(user=self.user).count(), 3) self.assertEqual(CreditTransaction.objects.filter(user=self.user).count(), 0) def test_redeem_voucher_used(self): voucher_code = "11111-11111-11111-11111-11111" CreditVoucher.objects.create( unique_code=voucher_code, credits=100, valid_before=timezone.now() + timedelta(days=30), used=CreditVoucher.UsedChoices.Y, redeem_at=timezone.now(), redeem_by=self.user, ) with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_credits(self.user, voucher_code) self._assert_user_credit_not_changed() def test_redeem_voucher_expired(self): voucher_code = "11111-11111-11111-11111-11111" CreditVoucher.objects.create( unique_code=voucher_code, credits=100, valid_before=timezone.now() - timedelta(days=30), ) with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_credits(self.user, voucher_code) self._assert_user_credit_not_changed() def test_redeem_voucher_not_found(self): voucher_code = "11111-11111-11111-11111-11111" with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_credits(self.user, voucher_code) self._assert_user_credit_not_changed() def test_redeem_voucher_pattern_not_match(self): voucher_code = "11111" voucher = CreditVoucher.objects.create( unique_code=voucher_code, credits=100, valid_before=timezone.now() + timedelta(days=30), used=CreditVoucher.UsedChoices.N, marketing_code="test", ) with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_credits(self.user, voucher_code) self._assert_user_credit_not_changed() voucher.refresh_from_db() self.assertEqual(voucher.used, CreditVoucher.UsedChoices.N) def test_bulk_generate_credits_voucher_failed_non_staff_user(self): with self.assertRaises(PermissionDenied): bulk_generate_credits_voucher( user=self.non_staff_user, lark_user_union_id=None, redeemable_days=30, campaign_code="mycampaign", credits_per_voucher=100, voucher_count=1000, voucher_category="test", customer_name="test", ) def test_bulk_generate_credits_voucher_failed_invalid_parameters(self): # credits_per_voucher out of range with self.assertRaises(ValueError): bulk_generate_credits_voucher( user=self.user, lark_user_union_id=None, redeemable_days=30, campaign_code="mycampaign", credits_per_voucher=1000, voucher_count=1000, voucher_category="test", customer_name="test", ) # credits_per_voucher is 0 with self.assertRaises(ValueError): bulk_generate_credits_voucher( user=self.user, lark_user_union_id=None, redeemable_days=30, campaign_code="mycampaign", credits_per_voucher=0, voucher_count=1000, voucher_category="test", customer_name="test", ) # voucher_count is 0 with self.assertRaises(ValueError): bulk_generate_credits_voucher( user=self.user, lark_user_union_id=None, redeemable_days=30, campaign_code="mycampaign", credits_per_voucher=100, voucher_count=0, voucher_category="test", customer_name="test", ) # voucher_count is out of range with self.assertRaises(ValueError): bulk_generate_credits_voucher( user=self.user, lark_user_union_id=None, redeemable_days=30, campaign_code="mycampaign", credits_per_voucher=100, voucher_count=1001, voucher_category="test", customer_name="test", ) class WorkshopVoucherTests(TestCase): fixtures = [ "labs/tests/fixtures/user0.json", ] def setUp(self): self.user = User.objects.get(email="test1@example.com") def test_redeem_voucher_success(self): voucher_code = "WORKSHOP#TEST_1" lab_code = "demo_001" lab_spec = load_lab_spec(lab_code) now = timezone.now() today = now.date() result = check_user_can_start_lab(self.user, lab_spec) self.assertEqual(result.can_start, False) voucher = EventVoucher.objects.create( unique_code=voucher_code, lab_group_name="-", lab_codes=[lab_code], start_begin_date=today, start_end_date=today, event_name="test", ) self.assertEqual(UserLabWorkshop.objects.filter(user=self.user).count(), 0) redeem_voucher_for_workshop(self.user, voucher_code) user_lab_workshop = UserLabWorkshop.objects.get(user=self.user) self.assertEqual(user_lab_workshop.workshop, voucher) self.assertEqual(user_lab_workshop.workshop_name, voucher_code) self.assertEqual(user_lab_workshop.lab_group_name, "-") self.assertEqual(user_lab_workshop.lab_codes, [lab_code]) self.assertEqual(user_lab_workshop.valid_from, round_to_start_of_day(now)) self.assertEqual(user_lab_workshop.valid_until, round_to_end_of_day(now)) result = check_user_can_start_lab(self.user, lab_spec) self.assertEqual(result.can_start, True) self.assertEqual(result.access_type, LabInstance.LabAccessType.WORKSHOP) self.assertEqual(result.user_workshop, user_lab_workshop) self.assertEqual(result.user_redemption, None) def _assert_user_lab_workshop_not_created(self): self.assertEqual(UserLabWorkshop.objects.filter(user=self.user).count(), 0) def test_redeem_voucher_not_exist(self): voucher_code = "WORKSHOP#TEST_1" now = timezone.now() with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_workshop(self.user, voucher_code) self._assert_user_lab_workshop_not_created() def test_redeem_workshop_expired(self): voucher_code = "WORKSHOP#TEST_1" lab_group_name = "demo_001" now = timezone.now() - timedelta(days=1) yesterday = now.date() EventVoucher.objects.create( unique_code=voucher_code, lab_group_name=lab_group_name, start_begin_date=yesterday, start_end_date=yesterday, event_name="test", ) with self.assertRaises(InvalidVoucherCodeError): redeem_voucher_for_workshop(self.user, voucher_code) self._assert_user_lab_workshop_not_created() class VoucherTests(TestCase): fixtures = [ "labs/tests/fixtures/user0.json", ] def setUp(self): self.user = User.objects.get(email="test1@example.com") def test_redeem_credits_voucher_success(self): voucher_code = "90US6-AGSDW-L98TG-HKOHI-JGSIL" CreditVoucher.objects.create( unique_code=voucher_code, credits=100, valid_before=timezone.now() + timedelta(days=30), used=CreditVoucher.UsedChoices.N, marketing_code="test", ) user_credit_summary = UserCreditSummary.objects.get(user=self.user) self.assertEqual(user_credit_summary.free_credits, 160) self.assertEqual(user_credit_summary.paid_credits, 0) redeem_voucher(self.user, voucher_code) user_credit_summary.refresh_from_db() self.assertEqual(user_credit_summary.free_credits, 260) self.assertEqual(user_credit_summary.paid_credits, 0) self.assertEqual(UserCreditDetail.objects.filter(user=self.user).count(), 4) self.assertEqual(CreditTransaction.objects.filter(user=self.user).count(), 1) self.assertEqual( CreditTransaction.objects.get(user=self.user).transaction_type, CreditTransaction.TransactionType.BONUS, ) def test_redeem_workshop_voucher_success(self): voucher_code = "WORKSHOP#TEST_1" lab_group_name = "demo_001" now = timezone.now() today = now.date() EventVoucher.objects.create( unique_code=voucher_code, lab_group_name=lab_group_name, start_begin_date=today, start_end_date=today, event_name="test", ) self.assertEqual(UserLabWorkshop.objects.filter(user=self.user).count(), 0) redeem_voucher(self.user, voucher_code) self.assertEqual(UserLabWorkshop.objects.filter(user=self.user).count(), 1)