Mirrors aqua-iq's pattern but adapted for pictureFrame's stack: postgres 16, php 8.4, node 22, imagick + pcov via apt extras, Mercure hub at https://pictureframe.ddev.site/.well-known/mercure, and four custom commands — `ddev tests`, `ddev coverage`, `ddev frontend` (vite HMR), `ddev worker`. Also restores dev deps (DAMA, Doctrine fixtures, symfony/uid) that got dropped during earlier composer reshuffles, and adds a separate `db_test` connection in .env.test so DAMA's transactional isolation doesn't share state with whatever dev is mid-experiment with. Two test fixes the new env exposed: - RotationServiceTest::test_prioritize_never_shown_falls_through_when_all_shown needed uniquenessWindow=2 so the recent-window filter wipes the set and the fallback restores the full pool — otherwise window=1 excluded the most-recently-served image and the assertion drifted. - DeviceImageControllerTest::test_locked_image_served_without_rotation_advance was asserting currentImage stays null on a lock poll, but the controller intentionally sets currentImage on the lock path so Home reflects the live frame state. Now asserts both the currentImage update AND that no DeviceImageHistory row was written (the actual rotation-bypass guarantee). Backend coverage (full suite via `ddev coverage`): 89.08% lines / 92.24% methods / 74.36% classes — the first real number we've had. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,7 @@ class DeviceImageControllerTest extends AppWebTestCase
|
||||
$device = $setup['device'];
|
||||
$deviceId = $device->getId();
|
||||
$image = $setup['image'];
|
||||
$imageId = $image->getId();
|
||||
|
||||
$device->setLockedImage($image);
|
||||
$this->em()->flush();
|
||||
@@ -154,10 +155,25 @@ class DeviceImageControllerTest extends AppWebTestCase
|
||||
|
||||
$this->assertResponseStatusCodeSame(200);
|
||||
|
||||
// Re-fetch from DB; currentImage should still be null (advance() was never called)
|
||||
// After the locked-image poll, currentImage now points at the locked
|
||||
// image — the controller sets it directly because RotationService::
|
||||
// advance() was bypassed (lock takes precedence). This is what makes
|
||||
// Home's preview reflect what the frame is actually showing. The
|
||||
// "without rotation advance" guarantee is verified separately by
|
||||
// checking that no DeviceImageHistory row was written.
|
||||
$this->em()->clear();
|
||||
$device = $this->em()->find(\App\Entity\Device::class, $deviceId);
|
||||
$this->assertNull($device->getCurrentImage());
|
||||
$this->assertNotNull($device->getCurrentImage());
|
||||
$this->assertSame($imageId, $device->getCurrentImage()->getId());
|
||||
|
||||
$historyCount = (int) $this->em()->createQueryBuilder()
|
||||
->select('COUNT(h.id)')
|
||||
->from(\App\Entity\DeviceImageHistory::class, 'h')
|
||||
->where('h.device = :d')
|
||||
->setParameter('d', $device)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
$this->assertSame(0, $historyCount, 'lock path must NOT write a history row');
|
||||
}
|
||||
|
||||
public function test_returns_304_when_locked_image_matches_current_image_id(): void
|
||||
|
||||
@@ -480,7 +480,11 @@ class RotationServiceTest extends AppKernelTestCase
|
||||
}
|
||||
|
||||
// RM-06: prioritizeNeverShown is a no-op when no never-shown images
|
||||
// remain — falls through to the mode.
|
||||
// remain — falls through to the mode. Uses a uniquenessWindow large
|
||||
// enough that the recent-window filter wipes the candidate set and the
|
||||
// fallback restores the full pool, otherwise window=1 would exclude the
|
||||
// most-recently-served image and the mode-picks-from-2 assertion is
|
||||
// testing the wrong axis.
|
||||
public function test_prioritize_never_shown_falls_through_when_all_shown(): void
|
||||
{
|
||||
[$device, $images] = $this->setupDeviceAndImages('prio-fall@example.com', [
|
||||
@@ -488,10 +492,12 @@ class RotationServiceTest extends AppKernelTestCase
|
||||
]);
|
||||
$device->setRotationMode(RotationMode::NewestUpload);
|
||||
$device->setPrioritizeNeverShown(true);
|
||||
$device->setUniquenessWindow(1);
|
||||
$device->setUniquenessWindow(2);
|
||||
self::em()->flush();
|
||||
|
||||
// Both shown — never-shown set is empty, so mode (NewestUpload) takes over.
|
||||
// Both shown — never-shown set is empty, so mode (NewestUpload) takes
|
||||
// over. Window=2 wipes both via the recent filter and the fallback
|
||||
// restores the full pool, so the mode genuinely chooses between both.
|
||||
$this->recordHistoryAt($device, $images[0], '2025-06-01');
|
||||
$this->recordHistoryAt($device, $images[1], '2025-06-02');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user