* chore(ci): guard bedrock-sdk against automated version bumps The 2026-07 prod outage (empty Bedrock streams breaking invoice OCR and the assistant) came from an unreviewed @anthropic-ai/bedrock-sdk 0.32.0 bump. The package is exact-pinned to 0.29.1, but nothing stopped an automated PR from proposing the bump again. Add a dependabot config in security-updates-only posture (open-pull-requests-limit: 0) with an ignore for bedrock-sdk >=0.30.0 so neither scheduled nor security updates can reintroduce it silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(enable-banking): stop renewal history floods (gap-fill default + backfill reconciliation) Renewing a bank connection walks the same pending_selection -> active flow as a first connect, and a fresh consent often makes the bank release history the first connect never delivered. Two gaps turned that into a flood of falsely 'unhandled' rows over already-bookkept periods (11 companies, ~600 rows in prod): - The picker defaulted every renewal to the fiscal-year lookback. It now probes the connection's newest imported transaction and defaults a renewal to 'continue where the last fetch stopped' (7-day overlap, absorbed by external_id dedup), with an .attn warning when a longer lookback re-requests already-fetched periods. - The inline initial backfill ran without the SIE-overlap guard that the manual /sync route and the cron both apply. It now suppresses auto-categorization on overlap and runs the same unattended-threshold reconciliation sweep, scoped per ledger account via resolveCashAccountScope instead of the pooled unscoped form (#1290/#1298), and surfaces the linked count as auto_matched in the sync summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(enable-banking): renewal guard survives mid-open refetch; harden sweep scope and window Skeptic pass findings on the renewal-flood guard: - REFUTED: the settings panel's visibility refetch (near-certain in a BankID reconnect) hands the open picker a fresh accounts identity; the pre-existing reset effect then wiped the gap-fill state while the probe effect never re-ran, silently stranding the renewal back on the fiscal-year default with no warning. The probe now keys its state by connectionId and shares the reset's triggers via an accounts dep, so wipe and re-probe always pair up. - The sweep skips accounts whose cash_accounts row did not resolve (found: false) instead of degrading to the pooled currency-only form (#1290 write shape), which could otherwise follow a same-request mirror-upsert failure. - The sweep window opens at the oldest booking date the bank actually returned: over-returning ASPSPs ingest rows outside the requested window, which the sweep would otherwise never examine. - resolveGapFillStart clamps to the backend's 365-day lookback floor so the radio never promises a start date the backfill cannot honor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(enable-banking): address PR #1590 review findings - zizmor: add a 7-day cooldown to the dependabot npm entry. - CodeRabbit: clear the event bus in the accounts-route beforeEach (repo test convention); surface probe query failures in AccountPickerDialog so a failed probe cannot read as a first connect and silently restore the fiscal-year default; build the sweep's ledger-account list with a string filter instead of a nullish fallback so the pooled scope path is structurally unreachable. - Swedish compliance review: document at the sweep site that linking writes bank-feed metadata only, never journal tables, with the opening-balance link trigger and unlinkReconciliation reversibility spelled out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
126 lines
4.9 KiB
TypeScript
126 lines
4.9 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
resolveBookedCoverage,
|
|
resolveFiscalYearStart,
|
|
resolveGapFillStart,
|
|
} from '../date-suggestions'
|
|
|
|
describe('resolveBookedCoverage', () => {
|
|
it('suggests the day after the last posted verifikat date', () => {
|
|
expect(resolveBookedCoverage('2026-05-14')).toEqual({
|
|
lastBookedDate: '2026-05-14',
|
|
suggestedStartDate: '2026-05-15',
|
|
})
|
|
})
|
|
|
|
it('rolls over month and year boundaries', () => {
|
|
// Pin "today" past every case so the clamp does not kick in.
|
|
const today = new Date('2030-01-01T12:00:00Z')
|
|
expect(resolveBookedCoverage('2026-01-31', today)?.suggestedStartDate).toBe('2026-02-01')
|
|
expect(resolveBookedCoverage('2026-12-31', today)?.suggestedStartDate).toBe('2027-01-01')
|
|
// Leap year: 2028-02-28 is not the last day of February.
|
|
expect(resolveBookedCoverage('2028-02-28', today)?.suggestedStartDate).toBe('2028-02-29')
|
|
})
|
|
|
|
it('clamps to today when the last posted verifikat is dated today (backend rejects non-past dates)', () => {
|
|
// Day after 2026-07-09 would be 2026-07-10 (tomorrow), which the PATCH
|
|
// handler rejects with 400; the suggestion must stay clickable.
|
|
const today = new Date('2026-07-09T12:00:00Z')
|
|
expect(resolveBookedCoverage('2026-07-09', today)).toEqual({
|
|
lastBookedDate: '2026-07-09',
|
|
suggestedStartDate: '2026-07-09',
|
|
})
|
|
})
|
|
|
|
it('clamps to today when the last posted verifikat is dated in the future', () => {
|
|
const today = new Date('2026-07-09T12:00:00Z')
|
|
expect(resolveBookedCoverage('2026-08-15', today)).toEqual({
|
|
lastBookedDate: '2026-08-15',
|
|
suggestedStartDate: '2026-07-09',
|
|
})
|
|
})
|
|
|
|
it('returns null when the company has no posted entries (issue #917: never fall back to fiscal_year_end)', () => {
|
|
expect(resolveBookedCoverage(null)).toBeNull()
|
|
expect(resolveBookedCoverage(undefined)).toBeNull()
|
|
expect(resolveBookedCoverage('')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('resolveGapFillStart', () => {
|
|
it('suggests one week of overlap before the newest imported transaction', () => {
|
|
const today = new Date('2026-08-13T12:00:00Z')
|
|
expect(resolveGapFillStart('2026-08-06', today)).toEqual({
|
|
latestImportedDate: '2026-08-06',
|
|
suggestedStartDate: '2026-07-30',
|
|
})
|
|
})
|
|
|
|
it('rolls over month and year boundaries', () => {
|
|
// Todays within the 365-day floor of each case so only the 7-day overlap acts.
|
|
expect(resolveGapFillStart('2026-01-03', new Date('2026-01-15T12:00:00Z'))?.suggestedStartDate).toBe('2025-12-27')
|
|
expect(resolveGapFillStart('2026-03-04', new Date('2026-03-10T12:00:00Z'))?.suggestedStartDate).toBe('2026-02-25')
|
|
})
|
|
|
|
it('clamps to today when bank data claims a future date (backend rejects non-past dates)', () => {
|
|
const today = new Date('2026-08-13T12:00:00Z')
|
|
expect(resolveGapFillStart('2026-09-20', today)).toEqual({
|
|
latestImportedDate: '2026-09-20',
|
|
suggestedStartDate: '2026-08-13',
|
|
})
|
|
})
|
|
|
|
it('clamps a stale renewal to the backend 365-day lookback floor so the shown date matches the actual backfill', () => {
|
|
const today = new Date('2026-08-13T12:00:00Z')
|
|
// Newest import 2025-01-10; minus 7d = 2025-01-03, older than the 365-day
|
|
// floor (2025-08-13), which the backend would silently clamp to anyway.
|
|
expect(resolveGapFillStart('2025-01-10', today)).toEqual({
|
|
latestImportedDate: '2025-01-10',
|
|
suggestedStartDate: '2025-08-13',
|
|
})
|
|
})
|
|
|
|
it('returns null when the connection has never imported anything (first connect has no gap)', () => {
|
|
expect(resolveGapFillStart(null)).toBeNull()
|
|
expect(resolveGapFillStart(undefined)).toBeNull()
|
|
expect(resolveGapFillStart('')).toBeNull()
|
|
})
|
|
|
|
it('returns null for an unparsable date', () => {
|
|
expect(resolveGapFillStart('not-a-date')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('resolveFiscalYearStart', () => {
|
|
const calendarYearSettings = {
|
|
fiscal_year_start_month: 1,
|
|
entity_type: 'aktiebolag' as const,
|
|
}
|
|
|
|
it('prefers the actual fiscal period row over the recurring start month (issue #917: extended first year)', () => {
|
|
// Company with an extended first fiscal year 2025-10-01 to 2026-12-31 that
|
|
// later runs calendar years: the recurring setting would wrongly resolve
|
|
// to 2026-01-01.
|
|
expect(
|
|
resolveFiscalYearStart('2025-10-01', calendarYearSettings, new Date('2026-07-09')),
|
|
).toBe('2025-10-01')
|
|
})
|
|
|
|
it('falls back to the recurring fiscal_year_start_month when no period row exists', () => {
|
|
expect(
|
|
resolveFiscalYearStart(null, calendarYearSettings, new Date('2026-07-09')),
|
|
).toBe('2026-01-01')
|
|
expect(
|
|
resolveFiscalYearStart(
|
|
undefined,
|
|
{ fiscal_year_start_month: 7, entity_type: 'aktiebolag' },
|
|
new Date('2026-05-01'),
|
|
),
|
|
).toBe('2025-07-01')
|
|
})
|
|
|
|
it('falls back to calendar year when settings are missing too', () => {
|
|
expect(resolveFiscalYearStart(null, null, new Date('2026-07-09'))).toBe('2026-01-01')
|
|
})
|
|
})
|