459947f76b
Круг смены 6 оборвали средой, работа помощника лежала в дереве несохранённой и её снёс бы первый же чужой откат. Замерил целостность, прочёл его отчёт, прогнал своей рукой, вынес приговор. Заново ничего не переделывал. ПРИНЯТО. Третья и последняя дверь с ключом рендера закрыта той же проверенной дверью. Список разрешённых машин общий с рендером и это доказано работой, а не доводом. Закрытая дверь красит СЕРЫМ, а не красным, и ключ при этом физически никуда не уходит, замерено мной двумя случаями по отдельному запуску на каждый. Помощник сверх задания нашёл и починил ложь, которой никто не искал: подпись плитки называла серый словами не отвечает, то есть гнала владельца продлевать исправные прокси. Мои ошибки называю первыми, их три. Пятьдесят девятая. Я назвал adminDashboardHelpers.ts чужим файлом соседней смены прямо в промте смене 7, в списке не трогать и в коммит не брать. Взял из головы, не замерив. Вся правка в этом файле - работа моего же помощника. Исполни следующая смена мой промт дословно, починка подписи плитки не попала бы в коммит и сгорела. Шестидесятая. Мой снимок оборванного круга был неполон: записал шесть файлов, их семь. Перечень собрал командой, но раньше, чем помощник закончил, и не перемерил перед тем, как назвать его окончательным. Шестьдесят первая. Мой датчик прогона искал не то слово и я чуть не объявил состоявшийся прогон несостоявшимся. Спас узкий прогон, которым я прибор перепроверил. Вырезание второго рода дало дыру. Допущение защиты: дверь проверяет адрес из настройки и считает, что запрос уйдёт именно туда. Случай мимо: разрешённая машина отвечает иди на другой адрес. Замерено на двух подставных машинах: тайный ключ уехал на машину, которой в списке разрешённых НЕТ, а плитка осталась ЗЕЛЁНОЙ и показала чужой адрес как свой. Перемерил на живой двери боевого Поиска клиентов тем же ножом, отдельным запуском - то же самое. Причина прочитана в исходнике библиотеки: при уходе на чужой адрес снимаются только два заголовка, и нашего среди них нет. Вторым прибором замерено, что отключения переадресации нет нигде во всём боевом коде. Это не отменяет Р126, дверь делает обещанное на своих случаях, но обещание шире дела и про это не сказано нигде. Отдельным кругом. Датчики: полный прогон моей рукой на своей базе 5344 проверки, 5340 зелёных, 4 пропущено, красных 0, арифметика сходится. Проверки интерфейса 3 из 3. Разметка 0 ошибок.
76 lines
4.0 KiB
PHP
76 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\External\ProxyMarketProbe;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class);
|
|
|
|
beforeEach(function () {
|
|
config()->set('services.proxy_market.check_url', 'https://vm.local/proxy-check');
|
|
config()->set('services.proxy_market.amber_days', 7);
|
|
config()->set('services.self_render.key', 'SECRET');
|
|
// 🔴 Р126, третья дверь (07.08.2026): проба теперь ходит только на разрешённую машину и
|
|
// только по https. Выдуманный в этих проверках vm.local надо назвать вслух — ровно так же,
|
|
// как боевой адрес назван в config/services.php. Сама дверь проверяется отдельно
|
|
// (ProxyMarketProbeDverTest), тут её условия просто выполнены.
|
|
config()->set('services.self_render.allowed_hosts', ['vm.local']);
|
|
});
|
|
|
|
it('серый, когда check_url или срок не заданы', function () {
|
|
config()->set('services.proxy_market.check_url', '');
|
|
config()->set('services.proxy_market.expires_at', '2026-08-04');
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('grey');
|
|
|
|
config()->set('services.proxy_market.check_url', 'https://vm.local/proxy-check');
|
|
config()->set('services.proxy_market.expires_at', '');
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('grey');
|
|
});
|
|
|
|
it('зелёный: прокси жив и срок далёк (в детали — IP)', function () {
|
|
config()->set('services.proxy_market.expires_at', now()->addDays(30)->format('Y-m-d'));
|
|
Http::fake(['vm.local/proxy-check' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]);
|
|
$r = (new ProxyMarketProbe)->check();
|
|
expect($r->light)->toBe('green');
|
|
expect($r->detail)->toContain('178.9.9.9');
|
|
});
|
|
|
|
it('амбер: до конца срока меньше порога', function () {
|
|
config()->set('services.proxy_market.expires_at', now()->addDays(3)->format('Y-m-d'));
|
|
Http::fake(['vm.local/proxy-check' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]);
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('amber');
|
|
});
|
|
|
|
it('красный: срок вышел, даже если прокси жив', function () {
|
|
config()->set('services.proxy_market.expires_at', now()->subDay()->format('Y-m-d'));
|
|
Http::fake(['vm.local/proxy-check' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]);
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('red');
|
|
});
|
|
|
|
it('красный: прокси не отвечает (ok:false и сетевой сбой)', function () {
|
|
config()->set('services.proxy_market.expires_at', now()->addDays(30)->format('Y-m-d'));
|
|
Http::fake(['vm.local/proxy-check' => Http::response(['ok' => false, 'error' => 'timeout'], 200)]);
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('red');
|
|
|
|
Http::fake(fn () => throw new RuntimeException('conn refused'));
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('red');
|
|
});
|
|
|
|
it('серый при битой дате в сроке — проба не бросает', function () {
|
|
config()->set('services.proxy_market.expires_at', '2026-08-042'); // опечатка владельца в .env
|
|
Http::fake(['vm.local/proxy-check' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]);
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('grey');
|
|
});
|
|
|
|
it('амбер ровно на пороге amber_days и в день окончания (0 дней)', function () {
|
|
Http::fake(['vm.local/proxy-check' => Http::response(['ok' => true, 'ip' => '178.9.9.9'], 200)]);
|
|
|
|
config()->set('services.proxy_market.expires_at', now()->addDays(7)->format('Y-m-d'));
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('amber');
|
|
|
|
config()->set('services.proxy_market.expires_at', now()->format('Y-m-d'));
|
|
expect((new ProxyMarketProbe)->check()->light)->toBe('amber');
|
|
});
|