Files
portal/app/tests/Feature/AdminIncidentShowTest.php
T
Дмитрий 2ec70b338f
Accessibility (Pa11y live) / a11y (push) Has been cancelled
test: оздоровление тест-стенда — изоляция протекателей плюс фикстуры, партиции, видимость supplier-коннекта
Закрыто 36 из 55 пре-существующих падений backend-набора (55 to 19), всё тест-сторона,
код продукта не тронут. Группы:
- incident-показ/РКН: добавлен SharesSupplierPdo + синхрон уровня транзакции в трейте
  (вложенный transaction на общем PDO теперь делает SAVEPOINT, не повторный BEGIN).
- auto-pause и lead-delivery: тесты создают project_routing_snapshots, от которого
  зависит выбор кандидатов в LeadRouter (slepok-инвариант).
- изоляция 16 протекающих тестов: добавлен DatabaseTransactions (где нужно плюс
  SharesSupplierPdo) — перестали оставлять committed-строки, отравлявшие глобально
  сканирующие тесты (snapshot, verify-audit, size-N).
- partition time-bombs: ensureRange месячных партиций для тестов на дату 2026-05.
- устаревшие ассерты: SchemaDelta метрики v8.35 to v8.52, ProjectsStore телефон 8 to 7
  нормализуется, incidents-watch фильтр активного admin, register captcha_token,
  impersonation активный юзер тенанта, activity_log.deal_id, ProjectUpdateDedup пауза.

Остаток 19 (отдельно): verify-audit-chains и size-N (протекатели audit-строк),
webhook B-префикс (решение владельца), пара env/каскадных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:39:51 +03:00

84 lines
3.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class);
// AdminIncidentsController читает incidents_log через DB::connection('pgsql_supplier');
// под DatabaseTransactions записи дефолтного pgsql невидимы отдельному PDO supplier'а
// → show()/notifyRkn() abort(404). Трейт шарит один PDO между коннектами (откат сохраняется).
uses(SharesSupplierPdo::class);
beforeEach(function () {
DB::table('incidents_log')->delete();
$this->adminId = (int) DB::table('saas_admin_users')->insertGetId([
'email' => 'inc-'.bin2hex(random_bytes(3)).'@test',
'full_name' => 'Incident Admin',
'password_hash' => bcrypt('test1234'),
'is_active' => true,
'role' => 'support',
'created_at' => now(),
]);
});
function makeShowIncident(int $adminId, array $overrides = []): int
{
$started = $overrides['started_at'] ?? now()->subHours(3);
$detected = $overrides['detected_at'] ?? $started;
return (int) DB::table('incidents_log')->insertGetId(array_merge([
'type' => 'service_outage',
'severity' => 'high',
'started_at' => $started,
'detected_at' => $detected,
'resolved_at' => null,
'summary' => 'Show test incident',
'created_by_admin_id' => $adminId,
'created_at' => now(),
], $overrides));
}
test('GET /api/admin/incidents/{id} 200 + полная карточка', function () {
$id = makeShowIncident($this->adminId, ['summary' => 'API 502 burst', 'severity' => 'critical']);
$r = $this->getJson("/api/admin/incidents/{$id}");
$r->assertOk();
expect($r->json('incident.id'))->toBe($id);
expect($r->json('incident.summary'))->toBe('API 502 burst');
expect($r->json('incident.severity'))->toBe('critical');
expect($r->json('incident.incident_id'))->toMatch('/^INC-\d{4}-\d{4}-\d{4}$/');
expect($r->json('incident.status'))->toBe('investigating');
expect($r->json('incident.created_by_admin'))->toBe('Incident Admin');
});
test('GET /api/admin/incidents/{id} несуществующий → 404', function () {
$this->getJson('/api/admin/incidents/99999999')->assertStatus(404);
});
test('GET /api/admin/incidents/{id} data_breach без rkn_notified_at → rkn_deadline_at +24ч', function () {
$id = makeShowIncident($this->adminId, ['type' => 'data_breach', 'detected_at' => now()->subHour()]);
$r = $this->getJson("/api/admin/incidents/{$id}");
expect($r->json('incident.rkn_notified'))->toBeFalse();
expect($r->json('incident.rkn_deadline_at'))->toBeString();
});
test('GET /api/admin/incidents/{id} разрешает имена affected_tenants', function () {
$tenantId = (int) DB::table('tenants')->insertGetId([
'subdomain' => 'inc-'.bin2hex(random_bytes(4)),
'organization_name' => 'Affected Org',
'contact_email' => 'a@test.local',
'created_at' => now(),
]);
$id = makeShowIncident($this->adminId, ['affected_tenant_ids' => '{'.$tenantId.'}']);
$r = $this->getJson("/api/admin/incidents/{$id}");
expect($r->json('incident.affected_tenants'))->toHaveCount(1);
expect($r->json('incident.affected_tenants.0.organization_name'))->toBe('Affected Org');
});
test('GET /api/admin/incidents/{id} resolved инцидент → status resolved', function () {
$id = makeShowIncident($this->adminId, ['resolved_at' => now()]);
expect($this->getJson("/api/admin/incidents/{$id}")->json('incident.status'))->toBe('resolved');
});