tenant = Tenant::factory()->create(); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); config(['client_tg.sandbox' => true]); }); it('песочница: создать → запустить → робот-черновик → draft_ready + matched виден в API', function () { Queue::fake(); // Робот замокан: черновик готов, нашлось 2 совпадения в кабинете МТС. $mock = Mockery::mock(TelegramRobotRunner::class); $mock->shouldReceive('run')->once()->andReturn(new RobotResult(ok: true, matched: 2)); app()->instance(TelegramRobotRunner::class, $mock); // 1. Клиент создаёт черновик из списка своих номеров. $create = $this->postJson('/api/telegram/campaigns', [ 'ad_text' => 'Заходите в наш канал', 'ad_link' => 'https://t.me/example_channel', 'audience_kind' => 'list', 'budget_cap_rub' => '1000.00', 'phones' => ['79990000001', '79990000002'], ]); $create->assertCreated()->assertJsonPath('status', Campaign::STATUS_DRAFT); $id = $create->json('id'); expect($create->json('planned_count'))->toBe(2); // 2. Клиент жмёт «Запустить» — кампания в очереди, джоб поставлен. $this->postJson("/api/telegram/campaigns/{$id}/launch") ->assertOk() ->assertJsonPath('status', Campaign::STATUS_QUEUED); Queue::assertPushed(RunTelegramCampaignJob::class); // 3. Отрабатывает воркер очереди (робот замокан) — доводит до черновика. // Зовём handle через контейнер: под Queue::fake() dispatchSync не выполняется, // а нам нужен реальный прогон джоба с замоканным роботом. app()->call([new RunTelegramCampaignJob($id, $this->tenant->id), 'handle']); // 4. Клиент видит в кабинете: черновик готов, совпадений — 2. $this->getJson("/api/telegram/campaigns/{$id}") ->assertOk() ->assertJsonPath('campaign.status', Campaign::STATUS_DRAFT_READY) ->assertJsonPath('campaign.matched_count', 2); // Список тоже отражает финальный статус. $index = $this->getJson('/api/telegram/campaigns')->assertOk(); expect(collect($index->json('campaigns'))->firstWhere('id', $id)['status']) ->toBe(Campaign::STATUS_DRAFT_READY); });