response->expects($this->once()) ->method('withView') ->willReturnCallback(function (string $view, array $data) { $this->assertEquals('admin/schedule/index', $view); $this->assertArrayHasKey('schedules', $data); /** @var Schedule[] $schedules */ $schedules = $data['schedules']; $this->assertNotEmpty($schedules); $this->assertEquals('Foo Schedule', $schedules[0]->name); return $this->response; }); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->index(); $this->assertEquals($this->response, $response); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::edit */ public function testEdit(): void { $this->response->expects($this->once()) ->method('withView') ->willReturnCallback(function (string $view, array $data) { $this->assertEquals('admin/schedule/edit', $view); $this->assertArrayHasKey('schedule', $data); /** @var Schedule $schedule */ $schedule = $data['schedule']; $this->assertNotEmpty($schedule); $this->assertEquals('Foo Schedule', $schedule->name); return $this->response; }); $request = $this->request->withAttribute('schedule_id', $this->schedule->id); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->edit($request); $this->assertEquals($this->response, $response); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::save */ public function testSaveNew(): void { $newId = $this->schedule->id + 1; $this->setExpects($this->redirect, 'to', ['/admin/schedule/load/' . $newId], $this->response); $request = Request::create('', 'POST', [ 'name' => 'Name', 'url' => 'https://example.test/schedule.xml', 'shift_type' => $this->shiftType->id, 'location_' . $this->location->id => 1, 'minutes_before' => 20, 'minutes_after' => 25, ]); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $controller->setValidator($this->validator); $response = $controller->save($request); $this->assertEquals($this->response, $response); $schedule = Schedule::find($newId); $this->assertNotNull($schedule); $this->assertEquals('Name', $schedule->name); $this->assertEquals('https://example.test/schedule.xml', $schedule->url); $this->assertEquals($this->shiftType->id, $schedule->shift_type); $this->assertEquals(20, $schedule->minutes_before); $this->assertEquals(25, $schedule->minutes_after); $this->assertCount(1, $schedule->activeLocations); /** @var Location $location */ $location = $schedule->activeLocations->first(); $this->assertEquals($this->location->id, $location->id); $this->assertHasNotification('schedule.edit.success'); $this->assertTrue($this->log->hasInfoThatContains('Schedule {name}')); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::save */ public function testSaveEdit(): void { $this->setExpects($this->redirect, 'to', ['/admin/schedule/load/' . $this->schedule->id], $this->response); $shiftType = ShiftType::factory()->create(); $request = Request::create('', 'POST', [ 'name' => 'New name', 'url' => 'https://test.example/schedule.xml', 'shift_type' => $shiftType->id, 'minutes_before' => 10, 'minutes_after' => 5, ]) ->withAttribute('schedule_id', $this->schedule->id); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $controller->setValidator($this->validator); $response = $controller->save($request); $this->assertEquals($this->response, $response); $schedule = Schedule::find($this->schedule->id); $this->assertNotNull($schedule); $this->assertEquals('New name', $schedule->name); $this->assertEquals('https://test.example/schedule.xml', $schedule->url); $this->assertEquals($shiftType->id, $schedule->shift_type); $this->assertEquals(10, $schedule->minutes_before); $this->assertEquals(5, $schedule->minutes_after); $this->assertHasNotification('schedule.edit.success'); $this->assertTrue($this->log->hasInfoThatContains('Schedule {name}')); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::save */ public function testSaveInvalidShiftType(): void { $request = Request::create('', 'POST', [ 'name' => 'Test', 'url' => 'https://test.example', 'shift_type' => 1337, 'minutes_before' => 0, 'minutes_after' => 0, ]); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $controller->setValidator($this->validator); $this->expectException(ModelNotFoundException::class); $controller->save($request); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::save * @covers \Engelsystem\Controllers\Admin\ScheduleController::delete * @covers \Engelsystem\Controllers\Admin\ScheduleController::fireDeleteShiftEvents * @covers \Engelsystem\Controllers\Admin\ScheduleController::deleteEvent */ public function testSaveDelete(): void { $this->setExpects($this->redirect, 'to', ['/admin/schedule'], $this->response); $this->event->expects($this->exactly(3)) ->method('dispatch') ->with('shift.deleting') ->willReturn([]); $request = Request::create('', 'POST', ['delete' => 'yes']) ->withAttribute('schedule_id', $this->schedule->id); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->save($request); $this->assertEquals($this->response, $response); $this->assertNull(Schedule::find($this->schedule->id)); $this->assertHasNotification('schedule.delete.success'); $this->assertTrue($this->log->hasInfoThatContains('Deleted schedule ({schedule}) shift')); $this->assertTrue($this->log->hasInfoThatContains('Schedule {name}')); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::loadSchedule * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleData */ public function testLoadSchedule(): void { $this->setScheduleResponses([new Response(file_get_contents($this->scheduleFile))]); $this->response->expects($this->once()) ->method('withView') ->willReturnCallback(function (string $view, array $data) { $this->assertEquals('admin/schedule/load', $view); $this->assertArrayHasKey('schedule', $data); /** @var ScheduleData $scheduleData */ $scheduleData = $data['schedule']; $this->assertInstanceOf(ScheduleData::class, $scheduleData); $this->assertArrayHasKey('locations', $data); $this->assertArrayHasKey('add', $data['locations']); /** @var RoomData[] $roomData */ $roomData = $data['locations']['add']; $this->assertNotEmpty($roomData); $this->assertInstanceOf(RoomData::class, $roomData[0]); $this->assertArrayHasKey('shifts', $data); foreach (['add', 'update', 'delete'] as $type) { $this->assertArrayHasKey($type, $data['shifts']); /** @var EventData[] $eventData */ $eventData = $data['shifts'][$type]; $this->assertNotEmpty($eventData); $this->assertInstanceOf(EventData::class, $eventData[array_key_first($eventData)]); } return $this->response; }); $request = Request::create('', 'POST') ->withAttribute('schedule_id', $this->schedule->id); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->loadSchedule($request); $this->assertEquals($this->response, $response); } public function loadScheduleErrorsData(): array { return [ // Server error [new RequestException('Error Communicating with Server', new Request()), 'schedule.import.request-error'], // Not found [new Response('', 202), 'schedule.import.request-error', true], // Decoding error [new Response(''), 'schedule.import.read-error', true], ]; } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::loadSchedule * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleData * @dataProvider loadScheduleErrorsData */ public function testScheduleResponseErrors(object $request, string $notification, bool $logWarning = false): void { $this->setScheduleResponses([$request]); $this->setExpects($this->redirect, 'back', null, $this->response); $request = Request::create('', 'POST') ->withAttribute('schedule_id', $this->schedule->id); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->loadSchedule($request); $this->assertEquals($this->response, $response); $this->assertHasNotification($notification, NotificationType::ERROR); if ($logWarning) { $this->assertTrue($this->log->hasWarningThatContains(' during schedule ')); } else { $this->assertTrue($this->log->hasErrorThatContains(' during schedule ')); } } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleData */ public function testGetScheduleDataScheduleNotFound(): void { $request = Request::create('', 'POST', ['delete' => 'yes']) ->withAttribute('schedule_id', 42); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $this->expectException(ModelNotFoundException::class); $controller->loadSchedule($request); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::importSchedule * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleData * @covers \Engelsystem\Controllers\Admin\ScheduleController::newRooms * @covers \Engelsystem\Controllers\Admin\ScheduleController::shiftsDiff * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleShiftsByGuid * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleShiftsWhereNotGuid * @covers \Engelsystem\Controllers\Admin\ScheduleController::eventFromScheduleShift * @covers \Engelsystem\Controllers\Admin\ScheduleController::fireUpdateShiftUpdateEvent * @covers \Engelsystem\Controllers\Admin\ScheduleController::getAllLocations * @covers \Engelsystem\Controllers\Admin\ScheduleController::createLocation * @covers \Engelsystem\Controllers\Admin\ScheduleController::createEvent * @covers \Engelsystem\Controllers\Admin\ScheduleController::updateEvent */ public function testImportSchedule(): void { $this->setScheduleResponses([new Response(file_get_contents($this->scheduleFile))]); $this->setExpects($this->redirect, 'to', ['/admin/schedule'], $this->response); $request = Request::create('', 'POST') ->withAttribute('schedule_id', $this->schedule->id); $this->event->expects($this->exactly(3)) ->method('dispatch') ->withConsecutive(['shift.updating'], ['shift.deleting']) ->willReturn([]); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->importSchedule($request); $this->assertEquals($this->response, $response); $this->assertTrue($this->log->hasInfoThatContains('Started schedule')); $this->assertTrue($this->log->hasInfoThatContains('Created schedule location')); $this->assertTrue($this->log->hasInfoThatContains('Created schedule ({schedule}) shift')); $this->assertTrue($this->log->hasInfoThatContains('Updated schedule ({schedule}) shift')); $this->assertTrue($this->log->hasInfoThatContains('Deleted schedule ({schedule}) shift')); $this->assertTrue($this->log->hasInfoThatContains('Ended schedule')); $this->assertHasNotification('schedule.import.success'); $this->assertCount(4, $this->schedule->shifts); $location = Location::whereName('Example Room')->get(); $this->assertCount(1, $location); $location2 = Location::whereName('Another Room')->get(); $this->assertCount(1, $location2); $location3 = Location::whereName('Third Room')->get(); $this->assertCount(1, $location3); /** @var Location $location */ $location = $location->first(); /** @var Location $location2 */ $location2 = $location2->first(); /** @var Location $location3 */ $location3 = $location3->first(); $this->assertCount(1, $location->shifts); $this->assertCount(3, $location2->shifts); $this->assertCount(0, $location3->shifts); // Deleted shift $this->assertNull(Shift::find($this->oldShift->id)); // Updated shift /** @var ScheduleShift $scheduleShift */ $scheduleShift = ScheduleShift::whereGuid('3e896c59-0d90-4817-8f74-af7fbb758f32')->first(); $this->assertNotEmpty($scheduleShift); $shift = $scheduleShift->shift; $this->assertEquals('First event [DE]', $shift->title); $this->assertEquals('https://example.com/first-1-event', $shift->url); $this->assertEquals('2042-10-02 09:45:00', $shift->start->toDateTimeString()); $this->assertEquals('2042-10-02 11:45:00', $shift->end->toDateTimeString()); $this->assertEquals($this->shiftType->id, $shift->shift_type_id); $this->assertEquals($location->id, $shift->location_id); $this->assertEquals($this->user->id, $shift->updated_by); // Created shift /** @var ScheduleShift $scheduleShift */ $scheduleShift = ScheduleShift::whereGuid('6e662ec5-d18d-417d-8719-360a416bb153')->first(); $this->assertNotEmpty($scheduleShift); $shift = $scheduleShift->shift; $this->assertEquals('Third event', $shift->title); $this->assertEquals('https://example.com/third-3-event', $shift->url); $this->assertEquals('2042-10-02 10:45:00', $shift->start->toDateTimeString()); $this->assertEquals('2042-10-02 11:45:00', $shift->end->toDateTimeString()); $this->assertEquals($this->shiftType->id, $shift->shift_type_id); $this->assertEquals($location2->id, $shift->location_id); $this->assertEquals($this->user->id, $shift->created_by); $this->assertNull($shift->updated_by); } /** * @covers \Engelsystem\Controllers\Admin\ScheduleController::importSchedule * @covers \Engelsystem\Controllers\Admin\ScheduleController::getScheduleData */ public function testImportScheduleError(): void { $this->setScheduleResponses([new Response('', 404)]); $this->setExpects($this->redirect, 'back', null, $this->response); $request = Request::create('', 'POST') ->withAttribute('schedule_id', $this->schedule->id); /** @var ScheduleController $controller */ $controller = $this->app->make(ScheduleController::class); $response = $controller->importSchedule($request); $this->assertEquals($this->response, $response); $this->assertHasNotification('schedule.import.request-error', NotificationType::ERROR); } protected function setScheduleResponses(array $queue): void { $handler = new MockHandler($queue); $guzzle = new Client(['handler' => HandlerStack::create($handler)]); $this->app->instance(Client::class, $guzzle); } public function setUp(): void { parent::setUp(); $this->validator = new Validator(); $this->redirect = $this->createMock(Redirector::class); $this->app->instance('redirect', $this->redirect); $this->event = $this->createMock(EventDispatcher::class); $this->app->instance('events.dispatcher', $this->event); $this->shiftType = ShiftType::factory()->create(); $this->location = Location::factory()->create(['name' => 'Example Room']); $location3 = Location::factory()->create(['name' => 'Another Room']); $this->angelType = AngelType::factory()->create(); $this->schedule = Schedule::factory()->create([ 'shift_type' => $this->shiftType->id, 'name' => 'Foo Schedule', 'needed_from_shift_type' => false, ]); $this->schedule->activeLocations()->attach($this->location); $this->schedule->activeLocations()->attach($location3); /** @var Shift[] $shifts */ $shifts = Shift::factory(3)->create([ 'location_id' => $this->location->id, 'shift_type_id' => $this->shiftType->id, ]); foreach ($shifts as $shift) { (new ScheduleShift([ 'shift_id' => $shift->id, 'schedule_id' => $this->schedule->id, 'guid' => Uuid::uuid(), ]))->save(); } $this->oldShift = $shifts[1]; /** @var ScheduleShift $firstScheduleShift */ $firstScheduleShift = ScheduleShift::query()->first(); $firstScheduleShift->guid = '3e896c59-0d90-4817-8f74-af7fbb758f32'; $firstScheduleShift->save(); $firstShift = $firstScheduleShift->shift; $firstShift->neededAngelTypes()->create(['angel_type_id' => $this->angelType->id, 'count' => 3]); $this->user = User::factory()->create(); // Shift from import ShiftEntry::factory()->create([ 'shift_id' => $firstShift->id, 'angel_type_id' => $this->angelType->id, 'user_id' => $this->user->id, ]); // Shift from some previous import ShiftEntry::factory()->create([ 'shift_id' => $this->oldShift->id, 'angel_type_id' => $this->angelType->id, 'user_id' => $this->user->id, ]); $authenticator = $this->createMock(Authenticator::class); $this->setExpects($authenticator, 'user', null, $this->user, $this->any()); $this->app->instance('authenticator', $authenticator); } }