*/ #[CoversClass(TimesheetBasic::class)] #[CoversClass(TimesheetBasicValidator::class)] class TimesheetBasicValidatorTest extends ConstraintValidatorTestCase { protected function createValidator(): TimesheetBasicValidator { return $this->createMyValidator(); } protected function createMyValidator(): TimesheetBasicValidator { $configuration = SystemConfigurationFactory::createStub(['timesheet' => ['rules' => ['require_activity' => true]]]); return new TimesheetBasicValidator($configuration); } public function testConstraintIsInvalid(): void { $this->expectException(UnexpectedTypeException::class); $this->validator->validate(new Timesheet(), new NotBlank()); } public function testInvalidValueThrowsException(): void { $this->expectException(UnexpectedTypeException::class); $this->validator->validate(new NotBlank(), new TimesheetBasic(['message' => 'myMessage'])); } public function testEmptyTimesheet(): void { $timesheet = new Timesheet(); $this->validator->validate($timesheet, new TimesheetBasic(['message' => 'myMessage'])); $this->buildViolation('You must submit a begin date.') ->atPath('property.path.begin_date') ->setCode(TimesheetBasic::MISSING_BEGIN_ERROR) ->buildNextViolation('An activity needs to be selected.') ->atPath('property.path.activity') ->setCode(TimesheetBasic::MISSING_ACTIVITY_ERROR) ->buildNextViolation('A project needs to be selected.') ->atPath('property.path.project') ->setCode(TimesheetBasic::MISSING_PROJECT_ERROR) ->assertRaised(); } public function testFutureBegin(): void { $begin = new \DateTime('+10 hour'); $timesheet = new Timesheet(); $timesheet->setBegin($begin); $this->validator->validate($timesheet, new TimesheetBasic(['message' => 'myMessage'])); $this ->buildViolation('An activity needs to be selected.') ->atPath('property.path.activity') ->setCode(TimesheetBasic::MISSING_ACTIVITY_ERROR) ->buildNextViolation('A project needs to be selected.') ->atPath('property.path.project') ->setCode(TimesheetBasic::MISSING_PROJECT_ERROR) // The test context is not able to handle calls to validate() - see ConstraintValidatorTestCase::createContext() // therefor sub-constraints will not be executed :-( /* ->buildNextViolation('The begin date cannot be in the future.') ->atPath('property.path.begin_date') ->setCode(TimesheetFutureTimes::BEGIN_IN_FUTURE_ERROR) */ ->assertRaised(); } public function testEndBeforeBegin(): void { $end = new \DateTime('-10 hour'); $begin = new \DateTime('-1 hour'); $timesheet = new Timesheet(); $timesheet->setBegin($begin); $timesheet->setEnd($end); $this->validator->validate($timesheet, new TimesheetBasic(['message' => 'myMessage'])); $this->buildViolation('End date must not be earlier then start date.') ->atPath('property.path.end_date') ->setCode(TimesheetBasic::END_BEFORE_BEGIN_ERROR) ->buildNextViolation('An activity needs to be selected.') ->atPath('property.path.activity') ->setCode(TimesheetBasic::MISSING_ACTIVITY_ERROR) ->buildNextViolation('A project needs to be selected.') ->atPath('property.path.project') ->setCode(TimesheetBasic::MISSING_PROJECT_ERROR) ->assertRaised(); } public function testProjectMismatch(): void { $end = new \DateTime('-1 hour'); $begin = new \DateTime('-10 hour'); $activity = new Activity(); $project1 = new Project(); $project2 = new Project(); $project2->setCustomer(new Customer('foo')); $activity->setProject($project1); $timesheet = new Timesheet(); $timesheet ->setBegin($begin) ->setEnd($end) ->setActivity($activity) ->setProject($project2) ; $this->validator->validate($timesheet, new TimesheetBasic(['message' => 'myMessage'])); $this->buildViolation('Project mismatch, project specific activity and timesheet project are different.') ->atPath('property.path.project') ->setCode(TimesheetBasic::ACTIVITY_PROJECT_MISMATCH_ERROR) ->assertRaised(); } public static function getProjectStartEndTestData(): iterable { yield [new \DateTime(), new \DateTime(), [ ['begin_date', TimesheetBasic::PROJECT_NOT_STARTED, 'The project has not started at that time.'], ['end_date', TimesheetBasic::PROJECT_NOT_STARTED, 'The project has not started at that time.'], ]]; yield [new \DateTime('-9 hour'), new \DateTime('-2 hour'), [ ['begin_date', TimesheetBasic::PROJECT_NOT_STARTED, 'The project has not started at that time.'], ['end_date', TimesheetBasic::PROJECT_ALREADY_ENDED, 'The project is finished at that time.'], ]]; yield [new \DateTime('-19 hour'), new \DateTime('-12 hour'), [ ['begin_date', TimesheetBasic::PROJECT_ALREADY_ENDED, 'The project is finished at that time.'], ['end_date', TimesheetBasic::PROJECT_ALREADY_ENDED, 'The project is finished at that time.'], ]]; yield [new \DateTime('-19 hour'), new \DateTime('-2 hour'), [ ['end_date', TimesheetBasic::PROJECT_ALREADY_ENDED, 'The project is finished at that time.'], ]]; yield [new \DateTime('-9 hour'), new \DateTime(), [ ['begin_date', TimesheetBasic::PROJECT_NOT_STARTED, 'The project has not started at that time.'], ]]; } #[DataProvider('getProjectStartEndTestData')] public function testEndBeforeWithProjectStartAndEnd(\DateTime $start, \DateTime $end, array $violations): void { $timesheet = new Timesheet(); $timesheet->setBegin(new \DateTime('-10 hour')); $timesheet->setEnd(new \DateTime('-1 hour')); $customer = new Customer('foo'); $project = new Project(); $project->setStart($start); $project->setEnd($end); $project->setCustomer($customer); $timesheet->setProject($project); $timesheet->setActivity(new Activity()); $this->validator->validate($timesheet, new TimesheetBasic(['message' => 'myMessage'])); $assertion = null; foreach ($violations as $violation) { if (null === $assertion) { $assertion = $this->buildViolation($violation[2]) ->atPath('property.path.' . $violation[0]) ->setCode($violation[1]) ; } else { $assertion = $assertion->buildNextViolation($violation[2]) ->atPath('property.path.' . $violation[0]) ->setCode($violation[1]) ; } } $assertion->assertRaised(); } public function testGetTargets(): void { $constraint = new TimesheetBasic(); self::assertEquals('class', $constraint->getTargets()); } }