commit 08-2025

This commit is contained in:
Kevin Adametz 2025-08-12 18:01:59 +02:00
parent 9ae662f63e
commit 480fdc65ed
404 changed files with 65310 additions and 2600431 deletions

View file

@ -0,0 +1,184 @@
<?php
namespace Tests\Feature\BusinessPlan;
use Tests\TestCase;
use App\User;
use App\Models\UserAccount;
use App\Models\UserLevel;
use App\Models\UserBusiness;
use App\Models\UserBusinessStructure;
use App\Services\BusinessPlan\TreeCalcBotOptimized;
use App\Services\BusinessPlan\TreeCalcBot;
use Illuminate\Foundation\Testing\RefreshDatabase;
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 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'));
}
}

View file

@ -0,0 +1,297 @@
<?php
namespace Tests\Unit\BusinessPlan;
use Tests\TestCase;
use App\User;
use App\Models\UserBusiness;
use App\Models\UserLevel;
use App\Models\UserAccount;
use App\Services\BusinessPlan\BusinessUserItemOptimized;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Carbon\Carbon;
use stdClass;
class BusinessUserItemOptimizedTest extends TestCase
{
use RefreshDatabase;
private $dateObj;
private $businessUserItem;
protected function setUp(): void
{
parent::setUp();
$this->dateObj = new stdClass();
$this->dateObj->month = 1;
$this->dateObj->year = 2024;
$this->dateObj->start_date = '2024-01-01 00:00:00';
$this->dateObj->end_date = '2024-01-31 23:59:59';
$this->businessUserItem = new BusinessUserItemOptimized($this->dateObj);
}
/** @test */
public function constructor_initializes_properties_correctly()
{
$businessUserItem = new BusinessUserItemOptimized($this->dateObj);
$this->assertEquals($this->dateObj, $businessUserItem->__get('date'));
$this->assertEquals([], $businessUserItem->businessUserItems);
}
/** @test */
public function makeUser_finds_existing_business_user()
{
// Create user and existing business data
$user = User::factory()->create();
$existingBusiness = UserBusiness::create([
'user_id' => $user->id,
'month' => 1,
'year' => 2024,
'sales_volume_KP_points' => 100
]);
$this->businessUserItem->makeUser($user->id);
$this->assertEquals($existingBusiness->id, $this->businessUserItem->getBUser()->id);
}
/** @test */
public function makeUser_handles_non_existent_user()
{
$this->businessUserItem->makeUser(999);
// Should not throw exception and should handle gracefully
$this->assertNull($this->businessUserItem->getBUser());
}
/** @test */
public function makeUserFromModel_throws_exception_for_invalid_user()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid user model provided');
$invalidUser = new User(); // User without ID
$this->businessUserItem->makeUserFromModel($invalidUser);
}
/** @test */
public function makeUserFromModel_creates_business_user_from_valid_model()
{
// Create user with account and level
$user = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
$account = UserAccount::factory()->create([
'user_id' => $user->id,
'first_name' => 'John',
'last_name' => 'Doe',
'm_account' => 'TEST123'
]);
$userLevel = UserLevel::factory()->create([
'id' => 1,
'name' => 'Bronze',
'pos' => 1,
'margin' => 10,
'margin_shop' => 15,
'qual_kp' => 100,
'qual_pp' => 200
]);
// Load user with relations
$userWithRelations = User::with(['account', 'userLevel'])->find($user->id);
$this->businessUserItem->makeUserFromModel($userWithRelations);
$bUser = $this->businessUserItem->getBUser();
$this->assertNotNull($bUser);
$this->assertEquals($user->id, $bUser->user_id);
$this->assertEquals('John', $bUser->first_name);
$this->assertEquals('Doe', $bUser->last_name);
$this->assertEquals('Bronze', $bUser->user_level_name);
}
/** @test */
public function addBusinessLinePoints_handles_non_existent_line()
{
$user = User::factory()->create();
$this->businessUserItem->makeUser($user->id);
// This should log a warning but not throw an exception
$this->businessUserItem->addBusinessLinePoints(1, 100);
// Test passes if no exception is thrown
$this->assertTrue(true);
}
/** @test */
public function addBusinessLinePoints_adds_points_correctly()
{
$user = User::factory()->create();
$this->businessUserItem->makeUser($user->id);
// First, add a business line
$obj = new stdClass();
$obj->points = 50;
$this->businessUserItem->addBusinessLineToUser(1, $obj);
// Then add more points
$this->businessUserItem->addBusinessLinePoints(1, 100);
$bUser = $this->businessUserItem->getBUser();
$this->assertEquals(150, $bUser->business_lines[1]->points);
}
/** @test */
public function addTotalTP_adds_points_with_type_safety()
{
$user = User::factory()->create();
$this->businessUserItem->makeUser($user->id);
$this->businessUserItem->addTotalTP('100.5'); // String should be converted to float
$this->businessUserItem->addTotalTP(50);
$bUser = $this->businessUserItem->getBUser();
$this->assertEquals(150.5, $bUser->total_pp);
}
/** @test */
public function isQualKP_returns_correct_boolean()
{
$user = User::factory()->create();
$userLevel = UserLevel::factory()->create([
'id' => 1,
'qual_kp' => 100
]);
$this->businessUserItem->makeUser($user->id);
// Manually set values for testing
$bUser = $this->businessUserItem->getBUser();
$bUser->sales_volume_points_KP_sum = 150;
$bUser->qual_kp = 100;
$this->assertTrue($this->businessUserItem->isQualKP());
$bUser->sales_volume_points_KP_sum = 50;
$this->assertFalse($this->businessUserItem->isQualKP());
}
/** @test */
public function getRestQualKP_returns_positive_value_or_zero()
{
$user = User::factory()->create();
$this->businessUserItem->makeUser($user->id);
$bUser = $this->businessUserItem->getBUser();
$bUser->sales_volume_points_KP_sum = 150;
$bUser->qual_kp = 100;
$this->assertEquals(50, $this->businessUserItem->getRestQualKP());
$bUser->sales_volume_points_KP_sum = 50;
$this->assertEquals(0, $this->businessUserItem->getRestQualKP()); // Should not be negative
}
/** @test */
public function getCommissionTotal_calculates_correctly()
{
$user = User::factory()->create();
$this->businessUserItem->makeUser($user->id);
$bUser = $this->businessUserItem->getBUser();
$bUser->commission_shop_sales = 10.50;
$bUser->commission_pp_total = 25.75;
$bUser->commission_growth_total = 15.25;
$total = $this->businessUserItem->getCommissionTotal();
$this->assertEquals(51.50, $total);
}
/** @test */
public function checkSponsor_handles_user_without_sponsor()
{
$user = User::factory()->create(['m_sponsor' => null]);
$this->businessUserItem->makeUser($user->id);
$this->businessUserItem->checkSponsor($user);
$bUser = $this->businessUserItem->getBUser();
$this->assertFalse($bUser->sponsor->is_sponsor);
$this->assertEquals('Keinen Sponsor zugewiesen', $bUser->sponsor->full_name);
}
/** @test */
public function isSave_returns_correct_boolean()
{
// New business user item should not be saved
$this->assertFalse($this->businessUserItem->isSave());
// Create and save a business user
$user = User::factory()->create();
$business = UserBusiness::create([
'user_id' => $user->id,
'month' => 1,
'year' => 2024
]);
$this->businessUserItem->makeUser($user->id);
$this->assertTrue($this->businessUserItem->isSave());
}
/** @test */
public function magic_get_method_returns_properties()
{
$user = User::factory()->create();
$this->businessUserItem->makeUser($user->id);
$userId = $this->businessUserItem->__get('user_id');
$this->assertEquals($user->id, $userId);
$nullValue = $this->businessUserItem->__get('non_existent_property');
$this->assertNull($nullValue);
}
/** @test */
public function readParentsBusinessUsers_loads_child_users()
{
// Create parent user
$parentUser = User::factory()->create([
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01',
'm_level' => 1
]);
// Create child users
$childUser1 = User::factory()->create([
'm_sponsor' => $parentUser->id,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01',
'm_level' => 1
]);
$childUser2 = User::factory()->create([
'm_sponsor' => $parentUser->id,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01',
'm_level' => 1
]);
// Create accounts for users
UserAccount::factory()->create(['user_id' => $parentUser->id]);
UserAccount::factory()->create(['user_id' => $childUser1->id]);
UserAccount::factory()->create(['user_id' => $childUser2->id]);
$this->businessUserItem->makeUser($parentUser->id);
$this->businessUserItem->readParentsBusinessUsers();
// Should have loaded 2 child business users
$this->assertCount(2, $this->businessUserItem->businessUserItems);
}
}

View file

@ -0,0 +1,285 @@
<?php
namespace Tests\Unit\BusinessPlan;
use Tests\TestCase;
use App\User;
use App\Models\UserAccount;
use App\Models\UserBusinessStructure;
use App\Services\BusinessPlan\BusinessUserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
class BusinessUserRepositoryTest extends TestCase
{
use RefreshDatabase;
private $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = new BusinessUserRepository(1, 2024);
}
/** @test */
public function constructor_initializes_dates_correctly()
{
$repository = new BusinessUserRepository(3, 2024);
// Using reflection to access private properties for testing
$reflection = new \ReflectionClass($repository);
$monthProperty = $reflection->getProperty('month');
$monthProperty->setAccessible(true);
$yearProperty = $reflection->getProperty('year');
$yearProperty->setAccessible(true);
$this->assertEquals(3, $monthProperty->getValue($repository));
$this->assertEquals(2024, $yearProperty->getValue($repository));
}
/** @test */
public function getStoredStructure_returns_completed_structure()
{
$structure = UserBusinessStructure::create([
'month' => 1,
'year' => 2024,
'structure' => json_encode([]),
'completed' => true
]);
$result = $this->repository->getStoredStructure();
$this->assertNotNull($result);
$this->assertEquals($structure->id, $result->id);
}
/** @test */
public function getStoredStructure_returns_null_for_incomplete_structure()
{
UserBusinessStructure::create([
'month' => 1,
'year' => 2024,
'structure' => json_encode([]),
'completed' => false
]);
$result = $this->repository->getStoredStructure();
$this->assertNull($result);
}
/** @test */
public function getRootUsers_returns_users_with_relations()
{
// Create root users (no sponsor)
$rootUser1 = User::factory()->create([
'm_sponsor' => null,
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
$rootUser2 = User::factory()->create([
'm_sponsor' => null,
'm_level' => 2,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
// Create non-root user (has sponsor)
$childUser = User::factory()->create([
'm_sponsor' => $rootUser1->id,
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
// Create accounts
UserAccount::factory()->create(['user_id' => $rootUser1->id]);
UserAccount::factory()->create(['user_id' => $rootUser2->id]);
UserAccount::factory()->create(['user_id' => $childUser->id]);
$result = $this->repository->getRootUsers();
// Should return only root users (2), not child user
$this->assertEquals(2, $result->count());
// Verify relations are loaded
$firstUser = $result->first();
$this->assertTrue($firstUser->relationLoaded('account'));
}
/** @test */
public function getUserWithRelations_returns_user_with_loaded_relations()
{
$user = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15'
]);
UserAccount::factory()->create(['user_id' => $user->id]);
$result = $this->repository->getUserWithRelations($user->id);
$this->assertNotNull($result);
$this->assertEquals($user->id, $result->id);
$this->assertTrue($result->relationLoaded('account'));
$this->assertTrue($result->relationLoaded('userLevel'));
}
/** @test */
public function getUserWithRelations_returns_null_for_non_existent_user()
{
$result = $this->repository->getUserWithRelations(999);
$this->assertNull($result);
}
/** @test */
public function getParentlessUsers_excludes_processed_user_ids()
{
// Create users
$user1 = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
$user2 = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
$user3 = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
// Create accounts
UserAccount::factory()->create(['user_id' => $user1->id]);
UserAccount::factory()->create(['user_id' => $user2->id]);
UserAccount::factory()->create(['user_id' => $user3->id]);
// Exclude user1 and user2
$excludeIds = [$user1->id, $user2->id];
$result = $this->repository->getParentlessUsers($excludeIds);
// Should only return user3
$this->assertEquals(1, $result->count());
$this->assertEquals($user3->id, $result->first()->id);
}
/** @test */
public function getSponsorForUser_returns_sponsor_user()
{
// Create sponsor
$sponsor = User::factory()->create(['m_level' => 2]);
UserAccount::factory()->create(['user_id' => $sponsor->id]);
// Create user with sponsor
$user = User::factory()->create(['m_sponsor' => $sponsor->id]);
UserAccount::factory()->create(['user_id' => $user->id]);
$result = $this->repository->getSponsorForUser($user->id);
$this->assertNotNull($result);
$this->assertEquals($sponsor->id, $result->id);
}
/** @test */
public function getSponsorForUser_returns_null_for_user_without_sponsor()
{
$user = User::factory()->create(['m_sponsor' => null]);
$result = $this->repository->getSponsorForUser($user->id);
$this->assertNull($result);
}
/** @test */
public function caching_works_correctly_for_root_users()
{
// Clear cache first
Cache::flush();
// Create root user
$rootUser = User::factory()->create([
'm_sponsor' => null,
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
UserAccount::factory()->create(['user_id' => $rootUser->id]);
// First call should hit database and cache result
$result1 = $this->repository->getRootUsers();
$this->assertEquals(1, $result1->count());
// Verify cache was set
$cacheKey = "root_users_1_2024";
$this->assertTrue(Cache::has($cacheKey));
// Second call should use cache
$result2 = $this->repository->getRootUsers();
$this->assertEquals(1, $result2->count());
}
/** @test */
public function lazy_loading_yields_users_in_batches()
{
// Create multiple users
for ($i = 0; $i < 5; $i++) {
$user = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
UserAccount::factory()->create(['user_id' => $user->id]);
}
$count = 0;
foreach ($this->repository->getParentlessUsers([]) as $user) {
$count++;
$this->assertInstanceOf(User::class, $user);
$this->assertTrue($user->relationLoaded('account'));
}
$this->assertEquals(5, $count);
}
/** @test */
public function memory_efficient_processing_works()
{
// Create many users to test memory efficiency
for ($i = 0; $i < 10; $i++) {
$user = User::factory()->create([
'm_level' => 1,
'payment_account' => '2024-01-15',
'active_date' => '2024-01-01'
]);
UserAccount::factory()->create(['user_id' => $user->id]);
}
$startMemory = memory_get_usage();
// Process users lazily
$count = 0;
foreach ($this->repository->getParentlessUsers([]) as $user) {
$count++;
// Simulate some processing
$user->toArray();
}
$endMemory = memory_get_usage();
$memoryUsed = $endMemory - $startMemory;
$this->assertEquals(10, $count);
// Memory usage should be reasonable (less than 10MB)
$this->assertLessThan(10 * 1024 * 1024, $memoryUsed);
}
}

View file

@ -0,0 +1,311 @@
<?php
namespace Tests\Unit\BusinessPlan;
use Tests\TestCase;
use App\User;
use App\Models\UserBusinessStructure;
use App\Services\BusinessPlan\TreeCalcBotOptimized;
use App\Services\BusinessPlan\BusinessUserRepository;
use App\Services\BusinessPlan\TreeHtmlRenderer;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
class TreeCalcBotOptimizedTest extends TestCase
{
use RefreshDatabase;
private $treeCalcBot;
private $mockRepository;
private $mockRenderer;
private $mockLogger;
protected function setUp(): void
{
parent::setUp();
$this->mockRepository = Mockery::mock(BusinessUserRepository::class);
$this->mockRenderer = Mockery::mock(TreeHtmlRenderer::class);
$this->mockLogger = Mockery::mock(\Psr\Log\LoggerInterface::class);
$this->treeCalcBot = new TreeCalcBotOptimized(
1, // month
2024, // year
'admin',
$this->mockRepository,
$this->mockRenderer,
$this->mockLogger
);
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
/** @test */
public function constructor_validates_month_boundaries()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid month: 13');
new TreeCalcBotOptimized(13, 2024);
}
/** @test */
public function constructor_validates_year_boundaries()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid year: 2019');
new TreeCalcBotOptimized(1, 2019);
}
/** @test */
public function constructor_initializes_date_object_correctly()
{
$treeCalcBot = new TreeCalcBotOptimized(3, 2024);
$date = $treeCalcBot->__get('date');
$this->assertEquals(3, $date->month);
$this->assertEquals(2024, $date->year);
$this->assertStringStartsWith('2024-03-01', $date->start_date);
$this->assertStringStartsWith('2024-03-31', $date->end_date);
}
/** @test */
public function isFromStored_returns_completed_structure()
{
// Create completed structure
$structure = UserBusinessStructure::create([
'month' => 1,
'year' => 2024,
'structure' => [],
'completed' => true
]);
$result = TreeCalcBotOptimized::isFromStored(1, 2024);
$this->assertNotNull($result);
$this->assertEquals($structure->id, $result->id);
}
/** @test */
public function isFromStored_returns_null_for_incomplete_structure()
{
// Create incomplete structure
UserBusinessStructure::create([
'month' => 1,
'year' => 2024,
'structure' => [],
'completed' => false
]);
$result = TreeCalcBotOptimized::isFromStored(1, 2024);
$this->assertNull($result);
}
/** @test */
public function initStructureAdmin_uses_stored_structure_when_available()
{
$mockStructure = new UserBusinessStructure();
$mockStructure->structure = [];
$mockStructure->parentless = [];
$this->mockRepository
->shouldReceive('getStoredStructure')
->once()
->andReturn($mockStructure);
$this->mockLogger
->shouldReceive('info')
->once()
->with('Loading stored business structure for 1/2024');
$this->treeCalcBot->initStructureAdmin(true);
}
/** @test */
public function initStructureAdmin_builds_fresh_when_no_stored_structure()
{
$this->mockRepository
->shouldReceive('getStoredStructure')
->once()
->andReturn(null);
$this->mockRepository
->shouldReceive('getRootUsers')
->once()
->andReturn(collect([]));
$this->mockLogger
->shouldReceive('info')
->once()
->with('Building fresh business structure for 1/2024');
$this->mockLogger
->shouldReceive('info')
->once()
->with('Loaded 0 root users with optimized relations. Memory used: 0 B');
$this->treeCalcBot->initStructureAdmin(true);
}
/** @test */
public function initStructureUser_handles_non_existent_user()
{
$this->mockRepository
->shouldReceive('getUserWithRelations')
->with(999)
->once()
->andReturn(null);
$this->mockLogger
->shouldReceive('info')
->once()
->with('Initializing structure for user: 999');
$this->mockLogger
->shouldReceive('warning')
->once()
->with('User not found: 999');
$this->treeCalcBot->initStructureUser(999);
}
/** @test */
public function getGrowthBonus_returns_empty_array_when_no_business_user()
{
$result = $this->treeCalcBot->getGrowthBonus();
$this->assertEquals([], $result);
}
/** @test */
public function getKeybyLine_returns_zero_when_no_business_user()
{
$result = $this->treeCalcBot->getKeybyLine(1, 'points');
$this->assertEquals(0, $result);
}
/** @test */
public function makeHtmlTree_delegates_to_renderer()
{
$expectedHtml = '<div>Test HTML</div>';
$this->mockRenderer
->shouldReceive('renderTree')
->once()
->with([])
->andReturn($expectedHtml);
$result = $this->treeCalcBot->makeHtmlTree();
$this->assertEquals($expectedHtml, $result);
}
/** @test */
public function makeParentlessHtml_delegates_to_renderer()
{
$expectedHtml = '<div>Parentless HTML</div>';
$this->mockRenderer
->shouldReceive('renderParentless')
->once()
->with([])
->andReturn($expectedHtml);
$result = $this->treeCalcBot->makeParentlessHtml();
$this->assertEquals($expectedHtml, $result);
}
/** @test */
public function isParentless_returns_false_when_empty()
{
$result = $this->treeCalcBot->isParentless();
$this->assertFalse($result);
}
/** @test */
public function getItems_returns_business_users_array()
{
$result = $this->treeCalcBot->getItems();
$this->assertEquals([], $result);
}
/** @test */
public function magic_get_method_returns_correct_properties()
{
$date = $this->treeCalcBot->__get('date');
$businessUsers = $this->treeCalcBot->__get('business_users');
$parentless = $this->treeCalcBot->__get('parentless');
$this->assertNotNull($date);
$this->assertEquals([], $businessUsers);
$this->assertEquals([], $parentless);
}
/** @test */
public function magic_get_method_throws_exception_for_invalid_property()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Property invalid_property does not exist');
$this->treeCalcBot->__get('invalid_property');
}
/** @test */
public function magic_set_method_allows_setting_valid_properties()
{
$testArray = ['test'];
$this->treeCalcBot->__set('business_users', $testArray);
$result = $this->treeCalcBot->__get('business_users');
$this->assertEquals($testArray, $result);
}
/** @test */
public function magic_set_method_throws_exception_for_invalid_property()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Property invalid_property cannot be set');
$this->treeCalcBot->__set('invalid_property', 'value');
}
/** @test */
public function memory_monitoring_detects_high_usage()
{
// This test would need reflection to access private methods
// For now, we'll test the public interface
$this->mockLogger
->shouldReceive('info')
->once()
->with('Building fresh business structure for 1/2024');
$this->mockLogger
->shouldReceive('info')
->once()
->with('Loaded 0 root users with optimized relations. Memory used: 0 B');
$this->mockRepository
->shouldReceive('getStoredStructure')
->once()
->andReturn(null);
$this->mockRepository
->shouldReceive('getRootUsers')
->once()
->andReturn(collect([]));
$this->treeCalcBot->initStructureAdmin(false);
}
}

View file

@ -0,0 +1,254 @@
<?php
namespace Tests\Unit\BusinessPlan;
use Tests\TestCase;
use App\Services\BusinessPlan\TreeHtmlRenderer;
use App\Services\BusinessPlan\BusinessUserItemOptimized;
use stdClass;
class TreeHtmlRendererTest extends TestCase
{
private $renderer;
private $dateObj;
protected function setUp(): void
{
parent::setUp();
$this->renderer = new TreeHtmlRenderer('admin');
$this->dateObj = new stdClass();
$this->dateObj->month = 1;
$this->dateObj->year = 2024;
$this->dateObj->start_date = '2024-01-01 00:00:00';
$this->dateObj->end_date = '2024-01-31 23:59:59';
}
/** @test */
public function constructor_sets_init_from_correctly()
{
$adminRenderer = new TreeHtmlRenderer('admin');
$memberRenderer = new TreeHtmlRenderer('member');
$defaultRenderer = new TreeHtmlRenderer();
// Using reflection to access private property
$reflection = new \ReflectionClass(TreeHtmlRenderer::class);
$property = $reflection->getProperty('initFrom');
$property->setAccessible(true);
$this->assertEquals('admin', $property->getValue($adminRenderer));
$this->assertEquals('member', $property->getValue($memberRenderer));
$this->assertEquals('member', $property->getValue($defaultRenderer));
}
/** @test */
public function renderTree_returns_info_message_for_empty_array()
{
$result = $this->renderer->renderTree([]);
$this->assertStringContainsString('Keine Business-User gefunden', $result);
$this->assertStringContainsString('alert-info', $result);
}
/** @test */
public function renderTree_generates_html_structure_for_business_users()
{
// Create mock business user items
$businessUsers = [
$this->createMockBusinessUser(1, 'John', 'Doe', 'john@example.com', true),
$this->createMockBusinessUser(2, 'Jane', 'Smith', 'jane@example.com', false)
];
$result = $this->renderer->renderTree($businessUsers);
// Check basic HTML structure
$this->assertStringContainsString('<ol class="dd-list">', $result);
$this->assertStringContainsString('</ol>', $result);
$this->assertStringContainsString('dd-item', $result);
// Check user data is included
$this->assertStringContainsString('John Doe', $result);
$this->assertStringContainsString('Jane Smith', $result);
$this->assertStringContainsString('john@example.com', $result);
$this->assertStringContainsString('jane@example.com', $result);
}
/** @test */
public function renderTree_includes_admin_buttons_for_admin_init()
{
$adminRenderer = new TreeHtmlRenderer('admin');
$businessUsers = [
$this->createMockBusinessUser(1, 'John', 'Doe', 'john@example.com', true)
];
$result = $adminRenderer->renderTree($businessUsers);
// Should include calculator button for admin
$this->assertStringContainsString('fa-calculator', $result);
$this->assertStringContainsString('business-user-detail', $result);
}
/** @test */
public function renderTree_excludes_admin_buttons_for_member_init()
{
$memberRenderer = new TreeHtmlRenderer('member');
$businessUsers = [
$this->createMockBusinessUser(1, 'John', 'Doe', 'john@example.com', true)
];
$result = $memberRenderer->renderTree($businessUsers);
// Should not include calculator button for regular members
$this->assertStringNotContainsString('fa-calculator', $result);
}
/** @test */
public function renderTree_handles_inactive_accounts_correctly()
{
$businessUsers = [
$this->createMockBusinessUser(1, 'Active', 'User', 'active@example.com', true),
$this->createMockBusinessUser(2, 'Inactive', 'User', 'inactive@example.com', false)
];
$result = $this->renderer->renderTree($businessUsers);
// Active user should have primary color icon
$this->assertStringContainsString('text-primary', $result);
// Inactive user should have danger/muted styling
$this->assertStringContainsString('text-danger', $result);
$this->assertStringContainsString('text-muted', $result);
}
/** @test */
public function renderParentless_returns_empty_message_for_empty_array()
{
$result = $this->renderer->renderParentless([]);
$this->assertStringContainsString('Keine parentlosen User gefunden', $result);
$this->assertStringContainsString('alert-info', $result);
}
/** @test */
public function renderParentless_generates_list_for_parentless_users()
{
$parentlessUsers = [
$this->createMockBusinessUser(1, 'Orphan', 'User', 'orphan@example.com', true)
];
$result = $this->renderer->renderParentless($parentlessUsers);
$this->assertStringContainsString('dd-item', $result);
$this->assertStringContainsString('Orphan User', $result);
$this->assertStringContainsString('orphan@example.com', $result);
}
/** @test */
public function renderSponsor_returns_no_sponsor_message_for_null()
{
$result = $this->renderer->renderSponsor(null);
$this->assertStringContainsString('alert-warning', $result);
$this->assertStringContainsString('team.no_sponsor_assigned', $result);
}
/** @test */
public function renderSponsor_generates_sponsor_html()
{
$sponsor = $this->createMockBusinessUser(1, 'Sponsor', 'User', 'sponsor@example.com', true);
$result = $this->renderer->renderSponsor($sponsor);
$this->assertStringContainsString('dd-item', $result);
$this->assertStringContainsString('Sponsor User', $result);
$this->assertStringContainsString('sponsor@example.com', $result);
}
/** @test */
public function renderSponsor_includes_admin_details_for_admin_init()
{
$adminRenderer = new TreeHtmlRenderer('admin');
$sponsor = $this->createMockBusinessUser(1, 'Sponsor', 'User', 'sponsor@example.com', true);
// Set some business data
$sponsor->sales_volume_points_KP_sum = 1000;
$sponsor->sales_volume_total_sum = 5000;
$result = $adminRenderer->renderSponsor($sponsor);
$this->assertStringContainsString('1000', $result);
$this->assertStringContainsString('5000', $result);
$this->assertStringContainsString('total_points', $result);
}
/** @test */
public function renderItem_handles_deep_nesting()
{
// Create business user with nested children
$parentUser = $this->createMockBusinessUser(1, 'Parent', 'User', 'parent@example.com', true);
$childUser = $this->createMockBusinessUser(2, 'Child', 'User', 'child@example.com', true);
// Set up nesting
$parentUser->businessUserItems = [$childUser];
$result = $this->renderer->renderTree([$parentUser]);
// Should contain both parent and child
$this->assertStringContainsString('Parent User', $result);
$this->assertStringContainsString('Child User', $result);
// Should have nested structure
$this->assertStringContainsString('<ol class="dd-list dd-nodrag">', $result);
}
/** @test */
public function html_output_is_properly_escaped()
{
$businessUsers = [
$this->createMockBusinessUser(1, 'John <script>', 'Doe & Co', 'john@example.com', true)
];
$result = $this->renderer->renderTree($businessUsers);
// HTML should be escaped
$this->assertStringNotContainsString('<script>', $result);
$this->assertStringContainsString('&lt;script&gt;', $result);
$this->assertStringContainsString('&amp;', $result);
}
/**
* Helper method to create mock business user
*/
private function createMockBusinessUser($id, $firstName, $lastName, $email, $activeAccount)
{
$businessUser = new BusinessUserItemOptimized($this->dateObj);
// Create a mock b_user object
$bUser = new stdClass();
$bUser->user_id = $id;
$bUser->first_name = $firstName;
$bUser->last_name = $lastName;
$bUser->email = $email;
$bUser->active_account = $activeAccount;
$bUser->user_level_name = 'Bronze';
$bUser->m_account = 'TEST' . $id;
$bUser->user_birthday = '1990-01-01';
$bUser->user_phone = '+1234567890';
$bUser->sales_volume_points_KP_sum = 100;
$bUser->sales_volume_KP_points = 50;
$bUser->sales_volume_points_shop = 50;
$bUser->sales_volume_total_sum = 1000;
$bUser->sales_volume_total = 500;
$bUser->sales_volume_total_shop = 500;
$bUser->payment_account_date = '2024-01-15';
$bUser->businessUserItems = [];
// Use reflection to set private property
$reflection = new \ReflectionClass($businessUser);
$property = $reflection->getProperty('b_user');
$property->setAccessible(true);
$property->setValue($businessUser, $bUser);
return $businessUser;
}
}