user = User::factory()->create([ 'email_verified_at' => now(), 'onboarded_at' => now(), ]); $this->account = Account::factory()->create(['user_id' => $this->user->id]); $this->category = Category::factory()->create(['user_id' => $this->user->id]); }); it('can bulk update transactions by IDs', function () { $transactions = Transaction::factory() ->count(3) ->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'category_id' => null, ]); $newCategory = Category::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'transaction_ids' => $transactions->pluck('id')->toArray(), 'category_id' => $newCategory->id, ]); $response->assertSuccessful(); $response->assertJson([ 'message' => 'Transactions updated successfully', 'count' => 3, ]); foreach ($transactions as $transaction) { expect($transaction->fresh()->category_id)->toBe($newCategory->id); } }); it('can bulk update transactions by filters with date range', function () { Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'transaction_date' => '2024-01-15', 'category_id' => null, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'transaction_date' => '2024-01-20', 'category_id' => null, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'transaction_date' => '2024-02-01', 'category_id' => null, ]); $newCategory = Category::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'filters' => [ 'date_from' => '2024-01-01', 'date_to' => '2024-01-31', ], 'category_id' => $newCategory->id, ]); $response->assertSuccessful(); $response->assertJson([ 'message' => 'Transactions updated successfully', 'count' => 2, ]); $januaryTransactions = Transaction::query() ->where('user_id', $this->user->id) ->whereDate('transaction_date', '>=', '2024-01-01') ->whereDate('transaction_date', '<=', '2024-01-31') ->get(); foreach ($januaryTransactions as $transaction) { expect($transaction->category_id)->toBe($newCategory->id); } $februaryTransaction = Transaction::query() ->where('user_id', $this->user->id) ->whereDate('transaction_date', '2024-02-01') ->first(); expect($februaryTransaction->category_id)->toBeNull(); }); it('can bulk update transactions by filters with amount range', function () { Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'amount' => '5000', 'category_id' => null, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'amount' => '15000', 'category_id' => null, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'amount' => '25000', 'category_id' => null, ]); $newCategory = Category::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'filters' => [ 'amount_min' => 100, 'amount_max' => 200, ], 'category_id' => $newCategory->id, ]); $response->assertSuccessful(); $midRangeTransaction = Transaction::query() ->where('user_id', $this->user->id) ->where('amount', '15000') ->first(); expect($midRangeTransaction->category_id)->toBe($newCategory->id); $lowTransaction = Transaction::query() ->where('user_id', $this->user->id) ->where('amount', '5000') ->first(); expect($lowTransaction->category_id)->toBeNull(); $highTransaction = Transaction::query() ->where('user_id', $this->user->id) ->where('amount', '25000') ->first(); expect($highTransaction->category_id)->toBeNull(); }); it('can bulk update transactions by filters with category filter', function () { $categoryA = Category::factory()->create(['user_id' => $this->user->id]); $categoryB = Category::factory()->create(['user_id' => $this->user->id]); $categoryC = Category::factory()->create(['user_id' => $this->user->id]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'category_id' => $categoryA->id, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'category_id' => $categoryA->id, ]); Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'category_id' => $categoryB->id, ]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'filters' => [ 'category_ids' => [$categoryA->id], ], 'category_id' => $categoryC->id, ]); $response->assertSuccessful(); $response->assertJson([ 'count' => 2, ]); $updatedTransactions = Transaction::query() ->where('user_id', $this->user->id) ->where('category_id', $categoryC->id) ->count(); expect($updatedTransactions)->toBe(2); $unchangedTransaction = Transaction::query() ->where('user_id', $this->user->id) ->where('category_id', $categoryB->id) ->count(); expect($unchangedTransaction)->toBe(1); }); it('can bulk update transactions with labels by IDs', function () { $transactions = Transaction::factory() ->count(2) ->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, ]); $label1 = Label::factory()->create(['user_id' => $this->user->id]); $label2 = Label::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'transaction_ids' => $transactions->pluck('id')->toArray(), 'label_ids' => [$label1->id, $label2->id], ]); $response->assertSuccessful(); foreach ($transactions as $transaction) { $labelIds = $transaction->fresh()->labels->pluck('id')->toArray(); expect($labelIds)->toContain($label1->id); expect($labelIds)->toContain($label2->id); } }); it('bulk update replaces labels instead of merging them', function () { $label1 = Label::factory()->create(['user_id' => $this->user->id, 'name' => 'Old Label 1']); $label2 = Label::factory()->create(['user_id' => $this->user->id, 'name' => 'Old Label 2']); $label3 = Label::factory()->create(['user_id' => $this->user->id, 'name' => 'New Label']); $transactions = Transaction::factory() ->count(2) ->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, ]); // Attach existing labels foreach ($transactions as $transaction) { $transaction->labels()->attach([$label1->id, $label2->id]); } // Bulk update with new label should replace, not merge $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'transaction_ids' => $transactions->pluck('id')->toArray(), 'label_ids' => [$label3->id], ]); $response->assertSuccessful(); // Verify labels were replaced, not merged foreach ($transactions as $transaction) { $labelIds = $transaction->fresh()->labels->pluck('id')->toArray(); expect($labelIds)->toHaveCount(1); expect($labelIds)->toEqual([$label3->id]); expect($labelIds)->not->toContain($label1->id); expect($labelIds)->not->toContain($label2->id); } }); it('can update all transactions when no filters or IDs are provided', function () { Transaction::factory() ->count(3) ->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'category_id' => null, ]); $newCategory = Category::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'category_id' => $newCategory->id, ]); $response->assertSuccessful(); $response->assertJson([ 'message' => 'Transactions updated successfully', 'count' => 3, ]); $allTransactions = Transaction::query() ->where('user_id', $this->user->id) ->get(); foreach ($allTransactions as $transaction) { expect($transaction->category_id)->toBe($newCategory->id); } }); it('validates that at least one update field is provided', function () { $transaction = Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, ]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'transaction_ids' => [$transaction->id], ]); $response->assertStatus(400); $response->assertJson([ 'message' => 'No update data provided.', ]); }); it('only updates transactions belonging to the authenticated user', function () { $otherUser = User::factory()->create([ 'email_verified_at' => now(), 'onboarded_at' => now(), ]); $otherAccount = Account::factory()->create(['user_id' => $otherUser->id]); $myTransaction = Transaction::factory()->create([ 'user_id' => $this->user->id, 'account_id' => $this->account->id, 'category_id' => null, ]); $otherTransaction = Transaction::factory()->create([ 'user_id' => $otherUser->id, 'account_id' => $otherAccount->id, 'category_id' => null, ]); $newCategory = Category::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->patchJson('/transactions/bulk', [ 'transaction_ids' => [$myTransaction->id, $otherTransaction->id], 'category_id' => $newCategory->id, ]); $response->assertStatus(403); expect($myTransaction->fresh()->category_id)->toBeNull(); expect($otherTransaction->fresh()->category_id)->toBeNull(); });