feat: chatbot trip search finds trips by trip name, hotel name and code
This commit is contained in:
1 parent
26d4140c18
commit
fe40e1807e
8 files changed
+448
-52
No files matched your search
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Chatbot;
|
||||
|
||||
use App\BusProNet\Model\Hotel;
|
||||
use App\BusProNet\XmlLoader\HotelLoader;
|
||||
use App\Chatbot\TripCatalog;
|
||||
use App\Entity\ContentDocument;
|
||||
use App\Enum\ContentFetchState;
|
||||
@@ -35,6 +37,7 @@ class TripCatalogTest extends TestCase
|
||||
'code' => 'VTH',
|
||||
'dateCode' => 'VTH2',
|
||||
'hotel' => 'Hotel Zwei',
|
||||
'hotelCode' => 'HOT2',
|
||||
'accommodation' => null,
|
||||
'destination' => ['country' => 'Frankreich'],
|
||||
'concept' => 'Skiurlaub ohne Programm',
|
||||
@@ -115,6 +118,91 @@ class TripCatalogTest extends TestCase
|
||||
self::assertCount(0, $this->catalog($rows)->search(new TripCriteria(concept: 'party'))['results']);
|
||||
}
|
||||
|
||||
public function testANameInTheQueryReturnsWhatItNamesAndNotWhatMentionsIt(): void
|
||||
{
|
||||
$rows = [
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT1', [$this->room(500)]),
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT2', [$this->room(480)]),
|
||||
$this->row('VTH', 3, '2027-01-09', 'HOT1', [$this->room(520)]),
|
||||
];
|
||||
// Every page mentions every word: the texts must not be asked when a name answers.
|
||||
$catalog = $this->catalog($rows, static fn (): array => [['type' => 'product', 'uid' => 1]]);
|
||||
$search = static fn (string $query): array => array_map(
|
||||
static fn (array $result): string => $result['dateCode'].'/'.$result['hotelCode'],
|
||||
$catalog->search(new TripCriteria(query: $query))['results'],
|
||||
);
|
||||
|
||||
self::assertSame(['VTH2/HOT2'], $search('Hotel Zwei'), 'a hotel stands for itself, not for its trip');
|
||||
self::assertSame(['VTH2/HOT2'], $search('zwei'));
|
||||
self::assertSame(['VTH2/HOT2'], $search('Gibt es das Hotel Zwei in Frankreich?'), 'spelled out among other words, the hotel beats the country');
|
||||
self::assertSame(['VTH2/HOT2', 'VTH2/HOT1', 'VTH3/HOT1'], $search('Hotel'));
|
||||
self::assertSame(['VTH2/HOT2', 'VTH2/HOT1', 'VTH3/HOT1'], $search('val thorens'));
|
||||
self::assertSame(['VTH2/HOT2', 'VTH2/HOT1', 'VTH3/HOT1'], $search('Urlaub in Frankreich'));
|
||||
self::assertSame([], $search('Gruppenreise Uni X'), 'a hidden trip is not offered, and nothing else in its place');
|
||||
}
|
||||
|
||||
public function testACodeInTheQueryFindsItsTripHotelOrDate(): void
|
||||
{
|
||||
$catalog = $this->catalog([
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT1', [$this->room(500)]),
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT2', [$this->room(480)]),
|
||||
$this->row('VTH', 3, '2027-01-09', 'HOT1', [$this->room(520)]),
|
||||
]);
|
||||
$search = static fn (string $query): array => array_map(
|
||||
static fn (array $result): string => $result['dateCode'].'/'.$result['hotelCode'],
|
||||
$catalog->search(new TripCriteria(query: $query))['results'],
|
||||
);
|
||||
|
||||
self::assertSame(['VTH2/HOT2', 'VTH2/HOT1', 'VTH3/HOT1'], $search('vth'));
|
||||
self::assertSame(['VTH2/HOT1', 'VTH3/HOT1'], $search('HOT1ALIAS'));
|
||||
self::assertSame(['VTH3/HOT1'], $search('vth-3'));
|
||||
}
|
||||
|
||||
public function testHotelsAreAlsoFoundByTheirBusProNameAndShownByItWithoutAWebsiteRecord(): void
|
||||
{
|
||||
$catalog = $this->catalog([
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT1', [$this->room(500)]),
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT2', [$this->room(480)]),
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT3', [$this->room(520)]),
|
||||
]);
|
||||
$search = static fn (string $query): array => array_map(
|
||||
static fn (array $result): string => $result['hotelCode'].'/'.$result['hotel'],
|
||||
$catalog->search(new TripCriteria(query: $query))['results'],
|
||||
);
|
||||
|
||||
self::assertSame(['HOT3/Oceanpeak'], $search('oceanpeak'), 'no website record: found and shown by BusPro\'s name');
|
||||
self::assertSame(['HOT3/Oceanpeak'], $search('hot3'));
|
||||
self::assertSame(['HOT3/Oceanpeak'], $search('Hotel Oceanpeak in Frankreich'));
|
||||
self::assertSame(['HOT1/Hotel Eins'], $search('Residence Eins'), 'BusPro\'s wording finds the hotel, the website\'s names it');
|
||||
self::assertSame('Oceanpeak', $catalog->get('VTH', 'HOT3')['dates'][0]['hotels'][0]['hotel'] ?? null);
|
||||
self::assertSame([], $catalog->get('VTH', 'HOT3')['hotels'] ?? null, 'not the trip\'s other hotels');
|
||||
}
|
||||
|
||||
public function testOtherWordsAreLookedUpInThePageTextsEveryTermBeforeAnyTerm(): void
|
||||
{
|
||||
$rows = [
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT1', [$this->room(500)]),
|
||||
$this->row('VTH', 2, '2026-12-05', 'HOT2', [$this->room(480)]),
|
||||
];
|
||||
$asked = [];
|
||||
$hits = static function (string $booleanQuery) use (&$asked): array {
|
||||
$asked[] = $booleanQuery;
|
||||
|
||||
return match ($booleanQuery) {
|
||||
'+sauna* +whirlpool*' => [['type' => 'hotel', 'uid' => 11]],
|
||||
'kamin* whirlpool*' => [['type' => 'country', 'uid' => 2]],
|
||||
default => [],
|
||||
};
|
||||
};
|
||||
$search = fn (string $query): array => array_column($this->catalog($rows, $hits)->search(new TripCriteria(query: $query))['results'], 'hotelCode');
|
||||
|
||||
self::assertSame(['HOT2'], $search('Sauna Whirlpool'));
|
||||
self::assertSame(['+sauna* +whirlpool*'], $asked);
|
||||
self::assertSame(['HOT2', 'HOT1'], $search('Kamin Whirlpool'), 'no page has both, so any of them');
|
||||
self::assertSame([], $search('Kamin'));
|
||||
self::assertSame(['HOT2', 'HOT1'], $search('was ist das'), 'nothing searchable restricts nothing');
|
||||
}
|
||||
|
||||
public function testGetJoinsHotelsOnTheirAliasesAndPreferTheOwner(): void
|
||||
{
|
||||
$trip = $this->catalog([
|
||||
@@ -192,9 +280,10 @@ class TripCatalogTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $rows
|
||||
* @param list<array<string, mixed>> $rows
|
||||
* @param (\Closure(string): list<array{type: string, uid: int}>)|null $textHits the full-text index, by boolean query
|
||||
*/
|
||||
private function catalog(array $rows): TripCatalog
|
||||
private function catalog(array $rows, ?\Closure $textHits = null): TripCatalog
|
||||
{
|
||||
$export = $this->createStub(ProductExportBuilder::class);
|
||||
$export->method('build')->willReturn(new Export($rows, 0, 0, new \DateTimeImmutable('2026-10-08 09:00:00+00:00'), new \DateTimeImmutable('2026-10-08 06:00:00+00:00')));
|
||||
@@ -213,9 +302,21 @@ class TripCatalogTest extends TestCase
|
||||
|
||||
$repository = $this->createStub(ContentDocumentRepository::class);
|
||||
$repository->method('findRecords')->willReturn($records);
|
||||
$repository->method('matchRecords')->willReturn([]);
|
||||
$repository->method('matchRecords')->willReturnCallback($textHits ?? static fn (): array => []);
|
||||
|
||||
return new TripCatalog($export, $repository, new MockClock('2026-10-08 10:00:00'), new NullLogger());
|
||||
$hotels = [];
|
||||
foreach (['HOT1' => "Agence X\nRésidence Eins Wochenreisen", 'HOT3' => 'Oceanpeak'] as $code => $name) {
|
||||
$hotel = new Hotel();
|
||||
$hotel->id = crc32($code);
|
||||
$hotel->code = $code;
|
||||
$hotel->name = $name;
|
||||
$hotels[$hotel->id] = $hotel;
|
||||
}
|
||||
|
||||
$hotelLoader = $this->createStub(HotelLoader::class);
|
||||
$hotelLoader->method('loadAll')->willReturn($hotels);
|
||||
|
||||
return new TripCatalog($export, $repository, $hotelLoader, new MockClock('2026-10-08 10:00:00'), new NullLogger());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user