Commit graph

9 commits

Author SHA1 Message Date
3adb3d356a fix(accounts): match Beancount's DATE grammar in duplicate detection (libra-#48)
_open_directive_exists hardcoded '^YYYY-MM-DD open ' (dash-only, 2-digit,
single-space), but Beancount's DATE token (parser/lexer.l) is
(17|18|19|20)[0-9]{2}[-/][0-9]+[-/][0-9]+ and inter-token whitespace is any
[ \t\r] run. So a validly-formatted existing Open written as '2024/3/5 open X'
or '2020-01-01  open  X' escaped detection → duplicate Open appended →
bean-check rejects the file. Anchor on Beancount's actual date pattern and
[ \t]+ separators. Adds parametrized coverage for slash/single-digit/multi-
space/tab variants.

Found in a coherence pass over the Beancount source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:27:18 +02:00
39440b75a7 fix(accounts): recover ledger-only account instead of blanket 409 (libra-#50)
When add_account reported the Open already existed, the endpoint raised
409 before the DB-mirror step — so an account present in the ledger but
missing from libra's DB (a prior sync failure with no cross-DB atomicity,
or an out-of-band open) was stranded: invisible to permissions with no
recovery path. Now 409 only when the account is already in the DB too;
otherwise sync it and return success. Adds a recovery test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:13:43 +02:00
0ea96cd384 fix(accounts): anchor duplicate-account detection to a real Open directive (libra-#48)
The existence check matched 'open <name>' anywhere in the chart source,
so a prior account's description metadata or a comment mentioning the
name produced a false 409, while a real directive with an inline comment
and no space ('open X;legacy') was missed → a duplicate Open was appended
and bean-check then rejected the file, breaking every later /api/source
write. Extract the check into a pure _open_directive_exists() anchored to
'^YYYY-MM-DD open <name>' with an account-boundary negative-lookahead, and
unit-test both failure directions plus prefix/child non-matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:06:28 +02:00
89f0f8ac3a test(accounts): cover admin add-account endpoint
10 integration tests for POST /api/v1/admin/accounts: unconstrained Open
write + escaped description metadata, explicit-currency path, duplicate->409,
invalid-prefix->400, invalid-characters->400 (parametrized), super-user-only
->403. Adds the add_chart_account helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:25:27 +02:00
87a45ee4d5 test(harness): split-layout ledger + disable rate limiter
The test harness was never updated to the post-server-deploy#4 split ledger
layout, so libra's per-user account opens (routed to accounts/users.beancount
by fava_client._infer_target_file) 500'd as a 'non-source file' and fell back
to DB-only — breaking the balance test and contributing to settlement errors.
Make the harness ledger a faithful split (root includes accounts/chart.beancount
+ accounts/users.beancount; title stays in root so the slug still matches).

Also raise lnbits_rate_limit_no for the session: the full suite fires >200
req/min and the default limiter 429'd fixture setup intermittently (10-11
errors). The limiter is built once at app creation, so setting it in the
session settings fixture (before the app fixture) disables it suite-wide.

Net: full suite goes from 1 failed / ~10 errors to fully green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:25:27 +02:00
16ae6c2000 docs(tests): record known-good lnbits/dev invocation + env gotchas
The suite targets the lnbits dev worktree (needs lnbits.core.signers)
and trips on three non-obvious environment requirements, each of which
cost a failed run today: LNBITS_EXTENSIONS_PATH is the parent of an
extensions/ dir, the data folder must be a fresh temp dir per run, and
lnbits dev mandates LNBITS_KEY_MASTER at boot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 20:39:14 +02:00
15d9910073 Resolve entry identity via entry-id metadata; unfuse user references (libra-#42)
Approving a pending entry created with a reference (e.g. invoice
"42-144") 404'd with "Pending entry unknown not found": the list
endpoints recovered the entry id by parsing links for a libra- prefix,
but reference-bearing entries displace that link with the fused
"{reference}-{entry_id}" form, so the id surfaced as the literal
"unknown" and the approve call round-tripped it.

Make the entry-id transaction metadata the single canonical identity:

- _extract_entry_id() resolves metadata-first (libra- link parsing kept
  only for pre-dfdcc44 ledger history); used by /entries/user,
  /entries/pending, approve, and reject.
- Creation endpoints no longer fuse the reference with the entry id —
  the user reference becomes its own sanitized link and round-trips
  verbatim in API responses. Typed exp-/rcv-/inc- links stay as the
  settlement-tracking handles.
- format_revenue_entry now writes entry-id metadata like its siblings
  and sanitizes its reference link (was appended raw); generic
  POST /entries sanitizes its reference link too.
- User-journal reference extraction skips all system link prefixes
  (typed links used to leak into the reference field).

Contract documented in CLAUDE.md (Data Integrity → Entry Identity &
Links), pinned by tests/test_entry_identity_api.py and formatter
contract tests in test_unit.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 20:39:06 +02:00
116df46d38 Net settlement + credit overflow on /receivables/settle (libra-#33, libra-#41)
When the caller omits settled_entry_links (the default), the endpoint
auto-detects open entries across both directions for the user and writes
a single transaction that:

  - Zeros every per-user account that has an open balance, not just the
    net (the libra-#33 bug — previously the 2-leg form left both Payable
    and Receivable carrying non-zero balances after a complete cash
    settlement, while only netting the cash side).
  - Routes any cash above the net obligation to Liabilities:Credit:User-X
    (libra-#41), so over-payment lands on a real liability account
    instead of silently drifting.
  - Attaches every reconciled source entry's link
    (exp-..., rcv-...) so a reader scanning the settlement transaction
    can trace what it cleared.

Cash less than the net obligation, with no explicit links, returns 400
with a structured diff (cash_paid, net_obligation, receivable_total,
payable_total). The operator either pays the exact net or passes
settled_entry_links to settle a specific subset; partial settlement
without a coherent target is not silently absorbed.

The legacy explicit-links code path is unchanged — callers that pass
settled_entry_links keep the 2-leg shape with no auto-detection. None
of the callers in libra or aiolabs/webapp currently use that field, but
the contract is preserved for the partial-settle-of-specific-entries
flow.

format_fiat_net_settlement_entry is the new helper for the 2/3/4-leg
shape; it enforces the cash-balance constraint inline so callers can't
accidentally produce an unbalanced transaction.

tests/test_settlement_api.py (6 tests) locks in:
  - Nancy's #33 scenario: receivable 100 + payable 50 + cash 50
    zeros both per-user accounts, links both source entries
  - Overpay: cash 70 against net 50 → credit balance 20
  - Pure receivable overpay → credit appears
  - Underpay without explicit links → 400 with diff
  - No open receivables → 400 with hint pointing at /payables/pay
  - Explicit settled_entry_links uses legacy 2-leg path

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 15:39:45 +02:00
7a4b3022c2 Add integration test suite
113 passing tests + 3 skipped + 8 xfailed across 10 files, covering user
expense and income flow, admin receivable/revenue, settings + auth gates,
void/reject, manual payment requests, balance display, Lightning auth
paths, reconciliation API, and pure-function units. Runs against a real
Fava subprocess and full LNbits app via asgi_lifespan; the harness
captures the auth-flow / settings / env-var disciplines surfaced during
build-out (see tests/README.md and tests/conftest.py docstring).

Eight xfailed/skipped tests carry full implementations gated behind issues
#38, #39, #40 — they flip back on automatically when those land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 15:39:45 +02:00