service = app(BudgetTransactionService::class); $this->user = User::factory()->create(); }); test('assignHistoricalTransactionsToPeriod returns correct count', function () { $category = Category::factory()->create(['user_id' => $this->user->id]); // Create 5 historical transactions for ($i = 0; $i < 5; $i++) { Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays($i + 1), 'amount' => -1000, ]); } $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(5); }); test('assignHistoricalTransactionsToPeriod handles empty results', function () { $category = Category::factory()->create(['user_id' => $this->user->id]); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); // No transactions created $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(0); }); test('assignHistoricalTransactionsToPeriod processes large batches', function () { $category = Category::factory()->create(['user_id' => $this->user->id]); // Create 1000 historical transactions $transactions = collect(); for ($i = 0; $i < 1000; $i++) { $transactions->push([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays(rand(1, 25)), 'amount' => -rand(100, 10000), 'created_at' => now(), 'updated_at' => now(), ]); } // Insert in batches Transaction::insert($transactions->toArray()); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(1000); })->skip('Run only when testing performance with large datasets'); test('assignHistoricalTransactionsToPeriod excludes transactions outside date range', function () { $category = Category::factory()->create(['user_id' => $this->user->id]); // Create transactions outside period Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->subMonths(6), 'amount' => -1000, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->addMonths(6), 'amount' => -1000, ]); // Create transaction inside period Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(1); }); test('assignHistoricalTransactionsToPeriod works with category-based budgets', function () { $category = Category::factory()->create(['user_id' => $this->user->id]); $otherCategory = Category::factory()->create(['user_id' => $this->user->id]); // Create transaction with matching category Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); // Create transaction with non-matching category Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $otherCategory->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(1); }); test('assignHistoricalTransactionsToPeriod works with label-based budgets', function () { $label = Label::factory()->create(['user_id' => $this->user->id]); $otherLabel = Label::factory()->create(['user_id' => $this->user->id]); // Create transaction with matching label $transaction1 = Transaction::factory()->create([ 'user_id' => $this->user->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); $transaction1->labels()->attach($label->id); // Create transaction with non-matching label $transaction2 = Transaction::factory()->create([ 'user_id' => $this->user->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); $transaction2->labels()->attach($otherLabel->id); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'label_id' => $label->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(1); }); test('assignHistoricalTransactionsToPeriod works with transactions having multiple labels', function () { $targetLabel = Label::factory()->create(['user_id' => $this->user->id]); $otherLabel1 = Label::factory()->create(['user_id' => $this->user->id]); $otherLabel2 = Label::factory()->create(['user_id' => $this->user->id]); // Create transaction with multiple labels, including the target one $transaction = Transaction::factory()->create([ 'user_id' => $this->user->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); $transaction->labels()->attach([$targetLabel->id, $otherLabel1->id, $otherLabel2->id]); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'label_id' => $targetLabel->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); expect($count)->toBe(1); }); test('assignHistoricalTransactionsToPeriod stores absolute value of amount', function () { $category = Category::factory()->create(['user_id' => $this->user->id]); $transaction = Transaction::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays(5), 'amount' => -5000, // Negative amount ]); $budget = Budget::factory()->create([ 'user_id' => $this->user->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $this->service->assignHistoricalTransactionsToPeriod($period); $budgetTransaction = $period->budgetTransactions()->first(); expect($budgetTransaction->amount)->toBe(5000); // Should be positive }); test('assignHistoricalTransactionsToPeriod only assigns to correct user', function () { $user1 = User::factory()->create(); $user2 = User::factory()->create(); $category = Category::factory()->create(['user_id' => $user1->id]); // Create transaction for user2 Transaction::factory()->create([ 'user_id' => $user2->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); // Create transaction for user1 Transaction::factory()->create([ 'user_id' => $user1->id, 'category_id' => $category->id, 'transaction_date' => now()->subDays(5), 'amount' => -1000, ]); $budget = Budget::factory()->create([ 'user_id' => $user1->id, 'category_id' => $category->id, ]); $period = BudgetPeriod::factory()->create([ 'budget_id' => $budget->id, 'start_date' => now()->subDays(30), 'end_date' => now()->addDays(30), ]); $count = $this->service->assignHistoricalTransactionsToPeriod($period); // Should only assign user1's transaction expect($count)->toBe(1); });