mivita/tests/Feature/BusinessPlan/TreeCalcBotIntegrationTest.php
Kevin 841d87e161 Incentive: Ticket-Fixes 2026-08-12 + Team-Struktur nach active_date sortiert
Incentive (Montenegro 2026):
- Live-Tracker: whereDate() für Zeitraum-Vergleiche – Bestellungen am letzten
  Tag des Qualifikationszeitraums gingen verloren (DATE-Spalte vs. Datetime)
- Repair/Rebuild: Neupartner-Definition auf Starterpaket-Bestelldatum im
  Zeitraum umgestellt (statt users.created_at) – Bestandskunden-Upgrades
  zählen jetzt wie im Live-Tracking (Fall Dino Portaro → Steffi Karrer)
- Neu: removeStaleSponsorTrackings() – Partner-Trackings wandern nach
  Sponsorwechsel zum aktuellen Sponsor (Fall Anna Schall lag beim Firmenkonto)
- incentive:calculate: Sponsorwechsel-Bereinigung eingebunden + Statistik

Team-Struktur (/user/team/structure):
- Downline sortiert nach active_date (Partner-Aktivierung) statt users.id:
  zentral in TreeHtmlRenderer::sortByActivation() (wirkt sofort, auch für
  gespeicherte Monats-Snapshots) + orderBy in beiden Downline-Queries

Doku: dev/2026-08-12-intensiv-tickets/README.md (4 Kundentickets analysiert,
Lücke durch verspätete Zeitraum-Verlängerung hatte sich per Nacht-Cron geheilt)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 10:28:06 +00:00

221 lines
7.7 KiB
PHP

<?php
namespace Tests\Feature\BusinessPlan;
use App\Models\UserAccount;
use App\Models\UserLevel;
use App\Services\BusinessPlan\TreeCalcBot;
use App\Services\BusinessPlan\TreeCalcBotOptimized;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class TreeCalcBotIntegrationTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function optimized_version_can_replace_original_version()
{
// Create test data
$user = User::factory()->create([
'm_sponsor' => null,
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01',
]);
UserAccount::factory()->create([
'user_id' => $user->id,
'first_name' => 'Test',
'last_name' => 'User',
'm_account' => 'TEST123',
]);
UserLevel::factory()->create([
'id' => 1,
'name' => 'Bronze',
'pos' => 1,
]);
// Test original version
$originalBot = new TreeCalcBot(1, 2024, 'admin');
$originalBot->initStructureAdmin(false);
$originalItems = $originalBot->getItems();
// Test optimized version
$optimizedBot = new TreeCalcBotOptimized(1, 2024, 'admin');
$optimizedBot->initStructureAdmin(false);
$optimizedItems = $optimizedBot->getItems();
// Both should return same number of items
$this->assertEquals(count($originalItems), count($optimizedItems));
// Both should have same basic functionality
$this->assertEquals($originalBot->isParentless(), $optimizedBot->isParentless());
// HTML output should be generated (length > 0)
$this->assertGreaterThan(0, strlen($originalBot->makeHtmlTree()));
$this->assertGreaterThan(0, strlen($optimizedBot->makeHtmlTree()));
}
/** @test */
public function optimized_version_handles_memory_efficiently()
{
// Create multiple users to test memory efficiency
$users = [];
for ($i = 0; $i < 5; $i++) {
$user = User::factory()->create([
'm_sponsor' => null,
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01',
]);
UserAccount::factory()->create([
'user_id' => $user->id,
'first_name' => 'User',
'last_name' => $i,
'm_account' => 'TEST'.$i,
]);
$users[] = $user;
}
$startMemory = memory_get_usage();
$optimizedBot = new TreeCalcBotOptimized(1, 2024, 'admin');
$optimizedBot->initStructureAdmin(false);
$endMemory = memory_get_usage();
$memoryUsed = $endMemory - $startMemory;
// Memory usage should be reasonable (less than 5MB for 5 users)
$this->assertLessThan(5 * 1024 * 1024, $memoryUsed);
// Should still function correctly
$this->assertEquals(5, count($optimizedBot->getItems()));
}
/** @test */
public function optimized_version_maintains_backward_compatibility()
{
$optimizedBot = new TreeCalcBotOptimized(1, 2024, 'admin');
// Test all public methods exist and are callable
$this->assertTrue(method_exists($optimizedBot, 'initStructureAdmin'));
$this->assertTrue(method_exists($optimizedBot, 'initStructureUser'));
$this->assertTrue(method_exists($optimizedBot, 'getItems'));
$this->assertTrue(method_exists($optimizedBot, 'makeHtmlTree'));
$this->assertTrue(method_exists($optimizedBot, 'makeParentlessHtml'));
$this->assertTrue(method_exists($optimizedBot, 'makeSponsorHtml'));
$this->assertTrue(method_exists($optimizedBot, 'isParentless'));
$this->assertTrue(method_exists($optimizedBot, 'getGrowthBonus'));
$this->assertTrue(method_exists($optimizedBot, 'getKeybyLine'));
// Test static methods
$this->assertTrue(method_exists($optimizedBot, 'isFromStored'));
$this->assertTrue(method_exists($optimizedBot, 'addUserID'));
// Test magic methods for property access
$date = $optimizedBot->__get('date');
$this->assertNotNull($date);
$this->assertEquals(1, $date->month);
$this->assertEquals(2024, $date->year);
}
/** @test */
public function optimized_version_produces_valid_html_output()
{
// Create test user
$user = User::factory()->create([
'm_sponsor' => null,
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01',
]);
UserAccount::factory()->create([
'user_id' => $user->id,
'first_name' => 'Test',
'last_name' => 'User',
]);
$optimizedBot = new TreeCalcBotOptimized(1, 2024, 'admin');
$optimizedBot->initStructureAdmin(false);
// Test HTML outputs
$treeHtml = $optimizedBot->makeHtmlTree();
$parentlessHtml = $optimizedBot->makeParentlessHtml();
$sponsorHtml = $optimizedBot->makeSponsorHtml();
// All should produce valid HTML strings
$this->assertIsString($treeHtml);
$this->assertIsString($parentlessHtml);
$this->assertIsString($sponsorHtml);
// Tree HTML should contain user data when users exist
if (count($optimizedBot->getItems()) > 0) {
$this->assertStringContainsString('Test User', $treeHtml);
}
// HTML should be properly formed (basic check)
$this->assertStringNotContainsString('><', $treeHtml.$parentlessHtml.$sponsorHtml);
}
/** @test */
public function structure_children_are_ordered_by_active_date_not_by_user_id()
{
$makeUser = function (?int $sponsorId, string $activeDate): User {
return User::forceCreate([
'email' => 'tree-'.uniqid('', true).'@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_sponsor' => $sponsorId,
'm_level' => 1,
'admin' => 0,
'payment_account' => $activeDate,
'active_date' => $activeDate,
]);
};
$sponsor = $makeUser(null, '2023-01-01');
// Kinder in ID-Reihenfolge angelegt, aber active_date bewusst durcheinander:
// Bestandskunde (frühe Registrierung/ID) wird erst spät Partner
$lateUpgrader = $makeUser($sponsor->id, '2024-01-10');
$earlyPartner = $makeUser($sponsor->id, '2023-05-01');
$midPartner = $makeUser($sponsor->id, '2024-01-05');
$bot = new TreeCalcBotOptimized(1, 2024, 'member');
$bot->initStructureUser($sponsor->id);
$items = $bot->getItems();
$this->assertCount(1, $items);
$childIds = array_map(fn ($item) => $item->user_id, $items[0]->businessUserItems);
$this->assertEquals(
[$earlyPartner->id, $midPartner->id, $lateUpgrader->id],
$childIds,
'Downline muss nach active_date sortiert sein, nicht nach users.id'
);
}
/** @test */
public function optimized_version_error_handling_works()
{
$optimizedBot = new TreeCalcBotOptimized(1, 2024, 'admin');
// Should handle non-existent user gracefully
$optimizedBot->initStructureUser(999999);
// Should return empty results but not crash
$this->assertEquals([], $optimizedBot->getItems());
$this->assertFalse($optimizedBot->isParentless());
// Should handle method calls on empty state
$this->assertEquals([], $optimizedBot->getGrowthBonus());
$this->assertEquals(0, $optimizedBot->getKeybyLine(1, 'points'));
}
}