create([ 'tenant_id' => $tenantId, 'status' => Campaign::STATUS_QUEUED, 'ad_text' => 'Текст объявления', 'ad_link' => 'https://example.test', 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '1000.00', 'audience_kind' => Campaign::AUDIENCE_LIST, 'planned_count' => 1, 'estimated_cost_rub' => '0.00', 'created_by' => 1, ]); CampaignPhone::query()->create([ 'tenant_id' => $tenantId, 'campaign_id' => $campaign->id, 'phone' => '79990000009', ]); return $campaign; } /** @return array захваченные параметры робота */ function runWithSandbox(bool $sandbox): array { config()->set('client_tg.sandbox', $sandbox); $tenant = Tenant::factory()->create(); $campaign = sandboxQueuedCampaign($tenant->id); $captured = []; $mock = Mockery::mock(TelegramRobotRunner::class); $mock->shouldReceive('run')->andReturnUsing(function (array $params) use (&$captured) { $captured = $params; return new RobotResult(ok: true, matched: 400); }); app()->instance(TelegramRobotRunner::class, $mock); RunTelegramCampaignJob::dispatchSync($campaign->id, $tenant->id); return $captured; } it('по умолчанию песочница включена (TG_SANDBOX=true)', function () { // Конфиг грузится из config/client_tg.php с дефолтом env('TG_SANDBOX', true). expect(config('client_tg.sandbox'))->toBeTrue(); }); it('конфиг робота имеет дефолты (script → bin/run.js, node, cwd, timeout)', function () { // base_path() склеивает через '/', поэтому дефолт всегда оканчивается 'bin/run.js'. expect(config('client_tg.robot.script'))->toEndWith('bin/run.js') ->and(config('client_tg.robot.node'))->toBe('node') ->and(config('client_tg.robot.cwd'))->toContain('mts-telegram-ads') ->and(config('client_tg.robot.timeout'))->toBeGreaterThan(0); }); it('песочница вкл → робот вызывается в mode=draft', function () { $captured = runWithSandbox(true); expect($captured['mode'])->toBe('draft'); }); it('песочница выкл → mode=live (флаг реально управляет режимом)', function () { $captured = runWithSandbox(false); expect($captured['mode'])->toBe('live'); });