* fix(inbox): surface full BAS catalog in BookDirectlyDialog account picker The account combobox in the inbox book-directly flow was fed only the company's active chart, so typing a prefix like 65 showed just the two activated 65xx accounts and a search for 6540 found nothing, which reads as the account not existing. Pass the cached BAS catalogue (same pattern as JournalEntryForm) so every standard account is searchable; picking a not-yet-activated account flows through the existing ActivateAccountsDialog rail at booking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bookkeeping): log BAS catalog fetch failures Compliance swarm finding (SOC 2 CC7.2): loadBasCatalog swallowed fetch errors silently, leaving catalog-load failures unobservable. Log inside the client's catch, which is the only place the error actually surfaces: callers' own .catch handlers are unreachable since the shared promise already resolves to an empty list on failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(bookkeeping): unit-cover loadBasCatalog fetch, fallback, and retry CodeRabbit finding on PR #1543: the catalog client had no focused coverage. Tests assert the success path with promise caching, empty-list fallback with logging on non-OK responses, missing data field handling, and cache clearing after a failure so the next call refetches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
|
|
// loadBasCatalog caches its in-flight promise at module level, so every test
|
|
// re-imports a fresh module instance to start from an empty cache.
|
|
async function importFreshClient() {
|
|
vi.resetModules()
|
|
return import('@/lib/bookkeeping/bas-catalog-client')
|
|
}
|
|
|
|
const CATALOG = [
|
|
{ account_number: '6540', account_name: 'IT-tjänster', account_class: 6, account_group: '65', description: null },
|
|
]
|
|
|
|
describe('loadBasCatalog', () => {
|
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn>
|
|
|
|
beforeEach(() => {
|
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
})
|
|
|
|
afterEach(() => {
|
|
consoleErrorSpy.mockRestore()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
it('returns the catalog on a successful fetch and caches it across calls', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ data: CATALOG }),
|
|
})
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const { loadBasCatalog } = await importFreshClient()
|
|
|
|
await expect(loadBasCatalog()).resolves.toEqual(CATALOG)
|
|
await expect(loadBasCatalog()).resolves.toEqual(CATALOG)
|
|
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('resolves to an empty list and logs on a non-OK response', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({ ok: false, status: 500 })
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const { loadBasCatalog } = await importFreshClient()
|
|
|
|
await expect(loadBasCatalog()).resolves.toEqual([])
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'[bas-catalog] fetch failed:',
|
|
expect.any(Error),
|
|
)
|
|
})
|
|
|
|
it('resolves to an empty list when the body has no data field', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({}),
|
|
})
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const { loadBasCatalog } = await importFreshClient()
|
|
|
|
await expect(loadBasCatalog()).resolves.toEqual([])
|
|
})
|
|
|
|
it('clears the cache after a failure so the next call retries the fetch', async () => {
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('network down'))
|
|
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: CATALOG }) })
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const { loadBasCatalog } = await importFreshClient()
|
|
|
|
await expect(loadBasCatalog()).resolves.toEqual([])
|
|
await expect(loadBasCatalog()).resolves.toEqual(CATALOG)
|
|
expect(fetchMock).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|