From a2162866e3cc27c5dd9841feb881280f2e50ad06 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 10 Jun 2026 15:48:47 +0200 Subject: [PATCH 1/2] test: de-flake RelationsChecksumListenerTest media lookups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit testUpdateMedia, testAddMediaToSlideUpdatesChecksum and testRemoveMediaFromSlideUpdatesChecksum picked an arbitrary tenant-ABC slide with an unordered findOneBy() and then read the 'media' checksum key. The fixture set contains slide_abc_notified, which has no media and therefore no 'media' key, so the tests failed whenever the database happened to return that row first ("Undefined array key 'media'"). Look up slide_abc_1 by title instead — it is guaranteed media in fixtures/slide.yaml — matching the deterministic lookups the other tests in the class already use. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ .../EventListener/RelationsChecksumListenerTest.php | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e74f06f55..bd83d0daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- De-flaked `RelationsChecksumListenerTest`: the media-checksum tests now look up a fixture + slide that is guaranteed to have media, instead of an unordered `findOneBy()` that could + return the media-less `slide_abc_notified` and crash on the missing `media` checksum key. + ## [3.0.0-rc5] - 2026-06-10 - Added structured, channel-split application logging (ADR 011): per-domain Monolog channels with diff --git a/tests/EventListener/RelationsChecksumListenerTest.php b/tests/EventListener/RelationsChecksumListenerTest.php index 6619b007a..b12aa494c 100644 --- a/tests/EventListener/RelationsChecksumListenerTest.php +++ b/tests/EventListener/RelationsChecksumListenerTest.php @@ -211,8 +211,11 @@ public function testPersistSlide(): void public function testUpdateMedia(): void { $tenant = $this->em->getRepository(Tenant::class)->findOneBy(['tenantKey' => 'ABC']); + // Look up a fixture slide that is guaranteed to have media: an unordered + // findOneBy(['tenant' => ...]) can return slide_abc_notified, which has + // none — and then the 'media' checksum key doesn't exist. /** @var Tenant\Slide $slide */ - $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['tenant' => $tenant]); + $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_abc_1', 'tenant' => $tenant]); $before = $slide->getRelationsChecksum()['media']; @@ -526,8 +529,10 @@ public function testRemoveScreenFromScreenGroupUpdatesChecksum(): void public function testAddMediaToSlideUpdatesChecksum(): void { $tenant = $this->em->getRepository(Tenant::class)->findOneBy(['tenantKey' => 'ABC']); + // slide_abc_1 is guaranteed to have media; an unordered findOneBy can + // return slide_abc_notified, which has none (no 'media' checksum key). /** @var Tenant\Slide $slide */ - $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['tenant' => $tenant]); + $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_abc_1', 'tenant' => $tenant]); $beforeChecksum = $slide->getRelationsChecksum()['media']; // Find a media not already on this slide @@ -554,8 +559,10 @@ public function testAddMediaToSlideUpdatesChecksum(): void public function testRemoveMediaFromSlideUpdatesChecksum(): void { $tenant = $this->em->getRepository(Tenant::class)->findOneBy(['tenantKey' => 'ABC']); + // slide_abc_1 is guaranteed to have media; an unordered findOneBy can + // return slide_abc_notified, which has none (no 'media' checksum key). /** @var Tenant\Slide $slide */ - $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['tenant' => $tenant]); + $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_abc_1', 'tenant' => $tenant]); $this->assertGreaterThan(0, $slide->getMedia()->count()); $beforeChecksum = $slide->getRelationsChecksum()['media']; From efa263582b655380933ea6a2a64cd2ac063fa8ec Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 10 Jun 2026 16:07:06 +0200 Subject: [PATCH 2/2] test: use a dedicated fixture slide for the media checksum tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pinning slide_abc_1 by title was still flaky: testRemoveSlide() deletes the oldest slide of playlist_abc_1, and with randomised fixture createdAt values that is sometimes slide_abc_1 itself — the lookup then returned null on the shared per-class database. Add slide_media_checksum_test (tenant ABC, two known media, attached to no playlist) following the existing *_relations_checksum_test fixture convention, and use it in the three media checksum tests. No other test can mutate or delete it. The ABC slide count grows to 62, so bump the totalItems assertion in SlidesTest. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 8 +++++--- fixtures/slide.yaml | 7 +++++++ tests/Api/SlidesTest.php | 2 +- .../RelationsChecksumListenerTest.php | 17 +++++------------ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 880efbd37..6fd185d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -- De-flaked `RelationsChecksumListenerTest`: the media-checksum tests now look up a fixture - slide that is guaranteed to have media, instead of an unordered `findOneBy()` that could - return the media-less `slide_abc_notified` and crash on the missing `media` checksum key. +- De-flaked `RelationsChecksumListenerTest`: the media-checksum tests now use a dedicated + fixture slide (`slide_media_checksum_test`) with known media and no playlist membership. + Previously an unordered `findOneBy()` could return the media-less `slide_abc_notified` + (crashing on the missing `media` checksum key) or a slide deleted earlier in the class by + `testRemoveSlide()`, which removes a random-`createdAt` fixture slide from the shared DB. - Fixed the 2.x → 3.0 screen client auto-upgrade path: ship a copy of `release.json` at the deprecated `/client/release.json` location polled by 2.x clients. Without it the catch-all `/client` route answered with the SPA's HTML and already-running 2.x screens never detected diff --git a/fixtures/slide.yaml b/fixtures/slide.yaml index 782e8d2a0..7c8c0cf7f 100644 --- a/fixtures/slide.yaml +++ b/fixtures/slide.yaml @@ -37,3 +37,10 @@ App\Entity\Tenant\Slide: slide_relations_checksum_test (extends slide): title: "slide_relations_checksum_test" tenant: "@tenant_xyz" + # Dedicated to the media tests in RelationsChecksumListenerTest: known media + # (two distinct, so one can be removed and the 'media' checksum key remains) + # and on no playlist, so testRemoveSlide() can never delete it. + slide_media_checksum_test (extends slide): + title: "slide_media_checksum_test" + tenant: "@tenant_abc" + media: ["@media_abc_1", "@media_abc_2"] diff --git a/tests/Api/SlidesTest.php b/tests/Api/SlidesTest.php index 492ef980b..d4a3e614c 100644 --- a/tests/Api/SlidesTest.php +++ b/tests/Api/SlidesTest.php @@ -22,7 +22,7 @@ public function testGetCollection(): void '@context' => '/contexts/Slide', '@id' => '/v2/slides', '@type' => 'hydra:Collection', - 'hydra:totalItems' => 61, + 'hydra:totalItems' => 62, 'hydra:view' => [ '@id' => '/v2/slides?itemsPerPage=10&page=1', '@type' => 'hydra:PartialCollectionView', diff --git a/tests/EventListener/RelationsChecksumListenerTest.php b/tests/EventListener/RelationsChecksumListenerTest.php index b12aa494c..efe2dd556 100644 --- a/tests/EventListener/RelationsChecksumListenerTest.php +++ b/tests/EventListener/RelationsChecksumListenerTest.php @@ -210,12 +210,8 @@ public function testPersistSlide(): void public function testUpdateMedia(): void { - $tenant = $this->em->getRepository(Tenant::class)->findOneBy(['tenantKey' => 'ABC']); - // Look up a fixture slide that is guaranteed to have media: an unordered - // findOneBy(['tenant' => ...]) can return slide_abc_notified, which has - // none — and then the 'media' checksum key doesn't exist. /** @var Tenant\Slide $slide */ - $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_abc_1', 'tenant' => $tenant]); + $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_media_checksum_test']); $before = $slide->getRelationsChecksum()['media']; @@ -529,10 +525,8 @@ public function testRemoveScreenFromScreenGroupUpdatesChecksum(): void public function testAddMediaToSlideUpdatesChecksum(): void { $tenant = $this->em->getRepository(Tenant::class)->findOneBy(['tenantKey' => 'ABC']); - // slide_abc_1 is guaranteed to have media; an unordered findOneBy can - // return slide_abc_notified, which has none (no 'media' checksum key). /** @var Tenant\Slide $slide */ - $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_abc_1', 'tenant' => $tenant]); + $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_media_checksum_test']); $beforeChecksum = $slide->getRelationsChecksum()['media']; // Find a media not already on this slide @@ -558,11 +552,8 @@ public function testAddMediaToSlideUpdatesChecksum(): void public function testRemoveMediaFromSlideUpdatesChecksum(): void { - $tenant = $this->em->getRepository(Tenant::class)->findOneBy(['tenantKey' => 'ABC']); - // slide_abc_1 is guaranteed to have media; an unordered findOneBy can - // return slide_abc_notified, which has none (no 'media' checksum key). /** @var Tenant\Slide $slide */ - $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_abc_1', 'tenant' => $tenant]); + $slide = $this->em->getRepository(Tenant\Slide::class)->findOneBy(['title' => 'slide_media_checksum_test']); $this->assertGreaterThan(0, $slide->getMedia()->count()); $beforeChecksum = $slide->getRelationsChecksum()['media']; @@ -573,6 +564,8 @@ public function testRemoveMediaFromSlideUpdatesChecksum(): void $this->em->flush(); $this->em->refresh($slide); + // The fixture slide has two media, so one remains after the removal and + // the 'media' checksum key still exists. $this->assertNotEquals($beforeChecksum, $slide->getRelationsChecksum()['media']); $this->assertFalse($slide->isChanged()); }