create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); $campaign = AdCampaign::create([ 'tenant_id' => $tenant->id, 'name' => 'C', 'audience_days' => 10, 'use_uploaded_list' => false, ]); return [$tenant, $user, $campaign]; } it('список слотов без загрузок содержит все 15 размеров, все не загружены', function () { [, $user, $campaign] = bannerCampaign(); $res = $this->actingAs($user)->getJson("/api/advertising/campaigns/{$campaign->id}/banners"); $res->assertOk(); $res->assertJsonPath('approved_at', null); $res->assertJsonPath('max_bytes', BannerUploadPolicy::MAX_BYTES); expect($res->json('formats'))->toBe(BannerUploadPolicy::FORMATS); $slots = $res->json('slots'); expect($slots)->toHaveCount(15); foreach ($slots as $slot) { expect($slot['uploaded'])->toBeFalse(); expect($slot['banner_id'])->toBeNull(); expect($slot['preview_url'])->toBeNull(); } }); it('загрузка баннера точного размера создаёт слот', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 728, 90), ]); $res->assertCreated(); expect($res->json('slot.uploaded'))->toBeTrue(); expect($res->json('slot.width'))->toBe(728); expect($res->json('slot.height'))->toBe(90); expect($res->json('slot.included'))->toBeTrue(); $banner = AdCampaignBanner::where('campaign_id', $campaign->id)->where('width', 728)->where('height', 90)->first(); expect($banner)->not->toBeNull(); Storage::disk('local')->assertExists($banner->path); expect($campaign->fresh()->banners_approved_at)->toBeNull(); }); it('картинка не того размера — 422 с понятным сообщением', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 800, 100), ]); $res->assertStatus(422); expect($res->json('message'))->toContain('Нужен ровно 728×90'); }); it('неизвестный размер — 422', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 999, 'height' => 999, 'file' => UploadedFile::fake()->image('b.jpg', 999, 999), ]); $res->assertStatus(422); expect($res->json('message'))->toBe('Неизвестный размер баннера.'); }); it('не-картинка отклоняется валидатором', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->create('b.txt', 5, 'text/plain'), ]); $res->assertStatus(422); }); it('повторная загрузка того же размера заменяет строку, а не дублирует', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b1.jpg', 728, 90), ])->assertCreated(); $campaign->update(['banners_approved_at' => now()]); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b2.png', 728, 90), ]); $res->assertCreated(); expect(AdCampaignBanner::where('campaign_id', $campaign->id)->where('width', 728)->where('height', 90)->count())->toBe(1); expect($campaign->fresh()->banners_approved_at)->toBeNull(); }); it('переключает included баннера', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $up = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 728, 90), ])->assertCreated(); $bannerId = $up->json('slot.banner_id'); $res = $this->actingAs($user)->patchJson("/api/advertising/campaigns/{$campaign->id}/banners/{$bannerId}", [ 'included' => false, ]); $res->assertOk(); expect($res->json('slot.included'))->toBeFalse(); $banner = AdCampaignBanner::find($bannerId); expect($banner->included)->toBeFalse(); }); it('удаляет баннер и сбрасывает утверждение', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $up = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 728, 90), ])->assertCreated(); $bannerId = $up->json('slot.banner_id'); $campaign->update(['banners_approved_at' => now()]); $res = $this->actingAs($user)->deleteJson("/api/advertising/campaigns/{$campaign->id}/banners/{$bannerId}"); $res->assertStatus(204); expect(AdCampaignBanner::find($bannerId))->toBeNull(); expect($campaign->fresh()->banners_approved_at)->toBeNull(); }); it('утверждает при хотя бы одном included баннере', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 728, 90), ])->assertCreated(); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners/approve"); $res->assertOk(); expect($campaign->fresh()->banners_approved_at)->not->toBeNull(); }); it('не утверждает, если все included=false', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $up = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 728, 90), ])->assertCreated(); $bannerId = $up->json('slot.banner_id'); $this->actingAs($user)->patchJson("/api/advertising/campaigns/{$campaign->id}/banners/{$bannerId}", ['included' => false])->assertOk(); $res = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners/approve"); $res->assertStatus(422); expect($res->json('message'))->toBe('Отметьте хотя бы один баннер для показа.'); }); it('нельзя утвердить пустой набор', function () { [, $user, $campaign] = bannerCampaign(); $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners/approve")->assertStatus(422); }); it('превью работает и отдаёт content-type по расширению', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $up = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.png', 728, 90), ])->assertCreated(); $bannerId = $up->json('slot.banner_id'); $preview = $this->actingAs($user)->get("/api/advertising/campaigns/{$campaign->id}/banners/{$bannerId}/preview"); $preview->assertOk(); expect($preview->headers->get('content-type'))->toContain('image/png'); }); it('чужой тенант не видит превью баннера (404)', function () { Storage::fake('local'); [, $user, $campaign] = bannerCampaign(); $up = $this->actingAs($user)->postJson("/api/advertising/campaigns/{$campaign->id}/banners", [ 'width' => 728, 'height' => 90, 'file' => UploadedFile::fake()->image('b.jpg', 728, 90), ])->assertCreated(); $bannerId = $up->json('slot.banner_id'); $otherTenant = Tenant::factory()->create(); $otherUser = User::factory()->create(['tenant_id' => $otherTenant->id]); $this->actingAs($otherUser)->get("/api/advertising/campaigns/{$campaign->id}/banners/{$bannerId}/preview")->assertNotFound(); }); it('размеры вне BannerSizes перечислены целиком и по порядку', function () { [, $user, $campaign] = bannerCampaign(); $res = $this->actingAs($user)->getJson("/api/advertising/campaigns/{$campaign->id}/banners"); $slots = $res->json('slots'); foreach (BannerSizes::all() as $i => [$w, $h]) { expect($slots[$i]['width'])->toBe($w); expect($slots[$i]['height'])->toBe($h); } });