1000]); $p2 = new IncentiveParticipant(['total_points' => 3000]); $p3 = new IncentiveParticipant(['total_points' => 2000]); // Simulate the ranking logic from IncentiveTracker::updateRanking $participants = collect([$p1, $p2, $p3])->sortByDesc('total_points')->values(); $rank = 1; foreach ($participants as $p) { $p->rank = $rank; $rank++; } expect($p2->rank)->toBe(1) ->and($p3->rank)->toBe(2) ->and($p1->rank)->toBe(3); }); // === Determine Log Type === it('determines abo type for shop orders', function () { $method = new ReflectionMethod(IncentiveTracker::class, 'determineLogType'); $usv = new \App\Models\UserSalesVolume([ 'status_turnover' => 2, 'status_points' => 1, ]); expect($method->invoke(null, $usv))->toBe('abo'); }); it('determines abo type for customer-only points', function () { $method = new ReflectionMethod(IncentiveTracker::class, 'determineLogType'); $usv = new \App\Models\UserSalesVolume([ 'status_turnover' => 1, 'status_points' => 2, ]); expect($method->invoke(null, $usv))->toBe('abo'); }); it('determines partner type for advisor orders', function () { $method = new ReflectionMethod(IncentiveTracker::class, 'determineLogType'); $usv = new \App\Models\UserSalesVolume([ 'status_turnover' => 1, 'status_points' => 1, ]); expect($method->invoke(null, $usv))->toBe('partner'); }); // === Edge Cases === it('qualification changes correctly on increment/decrement', function () { $incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]); $participant = new IncentiveParticipant([ 'qualified_partners' => 3, 'qualified_abos' => 6, ]); $participant->setRelation('incentive', $incentive); // Not qualified yet expect($participant->checkQualification())->toBeFalse(); // Add one more partner $participant->qualified_partners = 4; expect($participant->checkQualification())->toBeTrue(); // Storno removes a partner $participant->qualified_partners = 3; expect($participant->checkQualification())->toBeFalse(); }); it('handles zero max_winners edge case', function () { $incentive = new Incentive(['max_winners' => 1]); $winner = new IncentiveParticipant(['is_qualified' => true, 'rank' => 1]); $winner->setRelation('incentive', $incentive); $loser = new IncentiveParticipant(['is_qualified' => true, 'rank' => 2]); $loser->setRelation('incentive', $incentive); expect($winner->isWinner())->toBeTrue() ->and($loser->isWinner())->toBeFalse(); }); it('points log total handles negative values for storno', function () { $log = new IncentivePointsLog([ 'points_onetime' => -600, 'points_accumulated' => 0, ]); expect($log->getTotalPoints())->toBe(-600); }); it('points log total handles mixed values', function () { $log = new IncentivePointsLog([ 'points_onetime' => 0, 'points_accumulated' => 250, ]); expect($log->getTotalPoints())->toBe(250); });