Prevent cached cashflow analytics responses (#368)

## Summary
- mark cashflow analytics JSON responses as no-store/private
- add coverage for cache-control header

## Why
Browser tests reuse the same cashflow API URLs across authenticated
users. Cached user-specific analytics can leak stale breakdown data
between sessions and hide the income category.

## Tests
- php artisan test --compact tests/Feature/CashflowAnalyticsTest.php
This commit is contained in:
Víctor Falcón 2026-05-08 15:51:27 +01:00 committed by GitHub
parent 360a38a880
commit 97df0597f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 4 deletions

View File

@ -28,7 +28,7 @@ class CashflowAnalyticsController extends Controller
$current = $this->calculateCashflowSummary($request->user()->id, $period->from, $period->to);
$previous = $this->calculateCashflowSummary($request->user()->id, $previousPeriod->from, $previousPeriod->to);
return response()->json([
return $this->cashflowJson([
'current' => $current,
'previous' => $previous,
]);
@ -53,7 +53,7 @@ class CashflowAnalyticsController extends Controller
$totalIncome = $incomeCategories->sum('amount');
$totalExpense = $expenseCategories->sum('amount');
return response()->json([
return $this->cashflowJson([
'income_categories' => $incomeCategories->values(),
'expense_categories' => $expenseCategories->values(),
'total_income' => $totalIncome,
@ -96,7 +96,7 @@ class CashflowAnalyticsController extends Controller
$current->addMonth();
}
return response()->json([
return $this->cashflowJson([
'data' => $data,
]);
}
@ -134,13 +134,20 @@ class CashflowAnalyticsController extends Controller
];
})->sortByDesc('amount')->values();
return response()->json([
return $this->cashflowJson([
'data' => $currentWithPercentage,
'total' => $currentTotal,
'previous_total' => $previousTotal,
]);
}
private function cashflowJson(array $data): JsonResponse
{
return response()
->json($data)
->header('Cache-Control', 'no-store, private');
}
private function calculateCashflowSummary(string $userId, Carbon $from, Carbon $to): array
{
$income = $this->getTransactionSum($userId, $from, $to, CategoryType::Income);

View File

@ -12,6 +12,16 @@ beforeEach(function () {
$this->actingAs($this->user);
});
test('cashflow analytics responses are not cached between users', function () {
$response = $this->getJson('/api/cashflow/summary?'.http_build_query([
'from' => now()->startOfMonth()->toDateString(),
'to' => now()->endOfMonth()->toDateString(),
]));
$response->assertOk()
->assertHeader('Cache-Control', 'no-store, private');
});
test('cashflow summary returns income, expense, net, and savings rate', function () {
$incomeCategory = Category::factory()->create([
'user_id' => $this->user->id,