diff --git a/tests/CouponServiceTest.php b/tests/CouponServiceTest.php new file mode 100644 index 0000000..9e58e6a --- /dev/null +++ b/tests/CouponServiceTest.php @@ -0,0 +1,93 @@ +coupons = new CouponRepository($this->db); + $this->service = new CouponService($this->coupons); + } + + private function coupon(array $overrides = []): int + { + return $this->coupons->create(array_merge([ + 'code' => 'SAVE10', + 'type' => 'percent', + 'value' => 10, + 'minSubtotalCents' => 0, + 'maxRedemptions' => null, + 'startsAt' => null, + 'expiresAt' => null, + 'isActive' => true, + ], $overrides)); + } + + public function testPercentDiscountIsFloored(): void + { + $this->coupon(['code' => 'SAVE15', 'value' => 15]); + $result = $this->service->evaluate('SAVE15', 999); + + $this->assertTrue($result->isValid()); + $this->assertSame((int) floor(999 * 0.15), $result->discountCents); + } + + public function testFixedDiscountNeverExceedsSubtotal(): void + { + $this->coupon(['code' => 'FLAT50', 'type' => 'fixed', 'value' => 5000]); + $result = $this->service->evaluate('FLAT50', 3000); + + $this->assertTrue($result->isValid()); + $this->assertSame(3000, $result->discountCents); + } + + public function testCodeLookupIsCaseInsensitive(): void + { + $this->coupon(); + $this->assertTrue($this->service->evaluate('save10', 5000)->isValid()); + } + + public function testUnknownCodeIsRejected(): void + { + $this->assertSame(CouponResult::notFound, $this->service->evaluate('NOPE', 5000)->status); + } + + public function testInactiveCouponIsRejected(): void + { + $this->coupon(['code' => 'OFF', 'isActive' => false]); + $this->assertSame(CouponResult::inactive, $this->service->evaluate('OFF', 5000)->status); + } + + public function testBelowMinimumIsRejected(): void + { + $this->coupon(['code' => 'BIG', 'minSubtotalCents' => 10000]); + $this->assertSame(CouponResult::belowMinimum, $this->service->evaluate('BIG', 5000)->status); + } + + public function testExpiredAndNotStartedWindows(): void + { + $this->coupon(['code' => 'PAST', 'expiresAt' => '2020-01-01T00:00:00+00:00']); + $this->coupon(['code' => 'FUTURE', 'startsAt' => '2999-01-01T00:00:00+00:00']); + + $this->assertSame(CouponResult::expired, $this->service->evaluate('PAST', 5000)->status); + $this->assertSame(CouponResult::notStarted, $this->service->evaluate('FUTURE', 5000)->status); + } + + public function testExhaustedWhenRedemptionLimitReached(): void + { + $id = $this->coupon(['code' => 'ONCE', 'maxRedemptions' => 1]); + $this->assertTrue($this->service->evaluate('ONCE', 5000)->isValid()); + + $this->service->redeem($id); + $this->assertSame(CouponResult::exhausted, $this->service->evaluate('ONCE', 5000)->status); + } +}