whisper-money/tests/Performance/ApiQueryCountTest.php

115 lines
5.1 KiB
PHP

<?php
use App\Models\Account;
use App\Models\AccountBalance;
/*
|--------------------------------------------------------------------------
| API Analytics Query Count Tests
|--------------------------------------------------------------------------
|
| These tests enforce query count ceilings for all analytics API endpoints
| used by the dashboard and cashflow pages. These endpoints are called
| asynchronously from the frontend and are critical to page load performance.
|
| Run this suite in isolation:
| php artisan test --testsuite=Performance
|
*/
beforeEach(function () {
$this->user = performanceSeedUser();
$this->actingAs($this->user);
$this->dateParams = http_build_query([
'from' => now()->subDays(30)->toDateString(),
'to' => now()->toDateString(),
]);
});
// ──────────────────────────────────────────────────────────────────────────
// Dashboard Analytics API
// ──────────────────────────────────────────────────────────────────────────
test('net worth API does not exceed query threshold', function () {
assertMaxQueries(25, function () {
$this->getJson("/api/dashboard/net-worth?{$this->dateParams}")->assertOk();
}, 'API Net Worth');
});
test('monthly spending API does not exceed query threshold', function () {
assertMaxQueries(12, function () {
$this->getJson("/api/dashboard/monthly-spending?{$this->dateParams}")->assertOk();
}, 'API Monthly Spending');
});
test('cash flow API does not exceed query threshold', function () {
assertMaxQueries(15, function () {
$this->getJson("/api/dashboard/cash-flow?{$this->dateParams}")->assertOk();
}, 'API Cash Flow');
});
test('top categories API does not exceed query threshold', function () {
assertMaxQueries(13, function () {
$this->getJson("/api/dashboard/top-categories?{$this->dateParams}")->assertOk();
}, 'API Top Categories');
});
test('net worth evolution API does not exceed query threshold', function () {
assertMaxQueries(25, function () {
$this->getJson("/api/dashboard/net-worth-evolution?{$this->dateParams}")->assertOk();
}, 'API Net Worth Evolution');
});
// ──────────────────────────────────────────────────────────────────────────
// Cashflow Analytics API
// ──────────────────────────────────────────────────────────────────────────
test('cashflow summary API does not exceed query threshold', function () {
assertMaxQueries(15, function () {
$this->getJson("/api/cashflow/summary?{$this->dateParams}")->assertOk();
}, 'API Cashflow Summary');
});
test('cashflow trend API does not exceed query threshold', function () {
assertMaxQueries(35, function () {
$this->getJson('/api/cashflow/trend')->assertOk();
}, 'API Cashflow Trend');
});
test('cashflow breakdown API does not exceed query threshold', function () {
assertMaxQueries(15, function () {
$this->getJson("/api/cashflow/breakdown?type=expense&{$this->dateParams}")->assertOk();
}, 'API Cashflow Breakdown');
});
// ──────────────────────────────────────────────────────────────────────────
// Query count must not scale with data volume
// ──────────────────────────────────────────────────────────────────────────
test('net worth API query count does not scale with number of accounts', function () {
$categories = $this->user->categories;
$extraAccounts = Account::factory(7)->create(['user_id' => $this->user->id]);
foreach ($extraAccounts as $index => $account) {
for ($i = 0; $i < 5; $i++) {
AccountBalance::factory()->create([
'account_id' => $account->id,
'balance_date' => now()->subDays(($index * 5) + $i + 20)->toDateString(),
]);
}
}
// The net worth endpoint queries per-account, so we allow proportional growth
// but cap it to prevent unbounded scaling
assertMaxQueries(55, function () {
$this->getJson("/api/dashboard/net-worth?{$this->dateParams}")->assertOk();
}, 'API Net Worth with 10 accounts');
});
test('cashflow trend API query count does not scale with extra months', function () {
assertMaxQueries(35, function () {
$this->getJson('/api/cashflow/trend?months=12')->assertOk();
}, 'API Cashflow Trend 12 months');
});