resolver = new PricingTierResolver; // 7-tier сетка in-memory (без БД) $this->tiers = new Collection([ new PricingTier(['tier_no' => 1, 'leads_in_tier' => 100, 'price_per_lead_kopecks' => 50000]), new PricingTier(['tier_no' => 2, 'leads_in_tier' => 200, 'price_per_lead_kopecks' => 45000]), new PricingTier(['tier_no' => 3, 'leads_in_tier' => 400, 'price_per_lead_kopecks' => 40000]), new PricingTier(['tier_no' => 4, 'leads_in_tier' => 800, 'price_per_lead_kopecks' => 35000]), new PricingTier(['tier_no' => 5, 'leads_in_tier' => 1500, 'price_per_lead_kopecks' => 30000]), new PricingTier(['tier_no' => 6, 'leads_in_tier' => 3000, 'price_per_lead_kopecks' => 27000]), new PricingTier(['tier_no' => 7, 'leads_in_tier' => null, 'price_per_lead_kopecks' => 25000]), ]); }); it('returns tier 1 for the 1st lead', function () { $tier = $this->resolver->resolveForCount($this->tiers, 1); expect($tier->tier_no)->toBe(1); }); it('returns tier 1 at upper bound (100th lead)', function () { $tier = $this->resolver->resolveForCount($this->tiers, 100); expect($tier->tier_no)->toBe(1); }); it('crosses to tier 2 at 101st lead', function () { $tier = $this->resolver->resolveForCount($this->tiers, 101); expect($tier->tier_no)->toBe(2); }); it('returns tier 6 at cumulative sum of tiers 1-6 (6000th lead)', function () { // 100 + 200 + 400 + 800 + 1500 + 3000 = 6000; last in tier 6 = 6000th lead $tier = $this->resolver->resolveForCount($this->tiers, 6000); expect($tier->tier_no)->toBe(6); }); it('returns tier 7 (unlimited) for 6001st lead', function () { $tier = $this->resolver->resolveForCount($this->tiers, 6001); expect($tier->tier_no)->toBe(7); }); it('returns tier 7 for 1_000_000th lead (unlimited)', function () { $tier = $this->resolver->resolveForCount($this->tiers, 1_000_000); expect($tier->tier_no)->toBe(7); }); it('throws RuntimeException on empty tiers collection', function () { expect(fn () => $this->resolver->resolveForCount(new Collection, 1)) ->toThrow(RuntimeException::class, 'No active pricing tiers'); }); it('throws InvalidArgumentException on zero or negative ordinal', function () { expect(fn () => $this->resolver->resolveForCount($this->tiers, 0)) ->toThrow(InvalidArgumentException::class, 'leadOrdinal must be >= 1'); expect(fn () => $this->resolver->resolveForCount($this->tiers, -5)) ->toThrow(InvalidArgumentException::class, 'leadOrdinal must be >= 1'); });