236 lines
12 KiB
PHP
236 lines
12 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature;
|
||
|
||
use App\Models\AuditLog;
|
||
use App\Models\Campaign;
|
||
use App\Models\DirectoryGroup;
|
||
use App\Models\DirectoryUser;
|
||
use App\Models\Template;
|
||
use App\Models\Tenant;
|
||
use App\Models\User;
|
||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
use Illuminate\Http\UploadedFile;
|
||
use Illuminate\Support\Carbon;
|
||
use Illuminate\Support\Facades\File;
|
||
use Tests\TestCase;
|
||
|
||
class AdminCampaignsTest extends TestCase
|
||
{
|
||
use RefreshDatabase;
|
||
|
||
private Tenant $tenant;
|
||
private DirectoryGroup $group;
|
||
private string $dir;
|
||
|
||
// PNG 1×1 valide
|
||
private const PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||
|
||
private const OIDS = ['editor' => '22222222-2222-2222-2222-222222222222', 'admin' => '33333333-3333-3333-3333-333333333333'];
|
||
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
$this->dir = storage_path('framework/testing/img-'.uniqid());
|
||
config(['signature.admin_allowed_cidrs' => [], 'signature.image_path' => $this->dir, 'signature.timezone' => 'Europe/Paris']);
|
||
File::ensureDirectoryExists($this->dir);
|
||
file_put_contents($this->dir.'/banniere.png', base64_decode(self::PNG));
|
||
|
||
$this->tenant = Tenant::create(['name' => 'CABM', 'entra_tenant_id' => '11111111-1111-1111-1111-111111111111']);
|
||
$this->group = DirectoryGroup::create(['tenant_id' => $this->tenant->id, 'entra_oid' => '44444444-4444-4444-4444-444444444444', 'display_name' => 'signature_mail_directeurs']);
|
||
}
|
||
|
||
protected function tearDown(): void
|
||
{
|
||
File::deleteDirectory($this->dir);
|
||
parent::tearDown();
|
||
}
|
||
|
||
private function user(string $role): User
|
||
{
|
||
return User::firstOrCreate(['entra_oid' => self::OIDS[$role]], ['name' => $role, 'email' => "{$role}@x.fr", 'tenant_id' => $this->tenant->id, 'role' => $role]);
|
||
}
|
||
|
||
private function payload(array $over = []): array
|
||
{
|
||
return array_merge([
|
||
'name' => 'Encombrants', 'image_path' => 'banniere.png', 'alt_text' => 'Collecte des encombrants',
|
||
'link_url' => 'https://www.beziers-mediterranee.fr/encombrants',
|
||
'starts_at' => '2026-11-01T08:00', 'ends_at' => '2026-11-15T18:00', 'priority' => 20,
|
||
], $over);
|
||
}
|
||
|
||
private function campaign(array $over = []): Campaign
|
||
{
|
||
return Campaign::create($over + [
|
||
'tenant_id' => $this->tenant->id, 'name' => 'Encombrants', 'image_path' => 'banniere.png', 'alt_text' => 'Collecte',
|
||
'starts_at' => now()->subDay(), 'ends_at' => now()->addDay(), 'priority' => 10, 'active' => true,
|
||
]);
|
||
}
|
||
|
||
private function agent(int $n, bool $inGroup = false): DirectoryUser
|
||
{
|
||
$agent = DirectoryUser::create([
|
||
'tenant_id' => $this->tenant->id, 'entra_oid' => sprintf('55555555-5555-5555-5555-%012d', $n),
|
||
'display_name' => "Agent {$n}", 'mail' => "agent{$n}@beziers-mediterranee.fr", 'account_enabled' => true,
|
||
]);
|
||
if ($inGroup) {
|
||
$this->group->members()->attach($agent->id);
|
||
}
|
||
|
||
return $agent;
|
||
}
|
||
|
||
public function test_editeur_cree_une_campagne_ciblee_qui_reste_inactive(): void
|
||
{
|
||
$this->actingAs($this->user('editor'))
|
||
->post('/admin/campagnes', $this->payload(['groups' => [$this->group->id], 'active' => 1]))
|
||
->assertRedirect(route('admin.campaigns.index'))->assertSessionHasNoErrors();
|
||
|
||
$c = Campaign::firstWhere('name', 'Encombrants');
|
||
$this->assertFalse($c->active, 'seul un administrateur active une campagne');
|
||
$this->assertTrue($c->starts_at->equalTo(Carbon::parse('2026-11-01 08:00', 'Europe/Paris')), 'dates saisies en heure de Paris');
|
||
$this->assertTrue($c->ends_at->equalTo(Carbon::parse('2026-11-15 18:00', 'Europe/Paris')));
|
||
$this->assertSame(1, $c->width, 'largeur automatique = largeur réelle de l\'image');
|
||
$this->assertSame([$this->group->id], $c->groups->pluck('id')->all());
|
||
$this->assertTrue(AuditLog::where('action', 'campaign.create')->exists());
|
||
|
||
$this->actingAs($this->user('editor'))->get('/admin/campagnes')->assertOk()->assertSee('Encombrants')->assertSee('signature_mail_directeurs');
|
||
}
|
||
|
||
public function test_image_envoyee_depuis_le_formulaire(): void
|
||
{
|
||
$tmp = tempnam(sys_get_temp_dir(), 'png');
|
||
file_put_contents($tmp, base64_decode(self::PNG));
|
||
|
||
$this->actingAs($this->user('admin'))->post('/admin/campagnes', $this->payload([
|
||
'image_path' => null, 'image_file' => new UploadedFile($tmp, 'Banniere Noel 2026.png', 'image/png', null, true),
|
||
'width' => 450, 'active' => 1,
|
||
]))->assertSessionHasNoErrors();
|
||
|
||
$c = Campaign::firstWhere('name', 'Encombrants');
|
||
$this->assertSame('banniere-noel-2026.png', $c->image_path);
|
||
$this->assertFileExists($this->dir.'/banniere-noel-2026.png');
|
||
$this->assertSame(450, $c->width);
|
||
$this->assertTrue($c->active);
|
||
}
|
||
|
||
public function test_refuse_les_saisies_invalides(): void
|
||
{
|
||
$admin = $this->user('admin');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['ends_at' => '2026-10-01T08:00']))->assertSessionHasErrors('ends_at');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['link_url' => 'javascript:alert(1)']))->assertSessionHasErrors('link_url');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['image_path' => 'absente.png']))->assertSessionHasErrors('image_path');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['image_path' => '../banniere.png']))->assertSessionHasErrors('image_path');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['image_path' => null]))->assertSessionHasErrors('image_path');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['alt_text' => '']))->assertSessionHasErrors('alt_text');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload(['groups' => [999]]))->assertSessionHasErrors('groups.0');
|
||
|
||
$this->assertSame(0, Campaign::count());
|
||
}
|
||
|
||
public function test_activation_modification_et_suppression_reservees_a_l_admin(): void
|
||
{
|
||
$c = $this->campaign(['active' => false]);
|
||
$editor = $this->user('editor');
|
||
$admin = $this->user('admin');
|
||
|
||
$this->actingAs($editor)->post("/admin/campagnes/{$c->id}/activation")->assertForbidden();
|
||
$this->actingAs($editor)->delete("/admin/campagnes/{$c->id}")->assertForbidden();
|
||
|
||
// Inactive : l'éditeur la modifie, sans pouvoir l'activer.
|
||
$this->actingAs($editor)->put("/admin/campagnes/{$c->id}", $this->payload(['name' => 'Préparée', 'active' => 1]))->assertSessionHasNoErrors();
|
||
$this->assertSame('Préparée', $c->refresh()->name);
|
||
$this->assertFalse($c->active);
|
||
|
||
$this->actingAs($admin)->post("/admin/campagnes/{$c->id}/activation")->assertSessionHasNoErrors();
|
||
$this->assertTrue($c->refresh()->active);
|
||
|
||
// Active : verrouillée pour l'éditeur.
|
||
$this->actingAs($editor)->put("/admin/campagnes/{$c->id}", $this->payload(['name' => 'Piratée']))->assertForbidden();
|
||
$this->actingAs($editor)->get("/admin/campagnes/{$c->id}")->assertOk()->assertSee('seul un administrateur peut la modifier');
|
||
$this->assertSame('Préparée', $c->refresh()->name);
|
||
|
||
$this->actingAs($admin)->put("/admin/campagnes/{$c->id}", $this->payload(['name' => 'Validée', 'active' => 0]))->assertSessionHasNoErrors();
|
||
$this->assertSame('Validée', $c->refresh()->name);
|
||
$this->assertFalse($c->active);
|
||
|
||
$this->actingAs($admin)->delete("/admin/campagnes/{$c->id}")->assertSessionHasNoErrors();
|
||
$this->assertSame(0, Campaign::count());
|
||
$this->assertFileExists($this->dir.'/banniere.png');
|
||
}
|
||
|
||
public function test_liste_indique_la_portee_reelle_et_les_templates_sans_banniere(): void
|
||
{
|
||
$this->agent(1);
|
||
$this->agent(2);
|
||
$this->agent(3, inGroup: true);
|
||
|
||
$this->campaign(['name' => 'Générale', 'priority' => 10]);
|
||
$this->campaign(['name' => 'Directeurs', 'priority' => 50])->groups()->attach($this->group->id);
|
||
$this->campaign(['name' => 'Masquée', 'priority' => 5]);
|
||
$this->campaign(['name' => 'Future', 'priority' => 100, 'starts_at' => now()->addWeek(), 'ends_at' => now()->addWeeks(2)]);
|
||
$this->campaign(['name' => 'Passée', 'priority' => 100, 'starts_at' => now()->subWeeks(2), 'ends_at' => now()->subWeek()]);
|
||
|
||
Template::create(['tenant_id' => $this->tenant->id, 'name' => 'Sobre', 'draft_html_full' => '{{prenom}}'])->publish();
|
||
Template::create(['tenant_id' => $this->tenant->id, 'name' => 'Complet', 'draft_html_full' => '{{prenom}}{{{banniere}}}'])->publish();
|
||
|
||
$this->actingAs($this->user('admin'))->get('/admin/campagnes')->assertOk()
|
||
->assertSeeInOrder(['Directeurs', 'vue par 1 agent', 'Générale', 'vue par 2 agents', 'Masquée', 'aucun agent', 'Future', 'Programmée', 'Passée', 'Terminée'])
|
||
->assertSee('Sobre')
|
||
->assertDontSee('Complet');
|
||
}
|
||
|
||
public function test_largeur_automatique_suit_l_image_et_formulaire_reenregistrable(): void
|
||
{
|
||
$admin = $this->user('admin');
|
||
$this->actingAs($admin)->post('/admin/campagnes', $this->payload())->assertSessionHasNoErrors();
|
||
$c = Campaign::firstWhere('name', 'Encombrants');
|
||
$this->assertSame(1, $c->width);
|
||
|
||
// Le formulaire d'édition laisse la largeur vide (automatique) : il se réenregistre tel quel.
|
||
$this->actingAs($admin)->get("/admin/campagnes/{$c->id}")->assertOk()->assertSee('min="100" max="800" value=""', false);
|
||
$this->actingAs($admin)->put("/admin/campagnes/{$c->id}", $this->payload(['width' => null]))->assertSessionHasNoErrors();
|
||
|
||
// Une largeur saisie est conservée et réaffichée.
|
||
$this->actingAs($admin)->put("/admin/campagnes/{$c->id}", $this->payload(['width' => 450]))->assertSessionHasNoErrors();
|
||
$this->assertSame(450, $c->refresh()->width);
|
||
$this->actingAs($admin)->get("/admin/campagnes/{$c->id}")->assertSee('value="450"', false);
|
||
}
|
||
|
||
public function test_messages_d_activation(): void
|
||
{
|
||
$admin = $this->user('admin');
|
||
$running = $this->campaign(['active' => false]);
|
||
$ended = $this->campaign(['name' => 'Passée', 'starts_at' => now()->subWeeks(2), 'ends_at' => now()->subWeek()]);
|
||
|
||
$this->actingAs($admin)->post("/admin/campagnes/{$running->id}/activation")->assertSessionHas('status', fn ($m) => str_contains($m, 'dès le prochain message'));
|
||
$this->actingAs($admin)->post("/admin/campagnes/{$running->id}/activation")->assertSessionHas('status', fn ($m) => str_contains($m, 'désactivée'));
|
||
$this->actingAs($admin)->post("/admin/campagnes/{$ended->id}/activation")->assertSessionHas('status', fn ($m) => str_contains($m, 'désactivée'));
|
||
$this->assertFalse($ended->refresh()->active);
|
||
}
|
||
|
||
public function test_image_endommagee_refusee_avec_un_message(): void
|
||
{
|
||
$tmp = tempnam(sys_get_temp_dir(), 'png');
|
||
file_put_contents($tmp, substr(base64_decode(self::PNG), 0, 20)); // en-tête PNG tronqué
|
||
|
||
$this->actingAs($this->user('admin'))->post('/admin/campagnes', $this->payload([
|
||
'image_path' => null, 'image_file' => new UploadedFile($tmp, 'cassee.png', 'image/png', null, true),
|
||
]))->assertSessionHasErrors('image_file');
|
||
$this->assertSame(0, Campaign::count());
|
||
}
|
||
|
||
public function test_cloisonnement_par_tenant(): void
|
||
{
|
||
$other = Tenant::create(['name' => 'Commune', 'entra_tenant_id' => '99999999-9999-9999-9999-999999999999']);
|
||
$foreign = $this->campaign(['tenant_id' => $other->id, 'name' => 'Ailleurs']);
|
||
$admin = $this->user('admin');
|
||
|
||
$this->actingAs($admin)->get("/admin/campagnes/{$foreign->id}")->assertNotFound();
|
||
$this->actingAs($admin)->delete("/admin/campagnes/{$foreign->id}")->assertNotFound();
|
||
$this->actingAs($admin)->get('/admin/campagnes')->assertOk()->assertDontSee('Ailleurs');
|
||
$this->assertSame(1, Campaign::count());
|
||
}
|
||
}
|