@@ -396,6 +396,107 @@ public function testCampaignsLengthAndActiveCampaignsLength(): void
396396 $ this ->assertResponseStatusCodeSame (204 );
397397 }
398398
399+ public function testCampaignsLengthViaScreenGroupAndDeduplication (): void
400+ {
401+ $ client = $ this ->getAuthenticatedClient ('ROLE_ADMIN ' );
402+
403+ $ layoutIri = $ this ->findIriBy (ScreenLayout::class, ['title ' => 'full screen ' ]);
404+
405+ // Create a fresh screen
406+ $ screenResponse = $ client ->request ('POST ' , '/v2/screens ' , [
407+ 'json ' => [
408+ 'title ' => 'Test indirect campaigns screen ' ,
409+ 'layout ' => $ layoutIri ,
410+ ],
411+ 'headers ' => ['Content-Type ' => 'application/ld+json ' ],
412+ ]);
413+ $ this ->assertResponseStatusCodeSame (201 );
414+ $ screenIri = $ screenResponse ->toArray ()['@id ' ];
415+ $ screenUlid = $ this ->iriHelperUtils ->getUlidFromIRI ($ screenIri );
416+
417+ // Create a screen group
418+ $ screenGroupResponse = $ client ->request ('POST ' , '/v2/screen-groups ' , [
419+ 'json ' => ['title ' => 'Test indirect campaigns group ' ],
420+ 'headers ' => ['Content-Type ' => 'application/ld+json ' ],
421+ ]);
422+ $ this ->assertResponseStatusCodeSame (201 );
423+ $ screenGroupIri = $ screenGroupResponse ->toArray ()['@id ' ];
424+ $ screenGroupUlid = $ this ->iriHelperUtils ->getUlidFromIRI ($ screenGroupIri );
425+
426+ // Create an active campaign
427+ $ campaignResponse = $ client ->request ('POST ' , '/v2/playlists ' , [
428+ 'json ' => [
429+ 'title ' => 'Indirect campaign ' ,
430+ 'isCampaign ' => true ,
431+ 'published ' => [
432+ 'from ' => '2020-01-01T00:00:00.000Z ' ,
433+ 'to ' => '2099-01-01T00:00:00.000Z ' ,
434+ ],
435+ ],
436+ 'headers ' => ['Content-Type ' => 'application/ld+json ' ],
437+ ]);
438+ $ this ->assertResponseStatusCodeSame (201 );
439+ $ campaignIri = $ campaignResponse ->toArray ()['@id ' ];
440+ $ campaignUlid = $ this ->iriHelperUtils ->getUlidFromIRI ($ campaignIri );
441+
442+ // Add screen to the screen group
443+ $ client ->request ('PUT ' , '/v2/screens/ ' .$ screenUlid .'/screen-groups ' , [
444+ 'json ' => [$ screenGroupUlid ],
445+ 'headers ' => ['Content-Type ' => 'application/ld+json ' ],
446+ ]);
447+ $ this ->assertResponseStatusCodeSame (201 );
448+
449+ // Link campaign to the screen group (indirect path)
450+ $ client ->request ('PUT ' , '/v2/screen-groups/ ' .$ campaignUlid .'/campaigns ' , [
451+ 'json ' => [(object ) ['screengroup ' => $ screenGroupUlid ]],
452+ 'headers ' => ['Content-Type ' => 'application/ld+json ' ],
453+ ]);
454+ $ this ->assertResponseStatusCodeSame (201 );
455+
456+ // Verify the indirect campaign is counted
457+ $ client ->request ('GET ' , $ screenIri , ['headers ' => ['Content-Type ' => 'application/ld+json ' ]]);
458+ $ this ->assertResponseIsSuccessful ();
459+ $ this ->assertJsonContains ([
460+ 'campaignsLength ' => 1 ,
461+ 'activeCampaignsLength ' => 1 ,
462+ ]);
463+
464+ // Now also link the same campaign directly to the screen
465+ $ client ->request ('PUT ' , '/v2/screens/ ' .$ campaignUlid .'/campaigns ' , [
466+ 'json ' => [(object ) ['screen ' => $ screenUlid ]],
467+ 'headers ' => ['Content-Type ' => 'application/ld+json ' ],
468+ ]);
469+ $ this ->assertResponseStatusCodeSame (201 );
470+
471+ // Verify deduplication: campaign is linked both directly and via group,
472+ // but COUNT(DISTINCT) should count it only once
473+ $ client ->request ('GET ' , $ screenIri , ['headers ' => ['Content-Type ' => 'application/ld+json ' ]]);
474+ $ this ->assertResponseIsSuccessful ();
475+ $ this ->assertJsonContains ([
476+ 'campaignsLength ' => 1 ,
477+ 'activeCampaignsLength ' => 1 ,
478+ ]);
479+
480+ // Cleanup
481+ $ client ->request ('DELETE ' , '/v2/screens/ ' .$ screenUlid .'/campaigns/ ' .$ campaignUlid );
482+ $ this ->assertResponseStatusCodeSame (204 );
483+
484+ $ client ->request ('DELETE ' , '/v2/screen-groups/ ' .$ screenGroupUlid .'/campaigns/ ' .$ campaignUlid );
485+ $ this ->assertResponseStatusCodeSame (204 );
486+
487+ $ client ->request ('DELETE ' , '/v2/screens/ ' .$ screenUlid .'/screen-groups/ ' .$ screenGroupUlid );
488+ $ this ->assertResponseStatusCodeSame (204 );
489+
490+ $ client ->request ('DELETE ' , $ screenIri );
491+ $ this ->assertResponseStatusCodeSame (204 );
492+
493+ $ client ->request ('DELETE ' , $ screenGroupIri );
494+ $ this ->assertResponseStatusCodeSame (204 );
495+
496+ $ client ->request ('DELETE ' , $ campaignIri );
497+ $ this ->assertResponseStatusCodeSame (204 );
498+ }
499+
399500 public function testDeleteScreen (): void
400501 {
401502 $ client = $ this ->getAuthenticatedClient ('ROLE_ADMIN ' );
0 commit comments