<?php
|
declare(strict_types=1);
|
|
namespace App\Tests\Functional;
|
|
use App\Entity\OustedBook;
|
use App\Tests\DatabaseTestCase;
|
|
final class InventoryTest extends DatabaseTestCase {
|
public function testPageAndServiceWorkerAreServed(): void {
|
$this->client->request('GET', '/inventory/');
|
self::assertResponseIsSuccessful();
|
self::assertSelectorExists('#scan-input');
|
|
$this->client->request('GET', '/inventory/sw.js');
|
self::assertResponseIsSuccessful();
|
self::assertResponseHeaderSame('Content-Type', 'text/javascript; charset=UTF-8');
|
self::assertStringContainsString('"/js/inventory.js"', (string)$this->client->getResponse()->getContent());
|
}
|
|
public function testBookListContainsStandardNumbersAndPlacement(): void {
|
$this->createBook(2, 'Solaris', 'Stanisław Lem')->setIsbn('9788373843455')->setIssn('9770867041003')->setPlacement('HOL/4/1');
|
$this->createBook(1, 'Plain')->lendTo('A Reader', new \DateTimeImmutable());
|
$this->entityManager()->flush();
|
|
$this->client->request('GET', '/inventory/books');
|
|
self::assertResponseIsSuccessful();
|
self::assertSame(['books' => [
|
['id' => 1, 'title' => 'Plain', 'author' => 'Author', 'placement' => null, 'lastSeen' => null, 'borrowed' => true, 'isbn' => [], 'issn' => []],
|
['id' => 2, 'title' => 'Solaris', 'author' => 'Stanisław Lem', 'placement' => 'HOL/4/1', 'lastSeen' => null, 'borrowed' => false, 'isbn' => ['9788373843455'], 'issn' => ['9770867041003']],
|
]], $this->jsonResponse());
|
}
|
|
public function testSyncAssignsLocationsAndMarksBooksAsSeen(): void {
|
$this->createBook(1)->setPlacement('OLD/1');
|
$this->createBook(2);
|
$before = new \DateTimeImmutable('-1 second');
|
|
$this->sync(['1' => 'HOL/4/1', '2' => ' Attic ', '99' => 'X', '3' => '']);
|
|
self::assertResponseIsSuccessful();
|
self::assertSame(['results' => [
|
'1' => 'saved',
|
'2' => 'saved',
|
'99' => 'unknown_book',
|
'3' => 'invalid',
|
]], $this->jsonResponse());
|
|
$book = $this->findBook(1);
|
self::assertSame('HOL/4/1', $book?->getPlacement());
|
self::assertGreaterThanOrEqual($before, $book?->getLastSeen());
|
self::assertSame('Attic', $this->findBook(2)?->getPlacement());
|
}
|
|
public function testScanningABorrowedBookMarksItAsReturned(): void {
|
$lentAt = new \DateTimeImmutable('2026-10-01 10:00');
|
$this->createBook(1)->lendTo('A Reader', $lentAt);
|
$this->entityManager()->flush();
|
$this->entityManager()->clear();
|
|
$this->sync(['1' => 'HOL/4/1']);
|
|
self::assertSame(['results' => ['1' => 'returned']], $this->jsonResponse());
|
$book = $this->findBook(1);
|
self::assertFalse($book?->isBorrowed());
|
self::assertSame('HOL/4/1', $book?->getPlacement());
|
|
$history = $book?->getBorrowHistory()->first();
|
self::assertSame('A Reader', $history ? $history->getWho() : null);
|
self::assertEquals($lentAt, $history ? $history->getAt() : null);
|
self::assertEquals($book?->getLastSeen(), $history ? $history->getTill() : null);
|
|
$this->sync(['1' => 'HOL/4/1']);
|
self::assertSame(['results' => ['1' => 'saved']], $this->jsonResponse());
|
self::assertCount(1, $this->findBook(1)?->getBorrowHistory() ?? []);
|
}
|
|
public function testSyncAcceptsBookZero(): void {
|
$this->createBook(0);
|
|
$this->sync((object)['0' => 'HOL/1/1']);
|
|
self::assertResponseIsSuccessful();
|
self::assertSame('HOL/1/1', $this->findBook(0)?->getPlacement());
|
}
|
|
public function testSyncRejectsAnythingButAJsonMap(): void {
|
$this->createBook(1);
|
|
$this->client->request('POST', '/inventory/sync', ['1' => 'HOL/4/1']);
|
self::assertResponseStatusCodeSame(415);
|
|
$this->sync([['id' => 1, 'location' => 'HOL/4/1']]);
|
self::assertResponseStatusCodeSame(400);
|
|
$this->sync(['1' => ['nested']]);
|
self::assertResponseStatusCodeSame(400);
|
|
self::assertNull($this->findBook(1)?->getPlacement());
|
}
|
|
public function testDetailsSayWhenBookWasNeverSeen(): void {
|
$this->createBook(1);
|
|
$this->client->request('GET', '/book/1');
|
|
self::assertSelectorTextContains('.book-last-seen td', 'not seen during an inventory yet');
|
}
|
|
public function testOustingKeepsLastSeen(): void {
|
$this->createBook(1);
|
$this->sync(['1' => 'HOL/4/1']);
|
$seen = $this->findBook(1)?->getLastSeen();
|
|
$crawler = $this->client->request('GET', '/book/1');
|
self::assertSelectorTextContains('.book-last-seen td', (string)$seen?->format('Y-m-d H:i'));
|
$this->client->submit($crawler->selectButton('Take out')->form(['oust[reason]' => 'Lost']));
|
|
$this->entityManager()->clear();
|
self::assertEquals($seen, $this->entityManager()->find(OustedBook::class, 1)?->getLastSeen());
|
$this->client->request('GET', '/ousted/1');
|
self::assertSelectorTextContains('.book-last-seen td', (string)$seen?->format('Y-m-d H:i'));
|
}
|
|
/**
|
* @param array<mixed>|object $body
|
*/
|
private function sync(array|object $body): void {
|
$this->client->request('POST', '/inventory/sync', server: ['CONTENT_TYPE' => 'application/json'], content: json_encode($body, \JSON_THROW_ON_ERROR));
|
}
|
|
/**
|
* @return array<mixed>
|
*/
|
private function jsonResponse(): array {
|
return json_decode((string)$this->client->getResponse()->getContent(), true, flags: \JSON_THROW_ON_ERROR);
|
}
|
}
|