Files
portal/app/tests/Feature/Services/SupplierProjectResolverTest.php
T
Дмитрий 59c57f9ec0 feat(services): add SupplierProjectResolver (resolveOrStub with B1+SMS guard)
- app/Services/SupplierProjects/SupplierProjectResolver.php — резолвер
  по ключу (platform, signal_type, unique_key). Возвращает existing supplier_project
  или создаёт pending stub (физическая sync произойдёт в SyncSupplierProjectsJob, Plan 3).
- Защита 1: InvalidArgumentException на платформу не из {B1,B2,B3}.
- Защита 2: InvalidArgumentException на signal_type не из {site,call,sms}.
- Защита 3: DomainException на B1+SMS combo (chk_supplier_projects_b1_not_for_sms).
- tests/Feature/Services/SupplierProjectResolverTest.php — 6 тестов:
  resolve existing, create stub, idempotency (no duplicates), B1+SMS guard,
  invalid platform, invalid signal_type.

Pest: 475 / 473 passed / 2 skipped (467 + 6 новых = 473).
Larastan: 0 errors. Pint passed.

Spec: §2.2, §4.1
Plan: Task 11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:02:25 +03:00

63 lines
2.3 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\SupplierProject;
use App\Services\SupplierProjects\SupplierProjectResolver;
use Illuminate\Foundation\Testing\DatabaseTransactions;
uses(DatabaseTransactions::class);
test('resolveOrStub returns existing supplier_project for matching (platform, unique_key)', function () {
$existing = SupplierProject::factory()->create([
'platform' => 'B1',
'signal_type' => 'site',
'unique_key' => 'example.com',
]);
$resolver = new SupplierProjectResolver;
$resolved = $resolver->resolveOrStub('B1', 'site', 'example.com');
expect($resolved->id)->toBe($existing->id);
});
test('resolveOrStub creates pending stub when no existing project', function () {
$resolver = new SupplierProjectResolver;
$resolved = $resolver->resolveOrStub('B2', 'call', '79991234567');
expect($resolved->exists)->toBeTrue();
expect($resolved->platform)->toBe('B2');
expect($resolved->signal_type)->toBe('call');
expect($resolved->unique_key)->toBe('79991234567');
expect($resolved->sync_status)->toBe('pending');
expect($resolved->current_limit)->toBe(0);
expect($resolved->current_workdays)->toBe([1, 2, 3, 4, 5, 6, 7]);
});
test('resolveOrStub returns same row on second call (no duplicates)', function () {
$resolver = new SupplierProjectResolver;
$first = $resolver->resolveOrStub('B3', 'sms', 'TINKOFF');
$second = $resolver->resolveOrStub('B3', 'sms', 'TINKOFF');
expect($first->id)->toBe($second->id);
expect(SupplierProject::where('unique_key', 'TINKOFF')->count())->toBe(1);
});
test('resolveOrStub throws DomainException for B1+sms (forbidden combo)', function () {
$resolver = new SupplierProjectResolver;
expect(fn () => $resolver->resolveOrStub('B1', 'sms', 'TINKOFF'))
->toThrow(DomainException::class);
});
test('resolveOrStub throws InvalidArgumentException for invalid platform', function () {
$resolver = new SupplierProjectResolver;
expect(fn () => $resolver->resolveOrStub('B9', 'site', 'a.com'))
->toThrow(InvalidArgumentException::class);
});
test('resolveOrStub throws InvalidArgumentException for invalid signal_type', function () {
$resolver = new SupplierProjectResolver;
expect(fn () => $resolver->resolveOrStub('B1', 'unknown_type', 'a.com'))
->toThrow(InvalidArgumentException::class);
});