From e3e0fb33a21bd90e2ecc64166c93b5238c921e30 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sun, 30 Jul 2023 21:43:41 +0200 Subject: [PATCH] Added tests for schedule import --- resources/lang/de_DE/additional.po | 3 - resources/lang/en_US/additional.po | 3 - src/Controllers/Admin/ScheduleController.php | 74 +-- .../Admin/ScheduleControllerTest.php | 511 ++++++++++++++++++ .../Schedule/Assets/schedule-multiple.xml | 90 +++ 5 files changed, 638 insertions(+), 43 deletions(-) create mode 100644 tests/Unit/Controllers/Admin/ScheduleControllerTest.php create mode 100644 tests/Unit/Helpers/Schedule/Assets/schedule-multiple.xml diff --git a/resources/lang/de_DE/additional.po b/resources/lang/de_DE/additional.po index eda8a898..d8266521 100644 --- a/resources/lang/de_DE/additional.po +++ b/resources/lang/de_DE/additional.po @@ -85,9 +85,6 @@ msgstr "Das Programm konnte nicht abgerufen werden." msgid "schedule.import.read-error" msgstr "Das Programm konnte nicht gelesen werden." -msgid "schedule.import.invalid-shift-type" -msgstr "Der Schichttyp konnte nicht gefunden werden." - msgid "schedule.import.success" msgstr "Das Programm wurde erfolgreich importiert." diff --git a/resources/lang/en_US/additional.po b/resources/lang/en_US/additional.po index 441b94ff..66dcc284 100644 --- a/resources/lang/en_US/additional.po +++ b/resources/lang/en_US/additional.po @@ -83,9 +83,6 @@ msgstr "The schedule could not be requested." msgid "schedule.import.read-error" msgstr "Unable to parse schedule." -msgid "schedule.import.invalid-shift-type" -msgstr "The shift type can't not be found." - msgid "schedule.import.success" msgstr "Schedule import successful." diff --git a/src/Controllers/Admin/ScheduleController.php b/src/Controllers/Admin/ScheduleController.php index 9ac6702d..0d53631e 100644 --- a/src/Controllers/Admin/ScheduleController.php +++ b/src/Controllers/Admin/ScheduleController.php @@ -52,7 +52,7 @@ class ScheduleController extends BaseController public function index(): Response { return $this->response->withView( - 'admin/schedule/index.twig', + 'admin/schedule/index', [ 'is_index' => true, 'schedules' => ScheduleModel::all(), @@ -63,10 +63,10 @@ class ScheduleController extends BaseController public function edit(Request $request): Response { $scheduleId = $request->getAttribute('schedule_id'); // optional - $schedule = ScheduleModel::find($scheduleId); + $schedule = ScheduleModel::findOrNew($scheduleId); return $this->response->withView( - 'admin/schedule/edit.twig', + 'admin/schedule/edit', [ 'schedule' => $schedule, 'shift_types' => ShiftType::all()->sortBy('name')->pluck('name', 'id'), @@ -75,9 +75,6 @@ class ScheduleController extends BaseController ); } - /** - * @throws ErrorException - */ public function save(Request $request): Response { $scheduleId = $request->getAttribute('schedule_id'); // optional @@ -103,10 +100,7 @@ class ScheduleController extends BaseController 'minutes_before' => 'int', 'minutes_after' => 'int', ] + $locationsValidation); - - if (!ShiftType::find($data['shift_type'])) { - throw new ErrorException('schedule.import.invalid-shift-type'); - } + ShiftType::findOrFail($data['shift_type']); $schedule->name = $data['name']; $schedule->url = $data['url']; @@ -205,7 +199,7 @@ class ScheduleController extends BaseController } return $this->response->withView( - 'admin/schedule/load.twig', + 'admin/schedule/load', [ 'schedule_id' => $scheduleModel->id, 'schedule' => $schedule, @@ -294,7 +288,7 @@ class ScheduleController extends BaseController $this->log->info('Created schedule location "{location}"', ['location' => $room->getName()]); } - protected function fireDeleteShiftEntryEvents(Event $event, ScheduleUrl $schedule): void + protected function fireDeleteShiftEntryEvents(Event $event, ScheduleModel $schedule): void { $shiftEntries = $this->db ->table('shift_entries') @@ -325,7 +319,7 @@ class ScheduleController extends BaseController } } - protected function createEvent(Event $event, int $shiftTypeId, Location $location, ScheduleModel $scheduleUrl): void + protected function createEvent(Event $event, int $shiftTypeId, Location $location, ScheduleModel $schedule): void { $user = auth()->user(); $eventTimeZone = Carbon::now()->timezone; @@ -337,17 +331,17 @@ class ScheduleController extends BaseController $shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone); $shift->location()->associate($location); $shift->url = $event->getUrl() ?? ''; - $shift->transaction_id = Uuid::uuidBy($scheduleUrl->id, '5c4ed01e'); + $shift->transaction_id = Uuid::uuidBy($schedule->id, '5c4ed01e'); $shift->createdBy()->associate($user); $shift->save(); $scheduleShift = new ScheduleShift(['guid' => $event->getGuid()]); - $scheduleShift->schedule()->associate($scheduleUrl); + $scheduleShift->schedule()->associate($schedule); $scheduleShift->shift()->associate($shift); $scheduleShift->save(); $this->log->info( - 'Created schedule shift "{shift}" in "{location}" ({from} {to}, {guid})', + 'Created schedule shift "{shift}" in "{location}" ({from} - {to}, {guid})', [ 'shift' => $shift->title, 'location' => $shift->location->name, @@ -358,7 +352,7 @@ class ScheduleController extends BaseController ); } - protected function updateEvent(Event $event, int $shiftTypeId, Location $location, ScheduleUrl $schedule): void + protected function updateEvent(Event $event, int $shiftTypeId, Location $location, ScheduleModel $schedule): void { $user = auth()->user(); $eventTimeZone = Carbon::now()->timezone; @@ -390,15 +384,15 @@ class ScheduleController extends BaseController ); } - protected function deleteEvent(Event $event, ScheduleUrl $schedule): void + protected function deleteEvent(Event $event, ScheduleModel $schedule): void { /** @var ScheduleShift $scheduleShift */ $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first(); $shift = $scheduleShift->shift; - $shift->delete(); - $scheduleShift->delete(); $this->fireDeleteShiftEntryEvents($event, $schedule); + $shift->delete(); + $scheduleShift->delete(); $this->log->info( 'Deleted schedule shift "{shift}" in {location} ({from} {to}, {guid})', @@ -428,32 +422,38 @@ class ScheduleController extends BaseController { $scheduleId = (int) $request->getAttribute('schedule_id'); - /** @var ScheduleModel $scheduleUrl */ - $scheduleUrl = ScheduleModel::findOrFail($scheduleId); + /** @var ScheduleModel $scheduleModel */ + $scheduleModel = ScheduleModel::findOrFail($scheduleId); try { - $scheduleResponse = $this->guzzle->get($scheduleUrl->url); - } catch (ConnectException | GuzzleException) { + $scheduleResponse = $this->guzzle->get($scheduleModel->url); + } catch (ConnectException | GuzzleException $e) { + $this->log->error('Exception during schedule request', ['exception' => $e]); throw new ErrorException('schedule.import.request-error'); } if ($scheduleResponse->getStatusCode() != 200) { + $this->log->warning( + 'Problem during schedule request, got code {code}', + ['code' => $scheduleResponse->getStatusCode()] + ); throw new ErrorException('schedule.import.request-error'); } $scheduleData = (string) $scheduleResponse->getBody(); if (!$this->parser->load($scheduleData)) { + $this->log->warning('Problem during schedule parsing'); throw new ErrorException('schedule.import.read-error'); } - $shiftType = $scheduleUrl->shift_type; + $shiftType = $scheduleModel->shift_type; $schedule = $this->parser->getSchedule(); - $minutesBefore = $scheduleUrl->minutes_before; - $minutesAfter = $scheduleUrl->minutes_after; + $minutesBefore = $scheduleModel->minutes_before; + $minutesAfter = $scheduleModel->minutes_after; $newRooms = $this->newRooms($schedule->getRooms()); return array_merge( - $this->shiftsDiff($schedule, $scheduleUrl, $shiftType, $minutesBefore, $minutesAfter), - [$newRooms, $shiftType, $scheduleUrl, $schedule, $minutesBefore, $minutesAfter] + $this->shiftsDiff($schedule, $scheduleModel, $shiftType, $minutesBefore, $minutesAfter), + [$newRooms, $shiftType, $scheduleModel, $schedule, $minutesBefore, $minutesAfter] ); } @@ -482,7 +482,7 @@ class ScheduleController extends BaseController */ protected function shiftsDiff( Schedule $schedule, - ScheduleModel $scheduleUrl, + ScheduleModel $scheduleModel, int $shiftType, int $minutesBefore, int $minutesAfter @@ -500,7 +500,7 @@ class ScheduleController extends BaseController foreach ($schedule->getDay() as $day) { foreach ($day->getRoom() as $room) { - if (!$scheduleUrl->activeLocations->where('name', $room->getName())->count()) { + if (!$scheduleModel->activeLocations->where('name', $room->getName())->count()) { continue; } @@ -519,7 +519,7 @@ class ScheduleController extends BaseController } $scheduleEventsGuidList = array_keys($scheduleEvents); - $existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList); + $existingShifts = $this->getScheduleShiftsByGuid($scheduleModel, $scheduleEventsGuidList); foreach ($existingShifts as $scheduleShift) { $guid = $scheduleShift->guid; $shift = $scheduleShift->shift; @@ -545,7 +545,7 @@ class ScheduleController extends BaseController $newEvents[$scheduleEvent->getGuid()] = $scheduleEvent; } - $scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList); + $scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleModel, $scheduleEventsGuidList); foreach ($scheduleShifts as $scheduleShift) { $event = $this->eventFromScheduleShift($scheduleShift); $deleteEvents[$event->getGuid()] = $event; @@ -588,11 +588,11 @@ class ScheduleController extends BaseController * * @return Collection|ScheduleShift[] */ - protected function getScheduleShiftsByGuid(ScheduleModel $scheduleUrl, array $events): Collection | array + protected function getScheduleShiftsByGuid(ScheduleModel $schedule, array $events): Collection | array { return ScheduleShift::with('shift.location') ->whereIn('guid', $events) - ->where('schedule_id', $scheduleUrl->id) + ->where('schedule_id', $schedule->id) ->get(); } @@ -600,11 +600,11 @@ class ScheduleController extends BaseController * @param string[] $events * @return Collection|ScheduleShift[] */ - protected function getScheduleShiftsWhereNotGuid(ScheduleModel $scheduleUrl, array $events): Collection | array + protected function getScheduleShiftsWhereNotGuid(ScheduleModel $schedule, array $events): Collection | array { return ScheduleShift::with('shift.location') ->whereNotIn('guid', $events) - ->where('schedule_id', $scheduleUrl->id) + ->where('schedule_id', $schedule->id) ->get(); } } diff --git a/tests/Unit/Controllers/Admin/ScheduleControllerTest.php b/tests/Unit/Controllers/Admin/ScheduleControllerTest.php new file mode 100644 index 00000000..ad45f9fc --- /dev/null +++ b/tests/Unit/Controllers/Admin/ScheduleControllerTest.php @@ -0,0 +1,511 @@ +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::fireDeleteShiftEntryEvents + * @covers \Engelsystem\Controllers\Admin\ScheduleController::deleteEvent + */ + public function testSaveDelete(): void + { + $this->setExpects($this->redirect, 'to', ['/admin/schedule'], $this->response); + + $this->event->expects($this->exactly(2)) + ->method('dispatch') + ->with('shift.entry.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 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(2)) + ->method('dispatch') + ->withConsecutive(['shift.updating'], ['shift.entry.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 shift')); + $this->assertTrue($this->log->hasInfoThatContains('Updated schedule shift')); + $this->assertTrue($this->log->hasInfoThatContains('Deleted 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); + } +} diff --git a/tests/Unit/Helpers/Schedule/Assets/schedule-multiple.xml b/tests/Unit/Helpers/Schedule/Assets/schedule-multiple.xml new file mode 100644 index 00000000..1607b338 --- /dev/null +++ b/tests/Unit/Helpers/Schedule/Assets/schedule-multiple.xml @@ -0,0 +1,90 @@ + + + 4.2-frickel + + Some Bigger Test Event + test-4 + 2042-10-01 + 2042-10-02 + 2 + 00:30 + + + + + 2042-10-02T10:00:00+00:00 + First event + With a subtitle + 10:00 + 01:30 + Example Room + first-1-event + Testing + Talk + A minimal description + https://example.com/first-1-event + DE + + + + + 2042-10-02T13:30:00+00:00 + Second event + With a subtitle + 13:30 + 00:30 + Another Room + second-2-event + Tinkering + Talk + A minimal description + + + + + + + 2042-10-02T11:00:00+00:00 + Third event + With a subtitle + 11:00 + 00:30 + Another Room + third-3-event + Testing + Talk + A minimal description + https://example.com/third-3-event + + + 2042-10-02T11:45:00+00:00 + Fourth event + With a subtitle + 11:45 + 00:30 + Another Room + fourth-42-event + Testing + Talk + A minimal description + + + + + 2042-10-02T10:00:00+00:00 + Fifth event + Will be ignored + 10:00 + 01:00 + Third Room + fifth-55-event + Testing + Talk + + + + +