Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/Domain/YouTube/YouTubeMatchScorer.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,26 @@ private function videoTitleContainsSameLocationAndSeason(string $videoTitle, IFS
}
}

return
if (
str_contains(
str_replace(' ', '', $normalizedTitle),
str_replace(' ', '', $this->textNormalizer->normalize($event->location)),
);
)
) {
return true;
}

foreach ($this->eventNameTokens($this->textNormalizer->normalize($event->eventName)) as $token) {
if (is_numeric($token)) {
continue;
}

if (str_contains($normalizedTitle, $token)) {
return true;
}
}

return false;
}

/** @param Tag[] $videoTags */
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/Domain/YouTube/YouTubeLinkMatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,33 @@ final class YouTubeLinkMatcherTest extends TestCase
$this->assertSame('https://youtu.be/para-qual-id', $liveStream->url);
}

#[Test] public function alcobendas_location_matches_comunidad_de_madrid_video_title(): void
{
$event = $this->createEvent(
eventName: 'World Climbing Series Comunidad de Madrid 2026',
location: 'Alcobendas',
localStartDate: '2026-05-28T08:00:00Z',
localEndDate: '2026-05-30T20:00:00Z',
);
$videoCollection = new YouTubeVideoCollection();
$videoCollection->add(new YouTubeVideo(
title: "Men's Boulder semi-final | Comunidad de Madrid 2026",
duration: 120,
videoId: 'sfA6nto-LFw',
publishedAt: new DateTimeImmutable('2026-05-28T22:16:13Z'),
scheduledStartTime: null,
restrictedRegions: [],
));

$liveStream = $this->linkMatcher->findStreamUrlForRound(
event: $event,
roundName: "Men's Boulder Semi-Final",
videoCollection: $videoCollection,
);

$this->assertSame('https://youtu.be/sfA6nto-LFw', $liveStream->url);
}

private function createVideoCollection(): YouTubeVideoCollection
{
$titles = [
Expand Down
65 changes: 65 additions & 0 deletions tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,71 @@ final class YouTubeMatchScorerTest extends TestCase
$this->assertNotNull($score);
}

#[Test] public function event_name_tokens_match_when_location_differs(): void
{
$video = $this->createVideo(
title: "Women's Boulder semi-final | Comunidad de Madrid 2026",
duration: 120,
publishedAt: '2026-05-29T10:00:00Z',
scheduledStartTime: '2026-05-29T12:00:00Z',
);

$score = $this->matchScorer->score(
video: $video,
roundTags: $this->roundTags("Women's Boulder Semi-Final"),
event: new IFSCEventInfo(
eventId: 1479,
eventName: 'World Climbing Series Comunidad de Madrid 2026',
slug: 'world-climbing-series-comunidad-de-madrid-2026',
leagueId: 10,
leagueName: 'World Climbing Series',
leagueSeasonId: 99,
localStartDate: '2026-05-28T08:00:00Z',
localEndDate: '2026-05-30T20:00:00Z',
timeZone: new DateTimeZone('Europe/Madrid'),
location: 'Alcobendas',
country: 'ESP',
disciplines: [],
categories: [],
),
);

$this->assertNotNull($score);
$this->assertGreaterThanOrEqual(14, $score);
}

#[Test] public function different_location_video_is_rejected_even_when_same_year(): void
{
$video = $this->createVideo(
title: "Lead semi-finals | Wujiang 2026",
duration: 90,
publishedAt: '2026-05-21T11:00:00Z',
scheduledStartTime: '2026-05-21T12:00:00Z',
);

$score = $this->matchScorer->score(
video: $video,
roundTags: $this->roundTags("Men's & Women's Lead Semi-Final"),
event: new IFSCEventInfo(
eventId: 1500,
eventName: 'World Climbing Series Chamonix 2026',
slug: 'world-climbing-series-chamonix-2026',
leagueId: 10,
leagueName: 'World Climbing Series',
leagueSeasonId: 99,
localStartDate: '2026-05-20T08:00:00Z',
localEndDate: '2026-05-22T20:00:00Z',
timeZone: new DateTimeZone('Europe/Paris'),
location: 'Chamonix',
country: 'FRA',
disciplines: [],
categories: [],
),
);

$this->assertNull($score);
}

/** @return Tag[] */
private function roundTags(string $roundName): array
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php declare(strict_types=1);

/**
* @license http://opensource.org/licenses/mit-license.php MIT
* @link https://github.com/nicoSWD
* @author Nicolas Oelgart <nico@ifsc.stream>
*/
namespace SportClimbing\IfscCalendar\tests\Infrastructure\Calendar;

use DateTimeImmutable;
use DateTimeZone;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use SportClimbing\IfscCalendar\Domain\Discipline\IFSCDiscipline;
use SportClimbing\IfscCalendar\Domain\Discipline\IFSCDisciplines;
use SportClimbing\IfscCalendar\Domain\Event\IFSCEvent;
use SportClimbing\IfscCalendar\Domain\Round\IFSCRound;
use SportClimbing\IfscCalendar\Domain\Round\IFSCRoundCategory;
use SportClimbing\IfscCalendar\Domain\Round\IFSCRoundKind;
use SportClimbing\IfscCalendar\Domain\Round\IFSCRoundStatus;
use SportClimbing\IfscCalendar\Domain\Season\IFSCSeasonYear;
use SportClimbing\IfscCalendar\Domain\Stream\LiveStream;
use SportClimbing\IfscCalendar\Infrastructure\Calendar\CalendarFactory;
use SportClimbing\IfscCalendar\Infrastructure\Calendar\ICalCalendar;
use SportClimbing\IfscCalendar\Infrastructure\Calendar\JsonCalendar;

final class CalendarSpeedRelayFormattingTest extends TestCase
{
#[Test] public function json_calendar_uses_speed_relay_in_round_name_and_speed_in_disciplines(): void
{
$calendar = new JsonCalendar();
$output = $calendar->generateForEvents([$this->createEventWithSpeedRelayRound()]);
$payload = json_decode($output, associative: true, flags: JSON_THROW_ON_ERROR);

$this->assertSame(['speed'], $payload['events'][0]['disciplines']);
$this->assertSame("Men's Speed Relay Qualification", $payload['events'][0]['rounds'][0]['name']);
$this->assertSame(['speed'], $payload['events'][0]['rounds'][0]['disciplines']);
}

#[Test] public function ical_calendar_uses_speed_relay_in_summary(): void
{
$calendar = new ICalCalendar(
calendarFactory: new CalendarFactory(),
productIdentifier: '-//ifsc-calendar//tests//EN',
publishedTtl: 'PT1H',
calendarName: 'IFSC Calendar Tests',
);

$output = $calendar->generateForEvents([$this->createEventWithSpeedRelayRound()]);

$this->assertStringContainsString("Men's Speed Relay Qualification - Madrid (ES)", $output);
$this->assertStringNotContainsString('Speed_Relay', $output);
}

private function createEventWithSpeedRelayRound(): IFSCEvent
{
$timeZone = new DateTimeZone('Europe/Madrid');
$roundStart = new DateTimeImmutable('2026-09-01 10:00:00', $timeZone);
$roundEnd = new DateTimeImmutable('2026-09-01 11:30:00', $timeZone);
$round = new IFSCRound(
name: "Men's Speed Relay Qualification",
categories: [IFSCRoundCategory::MEN],
disciplines: new IFSCDisciplines([IFSCDiscipline::SPEED_RELAY]),
kind: IFSCRoundKind::QUALIFICATION,
liveStream: new LiveStream(url: 'https://youtube.com/watch?v=relay-test'),
startTime: $roundStart,
endTime: $roundEnd,
status: IFSCRoundStatus::CONFIRMED,
);

return new IFSCEvent(
season: IFSCSeasonYear::SEASON_2026,
eventId: 9999,
slug: 'ifsc-speed-relay-test-2026',
leagueName: 'World Cups and World Championships',
timeZone: $timeZone,
eventName: 'IFSC Speed Relay Test Event 2026',
location: 'Madrid',
country: 'ES',
siteUrl: 'https://ifsc.stream/season/2026/event/ifsc-speed-relay-test-2026',
infosheetUrl: null,
startsAt: $roundStart,
endsAt: $roundEnd,
disciplines: [IFSCDiscipline::SPEED_RELAY],
rounds: [$round],
startList: [],
startListTotal: 0,
ticketsSummary: null,
ticketsPurchaseUrl: null,
countryName: 'Spain',
);
}
}
Loading