create(). */ beforeEach(function () { $this->tenant = Tenant::factory()->create(); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); }); it('deletes a draft campaign of own tenant', function () { $campaign = AdCampaign::create([ 'tenant_id' => $this->tenant->id, 'name' => 'Черновик на удаление', 'audience_days' => 10, 'weekly_budget_rub' => '1000.00', ]); $response = $this->deleteJson("/api/advertising/campaigns/{$campaign->id}"); $response->assertStatus(204); $this->assertDatabaseMissing('ad_campaigns', ['id' => $campaign->id]); }); it('refuses to delete a running campaign', function () { $campaign = AdCampaign::create([ 'tenant_id' => $this->tenant->id, 'name' => 'Запущенная кампания', 'status' => AdCampaign::STATUS_RUNNING, 'audience_days' => 10, 'weekly_budget_rub' => '1000.00', ]); $response = $this->deleteJson("/api/advertising/campaigns/{$campaign->id}"); $response->assertStatus(409); $this->assertDatabaseHas('ad_campaigns', ['id' => $campaign->id]); }); it('returns 404 for another tenants campaign and does not delete it', function () { $tenantB = Tenant::factory()->create(); $campaignB = AdCampaign::create([ 'tenant_id' => $tenantB->id, 'name' => 'Чужой черновик', 'audience_days' => 10, 'weekly_budget_rub' => '1000.00', ]); $response = $this->deleteJson("/api/advertising/campaigns/{$campaignB->id}"); $response->assertStatus(404); $this->assertDatabaseHas('ad_campaigns', ['id' => $campaignB->id]); }); it('returns 404 for a non-existent campaign', function () { $response = $this->deleteJson('/api/advertising/campaigns/999999999'); $response->assertStatus(404); }); it('cascades delete to related ads and phones', function () { $campaign = AdCampaign::create([ 'tenant_id' => $this->tenant->id, 'name' => 'Черновик с детьми', 'audience_days' => 10, 'weekly_budget_rub' => '1000.00', ]); $ad = AdCampaignAd::create([ 'tenant_id' => $this->tenant->id, 'campaign_id' => $campaign->id, 'title' => 'Заголовок', 'text' => 'Текст объявления', 'href' => 'https://liderra.ru', 'moderation_status' => 'draft', ]); DB::table('ad_campaign_phones')->insert([ 'tenant_id' => $campaign->tenant_id, 'campaign_id' => $campaign->id, 'phone' => '79000000001', 'expires_at' => null, 'created_at' => now(), 'updated_at' => now(), ]); $response = $this->deleteJson("/api/advertising/campaigns/{$campaign->id}"); $response->assertStatus(204); $this->assertDatabaseMissing('ad_campaign_ads', ['id' => $ad->id]); $this->assertDatabaseMissing('ad_campaign_phones', ['campaign_id' => $campaign->id]); }); /** * Я-Л4 приёмочного листа — удаление кампании, под которой заперты деньги. * * Обычно одно другому не мешает: удалять разрешено только черновик, а деньги запираются * при запуске. Но черновик с живой заморозкой — состояние достижимое: запуск морозит деньги * ДО похода в Директ, и если Директ откажет на полпути, статус откатывается в черновик. * Возврат заморозки при этом делает отдельный шаг, и когда-нибудь он может не отработать — * например, процесс убьют между откатом статуса и возвратом денег. * * 🔴 Тогда клиент видит обычный черновик и спокойно жмёт «Удалить». Кампании больше нет, * а запись о заморозке ссылается на несуществующий номер: деньги клиента заперты навсегда, * и вернуть их некому — возврат ищут по кампании, которой уже нет. * * Требование карточки: либо удаление запрещено, либо деньги возвращаются. Молча потерянных * денег быть не должно. */ it('Я-Л4: черновик с живой заморозкой не уносит деньги клиента', function () { $wallet = app(AdWalletService::class); $wallet->topup($this->tenant->id, '5000.00', 'yandex', 'приёмка'); $campaign = AdCampaign::create([ 'tenant_id' => $this->tenant->id, 'name' => 'Черновик с деньгами', 'status' => AdCampaign::STATUS_DRAFT, 'audience_days' => 10, 'use_uploaded_list' => false, ]); $wallet->freeze($this->tenant->id, 'yandex', 'campaign', $campaign->id, '1200.00'); expect(AdWallet::where('tenant_id', $this->tenant->id)->first()->frozen_rub)->toBe('1200.00'); $otvet = $this->deleteJson("/api/advertising/campaigns/{$campaign->id}"); $svejy = AdWallet::where('tenant_id', $this->tenant->id)->first(); $visitZamorozka = AdWalletHold::where('tenant_id', $this->tenant->id) ->where('source_type', 'campaign')->where('source_id', $campaign->id) ->where('status', AdWalletHold::STATUS_ACTIVE)->exists(); if ($otvet->getStatusCode() === 204) { // Удалили — значит деньги обязаны вернуться на счёт целиком. expect($svejy->frozen_rub)->toBe('0.00') ->and($svejy->balance_rub)->toBe('5000.00') ->and($visitZamorozka)->toBeFalse(); } else { // Запретили — деньги остались на месте, кампания цела, и клиенту сказали почему. expect($otvet->getStatusCode())->toBe(409) ->and($svejy->frozen_rub)->toBe('1200.00') ->and(AdCampaign::find($campaign->id))->not->toBeNull(); expect($otvet->json('message'))->not->toBeEmpty(); } });