diff --git a/tests/OrderMailTest.php b/tests/OrderMailTest.php new file mode 100644 index 0000000..9e4160b --- /dev/null +++ b/tests/OrderMailTest.php @@ -0,0 +1,93 @@ +logPath = sys_get_temp_dir() . '/bazaar-mail-' . uniqid() . '.log'; + $this->mailer = new LogMailer($this->logPath); + $this->orders = new OrderRepository($this->db); + $this->products = new ProductRepository($this->db); + $this->service = new OrderService( + $this->db, + $this->orders, + $this->products, + new VariantRepository($this->db), + null, + $this->mailer + ); + $this->userId = (new UserRepository($this->db))->create('mailbuyer@bazaar.test', 'hash', 'Mail Buyer'); + } + + protected function tearDown(): void + { + if (is_file($this->logPath)) { + unlink($this->logPath); + } + } + + private function order(): int + { + $productId = $this->seedProduct('Teapot', 4200, 10); + $cart = new Cart(new Session(), $this->products); + $cart->add($productId, 2); + return $this->service->createFromCart($this->userId, $cart, 'usd'); + } + + public function testNoMailBeforePayment(): void + { + $this->order(); + $this->assertCount(0, $this->mailer->sent()); + } + + public function testConfirmationSentOnPaid(): void + { + $orderId = $this->order(); + $this->service->markPaid($orderId); + + $sent = $this->mailer->sent(); + $this->assertCount(1, $sent); + $this->assertSame('mailbuyer@bazaar.test', $sent[0]->to); + $this->assertStringContainsString('order #' . $orderId, $sent[0]->subject); + $this->assertStringContainsString('Teapot', $sent[0]->body); + } + + public function testStatusUpdateSentOnFulfilled(): void + { + $orderId = $this->order(); + $this->service->markPaid($orderId); + $this->service->markFulfilled($orderId); + + $sent = $this->mailer->sent(); + $this->assertCount(2, $sent); + $this->assertStringContainsString('fulfilled', $sent[1]->subject); + } + + public function testLogMailerWritesToDisk(): void + { + $orderId = $this->order(); + $this->service->markPaid($orderId); + + $this->assertFileExists($this->logPath); + $this->assertStringContainsString('mailbuyer@bazaar.test', file_get_contents($this->logPath)); + } +} diff --git a/tests/ReviewServiceTest.php b/tests/ReviewServiceTest.php new file mode 100644 index 0000000..e6be0ef --- /dev/null +++ b/tests/ReviewServiceTest.php @@ -0,0 +1,103 @@ +reviews = new ReviewRepository($this->db); + $this->orders = new OrderRepository($this->db); + $this->service = new ReviewService($this->db, $this->reviews); + $users = new UserRepository($this->db); + $this->buyerId = $users->create('buyer@bazaar.test', 'hash', 'Buyer'); + $this->strangerId = $users->create('stranger@bazaar.test', 'hash', 'Stranger'); + } + + private function purchase(int $userId, int $productId, string $status = 'paid'): void + { + $orderId = $this->orders->create($userId, 5000, 'usd'); + $this->orders->addItem($orderId, $productId, 'Item', 5000, 1); + $this->orders->setStatus($orderId, $status, $status === 'paid' ? 'paidAt' : 'fulfilledAt'); + } + + public function testStrangerCannotReview(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + $this->assertSame(ReviewService::notPurchased, $this->service->eligibility($this->strangerId, $productId)); + $this->assertFalse($this->service->canReview($this->strangerId, $productId)); + } + + public function testBuyerCanReviewAfterPaidOrder(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + $this->purchase($this->buyerId, $productId, 'paid'); + + $this->assertSame(ReviewService::eligible, $this->service->eligibility($this->buyerId, $productId)); + } + + public function testPendingOrderDoesNotUnlockReview(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + $orderId = $this->orders->create($this->buyerId, 5000, 'usd'); + $this->orders->addItem($orderId, $productId, 'Item', 5000, 1); + + $this->assertFalse($this->service->canReview($this->buyerId, $productId)); + } + + public function testSubmitThrowsForNonBuyer(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + + $this->expectException(\RuntimeException::class); + $this->service->submit($this->strangerId, $productId, 5, 'Nice', 'Great lamp'); + } + + public function testCannotReviewTwice(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + $this->purchase($this->buyerId, $productId); + $this->service->submit($this->buyerId, $productId, 4, 'Good', 'Solid'); + + $this->assertSame(ReviewService::alreadyReviewed, $this->service->eligibility($this->buyerId, $productId)); + } + + public function testReviewIsPendingUntilModeratedAndSummaryCountsApprovedOnly(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + $this->purchase($this->buyerId, $productId); + $reviewId = $this->service->submit($this->buyerId, $productId, 5, 'Great', 'Love it'); + + $this->assertSame('pending', $this->reviews->find($reviewId)['status']); + $this->assertSame(0, $this->reviews->summary($productId)['count']); + $this->assertCount(0, $this->reviews->approvedForProduct($productId)); + + $this->service->approve($reviewId); + + $summary = $this->reviews->summary($productId); + $this->assertSame(1, $summary['count']); + $this->assertSame(5.0, $summary['average']); + $this->assertCount(1, $this->reviews->approvedForProduct($productId)); + } + + public function testRatingIsClampedIntoRange(): void + { + $productId = $this->seedProduct('Lamp', 5000, 5); + $this->purchase($this->buyerId, $productId); + $reviewId = $this->service->submit($this->buyerId, $productId, 9, 'Too high', 'x'); + + $this->assertSame(5, (int) $this->reviews->find($reviewId)['rating']); + } +}