testStoragePath = $GLOBALS['test_storage_path']; // Clean up any existing test files array_map('unlink', glob("$this->testStoragePath/*.json")); $this->logger = $this->createMock(LoggerInterface::class); $this->repository = new FileItemRepository($this->testStoragePath, $this->logger); } protected function tearDown(): void { // Clean up test files array_map('unlink', glob("$this->testStoragePath/*.json")); // Remove storage dir only if empty rmdir($this->testStoragePath); } private function createTestItem1(): Item { return new Item( self::ITEM_ID_1, self::ITEM_NAME_1, new DateTimeImmutable('+1 day'), self::ORDER_URL_1, self::USER_ID_1 ); } private function createTestItem2(): Item { return new Item( self::ITEM_ID_2, self::ITEM_NAME_2, (new DateTimeImmutable(self::MOCKED_NOW))->modify('+2 days'), self::ORDER_URL_2, self::USER_ID_2 ); } private function createTestItem3(): Item { return new Item( self::ITEM_ID_3, self::ITEM_NAME_3, (new DateTimeImmutable(self::MOCKED_NOW))->modify('+3 days'), self::ORDER_URL_3, self::USER_ID_1 ); } private function createExpiredItem(): Item { return new Item( self::EXPIRED_ID, self::EXPIRED_NAME, (new DateTimeImmutable(self::MOCKED_NOW))->modify('-1 day'), self::EXPIRED_ORDER_URL, self::USER_ID ); } private function createValidItem(): Item { return new Item( self::VALID_ID, self::VALID_NAME, (new DateTimeImmutable(self::MOCKED_NOW))->modify('+1 day'), self::VALID_ORDER_URL, self::USER_ID ); } private function createExpiredItemForUser1(): Item { return new Item( self::ITEM_ID_1, self::ITEM_NAME_1, (new DateTimeImmutable(self::MOCKED_NOW))->modify('-1 day'), self::ORDER_URL_1, self::USER_ID_1 ); } private function createValidItemForUser1(): Item { return new Item( self::ITEM_ID_2, self::ITEM_NAME_2, (new DateTimeImmutable(self::MOCKED_NOW))->modify('+1 day'), self::ORDER_URL_2, self::USER_ID_1 ); } private function createExpiredItemForUser2(): Item { return new Item( self::ITEM_ID_3, self::ITEM_NAME_3, (new DateTimeImmutable(self::MOCKED_NOW))->modify('-1 day'), self::ORDER_URL_3, self::USER_ID_2 ); } public function testWhenItemIsSavedThenFileIsCreated(): void { // Given $item = $this->createTestItem1(); // When $this->repository->save($item); // Then $filePath = $this->testStoragePath . '/items.json'; $this->assertFileExists($filePath); $fileContent = file_get_contents($filePath); $data = json_decode($fileContent, true); $this->assertIsArray($data); $this->assertCount(1, $data); // Compare the essential fields $this->assertEquals($item->getId(), $data[$item->getId()]['id']); $this->assertEquals($item->getName(), $data[$item->getId()]['name']); $this->assertEquals($item->getOrderUrl(), $data[$item->getId()]['orderUrl']); $this->assertEquals($item->getUserId(), $data[$item->getId()]['userId']); } public function testWhenItemExistsThenFindByIdReturnsItem(): void { // Given $item = $this->createTestItem1(); $this->repository->save($item); // When $foundItem = $this->repository->findById(self::ITEM_ID_1); // Then $this->assertNotNull($foundItem); $this->assertSame($item->getId(), $foundItem->getId()); $this->assertSame($item->getName(), $foundItem->getName()); } public function testWhenItemDoesNotExistThenFindByIdReturnsNull(): void { // When $foundItem = $this->repository->findById(self::NON_EXISTENT_ID); // Then $this->assertNull($foundItem); } public function testWhenUserHasMultipleItemsThenFindByUserIdReturnsAllUserItems(): void { // Given $item1 = $this->createTestItem1(); $item2 = $this->createTestItem2(); $item3 = $this->createTestItem3(); $this->repository->save($item1); $this->repository->save($item2); $this->repository->save($item3); // When $userItems = $this->repository->findByUserId(self::USER_ID_1); // Then $this->assertCount(2, $userItems); $this->assertContainsEquals($item1->getId(), array_map(fn($i) => $i->getId(), $userItems)); $this->assertContainsEquals($item3->getId(), array_map(fn($i) => $i->getId(), $userItems)); } public function testWhenMultipleItemsExistThenFindAllReturnsAllItems(): void { // Given $item1 = $this->createTestItem1(); $item2 = $this->createTestItem2(); $this->repository->save($item1); $this->repository->save($item2); // When $allItems = $this->repository->findAll(); // Then $this->assertCount(2, $allItems); $this->assertContainsEquals($item1->getId(), array_map(fn($i) => $i->getId(), $allItems)); $this->assertContainsEquals($item2->getId(), array_map(fn($i) => $i->getId(), $allItems)); } public function testWhenItemIsDeletedThenItIsNoLongerFound(): void { // Given $item = $this->createTestItem1(); $this->repository->save($item); // When $this->repository->delete(self::ITEM_ID_1); // Then $foundItem = $this->repository->findById(self::ITEM_ID_1); $this->assertNull($foundItem); } public function testWhenNonExistentItemIsDeletedThenExceptionIsThrown(): void { // Given & When & Then $this->expectException(ApplicationException::class); $this->expectExceptionMessage("Item '" . self::NON_EXISTENT_ID . "' not found"); $this->repository->delete(self::NON_EXISTENT_ID); } public function testWhenFilteringByExpirationThenOnlyExpiredItemsAreReturned(): void { // Given $expiredItem = $this->createExpiredItem(); $validItem = $this->createValidItem(); $this->repository->save($expiredItem); $this->repository->save($validItem); // When $now = new DateTimeImmutable(self::MOCKED_NOW); $specification = new Specification( Spec::lte('expirationDate', $now->format(self::DATE_FORMAT)) ); $expiredItems = $this->repository->findWhere($specification); // Then $this->assertCount(1, $expiredItems); $this->assertSame($expiredItem->getId(), $expiredItems[0]->getId()); } public function testWhenFilteringByUserIdThenOnlyUserItemsAreReturned(): void { // Given $item1 = $this->createTestItem1(); $item2 = $this->createTestItem2(); $item3 = $this->createTestItem3(); $this->repository->save($item1); $this->repository->save($item2); $this->repository->save($item3); // When $specification = new Specification( Spec::eq('userId', self::USER_ID_1) ); $userItems = $this->repository->findWhere($specification); // Then $this->assertCount(2, $userItems); $this->assertContainsEquals($item1->getId(), array_map(fn($i) => $i->getId(), $userItems)); $this->assertContainsEquals($item3->getId(), array_map(fn($i) => $i->getId(), $userItems)); } public function testWhenUsingComplexFilterThenOnlyMatchingItemsAreReturned(): void { // Given $item1 = $this->createExpiredItemForUser1(); $item2 = $this->createValidItemForUser1(); $item3 = $this->createExpiredItemForUser2(); $this->repository->save($item1); $this->repository->save($item2); $this->repository->save($item3); // When $now = new DateTimeImmutable(self::MOCKED_NOW); $specification = new Specification( Spec::and([ Spec::eq('userId', self::USER_ID_1), Spec::lte('expirationDate', $now->format(self::DATE_FORMAT)) ]) ); $filteredItems = $this->repository->findWhere($specification); // Then $this->assertCount(1, $filteredItems); $this->assertSame($item1->getId(), $filteredItems[0]->getId()); } }