set('services.proxy_market.check_url', PM_BOEVOY); config()->set('services.proxy_market.expires_at', now()->addDays(30)->format('Y-m-d')); config()->set('services.proxy_market.amber_days', 7); config()->set('services.self_render.key', PM_KLYUCH); // 🔴 Список НЕ выдумываем — берём настоящее умолчание config/services.php, то самое, // что поедет на боевой. config()->set('services.self_render.allowed_hosts', (array) config('services.self_render.allowed_hosts')); }); function pmProba(): LivenessReading { return (new ProxyMarketProbe)->check(); } // ───────────────────────────────────────────────────────────────────────────── // 1. Дверь ВООБЩЕ применена: чужой хост и http:// наружу не выпускают // ───────────────────────────────────────────────────────────────────────────── it('чужой хост в PROXY_MARKET_CHECK_URL: запроса не было — ключ никуда не уехал', function () { Http::fake(); config()->set('services.proxy_market.check_url', 'https://51-250-1-97.sslip.io.zloumyshlennik.ru/proxy-check'); expect(pmProba()->light)->toBe('grey'); Http::assertNothingSent(); }); it('опечатка в одной букве хоста закрывает дверь', function () { Http::fake(); // sslip → ssIip (заглавная I вместо l) — ровно та опечатка, ради которой дверь заведена. config()->set('services.proxy_market.check_url', 'https://51-250-1-97.ssIip.io/proxy-check'); expect(pmProba()->light)->toBe('grey'); Http::assertNothingSent(); }); it('http на разрешённый хост: ключ не поехал открытым текстом', function () { Http::fake(); config()->set('services.proxy_market.check_url', 'http://51-250-1-97.sslip.io/proxy-check'); $r = pmProba(); expect($r->light)->toBe('grey'); expect($r->detail)->toContain('без шифрования'); Http::assertNothingSent(); }); it('адрес без схемы вовсе не принимается', function () { Http::fake(); config()->set('services.proxy_market.check_url', '51-250-1-97.sslip.io/proxy-check'); expect(pmProba()->light)->toBe('grey'); Http::assertNothingSent(); }); it('пустой тайный ключ закрывает дверь ДО запроса, а не ловится ответом 401', function () { Http::fake(); config()->set('services.self_render.key', ''); expect(pmProba()->light)->toBe('grey'); Http::assertNothingSent(); }); // ───────────────────────────────────────────────────────────────────────────── // 2. 🔴 СЕРЫЙ, А НЕ КРАСНЫЙ. Красный на этой плитке значит «прокси кончились» — // владелец побежит их продлевать. Соврать им нельзя. // ───────────────────────────────────────────────────────────────────────────── it('закрытая дверь красит серым, а живой отказ прокси — по-прежнему красным', function () { Http::fake(); config()->set('services.proxy_market.check_url', 'http://51-250-1-97.sslip.io/proxy-check'); expect(pmProba()->light)->toBe('grey'); // Та же плитка, но дверь открыта и прокси ответил отказом — красный сохранился. config()->set('services.proxy_market.check_url', PM_BOEVOY); Http::fake([PM_BOEVOY => Http::response(['ok' => false, 'error' => 'no proxies'], 200)]); expect(pmProba()->light)->toBe('red'); }); it('вышедший срок остаётся красным — закрытая дверь его не съела', function () { config()->set('services.proxy_market.expires_at', now()->subDay()->format('Y-m-d')); Http::fake([PM_BOEVOY => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]); expect(pmProba()->light)->toBe('red'); }); it('причина закрытия называет настройку PROXY_MARKET_CHECK_URL и НЕ называет ключ', function () { Http::fake(); config()->set('services.proxy_market.check_url', 'http://51-250-1-97.sslip.io/proxy-check'); $detail = pmProba()->detail; expect($detail)->toContain('PROXY_MARKET_CHECK_URL'); expect($detail)->not->toContain(PM_KLYUCH); }); // ───────────────────────────────────────────────────────────────────────────── // 3. 🔴 Живая плитка не сломана: боевой адрес проходит на НАСТОЯЩЕМ умолчании // настроек и тогда, когда приложение считает себя боевым. // ───────────────────────────────────────────────────────────────────────────── it('боевой адрес прокси открыт на настоящем умолчании и на боевом окружении', function () { app()->detectEnvironment(fn () => 'production'); Http::fake([PM_BOEVOY => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]); $r = pmProba(); expect($r->light)->toBe('green'); expect($r->detail)->toContain('178.9.9.9'); Http::assertSent(fn ($req) => $req->url() === PM_BOEVOY && $req->hasHeader('X-Render-Key', PM_KLYUCH)); }); it('пробелы по краям адреса не мешают: ходим ровно по проверенному адресу', function () { config()->set('services.proxy_market.check_url', ' '.PM_BOEVOY.' '); Http::fake([PM_BOEVOY => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]); expect(pmProba()->light)->toBe('green'); Http::assertSent(fn ($req) => $req->url() === PM_BOEVOY); }); // ───────────────────────────────────────────────────────────────────────────── // 4. 🔴 Список разрешённых машин — ОБЩИЙ с рендером. Доказывается работой: // трогаем ТОЛЬКО services.self_render.allowed_hosts и смотрим, меняется ли // поведение проксишной пробы. Свой список у неё завести молча не получится. // ───────────────────────────────────────────────────────────────────────────── it('опустошение списка машин РЕНДЕРА закрывает и проксишную дверь', function () { Http::fake(); config()->set('services.self_render.allowed_hosts', []); expect(pmProba()->light)->toBe('grey'); Http::assertNothingSent(); }); it('машина, добавленная в список РЕНДЕРА, открывает и проксишную дверь', function () { config()->set('services.self_render.allowed_hosts', ['novaya-vm.example.ru']); config()->set('services.proxy_market.check_url', 'https://novaya-vm.example.ru/proxy-check'); Http::fake(['novaya-vm.example.ru/*' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]); expect(pmProba()->light)->toBe('green'); Http::assertSent(fn ($req) => $req->hasHeader('X-Render-Key', PM_KLYUCH)); }); it('умолчание списка машин НЕ пусто — иначе плитка посерела бы в момент выката', function () { expect((array) config('services.self_render.allowed_hosts'))->not->toBeEmpty(); }); // ───────────────────────────────────────────────────────────────────────────── // 5. 🔴 ФОРМЫ АДРЕСА, какие могут стоять в боевом .env — что увидит владелец. // Считаем на БОЕВОМ окружении: петля на проде послаблением не пользуется. // ───────────────────────────────────────────────────────────────────────────── it('формы адреса боевого .env дают ровно тот цвет, который принял владелец', function (string $adres, string $cvet) { app()->detectEnvironment(fn () => 'production'); config()->set('services.proxy_market.check_url', $adres); Http::fake(['*' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]); expect(pmProba()->light)->toBe($cvet); })->with([ 'боевой https по имени' => [PM_BOEVOY, 'green'], 'боевой https по адресу машины' => ['https://51.250.1.97/proxy-check', 'green'], 'https с явным портом 443' => ['https://51-250-1-97.sslip.io:443/proxy-check', 'green'], 'HTTPS заглавными' => ['HTTPS://51-250-1-97.sslip.io/proxy-check', 'green'], 'https с пробелами по краям' => [' https://51-250-1-97.sslip.io/proxy-check ', 'green'], 'петля по https' => ['https://127.0.0.1/proxy-check', 'green'], '🔴 http — ЦЕНА, принятая владельцем' => ['http://51-250-1-97.sslip.io/proxy-check', 'grey'], '🔴 http на петлю, но окружение боевое' => ['http://127.0.0.1/proxy-check', 'grey'], 'опечатка в имени машины' => ['https://51-250-1-97.ssIip.io/proxy-check', 'grey'], 'чужой хост с боевым именем внутри' => ['https://51-250-1-97.sslip.io.chuzhoy.ru/proxy-check', 'grey'], 'вовсе без схемы' => ['51-250-1-97.sslip.io/proxy-check', 'grey'], 'пусто' => ['', 'grey'], ]);