insertGetId([ 'code' => $code.'-'.Str::lower(Str::random(4)), 'name' => 'Test '.$code, 'accepts_types' => '{websites,calls}', 'cost_rub' => $costRub, 'channel' => 'sites', 'quality_score' => 1.00, 'is_active' => $isActive, 'sort_order' => $sortOrder, ]); } function attachSupplier(Project $project, int $supplierId, bool $isActive = true): void { DB::table('project_suppliers')->insert([ 'project_id' => $project->id, 'supplier_id' => $supplierId, 'is_active' => $isActive, 'created_at' => now(), ]); } beforeEach(function () { $this->tenant = Tenant::factory()->create(); $this->project = Project::factory()->for($this->tenant)->create([ 'name' => 'Test Project', 'type' => 'webhook', ]); $this->resolver = new SupplierResolver; }); test('resolveForProject возвращает null когда у проекта нет связей', function () { expect($this->resolver->resolveForProject($this->project))->toBeNull(); }); test('resolveForProject возвращает supplier_id единственного активного поставщика', function () { $supplierId = seedSupplier('one', 50.00, isActive: true); attachSupplier($this->project, $supplierId); expect($this->resolver->resolveForProject($this->project))->toBe($supplierId); }); test('resolveForProject пропускает inactive supplier', function () { $inactive = seedSupplier('inactive', 50.00, isActive: false); $active = seedSupplier('active', 75.00, isActive: true); attachSupplier($this->project, $inactive); attachSupplier($this->project, $active); expect($this->resolver->resolveForProject($this->project))->toBe($active); }); test('resolveForProject пропускает inactive m2m-связь', function () { $a = seedSupplier('a', 50.00, isActive: true); $b = seedSupplier('b', 75.00, isActive: true); attachSupplier($this->project, $a, isActive: false); attachSupplier($this->project, $b, isActive: true); expect($this->resolver->resolveForProject($this->project))->toBe($b); }); test('resolveForProject соблюдает ORDER BY sort_order, id', function () { $second = seedSupplier('second', 100.00, isActive: true, sortOrder: 10); $first = seedSupplier('first', 200.00, isActive: true, sortOrder: 1); $third = seedSupplier('third', 50.00, isActive: true, sortOrder: 100); attachSupplier($this->project, $second); attachSupplier($this->project, $first); attachSupplier($this->project, $third); expect($this->resolver->resolveForProject($this->project))->toBe($first); }); test('resolveForProject возвращает null если все связи inactive', function () { $a = seedSupplier('a', 50.00, isActive: true); $b = seedSupplier('b', 75.00, isActive: true); attachSupplier($this->project, $a, isActive: false); attachSupplier($this->project, $b, isActive: false); expect($this->resolver->resolveForProject($this->project))->toBeNull(); }); test('resolveForProject изолирован по project_id', function () { $supplierId = seedSupplier('shared', 50.00, isActive: true); attachSupplier($this->project, $supplierId); $otherProject = Project::factory()->for($this->tenant)->create(); expect($this->resolver->resolveForProject($this->project))->toBe($supplierId); expect($this->resolver->resolveForProject($otherProject))->toBeNull(); }); test('costRubSnapshot возвращает текущую цену поставщика', function () { $supplierId = seedSupplier('priced', 137.50, isActive: true); expect($this->resolver->costRubSnapshot($supplierId))->toBe('137.50'); });