whisper-money/tests/Feature/Api/TransactionDuplicateCheckTe...

142 lines
4.7 KiB
PHP

<?php
use App\Models\Account;
use App\Models\Transaction;
use App\Models\User;
use function Pest\Laravel\actingAs;
beforeEach(function () {
$this->user = User::factory()->create();
$this->account = Account::factory()->create(['user_id' => $this->user->id]);
actingAs($this->user);
});
it('flags transactions that already exist on the account', function () {
Transaction::factory()->create([
'user_id' => $this->user->id,
'account_id' => $this->account->id,
'transaction_date' => '2026-01-15',
'amount' => 1234,
'description' => 'Coffee Shop',
]);
$response = $this->postJson('/api/transactions/check-duplicates', [
'account_id' => $this->account->id,
'transactions' => [
['transaction_date' => '2026-01-15', 'amount' => 1234, 'description' => 'Coffee Shop'],
['transaction_date' => '2026-01-15', 'amount' => 1235, 'description' => 'Coffee Shop'],
],
]);
$response->assertOk()->assertJson(['duplicates' => [true, false]]);
});
it('normalizes case and whitespace when matching descriptions', function () {
Transaction::factory()->create([
'user_id' => $this->user->id,
'account_id' => $this->account->id,
'transaction_date' => '2026-01-15',
'amount' => 1234,
'description' => 'Coffee Shop',
]);
$response = $this->postJson('/api/transactions/check-duplicates', [
'account_id' => $this->account->id,
'transactions' => [
['transaction_date' => '2026-01-15', 'amount' => 1234, 'description' => " cOFFee shOP \n"],
],
]);
$response->assertOk()->assertJson(['duplicates' => [true]]);
});
it('scopes duplicate detection to the given account', function () {
$otherAccount = Account::factory()->create(['user_id' => $this->user->id]);
Transaction::factory()->create([
'user_id' => $this->user->id,
'account_id' => $otherAccount->id,
'transaction_date' => '2026-01-15',
'amount' => 1234,
'description' => 'Coffee Shop',
]);
$response = $this->postJson('/api/transactions/check-duplicates', [
'account_id' => $this->account->id,
'transactions' => [
['transaction_date' => '2026-01-15', 'amount' => 1234, 'description' => 'Coffee Shop'],
],
]);
$response->assertOk()->assertJson(['duplicates' => [false]]);
});
it('does not leak another users account', function () {
$stranger = User::factory()->create();
$strangerAccount = Account::factory()->create(['user_id' => $stranger->id]);
$response = $this->postJson('/api/transactions/check-duplicates', [
'account_id' => $strangerAccount->id,
'transactions' => [
['transaction_date' => '2026-01-15', 'amount' => 1234, 'description' => 'Coffee Shop'],
],
]);
$response->assertNotFound();
});
it('treats non-breaking spaces as regular whitespace when matching', function () {
Transaction::factory()->create([
'user_id' => $this->user->id,
'account_id' => $this->account->id,
'transaction_date' => '2026-01-15',
'amount' => 1234,
'description' => 'Coffee Shop',
]);
$response = $this->postJson('/api/transactions/check-duplicates', [
'account_id' => $this->account->id,
'transactions' => [
['transaction_date' => '2026-01-15', 'amount' => 1234, 'description' => "Coffee\u{00A0}Shop"],
],
]);
$response->assertOk()->assertJson(['duplicates' => [true]]);
});
it('only matches existing transactions within the incoming date range', function () {
// Same amount + description as the incoming row, but outside its date range.
Transaction::factory()->create([
'user_id' => $this->user->id,
'account_id' => $this->account->id,
'transaction_date' => '2025-12-01',
'amount' => 1234,
'description' => 'Coffee Shop',
]);
$response = $this->postJson('/api/transactions/check-duplicates', [
'account_id' => $this->account->id,
'transactions' => [
['transaction_date' => '2026-01-15', 'amount' => 1234, 'description' => 'Coffee Shop'],
],
]);
$response->assertOk()->assertJson(['duplicates' => [false]]);
});
it('validates the request payload', function () {
$response = $this->postJson('/api/transactions/check-duplicates', [
'transactions' => [
['transaction_date' => 'not-a-date', 'amount' => 'abc', 'description' => ''],
],
]);
$response->assertUnprocessable()->assertJsonValidationErrors([
'account_id',
'transactions.0.transaction_date',
'transactions.0.amount',
'transactions.0.description',
]);
});