Commit Graph

29 Commits

Author SHA1 Message Date
Mouad Jaouhari 5c74292448
fix: publish and use production Docker image (#393)
## Summary
  - publish a GHCR production image built from `Dockerfile.production`
- point production Docker Compose and Coolify template to `:production`
- add `.dockerignore` so host `.env`, `vendor`, and `node_modules` do
not leak into image builds
  - clarify Docker self-hosting docs and Compose log command

  ## Verification
- `docker build -f Dockerfile.production -t
whisper-money:production-test .`
  - `docker compose -f docker-compose.production.yml config --images`
  - `vendor/bin/pint --dirty --format agent`
  - `vendor/bin/pest tests/Unit/DockerProductionImageTest.php`

---------

Co-authored-by: Víctor Falcón <victoor89@gmail.com>
2026-05-20 07:19:31 +00:00
Víctor Falcón f4ab4a1989
Fix Sentry release commit detection in image build (#371)
## Summary
- add --ignore-missing to Sentry set-commits so build-image does not
fail when previous release SHA is unavailable
- cover the workflow command in Sentry config test

## Tests
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Feature/SentryConfigTest.php
2026-05-09 16:51:51 +01:00
Víctor Falcón e36d6f3e16
Speed up PR CI browser path (#365)
## Summary
- rebalance Browser tests with explicit class-filter shards
- build PR browser assets inside shards, removing the build-assets
dependency gate
- run Browser shards with Pest parallelism ()

## Autoresearch metric
- baseline modeled PR critical path: 406.50s
- final modeled PR critical path: 234.27s
- modeled improvement: 172.23s (42.4%)

## Verification
- ran METRIC ci_total_s=234.270
METRIC actual_recent_pr_total_s=422.000
METRIC build_assets_s=0.000
METRIC tests_s=170.000
METRIC browser_matrix_s=232.270
METRIC linter_s=60.000
METRIC static_analysis_s=26.500
METRIC performance_tests_s=63.000
METRIC job_count=13.000
METRIC browser_shards=6.000 for each experiment
- coverage guard in autoresearch script checks Browser filters cover all
recent Browser classes exactly once

## Notes
- wall-clock faster; runner minutes likely higher due extra shards and
duplicate asset builds
- real CI should validate Browser parallelism flake risk
2026-05-07 20:40:13 +01:00
Víctor Falcón 21b5692174
fix: include production Dockerfile in deploy filter (#350)
## Summary
- include Dockerfile.* in CI changes filter
- ensure Dockerfile.production-only commits build and deploy

## Verification
- python3 assertion confirms Dockerfile.* filter exists and matches
Dockerfile.production
2026-05-04 15:36:56 +01:00
Víctor Falcón 0815548ac9
ci: tier 1 + tier 2 pipeline speedups (#342)
Continues the work merged in #341 with two focused commits.

## Baseline vs result (real numbers from runs on this PR)

| Job | Before (last run on #341) | After | Notes |
|---|---|---|---|
| build-assets | 70s (gates 3 jobs) | 74s | unchanged |
| tests | 215s | 243s | now starts at t=0 (no gate) |
| performance-tests | 72s | 64s | now starts at t=0 (no gate) |
| browser-tests longest shard | 341s | 323s | -18s |
| linter | 61s | 63s | same |
| static-analysis | 29s | 23s | -6s |
| **Wall (critical path)** | **~7m** | **~6.6m** | **-25s on the
critical path** |

The critical path is still `build-assets → browser-tests-matrix`. Real
wall-time wins for that path require either smarter shard balancing or a
Playwright Docker container — both deliberately deferred (see below).

## Tier 1 — wall-time wins (commit 1)

- **Pest:** `withoutVite()` is now applied globally in the `Feature` and
`Performance` suites via `pest()->beforeEach(...)->in(...)`. Those
suites never assert on the JS bundle, so they no longer need a compiled
Vite manifest.
- **Drop `needs: build-assets`** from `tests` and `performance-tests`.
Both now start at t=0 instead of waiting ~70s for the artifact. Browser
tests still gate on it (real Playwright session needs the manifest).
- **Playwright:** install only `chromium` (no firefox/webkit) since
that's all pest-plugin-browser uses. Saves ~15s of system-dep install
per shard.
- **Drop `actions/setup-node`** from browser-tests-matrix;
`oven-sh/setup-bun` already provides node for `npx playwright`.

I also tried bumping browser shards 4 → 6 but reverted (commit 3):
pest-plugin-browser's `--shard=N/M` distributes very unevenly at M=6 in
this codebase — shard 6 ended up running the entire 106-test suite
(13m26s) while other shards ran 23 tests each. Reverted to 4 shards for
predictable balance. Better balancing needs a test-time-aware split
(follow-up).

## Tier 2 — caching + setup dedup (commit 2)

- New composite actions:
- `.github/actions/setup-php-deps`: PHP + composer cache + `vendor/`
cache. On a `vendor/` cache hit, `composer install` is skipped entirely.
- `.github/actions/setup-bun-deps`: Bun + bun cache + `node_modules/`
cache. On a hit, `bun install` is skipped entirely.
- All 6 jobs refactored to use the composite actions. Removes ~150 lines
of duplicated YAML.
- **Bug fix: Pint cache key** dropped `${{ github.sha }}` from the
primary key so it actually reuses across runs (was effectively 0%
reuse).

## Deliberately not included

- **Pest `--parallel`**: paratest's per-process DB suffix conflicts with
Testcontainers user grants (see revert in 82e9a77 on the parent branch).
Worth revisiting in a follow-up that either widens grants or overrides
`ParallelTesting::setUpProcess`.
- **Playwright Docker container** (`mcr.microsoft.com/playwright`):
would eliminate the 34s system-deps install entirely but couples PHP
setup to a custom container; risky for one PR.
- **Test-time-aware browser shard split**: only real way to actually
shrink the critical path further given pest-plugin-browser's current
sharding behavior.

## Verification

- Final CI run on this PR is green (one flaky `Create Category` browser
timeout that passed on rerun — pre-existing flake unrelated to these
changes).
- `php artisan test --filter=InertiaSharedDataTest` and
`--filter=BudgetPeriodDateTest` pass with `public/build` deleted,
confirming the global `withoutVite()` covers feature tests that
previously rendered Inertia pages.
- All YAML files parse cleanly.
- `vendor/bin/pint --dirty` passes.
2026-05-01 19:07:04 +02:00
Víctor Falcón b94a285aad
ci: speed up pipeline (#341)
## Why

Last CI run on a PR took **~16m 36s** wall (browser-tests dominated).
Total CPU ~34 min. Several easy wins identified.

## Baseline (run 25169327223)

| Job | Wall | Critical step |
|---|---|---|
| browser-tests | 16m 36s | Browser Tests 718s, Build 166s, Playwright
50s |
| tests | 9m 38s | Tests 334s, Build 173s |
| performance-tests | 4m 19s | Build 173s |
| linter | 2m 54s | Pint 109s |
| static-analysis | 36s | — |

## Changes (one commit each)

**A. Shard browser tests 4×** — `pest --shard=N/4` matrix. ~12 min → ~3
min critical path.

**B. Build assets once, share via artifact** — new `build-assets` job
uploads `public/build`; tests/browser-tests/performance-tests download
it. Saves ~340s of duplicated CPU.

**C. Disable xdebug in tests job** — `coverage: xdebug` was set but
`--coverage` never passed. Silent ~30-50% slowdown.

**D. Parallel Pest with Testcontainers** — `pest --parallel
--processes=4`, `TESTCONTAINERS=true` so each worker gets isolated
MySQL. Drops job-level mysql service.

**E. Cache composer + bun deps** — `actions/cache` for composer cache
dir and `~/.bun/install/cache` keyed on lockfiles. Saves 30-60s/job
warm.

**F. Cache Playwright browsers** — cache `~/.cache/ms-playwright` keyed
on bun.lock; on hit only runs `install-deps`. Saves ~50s.

**G. Pint parallel + cache** — `--parallel --cache-file=.pint.cache`
with persisted cache. ~109s → ~5-10s warm.

## Expected wall time after

Browser shards become critical path: **~6-7 min** (down from 16-17 min).

## Risks

- **D**: 4 paratest workers each spawn a MySQL container — watch
RAM/runner load. Drop to `--processes=2` if flaky.
- **A**: more concurrent runners. Adjust shard count if MySQL service
init becomes the new bottleneck per shard.
- **B**: `build-assets` is now a hard dep for
tests/browser-tests/performance-tests.

## Test plan

CI itself is the test. Watch this PR run, compare timings to baseline.
2026-04-30 14:52:33 +00:00
Víctor Falcón af7ba727d5
ci: drop tests/** from code paths filter (#336) 2026-04-26 10:48:25 +02:00
Víctor Falcón 405e6bfc29
ci: skip build and deploy when no code changes (#335)
## Why

Pushes to `main` that only modify workflows, README, or docs were
triggering production deploys.

## What

- Adds a `changes` job using `dorny/paths-filter@v3` to detect whether
the push touched application code.
- Gates `build-image` and `deploy` on `needs.changes.outputs.code ==
'true'`.

## Code paths considered

`app/`, `bootstrap/`, `config/`, `database/`, `public/`, `resources/`,
`routes/`, `tests/`, `storage/`, `artisan`, `composer.{json,lock}`,
`package*.json`, `bun.lock*`, `vite.config.*`, `tsconfig*.json`,
`eslint.config.*`, `.prettierrc*`, `phpstan.neon*`, `phpunit.xml*`,
`pint.json`, `Dockerfile`, `docker/**`, `.dockerignore`, `.env.example`.

Anything else (e.g. `.github/**`, `*.md`, `docs/**`) is treated as
non-deployable.
2026-04-26 09:15:06 +01:00
Víctor Falcón bfe1af3c83
fix(ci): order sentry deploy after build (#309)
## Summary
- make the `deploy` job wait for `build-image` so Sentry release
creation finishes before deploy tracking runs
- keep the Sentry deploy marker on the same release name while removing
the race that caused `Release not found`
- add a focused regression test that asserts the workflow keeps this
dependency in place

## Testing
- php artisan test --compact tests/Feature/SentryConfigTest.php
2026-04-20 13:35:07 +01:00
Víctor Falcón 64ec047769
chore: track Sentry releases in CI (#294)
## Summary
- create and finalize Sentry releases in CI using
`whisper-money@<git-sha>`
- bake the release into the production image so Laravel tags backend
events with the deployed version
- mark production deploys in Sentry and add a focused test covering the
release binding

## Testing
- php artisan test --compact tests/Feature/SentryConfigTest.php
2026-04-16 08:04:59 +01:00
Víctor Falcón 6daa0d7345 ci: increase deploy retry delay to exponential backoff 2026-03-26 15:29:00 +01:00
Víctor Falcón b36197e76b fix(ci): skip outdated production deploys 2026-03-17 12:19:26 +01:00
Víctor Falcón cd40bc75d9
fix(ci): allow deploy retry loop to survive curl timeout (#233)
## Summary

- Adds `|| true` after the `curl` command so `bash -e` does not kill the
script on curl exit code 28 (timeout) before the retry loop can continue
- Guards the HTTP status integer comparison against an empty value,
which occurs when curl times out and produces no output
2026-03-17 09:57:38 +00:00
Víctor Falcón 3cbca1a17d
ci: add retry with exponential backoff to deploy step (#212)
## Summary

- Replaces curl's built-in `--retry` with a shell loop that retries up
to 5 times on any non-2xx response (not just connection failures)
- Delays increase with each attempt: 10s → 20s → 30s → 40s between
retries
- Exits immediately on success so successful deploys are unaffected
2026-03-07 14:18:14 +00:00
Víctor Falcón 3e087bdcd7
fix(static-analysis): clear phpstan-baseline by fixing all suppressed errors (#183)
## 🚪 Why?

### Problem

PHPStan was running with a baseline of 56 suppressed errors, meaning
static analysis was not enforcing type safety across a significant
portion of the codebase. These errors were real type mismatches,
redundant null-safety operators, and incorrect PHPDoc annotations that
could mask bugs and make the code harder to reason about.

## 🔑 What?

### Changes

- Add `@property` PHPDoc annotations to `Account`, `BankingConnection`,
`ExchangeRate`, and `Transaction` models so Enum casts and typed columns
are visible to PHPStan
- Add `instanceof User` guards in `ScheduleDripEmailsListener`,
`SyncUserToResendListener`, and `FortifyServiceProvider` to properly
narrow `Authenticatable` to `App\Models\User`
- Remove redundant `?? false` and unnecessary nullsafe `?->value` in
`HandleInertiaRequests`
- Fix `SyncBankingConnectionJob`: use `->name` instead of `?->name` on
an always-loaded `bank` relation
- Remove `is_countable()` guard in `BalanceLookup` (parameter is always
`Collection|array`, both countable)
- Remove `?? []` / `?? default` fallbacks on fully-typed array keys
across `BalanceSyncService`, `BinanceBalanceSyncService`,
`BinanceClient`, `BitpandaBalanceSyncService`, `BitpandaClient`,
`IndexaCapitalClient`, `IndexaCapitalBalanceSyncService`, and
`AuthorizationController`
- Fix `BinanceClient::publicClient()` `retry()` call: use `when:` named
argument and `\Throwable` type hint to match `PendingRequest::retry()`
signature
- Update `IndexaCapitalClient::getPerformance()` `@return` to include
`portfolios` and `net_amounts` keys; simplify sync service to remove
dead null checks
- Replace nullsafe chain with ternary in `BudgetPeriodService`
- Replace `match` statement in `SetupMainUser` with `if/else` to
eliminate always-true comparison
- Clear `phpstan-baseline.neon` entirely (was 56 suppressed errors, now
0)

##  Verification

### Tests

- Existing tests pass: PHPStan level 5 reports 0 errors with empty
baseline
2026-03-02 12:22:30 +00:00
Víctor Falcón e4243c2eaa
feat: use testcontainers for isolated MySQL in test runs (#153)
## Summary

- Add `testcontainers/testcontainers` to `require-dev` to spin up an
ephemeral MySQL 8.0 container per test process, eliminating the need for
a local database or Docker Compose stack
- Create `tests/bootstrap.php` that starts a `MySQLContainer`, sets
`DB_*` env vars dynamically, and auto-generates `APP_KEY` when missing
(supports fresh worktrees and CI)
- Update `phpunit.xml` to use the new bootstrap file and remove the
hardcoded `DB_DATABASE` env var

## How it works

Running `php artisan test` now automatically starts a short-lived MySQL
container on a random port. Each test process gets its own isolated
database, so multiple agents, worktrees, or CI jobs can run in parallel
without conflicts.

Set `TESTCONTAINERS=false` to bypass containers and use the database
configured in `.env` instead.
2026-02-25 10:14:20 +01:00
Víctor Falcón 93f1f82ac5
test: add performance test suite with query count ceilings (#148)
## Why

### Problem

As new features are added, dashboard and settings pages have been
accumulating extra database queries — risking N+1 regressions and
degraded response times. There was no automated way to detect these
regressions before they reach production.

## What

### Changes

- **New `Performance` test suite** (`tests/Performance/`) with 25 tests
that enforce query count ceilings on every page and API endpoint
accessible from the sidebar
- `PageQueryCountTest.php` — 15 tests covering Dashboard, Accounts,
Transactions, Budgets, Cashflow, and all Settings pages, plus scaling
tests that prove query count stays constant as data volume grows
- `ApiQueryCountTest.php` — 10 tests covering all Dashboard and Cashflow
analytics API endpoints, plus scaling tests
- **Separate CI job** (`performance-tests`) that runs in parallel with
`tests` and `linter`, and gates both `build-image` and `deploy`
- **Excluded from the main `tests` job** to avoid running them twice
(`--exclude-testsuite=Performance`)
- Helper functions (`performanceSeedUser`, `countQueries`,
`assertMaxQueries`) added to `tests/Pest.php` for reuse
- `phpunit.xml` updated with the new `Performance` testsuite entry

### How it works

Each test seeds a user with realistic data (3 accounts, 30 transactions,
15 balances, 5 categories, 3 labels, 1 budget) and asserts the endpoint
executes **at most N queries**. Thresholds have a small buffer (~3
queries) above current counts — tight enough to catch N+1 regressions
(which add dozens of queries) but loose enough to not break on minor
legitimate changes. On failure, every executed SQL query is dumped for
easy debugging.

### Run locally

```bash
php artisan test --testsuite=Performance
```

## Verification

### Tests

All 25 performance tests pass. Existing Feature (633) and Unit (22)
suites unaffected.
2026-02-24 10:47:51 +01:00
Víctor Falcón 39ef165655 Add production Docker setup for easy self-hosting with the CI-built image (#42)
- Add production Docker setup for easy self-hosting with the CI-built
image
- Build multi-architecture Docker images (amd64 + arm64)

### Changes

  Docker Production Setup
- Add docker-compose.production.yml for running the production image
locally
- Add docker/entrypoint.sh with automatic setup (migrations, caching,
storage, APP_KEY generation)
  - Add HEALTHCHECK to Dockerfile for container health monitoring
  - Add .env.production.example with documented production defaults

### CI/CD
  - Add QEMU for multi-arch builds
  - Build Docker images for both linux/amd64 and linux/arm64

### Coolify / WIP
  - Add templates/coolify/whisper-money.yaml one-click template
  - Includes MySQL service with health checks
  - Auto-generates credentials using Coolify's SERVICE_* variables

### Documentation
  - Update README with production Docker instructions
  - Add environment variable reference table
2025-12-30 07:22:19 +01:00
Víctor Falcón 9b5158dec2 docker: add new build-image step on github ci 2025-12-30 07:22:19 +01:00
Víctor Falcón c5df59c285 feat: add multiple chart view modes for net worth evolution (#37)
## Summary

- Add four view modes for net worth and account balance charts:
- **Stacked Accounts**: existing stacked bar chart showing balances by
account
  - **Line**: net worth line chart with linear/log scale toggle
  - **Change**: bar chart showing MoM%, YoY%, Rolling 12M changes
- **Waterfall**: bridge chart showing Start -> Change -> End transition

- Add Vitest for frontend unit testing with 52 tests covering all
calculation functions
- Proper sign handling for liabilities (credit cards and loans subtract
from net worth)
- Log scale disabled with warning when net worth includes zero or
negative values
- Shared components and logic between dashboard and account detail
charts

## New Files

- `vitest.config.ts` - Vitest configuration
- `resources/js/lib/chart-calculations.ts` - Pure computation functions
- `resources/js/lib/chart-calculations.test.ts` - 52 unit tests
- `resources/js/hooks/use-chart-views.ts` - Shared chart view state hook
- `resources/js/components/charts/` - Reusable chart components

## Test plan

- [x] Dashboard net worth chart shows view toggle and all four views
work
- [ ] Account detail page chart shows view toggle and three views work
(no stacked)
- [x] Line chart scale toggle works (log disabled with warning when
applicable)
- [x] Change chart series toggle (MoM, YoY, 12M EUR, 12M %) works
- [x] Waterfall chart month selector works
- [x] All 52 unit tests pass (`bun run test`)
- [x] Build succeeds (`bun run build`)
- [x] Dark mode styling correct
2025-12-30 07:22:19 +01:00
Víctor Falcón 44860941ee undo deploy ci check 2025-12-08 18:31:03 +01:00
Víctor Falcón d4410a67fe fix: deploy ci 2025-12-08 18:27:39 +01:00
Víctor Falcón 4ad965b44e Fix ci deploy 2025-12-08 18:20:22 +01:00
Víctor Falcón aaaba66684 ci: skip deployment if there is another HEAD deploy in queue 2025-12-08 18:14:19 +01:00
Víctor Falcón a13253d32b ci.yml: Remove browser-tests dependency 2025-12-04 10:11:34 +01:00
Víctor Falcón ce3527a2f9 Add GitHub Actions workflow for pull request testing 2025-12-04 10:04:45 +01:00
Víctor Falcón 8a0d27a9df
Execute browser tests on CI (#10) 2025-12-03 16:26:30 +01:00
Víctor Falcón 7773c97b0a Add retry mechanism to GitHub Actions workflow 2025-11-26 13:05:50 +01:00
Víctor Falcón e39336bf76 Add GitHub Actions CI/CD pipeline for Laravel project 2025-11-26 12:35:23 +01:00