withHeaders(['Accept' => 'application/json']); $this->tenant = Tenant::factory()->create(); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); config(['client_tg.auto_batch_threshold' => 3]); // в тестах порог занижен }); /** Тело сохранения правила: по умолчанию — реклама сайта (там заголовок обязателен). */ function pravilo(array $override = []): array { return array_merge([ 'enabled' => true, 'ad_text' => 'Займы под залог за час', 'ad_link' => 'https://example.com/zaim', 'ad_headline' => 'Ломбард «Омега»', 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '2500.00', 'daily_limit_rub' => '5000.00', 'batch_threshold' => 400, ], $override); } // ── Заголовок ─────────────────────────────────────────────────────────────── it('сохраняет заголовок объявления и отдаёт его обратно', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); expect(AutoRule::where('tenant_id', $this->tenant->id)->first()->ad_headline) ->toBe('Ломбард «Омега»'); $this->getJson('/api/telegram/auto-rule') ->assertOk() ->assertJsonPath('ad_headline', 'Ломбард «Омега»'); }); it('реклама САЙТА без заголовка не сохраняется → 422', function () { $this->putJson('/api/telegram/auto-rule', pravilo(['ad_headline' => ''])) ->assertStatus(422) ->assertJsonValidationErrors('ad_headline'); expect(AutoRule::where('tenant_id', $this->tenant->id)->count())->toBe(0); }); it('реклама телеграм-канала без заголовка сохраняется — там его кабинет не спрашивает', function () { $this->putJson('/api/telegram/auto-rule', pravilo([ 'ad_link' => 'https://t.me/example_channel', 'ad_headline' => '', ]))->assertOk(); expect(AutoRule::where('tenant_id', $this->tenant->id)->first()->ad_headline)->toBeNull(); }); it('выключенное авто без заголовка сохраняется — правило ещё не работает', function () { $this->putJson('/api/telegram/auto-rule', pravilo(['enabled' => false, 'ad_headline' => ''])) ->assertOk(); }); it('заголовок длиннее 40 знаков не проходит — столько же, сколько в кабинете', function () { $this->putJson('/api/telegram/auto-rule', pravilo(['ad_headline' => str_repeat('я', 41)])) ->assertStatus(422) ->assertJsonValidationErrors('ad_headline'); }); it('накопитель кладёт заголовок из правила в созданную авто-кампанию', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); app(TelegramAutoAccumulator::class)->accumulate($this->tenant->id, '79990000001'); $campaign = Campaign::where('tenant_id', $this->tenant->id)->firstOrFail(); expect($campaign->ad_headline)->toBe('Ломбард «Омега»'); }); // ── Картинка / видео ──────────────────────────────────────────────────────── it('прикладывает картинку к авто-правилу: файл сохранён, путь записан', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); $res = $this->post('/api/telegram/auto-rule/media', [ 'media' => UploadedFile::fake()->image('ad.jpg', 1280, 720), ]); $res->assertOk(); $path = AutoRule::where('tenant_id', $this->tenant->id)->first()->media_path; expect($path)->not->toBeNull(); Storage::disk('local')->assertExists($path); expect($res->json('media_path'))->toBe($path); }); it('картинка авто-правила проверяется теми же требованиями МТС', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); $res = $this->post('/api/telegram/auto-rule/media', [ 'media' => UploadedFile::fake()->image('ad.jpg', 300, 200), ]); $res->assertStatus(422); expect((string) $res->json('errors.media.0'))->toContain('640'); }); it('замена картинки авто-правила удаляет прежний файл', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); $this->post('/api/telegram/auto-rule/media', [ 'media' => UploadedFile::fake()->image('first.jpg', 1280, 720), ])->assertOk(); $first = AutoRule::where('tenant_id', $this->tenant->id)->first()->media_path; $this->post('/api/telegram/auto-rule/media', [ 'media' => UploadedFile::fake()->image('second.jpg', 1280, 720), ])->assertOk(); $second = AutoRule::where('tenant_id', $this->tenant->id)->first()->media_path; expect($second)->not->toBe($first); Storage::disk('local')->assertMissing($first); Storage::disk('local')->assertExists($second); }); it('накопитель кладёт картинку из правила в созданную авто-кампанию', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); $this->post('/api/telegram/auto-rule/media', [ 'media' => UploadedFile::fake()->image('ad.jpg', 1280, 720), ])->assertOk(); $rulePath = AutoRule::where('tenant_id', $this->tenant->id)->first()->media_path; app(TelegramAutoAccumulator::class)->accumulate($this->tenant->id, '79990000001'); expect(Campaign::where('tenant_id', $this->tenant->id)->firstOrFail()->media_path) ->toBe($rulePath); }); it('сохранение правила НЕ стирает уже приложенную картинку', function () { $this->putJson('/api/telegram/auto-rule', pravilo())->assertOk(); $this->post('/api/telegram/auto-rule/media', [ 'media' => UploadedFile::fake()->image('ad.jpg', 1280, 720), ])->assertOk(); $this->putJson('/api/telegram/auto-rule', pravilo(['ad_text' => 'Другой текст']))->assertOk(); expect(AutoRule::where('tenant_id', $this->tenant->id)->first()->media_path)->not->toBeNull(); }); // ── Предохранитель накопителя ─────────────────────────────────────────────── it('старое правило: реклама САЙТА без заголовка — пачку не запускаем, деньги целы', function () { // Такое правило нельзя сохранить через кабинет (валидация не пустит), но оно могло // остаться от прежних версий — до 02.08.2026 поля заголовка не существовало вовсе. // Запусти мы его, кабинет МТС встал бы на шаге «Объявление» уже ПОСЛЕ списания. AutoRule::create([ 'tenant_id' => $this->tenant->id, 'enabled' => true, 'ad_text' => 'Займы под залог', 'ad_link' => 'https://example.com/zaim', 'ad_headline' => null, 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '2500.00', 'daily_limit_rub' => '5000.00', ]); foreach (['79990000001', '79990000002', '79990000003', '79990000004'] as $phone) { app(TelegramAutoAccumulator::class)->accumulate($this->tenant->id, $phone); } $campaign = Campaign::where('tenant_id', $this->tenant->id)->firstOrFail(); expect($campaign->status)->toBe(Campaign::STATUS_DRAFT); Queue::assertNothingPushed(); }); it('то же правило со ссылкой на канал — запускается: там заголовок не нужен', function () { AutoRule::create([ 'tenant_id' => $this->tenant->id, 'enabled' => true, 'ad_text' => 'Заходите в канал', 'ad_link' => 'https://t.me/example_channel', 'ad_headline' => null, 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '2500.00', 'daily_limit_rub' => '5000.00', ]); foreach (['79990000001', '79990000002', '79990000003', '79990000004'] as $phone) { app(TelegramAutoAccumulator::class)->accumulate($this->tenant->id, $phone); } expect(Campaign::where('tenant_id', $this->tenant->id)->firstOrFail()->status) ->toBe(Campaign::STATUS_QUEUED); }); it('изоляция тенантов: каждый видит только своё правило', function () { $this->putJson('/api/telegram/auto-rule', pravilo(['ad_headline' => 'Наш заголовок']))->assertOk(); $tenantB = Tenant::factory()->create(); $userB = User::factory()->create(['tenant_id' => $tenantB->id]); $this->actingAs($userB) ->getJson('/api/telegram/auto-rule') ->assertOk() ->assertJsonPath('ad_headline', ''); });