219 lines
6.7 KiB
PHP
219 lines
6.7 KiB
PHP
<?php
|
|
|
|
use App\Models\Account;
|
|
use App\Models\Category;
|
|
use App\Models\Transaction;
|
|
use App\Models\User;
|
|
|
|
beforeEach(function () {
|
|
$this->user = User::factory()->onboarded()->create();
|
|
$this->actingAs($this->user);
|
|
});
|
|
|
|
test('encrypted transactions endpoint returns only encrypted transactions', function () {
|
|
$encrypted = Transaction::factory()->create([
|
|
'user_id' => $this->user->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
Transaction::factory()->plaintext()->create([
|
|
'user_id' => $this->user->id,
|
|
]);
|
|
|
|
$response = $this->getJson('/api/transactions?encrypted=true');
|
|
|
|
$response->assertOk();
|
|
$data = $response->json('data');
|
|
expect($data)->toHaveCount(1);
|
|
expect($data[0]['id'])->toBe($encrypted->id);
|
|
});
|
|
|
|
test('encrypted transactions endpoint paginates correctly', function () {
|
|
$account = Account::factory()->create(['user_id' => $this->user->id]);
|
|
$category = Category::factory()->create(['user_id' => $this->user->id]);
|
|
|
|
Transaction::factory()->count(150)->create([
|
|
'user_id' => $this->user->id,
|
|
'account_id' => $account->id,
|
|
'category_id' => $category->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
|
|
$response = $this->getJson('/api/transactions?encrypted=true');
|
|
|
|
$response->assertOk();
|
|
expect($response->json('data'))->toHaveCount(100);
|
|
expect($response->json('next_page_url'))->not->toBeNull();
|
|
|
|
$response2 = $this->getJson('/api/transactions?encrypted=true&page=2');
|
|
expect($response2->json('data'))->toHaveCount(50);
|
|
expect($response2->json('next_page_url'))->toBeNull();
|
|
});
|
|
|
|
test('encrypted transactions endpoint is scoped to authenticated user', function () {
|
|
$otherUser = User::factory()->create();
|
|
|
|
Transaction::factory()->create([
|
|
'user_id' => $otherUser->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
$myTransaction = Transaction::factory()->create([
|
|
'user_id' => $this->user->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
|
|
$response = $this->getJson('/api/transactions?encrypted=true');
|
|
|
|
$response->assertOk();
|
|
$data = $response->json('data');
|
|
expect($data)->toHaveCount(1);
|
|
expect($data[0]['id'])->toBe($myTransaction->id);
|
|
});
|
|
|
|
test('bulk update clears IVs and sets plaintext values', function () {
|
|
$transaction = Transaction::factory()->create([
|
|
'user_id' => $this->user->id,
|
|
'description' => 'encrypted-desc',
|
|
'description_iv' => 'some-iv',
|
|
'notes' => 'encrypted-notes',
|
|
'notes_iv' => 'some-notes-iv',
|
|
]);
|
|
|
|
$response = $this->patchJson('/api/transactions/bulk', [
|
|
'transactions' => [
|
|
[
|
|
'id' => $transaction->id,
|
|
'description' => 'Decrypted description',
|
|
'notes' => 'Decrypted notes',
|
|
'description_iv' => null,
|
|
'notes_iv' => null,
|
|
],
|
|
],
|
|
]);
|
|
|
|
$response->assertOk();
|
|
|
|
$transaction->refresh();
|
|
expect($transaction->description)->toBe('Decrypted description');
|
|
expect($transaction->notes)->toBe('Decrypted notes');
|
|
expect($transaction->description_iv)->toBeNull();
|
|
expect($transaction->notes_iv)->toBeNull();
|
|
});
|
|
|
|
test('bulk update rejects other user transactions', function () {
|
|
$otherUser = User::factory()->create();
|
|
$transaction = Transaction::factory()->create([
|
|
'user_id' => $otherUser->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
|
|
$response = $this->patchJson('/api/transactions/bulk', [
|
|
'transactions' => [
|
|
[
|
|
'id' => $transaction->id,
|
|
'description' => 'Stolen data',
|
|
'description_iv' => null,
|
|
'notes_iv' => null,
|
|
],
|
|
],
|
|
]);
|
|
|
|
$response->assertForbidden();
|
|
});
|
|
|
|
test('bulk update validates max batch size', function () {
|
|
$account = Account::factory()->create(['user_id' => $this->user->id]);
|
|
$category = Category::factory()->create(['user_id' => $this->user->id]);
|
|
|
|
$transactions = Transaction::factory()->count(51)->create([
|
|
'user_id' => $this->user->id,
|
|
'account_id' => $account->id,
|
|
'category_id' => $category->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
|
|
$payload = $transactions->map(fn ($t) => [
|
|
'id' => $t->id,
|
|
'description' => 'decrypted',
|
|
'description_iv' => null,
|
|
'notes_iv' => null,
|
|
])->toArray();
|
|
|
|
$response = $this->patchJson('/api/transactions/bulk', [
|
|
'transactions' => $payload,
|
|
]);
|
|
|
|
$response->assertUnprocessable();
|
|
$response->assertJsonValidationErrors('transactions');
|
|
});
|
|
|
|
test('bulk update validates required fields', function () {
|
|
$response = $this->patchJson('/api/transactions/bulk', [
|
|
'transactions' => [
|
|
['description' => 'missing id'],
|
|
],
|
|
]);
|
|
|
|
$response->assertUnprocessable();
|
|
$response->assertJsonValidationErrors('transactions.0.id');
|
|
});
|
|
|
|
test('bulk update handles nullable notes correctly', function () {
|
|
$transaction = Transaction::factory()->create([
|
|
'user_id' => $this->user->id,
|
|
'description' => 'encrypted-desc',
|
|
'description_iv' => 'some-iv',
|
|
'notes' => null,
|
|
'notes_iv' => null,
|
|
]);
|
|
|
|
$response = $this->patchJson('/api/transactions/bulk', [
|
|
'transactions' => [
|
|
[
|
|
'id' => $transaction->id,
|
|
'description' => 'Decrypted description',
|
|
'notes' => null,
|
|
'description_iv' => null,
|
|
'notes_iv' => null,
|
|
],
|
|
],
|
|
]);
|
|
|
|
$response->assertOk();
|
|
|
|
$transaction->refresh();
|
|
expect($transaction->description)->toBe('Decrypted description');
|
|
expect($transaction->notes)->toBeNull();
|
|
expect($transaction->description_iv)->toBeNull();
|
|
expect($transaction->notes_iv)->toBeNull();
|
|
});
|
|
|
|
test('guests cannot access transactions API', function () {
|
|
auth()->logout();
|
|
|
|
$this->getJson('/api/transactions')->assertUnauthorized();
|
|
$this->patchJson('/api/transactions/bulk', ['transactions' => []])->assertUnauthorized();
|
|
});
|
|
|
|
test('bulk update does not fire model events', function () {
|
|
$transaction = Transaction::factory()->create([
|
|
'user_id' => $this->user->id,
|
|
'description_iv' => 'some-iv',
|
|
]);
|
|
|
|
$originalUpdatedAt = $transaction->updated_at;
|
|
|
|
$this->patchJson('/api/transactions/bulk', [
|
|
'transactions' => [
|
|
[
|
|
'id' => $transaction->id,
|
|
'description' => 'Decrypted',
|
|
'description_iv' => null,
|
|
'notes_iv' => null,
|
|
],
|
|
],
|
|
])->assertOk();
|
|
|
|
$transaction->refresh();
|
|
expect($transaction->updated_at->toDateTimeString())->toBe($originalUpdatedAt->toDateTimeString());
|
|
});
|