ui = $this->getMockBuilder(CronUIInterface::class)->disableOriginalConstructor()->getMock(); $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); $this->connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock(); $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock(); $this->storage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock(); } private function getCleanWorkToDoMockFor(string $methodName): TestableCleanWorkToDo|MockObject { $cleanWorkToDo = $this->getMockBuilder(TestableCleanWorkToDo::class) ->setConstructorArgs([$this->connection, $this->entityManager, $this->storage]) ->setMethodsExcept([$methodName, 'setUI', 'setLoggerInterface']) ->getMock(); $cleanWorkToDo->setUI($this->ui); $cleanWorkToDo->setLoggerInterface($this->logger); return $cleanWorkToDo; } /** * @see CleanWorkToDo::preview() */ public function testPreview(): void { $cleanWorkToDo = $this->getCleanWorkToDoMockFor('preview'); $cleanWorkToDo->expects(self::once()) ->method('deleteWorks') ->with(true); $cleanWorkToDo->preview(); } /** * @see CleanWorkToDo::execute() */ public function testExecute(): void { $cleanWorkToDo = $this->getCleanWorkToDoMockFor('execute'); $cleanWorkToDo->expects(self::once()) ->method('deleteWorks') ->with(); $cleanWorkToDo->execute(); } /** * @see CleanWorkToDo::deleteWorks() */ public function testDeleteWorks(): void { DatesUtils::setFakeDatetime('2023-12-08 00:00:00'); $expectedMaxDate = DatesUtils::new(); $expectedMaxDate->sub(new \DateInterval('P12M')); $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks'); $work1 = $this->getMockBuilder(Work::class)->getMock(); $work1->method('getId')->willReturn(1); $work2 = $this->getMockBuilder(Work::class)->getMock(); $work2->method('getId')->willReturn(2); $works = [$work1, $work2]; $cleanWorkToDo->expects(self::once()) ->method('listWorksToDelete') ->with($this->callback(function ($date) use ($expectedMaxDate) { return $date->format('Y-m-d') === $expectedMaxDate->format('Y-m-d'); })) ->willReturn($works); $this->ui->expects(self::atLeastOnce()) ->method('print') ->with('2 works to be removed'); $this->ui->expects(self::exactly(3)) ->method('progress'); $this->connection->expects(self::exactly(2)) ->method('beginTransaction'); $this->connection->expects(self::exactly(2)) ->method('commit'); $this->connection->expects(self::exactly(6)) ->method('executeStatement'); $this->logger->expects(self::exactly(2)) ->method('info') ->withConsecutive( [$this->stringContains('Finding works with courses created before')], [$this->stringContains('Works cleanup completed')] ); $cleanWorkToDo->deleteWorks(); } /** * @see CleanWorkToDo::deleteWorks() */ public function testDeleteWorksWhenPreview(): void { DatesUtils::setFakeDatetime('2023-12-08 00:00:00'); $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks'); $work1 = $this->getMockBuilder(Work::class)->getMock(); $work2 = $this->getMockBuilder(Work::class)->getMock(); $works = [$work1, $work2]; $cleanWorkToDo->expects(self::once()) ->method('listWorksToDelete') ->willReturn($works); $cleanWorkToDo->expects(self::once()) ->method('previewWorks') ->with($works); $this->ui->expects(self::exactly(2)) ->method('print') ->withConsecutive( ['2 works to be removed'], ['> Printing the first and last 10 :'] ); $cleanWorkToDo->deleteWorks(true); } /** * @see CleanWorkToDo::deleteWorks() */ public function testDeleteWorksWhenNoWorks(): void { DatesUtils::setFakeDatetime('2023-12-08 00:00:00'); $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks'); $cleanWorkToDo->expects(self::once()) ->method('listWorksToDelete') ->willReturn([]); $this->ui->expects(self::once()) ->method('print') ->with('0 works to be removed'); $this->logger->expects(self::exactly(2)) ->method('info') ->withConsecutive( [$this->stringContains('Finding works with courses created before')], ['No works found to delete'] ); $cleanWorkToDo->deleteWorks(); } /** * @see CleanWorkToDo::deleteWorks() */ public function testDeleteWorksWithNonBlockingError(): void { DatesUtils::setFakeDatetime('2023-12-08 00:00:00'); $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks'); $work = $this->getMockBuilder(Work::class)->getMock(); $work->method('getId')->willReturn(1); $works = [$work]; $cleanWorkToDo->expects(self::once()) ->method('listWorksToDelete') ->willReturn($works); $this->connection->expects(self::once()) ->method('beginTransaction'); $this->connection->expects(self::once()) ->method('executeStatement') ->willThrowException(new \RuntimeException('Database error')); $this->connection->expects(self::once()) ->method('rollback'); $this->logger->expects(self::once()) ->method('error') ->with('ERROR deleting work 1: Database error'); $cleanWorkToDo->deleteWorks(); } /** * @see CleanWorkToDo::deleteWorks() */ public function testDeleteWorksWithBlockingError(): void { DatesUtils::setFakeDatetime('2023-12-08 00:00:00'); $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks'); $work = $this->getMockBuilder(Work::class)->getMock(); $work->method('getId')->willReturn(1); $works = [$work]; $cleanWorkToDo->expects(self::once()) ->method('listWorksToDelete') ->willReturn($works); $this->connection->expects(self::once()) ->method('beginTransaction'); $this->connection->expects(self::once()) ->method('executeStatement') ->willThrowException(new \Exception('Serious database error')); $this->connection->expects(self::once()) ->method('rollback'); $this->expectException(\Exception::class); $this->expectExceptionMessage('Serious database error'); $cleanWorkToDo->deleteWorks(); } /** * @see CleanWorkToDo::listWorksToDelete() */ public function testListWorksToDelete(): void { $cleanWorkToDo = $this->getCleanWorkToDoMockFor('listWorksToDelete'); $maxDate = new \DateTime('2022-01-01'); $queryBuilder = $this->getMockBuilder(QueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $query = $this->getMockBuilder(Query::class) ->disableOriginalConstructor() ->getMock(); $work1 = $this->getMockBuilder(Work::class)->getMock(); $work2 = $this->getMockBuilder(Work::class)->getMock(); $expectedResult = [$work1, $work2]; $this->entityManager->expects(self::once()) ->method('createQueryBuilder') ->willReturn($queryBuilder); $queryBuilder->expects(self::once()) ->method('select') ->with('w') ->willReturnSelf(); $queryBuilder->expects(self::once()) ->method('from') ->with(Work::class, 'w') ->willReturnSelf(); $queryBuilder->expects(self::once()) ->method('leftJoin') ->with('w.course', 'c') ->willReturnSelf(); $queryBuilder->expects(self::once()) ->method('where') ->with('c.datetimeStart <= :maxDate OR c.datetimeStart IS NULL') ->willReturnSelf(); $queryBuilder->expects(self::once()) ->method('setParameter') ->with('maxDate', $maxDate) ->willReturnSelf(); $queryBuilder->expects(self::once()) ->method('getQuery') ->willReturn($query); $query->expects(self::once()) ->method('getResult') ->willReturn($expectedResult); $result = $cleanWorkToDo->listWorksToDelete($maxDate); $this->assertEquals($expectedResult, $result); } /** * @see CleanWorkToDo::previewWorks() */ public function testPreviewWorks(): void { $cleanWorkToDo = $this->getCleanWorkToDoMockFor('previewWorks'); $course1 = $this->getMockBuilder(Course::class)->getMock(); $course1->method('getName')->willReturn('Test Course 1'); $course2 = $this->getMockBuilder(Course::class)->getMock(); $course2->method('getName')->willReturn('Test Course 2'); $work1 = $this->getMockBuilder(Work::class)->getMock(); $work1->method('getId')->willReturn(1); $work1->method('getCourse')->willReturn($course1); $work1->method('getFiles')->willReturn(new ArrayCollection([])); $work1->method('getWorkByUsers')->willReturn(new ArrayCollection([])); $work2 = $this->getMockBuilder(Work::class)->getMock(); $work2->method('getId')->willReturn(2); $work2->method('getCourse')->willReturn($course2); $work2->method('getFiles')->willReturn(new ArrayCollection([new File(), new File()])); $work2->method('getWorkByUsers')->willReturn(new ArrayCollection([new WorkByUser()])); $work3 = $this->getMockBuilder(Work::class)->getMock(); $work3->method('getId')->willReturn(3); $work3->method('getCourse')->willReturn(null); $work3->method('getFiles')->willReturn(new ArrayCollection([])); $work3->method('getWorkByUsers')->willReturn(new ArrayCollection([])); $works = [$work1, $work2, $work3]; $this->ui->expects(self::exactly(3)) ->method('print') ->withConsecutive( [' * Work ID 1 - Course: Test Course 1 - Files: 0 - Users: 0'], [' * Work ID 2 - Course: Test Course 2 - Files: 2 - Users: 1'], [' * Work ID 3 - Course: No Course - Files: 0 - Users: 0'] ); $cleanWorkToDo->previewWorks($works); } /** * @see CleanWorkToDo::previewWorks() */ public function testPreviewWorksWithManyWorks(): void { $cleanWorkToDo = $this->getCleanWorkToDoMockFor('previewWorks'); $works = []; // Create 25 mock works to test the preview logic for ($i = 1; $i <= 25; ++$i) { $course = $this->getMockBuilder(Course::class)->getMock(); $course->method('getName')->willReturn("Course $i"); $work = $this->getMockBuilder(Work::class)->getMock(); $work->method('getId')->willReturn($i); $work->method('getCourse')->willReturn($course); $work->method('getFiles')->willReturn(new ArrayCollection([])); $work->method('getWorkByUsers')->willReturn(new ArrayCollection([])); $works[] = $work; } // Expect calls for first 10 works, then "(...)", then last 10 works $expectedCalls = []; // First 10 for ($i = 1; $i <= 10; ++$i) { $expectedCalls[] = [" * Work ID $i - Course: Course $i - Files: 0 - Users: 0"]; } // Ellipsis $expectedCalls[] = [' (...)']; // Last 10 for ($i = 16; $i <= 25; ++$i) { $expectedCalls[] = [" * Work ID $i - Course: Course $i - Files: 0 - Users: 0"]; } $this->ui->expects(self::exactly(21)) ->method('print') ->withConsecutive(...$expectedCalls); $cleanWorkToDo->previewWorks($works); } }