Added tests for schedule import
This commit is contained in:
parent
790a04dc14
commit
e3e0fb33a2
|
@ -85,9 +85,6 @@ msgstr "Das Programm konnte nicht abgerufen werden."
|
||||||
msgid "schedule.import.read-error"
|
msgid "schedule.import.read-error"
|
||||||
msgstr "Das Programm konnte nicht gelesen werden."
|
msgstr "Das Programm konnte nicht gelesen werden."
|
||||||
|
|
||||||
msgid "schedule.import.invalid-shift-type"
|
|
||||||
msgstr "Der Schichttyp konnte nicht gefunden werden."
|
|
||||||
|
|
||||||
msgid "schedule.import.success"
|
msgid "schedule.import.success"
|
||||||
msgstr "Das Programm wurde erfolgreich importiert."
|
msgstr "Das Programm wurde erfolgreich importiert."
|
||||||
|
|
||||||
|
|
|
@ -83,9 +83,6 @@ msgstr "The schedule could not be requested."
|
||||||
msgid "schedule.import.read-error"
|
msgid "schedule.import.read-error"
|
||||||
msgstr "Unable to parse schedule."
|
msgstr "Unable to parse schedule."
|
||||||
|
|
||||||
msgid "schedule.import.invalid-shift-type"
|
|
||||||
msgstr "The shift type can't not be found."
|
|
||||||
|
|
||||||
msgid "schedule.import.success"
|
msgid "schedule.import.success"
|
||||||
msgstr "Schedule import successful."
|
msgstr "Schedule import successful."
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ class ScheduleController extends BaseController
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
return $this->response->withView(
|
return $this->response->withView(
|
||||||
'admin/schedule/index.twig',
|
'admin/schedule/index',
|
||||||
[
|
[
|
||||||
'is_index' => true,
|
'is_index' => true,
|
||||||
'schedules' => ScheduleModel::all(),
|
'schedules' => ScheduleModel::all(),
|
||||||
|
@ -63,10 +63,10 @@ class ScheduleController extends BaseController
|
||||||
public function edit(Request $request): Response
|
public function edit(Request $request): Response
|
||||||
{
|
{
|
||||||
$scheduleId = $request->getAttribute('schedule_id'); // optional
|
$scheduleId = $request->getAttribute('schedule_id'); // optional
|
||||||
$schedule = ScheduleModel::find($scheduleId);
|
$schedule = ScheduleModel::findOrNew($scheduleId);
|
||||||
|
|
||||||
return $this->response->withView(
|
return $this->response->withView(
|
||||||
'admin/schedule/edit.twig',
|
'admin/schedule/edit',
|
||||||
[
|
[
|
||||||
'schedule' => $schedule,
|
'schedule' => $schedule,
|
||||||
'shift_types' => ShiftType::all()->sortBy('name')->pluck('name', 'id'),
|
'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
|
public function save(Request $request): Response
|
||||||
{
|
{
|
||||||
$scheduleId = $request->getAttribute('schedule_id'); // optional
|
$scheduleId = $request->getAttribute('schedule_id'); // optional
|
||||||
|
@ -103,10 +100,7 @@ class ScheduleController extends BaseController
|
||||||
'minutes_before' => 'int',
|
'minutes_before' => 'int',
|
||||||
'minutes_after' => 'int',
|
'minutes_after' => 'int',
|
||||||
] + $locationsValidation);
|
] + $locationsValidation);
|
||||||
|
ShiftType::findOrFail($data['shift_type']);
|
||||||
if (!ShiftType::find($data['shift_type'])) {
|
|
||||||
throw new ErrorException('schedule.import.invalid-shift-type');
|
|
||||||
}
|
|
||||||
|
|
||||||
$schedule->name = $data['name'];
|
$schedule->name = $data['name'];
|
||||||
$schedule->url = $data['url'];
|
$schedule->url = $data['url'];
|
||||||
|
@ -205,7 +199,7 @@ class ScheduleController extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->response->withView(
|
return $this->response->withView(
|
||||||
'admin/schedule/load.twig',
|
'admin/schedule/load',
|
||||||
[
|
[
|
||||||
'schedule_id' => $scheduleModel->id,
|
'schedule_id' => $scheduleModel->id,
|
||||||
'schedule' => $schedule,
|
'schedule' => $schedule,
|
||||||
|
@ -294,7 +288,7 @@ class ScheduleController extends BaseController
|
||||||
$this->log->info('Created schedule location "{location}"', ['location' => $room->getName()]);
|
$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
|
$shiftEntries = $this->db
|
||||||
->table('shift_entries')
|
->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();
|
$user = auth()->user();
|
||||||
$eventTimeZone = Carbon::now()->timezone;
|
$eventTimeZone = Carbon::now()->timezone;
|
||||||
|
@ -337,17 +331,17 @@ class ScheduleController extends BaseController
|
||||||
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
|
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
|
||||||
$shift->location()->associate($location);
|
$shift->location()->associate($location);
|
||||||
$shift->url = $event->getUrl() ?? '';
|
$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->createdBy()->associate($user);
|
||||||
$shift->save();
|
$shift->save();
|
||||||
|
|
||||||
$scheduleShift = new ScheduleShift(['guid' => $event->getGuid()]);
|
$scheduleShift = new ScheduleShift(['guid' => $event->getGuid()]);
|
||||||
$scheduleShift->schedule()->associate($scheduleUrl);
|
$scheduleShift->schedule()->associate($schedule);
|
||||||
$scheduleShift->shift()->associate($shift);
|
$scheduleShift->shift()->associate($shift);
|
||||||
$scheduleShift->save();
|
$scheduleShift->save();
|
||||||
|
|
||||||
$this->log->info(
|
$this->log->info(
|
||||||
'Created schedule shift "{shift}" in "{location}" ({from} {to}, {guid})',
|
'Created schedule shift "{shift}" in "{location}" ({from} - {to}, {guid})',
|
||||||
[
|
[
|
||||||
'shift' => $shift->title,
|
'shift' => $shift->title,
|
||||||
'location' => $shift->location->name,
|
'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();
|
$user = auth()->user();
|
||||||
$eventTimeZone = Carbon::now()->timezone;
|
$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 */
|
/** @var ScheduleShift $scheduleShift */
|
||||||
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first();
|
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first();
|
||||||
$shift = $scheduleShift->shift;
|
$shift = $scheduleShift->shift;
|
||||||
$shift->delete();
|
|
||||||
$scheduleShift->delete();
|
|
||||||
|
|
||||||
$this->fireDeleteShiftEntryEvents($event, $schedule);
|
$this->fireDeleteShiftEntryEvents($event, $schedule);
|
||||||
|
$shift->delete();
|
||||||
|
$scheduleShift->delete();
|
||||||
|
|
||||||
$this->log->info(
|
$this->log->info(
|
||||||
'Deleted schedule shift "{shift}" in {location} ({from} {to}, {guid})',
|
'Deleted schedule shift "{shift}" in {location} ({from} {to}, {guid})',
|
||||||
|
@ -428,32 +422,38 @@ class ScheduleController extends BaseController
|
||||||
{
|
{
|
||||||
$scheduleId = (int) $request->getAttribute('schedule_id');
|
$scheduleId = (int) $request->getAttribute('schedule_id');
|
||||||
|
|
||||||
/** @var ScheduleModel $scheduleUrl */
|
/** @var ScheduleModel $scheduleModel */
|
||||||
$scheduleUrl = ScheduleModel::findOrFail($scheduleId);
|
$scheduleModel = ScheduleModel::findOrFail($scheduleId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$scheduleResponse = $this->guzzle->get($scheduleUrl->url);
|
$scheduleResponse = $this->guzzle->get($scheduleModel->url);
|
||||||
} catch (ConnectException | GuzzleException) {
|
} catch (ConnectException | GuzzleException $e) {
|
||||||
|
$this->log->error('Exception during schedule request', ['exception' => $e]);
|
||||||
throw new ErrorException('schedule.import.request-error');
|
throw new ErrorException('schedule.import.request-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($scheduleResponse->getStatusCode() != 200) {
|
if ($scheduleResponse->getStatusCode() != 200) {
|
||||||
|
$this->log->warning(
|
||||||
|
'Problem during schedule request, got code {code}',
|
||||||
|
['code' => $scheduleResponse->getStatusCode()]
|
||||||
|
);
|
||||||
throw new ErrorException('schedule.import.request-error');
|
throw new ErrorException('schedule.import.request-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
$scheduleData = (string) $scheduleResponse->getBody();
|
$scheduleData = (string) $scheduleResponse->getBody();
|
||||||
if (!$this->parser->load($scheduleData)) {
|
if (!$this->parser->load($scheduleData)) {
|
||||||
|
$this->log->warning('Problem during schedule parsing');
|
||||||
throw new ErrorException('schedule.import.read-error');
|
throw new ErrorException('schedule.import.read-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
$shiftType = $scheduleUrl->shift_type;
|
$shiftType = $scheduleModel->shift_type;
|
||||||
$schedule = $this->parser->getSchedule();
|
$schedule = $this->parser->getSchedule();
|
||||||
$minutesBefore = $scheduleUrl->minutes_before;
|
$minutesBefore = $scheduleModel->minutes_before;
|
||||||
$minutesAfter = $scheduleUrl->minutes_after;
|
$minutesAfter = $scheduleModel->minutes_after;
|
||||||
$newRooms = $this->newRooms($schedule->getRooms());
|
$newRooms = $this->newRooms($schedule->getRooms());
|
||||||
return array_merge(
|
return array_merge(
|
||||||
$this->shiftsDiff($schedule, $scheduleUrl, $shiftType, $minutesBefore, $minutesAfter),
|
$this->shiftsDiff($schedule, $scheduleModel, $shiftType, $minutesBefore, $minutesAfter),
|
||||||
[$newRooms, $shiftType, $scheduleUrl, $schedule, $minutesBefore, $minutesAfter]
|
[$newRooms, $shiftType, $scheduleModel, $schedule, $minutesBefore, $minutesAfter]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +482,7 @@ class ScheduleController extends BaseController
|
||||||
*/
|
*/
|
||||||
protected function shiftsDiff(
|
protected function shiftsDiff(
|
||||||
Schedule $schedule,
|
Schedule $schedule,
|
||||||
ScheduleModel $scheduleUrl,
|
ScheduleModel $scheduleModel,
|
||||||
int $shiftType,
|
int $shiftType,
|
||||||
int $minutesBefore,
|
int $minutesBefore,
|
||||||
int $minutesAfter
|
int $minutesAfter
|
||||||
|
@ -500,7 +500,7 @@ class ScheduleController extends BaseController
|
||||||
|
|
||||||
foreach ($schedule->getDay() as $day) {
|
foreach ($schedule->getDay() as $day) {
|
||||||
foreach ($day->getRoom() as $room) {
|
foreach ($day->getRoom() as $room) {
|
||||||
if (!$scheduleUrl->activeLocations->where('name', $room->getName())->count()) {
|
if (!$scheduleModel->activeLocations->where('name', $room->getName())->count()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,7 +519,7 @@ class ScheduleController extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
$scheduleEventsGuidList = array_keys($scheduleEvents);
|
$scheduleEventsGuidList = array_keys($scheduleEvents);
|
||||||
$existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList);
|
$existingShifts = $this->getScheduleShiftsByGuid($scheduleModel, $scheduleEventsGuidList);
|
||||||
foreach ($existingShifts as $scheduleShift) {
|
foreach ($existingShifts as $scheduleShift) {
|
||||||
$guid = $scheduleShift->guid;
|
$guid = $scheduleShift->guid;
|
||||||
$shift = $scheduleShift->shift;
|
$shift = $scheduleShift->shift;
|
||||||
|
@ -545,7 +545,7 @@ class ScheduleController extends BaseController
|
||||||
$newEvents[$scheduleEvent->getGuid()] = $scheduleEvent;
|
$newEvents[$scheduleEvent->getGuid()] = $scheduleEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList);
|
$scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleModel, $scheduleEventsGuidList);
|
||||||
foreach ($scheduleShifts as $scheduleShift) {
|
foreach ($scheduleShifts as $scheduleShift) {
|
||||||
$event = $this->eventFromScheduleShift($scheduleShift);
|
$event = $this->eventFromScheduleShift($scheduleShift);
|
||||||
$deleteEvents[$event->getGuid()] = $event;
|
$deleteEvents[$event->getGuid()] = $event;
|
||||||
|
@ -588,11 +588,11 @@ class ScheduleController extends BaseController
|
||||||
*
|
*
|
||||||
* @return Collection|ScheduleShift[]
|
* @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')
|
return ScheduleShift::with('shift.location')
|
||||||
->whereIn('guid', $events)
|
->whereIn('guid', $events)
|
||||||
->where('schedule_id', $scheduleUrl->id)
|
->where('schedule_id', $schedule->id)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,11 +600,11 @@ class ScheduleController extends BaseController
|
||||||
* @param string[] $events
|
* @param string[] $events
|
||||||
* @return Collection|ScheduleShift[]
|
* @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')
|
return ScheduleShift::with('shift.location')
|
||||||
->whereNotIn('guid', $events)
|
->whereNotIn('guid', $events)
|
||||||
->where('schedule_id', $scheduleUrl->id)
|
->where('schedule_id', $schedule->id)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,511 @@
|
||||||
|
<?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
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8' ?>
|
||||||
|
<schedule
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://c3voc.de/schedule/schema.xsd"
|
||||||
|
>
|
||||||
|
<version>4.2-frickel</version>
|
||||||
|
<conference>
|
||||||
|
<title>Some Bigger Test Event</title>
|
||||||
|
<acronym>test-4</acronym>
|
||||||
|
<start>2042-10-01</start>
|
||||||
|
<end>2042-10-02</end>
|
||||||
|
<days>2</days>
|
||||||
|
<timeslot_duration>00:30</timeslot_duration>
|
||||||
|
</conference>
|
||||||
|
<day index='1' date='2042-10-01' start='2042-10-01T10:00:00+02:00' end='2042-10-01T22:59:00+02:00'>
|
||||||
|
<room name='Example Room'>
|
||||||
|
<event guid='3e896c59-0d90-4817-8f74-af7fbb758f32' id='1'>
|
||||||
|
<date>2042-10-02T10:00:00+00:00</date>
|
||||||
|
<title>First event</title>
|
||||||
|
<subtitle>With a subtitle</subtitle>
|
||||||
|
<start>10:00</start>
|
||||||
|
<duration>01:30</duration>
|
||||||
|
<room>Example Room</room>
|
||||||
|
<slug>first-1-event</slug>
|
||||||
|
<track>Testing</track>
|
||||||
|
<type>Talk</type>
|
||||||
|
<abstract>A minimal description</abstract>
|
||||||
|
<url>https://example.com/first-1-event</url>
|
||||||
|
<language>DE</language>
|
||||||
|
</event>
|
||||||
|
</room>
|
||||||
|
<room name='Another Room'>
|
||||||
|
<event guid='bf675f51-2a3d-4fa9-a5b2-390d5e5bdff2' id='2'>
|
||||||
|
<date>2042-10-02T13:30:00+00:00</date>
|
||||||
|
<title>Second event</title>
|
||||||
|
<subtitle>With a subtitle</subtitle>
|
||||||
|
<start>13:30</start>
|
||||||
|
<duration>00:30</duration>
|
||||||
|
<room>Another Room</room>
|
||||||
|
<slug>second-2-event</slug>
|
||||||
|
<track>Tinkering</track>
|
||||||
|
<type>Talk</type>
|
||||||
|
<abstract>A minimal description</abstract>
|
||||||
|
</event>
|
||||||
|
</room>
|
||||||
|
</day>
|
||||||
|
<day index='2' date='2042-10-02' start='2042-10-02T10:00:00+02:00' end='2042-10-02T22:59:00+02:00'>
|
||||||
|
<room name='Another Room'>
|
||||||
|
<event guid='6e662ec5-d18d-417d-8719-360a416bb153' id='3'>
|
||||||
|
<date>2042-10-02T11:00:00+00:00</date>
|
||||||
|
<title>Third event</title>
|
||||||
|
<subtitle>With a subtitle</subtitle>
|
||||||
|
<start>11:00</start>
|
||||||
|
<duration>00:30</duration>
|
||||||
|
<room>Another Room</room>
|
||||||
|
<slug>third-3-event</slug>
|
||||||
|
<track>Testing</track>
|
||||||
|
<type>Talk</type>
|
||||||
|
<abstract>A minimal description</abstract>
|
||||||
|
<url>https://example.com/third-3-event</url>
|
||||||
|
</event>
|
||||||
|
<event guid='c6999865-5329-43f2-8aca-85ae39932d09' id='42'>
|
||||||
|
<date>2042-10-02T11:45:00+00:00</date>
|
||||||
|
<title>Fourth event</title>
|
||||||
|
<subtitle>With a subtitle</subtitle>
|
||||||
|
<start>11:45</start>
|
||||||
|
<duration>00:30</duration>
|
||||||
|
<room>Another Room</room>
|
||||||
|
<slug>fourth-42-event</slug>
|
||||||
|
<track>Testing</track>
|
||||||
|
<type>Talk</type>
|
||||||
|
<abstract>A minimal description</abstract>
|
||||||
|
</event>
|
||||||
|
</room>
|
||||||
|
<room name='Third Room'>
|
||||||
|
<event guid='1248925b-f608-4ebc-8d37-7a22fadc51a4' id='55'>
|
||||||
|
<date>2042-10-02T10:00:00+00:00</date>
|
||||||
|
<title>Fifth event</title>
|
||||||
|
<subtitle>Will be ignored</subtitle>
|
||||||
|
<start>10:00</start>
|
||||||
|
<duration>01:00</duration>
|
||||||
|
<room>Third Room</room>
|
||||||
|
<slug>fifth-55-event</slug>
|
||||||
|
<track>Testing</track>
|
||||||
|
<type>Talk</type>
|
||||||
|
<abstract/>
|
||||||
|
</event>
|
||||||
|
</room>
|
||||||
|
</day>
|
||||||
|
</schedule>
|
Loading…
Reference in New Issue