77 lines
3.2 KiB
PHP
77 lines
3.2 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use App\Exceptions\Supplier\SupplierClientException;
|
||
|
|
use App\Exceptions\Supplier\SupplierTransientException;
|
||
|
|
use App\Mail\SupplierCriticalAlertMail;
|
||
|
|
use App\Models\Project;
|
||
|
|
use App\Models\SupplierManualSyncQueue;
|
||
|
|
use App\Models\Tenant;
|
||
|
|
use App\Services\Supplier\Channel\Exceptions\TierEscalatedException;
|
||
|
|
use App\Services\Supplier\Channel\FailoverProjectChannel;
|
||
|
|
use App\Services\Supplier\Channel\SupplierProjectChannel;
|
||
|
|
use App\Services\Supplier\Dto\SupplierProjectDto;
|
||
|
|
use Illuminate\Contracts\Mail\Mailer;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Illuminate\Support\Facades\Mail;
|
||
|
|
|
||
|
|
uses(RefreshDatabase::class);
|
||
|
|
|
||
|
|
test('Tier-1 fail + Tier-2 fail → Tier-3 escalation creates manual queue row + queues alert mail', function (): void {
|
||
|
|
Mail::fake();
|
||
|
|
config(['services.supplier.alert_email' => 'ops@liderra.local']);
|
||
|
|
|
||
|
|
$tenant = Tenant::factory()->create();
|
||
|
|
$project = Project::factory()->for($tenant)->create();
|
||
|
|
|
||
|
|
$tier1 = mock(SupplierProjectChannel::class);
|
||
|
|
$tier1->shouldReceive('listProjects')->andReturn([]); // dedup-сверка: нет совпадений
|
||
|
|
$tier1->shouldReceive('createProject')->andThrow(new SupplierClientException('Tier-1 mock fail'));
|
||
|
|
|
||
|
|
$tier2 = mock(SupplierProjectChannel::class);
|
||
|
|
$tier2->shouldReceive('createProject')->andThrow(new RuntimeException('Tier-2 manage-project.js selector break'));
|
||
|
|
|
||
|
|
$channel = new FailoverProjectChannel($tier1, $tier2, app(Mailer::class));
|
||
|
|
|
||
|
|
$dto = new SupplierProjectDto(
|
||
|
|
platform: 'B1', signalType: 'site', uniqueKey: 'failover-smoke.example',
|
||
|
|
limit: 1, workdays: [1, 2, 3, 4, 5], regions: [], regionsReverse: false, status: 'active',
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(fn () => $channel->createProjectForLiderra($project, $dto))
|
||
|
|
->toThrow(TierEscalatedException::class);
|
||
|
|
|
||
|
|
expect(SupplierManualSyncQueue::where('project_id', $project->id)->count())->toBe(1);
|
||
|
|
|
||
|
|
Mail::assertQueued(SupplierCriticalAlertMail::class, fn ($m) => $m->alertType === 'manual_required');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Tier-1 transient fail (portal unreachable) bypasses Tier-2 and goes straight to Tier-3', function (): void {
|
||
|
|
Mail::fake();
|
||
|
|
config(['services.supplier.alert_email' => 'ops@liderra.local']);
|
||
|
|
|
||
|
|
$tenant = Tenant::factory()->create();
|
||
|
|
$project = Project::factory()->for($tenant)->create();
|
||
|
|
|
||
|
|
$tier1 = mock(SupplierProjectChannel::class);
|
||
|
|
$tier1->shouldReceive('listProjects')->andReturn([]);
|
||
|
|
$tier1->shouldReceive('createProject')->andThrow(new SupplierTransientException('Connection refused'));
|
||
|
|
|
||
|
|
$tier2 = mock(SupplierProjectChannel::class);
|
||
|
|
$tier2->shouldNotReceive('createProject'); // КЛЮЧЕВОЕ — transient НЕ должен попасть в tier-2
|
||
|
|
|
||
|
|
$channel = new FailoverProjectChannel($tier1, $tier2, app(Mailer::class));
|
||
|
|
|
||
|
|
$dto = new SupplierProjectDto(
|
||
|
|
platform: 'B1', signalType: 'site', uniqueKey: 'transient-smoke.example',
|
||
|
|
limit: 1, workdays: [1, 2, 3, 4, 5], regions: [], regionsReverse: false, status: 'active',
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(fn () => $channel->createProjectForLiderra($project, $dto))
|
||
|
|
->toThrow(TierEscalatedException::class);
|
||
|
|
|
||
|
|
$row = SupplierManualSyncQueue::where('project_id', $project->id)->first();
|
||
|
|
expect($row->failure_reason)->toBe('portal_unreachable');
|
||
|
|
});
|