engelsystem/tests/Unit/Controllers/Admin/ScheduleControllerTest.php

512 lines
21 KiB
PHP
Raw Normal View History

2023-07-30 21:43:41 +02:00
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Admin;
use Engelsystem\Controllers\Admin\ScheduleController;
use Engelsystem\Controllers\HasUserNotifications;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Schedule\Event as EventData;
use Engelsystem\Helpers\Schedule\Room as RoomData;
use Engelsystem\Helpers\Schedule\Schedule as ScheduleData;
use Engelsystem\Helpers\Uuid;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Controllers\ControllerTest;
use Engelsystem\Test\Unit\HasDatabase;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use PHPUnit\Framework\MockObject\MockObject;
class ScheduleControllerTest extends ControllerTest
{
use HasDatabase;
use HasUserNotifications;
protected AngelType $angelType;
protected EventDispatcher | MockObject $event;
protected Location $location;
protected Shift $oldShift;
protected Redirector | MockObject $redirect;
protected Schedule $schedule;
protected string $scheduleFile = __DIR__ . '/../../Helpers/Schedule/Assets/schedule-multiple.xml';
protected ShiftType $shiftType;
protected User $user;
protected Validator $validator;
/**
* @covers \Engelsystem\Controllers\Admin\ScheduleController::index
* @covers \Engelsystem\Controllers\Admin\ScheduleController::__construct
*/
public function testIndex(): void
{
$this->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
2024-03-19 20:33:14 +01:00
* @covers \Engelsystem\Controllers\Admin\ScheduleController::fireDeleteShiftEvents
2023-07-30 21:43:41 +02:00
* @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))
2023-07-30 21:43:41 +02:00
->method('dispatch')
->with('shift.deleting')
2023-07-30 21:43:41 +02:00
->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(3))
2023-07-30 21:43:41 +02:00
->method('dispatch')
->withConsecutive(['shift.updating'], ['shift.deleting'])
2023-07-30 21:43:41 +02:00
->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);
}
}