UNPKG

@accounter/server

Version:
205 lines • 10 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AdminContextProvider } from '../providers/admin-context.provider.js'; const asQueryResult = (rows) => ({ rows, rowCount: rows.length }); describe('AdminContextProvider', () => { let provider; let dbProvider; let authContextProvider; beforeEach(() => { dbProvider = { pool: { query: vi.fn(), }, healthCheck: vi.fn(), query: vi.fn(), }; const mockAuthContext = { authType: 'jwt', tenant: { businessId: 'test-owner-id', }, }; authContextProvider = { getAuthContext: vi.fn().mockResolvedValue(mockAuthContext), }; // Inject mocks: authContextProvider (for auth context) and dbProvider provider = new AdminContextProvider(authContextProvider, dbProvider); }); it('should fetch admin context for the current user', async () => { dbProvider.query.mockResolvedValue({ rows: [{ owner_id: 'test-owner-id', default_local_currency: 'USD' }], rowCount: 1, }); const result = await provider.getAdminContext(); expect(result).toBeDefined(); expect(result?.defaultLocalCurrency).toBe('USD'); expect(dbProvider.query).toHaveBeenCalled(); }); it('should prefer single-business active scope over primary tenant business', async () => { const scopedOwnerId = 'scoped-owner-id'; const primaryOwnerId = 'primary-owner-id'; authContextProvider.getAuthContext.mockResolvedValue({ authType: 'jwt', tenant: { businessId: primaryOwnerId, }, activeReadScope: { businessIds: [scopedOwnerId], }, }); dbProvider.query.mockImplementation((_statement, values) => { const serializedValues = JSON.stringify(values ?? []); if (serializedValues.includes(scopedOwnerId)) { return Promise.resolve(asQueryResult([{ owner_id: scopedOwnerId, default_local_currency: 'USD' }])); } return Promise.resolve(asQueryResult([])); }); const result = await provider.getVerifiedAdminContext(); expect(result.ownerId).toBe(scopedOwnerId); }); it('should prefer primary tenant business when it is inside multi-business active scope', async () => { const primaryOwnerId = 'primary-owner-id'; authContextProvider.getAuthContext.mockResolvedValue({ authType: 'jwt', tenant: { businessId: primaryOwnerId, }, activeReadScope: { businessIds: ['scope-owner-a', primaryOwnerId, 'scope-owner-b'], }, }); dbProvider.query.mockImplementation((_statement, values) => { const serializedValues = JSON.stringify(values ?? []); if (serializedValues.includes(primaryOwnerId)) { return Promise.resolve(asQueryResult([{ owner_id: primaryOwnerId, default_local_currency: 'USD' }])); } return Promise.resolve(asQueryResult([])); }); const result = await provider.getVerifiedAdminContext(); expect(result.ownerId).toBe(primaryOwnerId); }); it('should fallback to first scoped business when primary tenant business is outside active scope', async () => { const primaryOwnerId = 'primary-owner-id'; const firstScopedOwnerId = 'scope-owner-a'; authContextProvider.getAuthContext.mockResolvedValue({ authType: 'jwt', tenant: { businessId: primaryOwnerId, }, activeReadScope: { businessIds: [firstScopedOwnerId, 'scope-owner-b'], }, }); dbProvider.query.mockImplementation((_statement, values) => { const serializedValues = JSON.stringify(values ?? []); if (serializedValues.includes(firstScopedOwnerId)) { return Promise.resolve(asQueryResult([{ owner_id: firstScopedOwnerId, default_local_currency: 'USD' }])); } return Promise.resolve(asQueryResult([])); }); const result = await provider.getVerifiedAdminContext(); expect(result.ownerId).toBe(firstScopedOwnerId); }); it('should use single-business active scope when updating admin context', async () => { const scopedOwnerId = 'scoped-owner-id'; const primaryOwnerId = 'primary-owner-id'; authContextProvider.getAuthContext.mockResolvedValue({ authType: 'jwt', tenant: { businessId: primaryOwnerId, }, activeReadScope: { businessIds: [scopedOwnerId], }, }); dbProvider.query.mockImplementation((_statement, values) => { const serializedValues = JSON.stringify(values ?? []); if (serializedValues.includes(scopedOwnerId)) { return Promise.resolve(asQueryResult([{ owner_id: scopedOwnerId, default_local_currency: 'EUR' }])); } return Promise.resolve(asQueryResult([])); }); const result = await provider.updateAdminContext({ defaultLocalCurrency: 'EUR' }); expect(result?.ownerId).toBe(scopedOwnerId); expect(result?.defaultLocalCurrency).toBe('EUR'); }); it('should cache the result', async () => { dbProvider.query.mockResolvedValue({ rows: [{ owner_id: 'test-owner-id' }], rowCount: 1, }); await provider.getAdminContext(); await provider.getAdminContext(); expect(dbProvider.query).toHaveBeenCalledTimes(1); }); it('should invalidate cache on update', async () => { dbProvider.query.mockResolvedValueOnce({ rows: [{ owner_id: 'test-owner-id', default_local_currency: 'USD' }], rowCount: 1, }); // get await provider.getAdminContext(); expect(dbProvider.query).toHaveBeenCalledTimes(1); dbProvider.query.mockResolvedValueOnce({ rows: [{ owner_id: 'test-owner-id', default_local_currency: 'EUR' }], rowCount: 1 }); // update await provider.updateAdminContext({ defaultLocalCurrency: 'EUR' }); expect(dbProvider.query).toHaveBeenCalledTimes(2); // +1 for update // The cache should now have the updated value from updateAdminContext const result = await provider.getAdminContext(); expect(result?.defaultLocalCurrency).toBe('EUR'); expect(dbProvider.query).toHaveBeenCalledTimes(2); // No additional call, using cache }); describe('normalizeContext (data-driven admin_business_roles)', () => { const buildRow = (overrides) => ({ owner_id: 'owner-1', default_local_currency: 'ILS', default_fiat_currency_for_crypto_conversions: 'USD', ...overrides, }); it('derives data-source arrays from the role aggregate columns', () => { const ctx = provider.normalizeContext(buildRow({ bank_account_business_ids: ['bank-1', 'bank-2'], credit_card_business_ids: ['card-1'], crypto_wallet_business_ids: ['wallet-1'], foreign_securities_business_id: 'fs-1', })); expect(ctx.financialAccounts.bankAccountIds).toEqual(['bank-1', 'bank-2']); expect(ctx.financialAccounts.creditCardIds).toEqual(['card-1']); expect(ctx.financialAccounts.internalWalletsIds).toEqual(expect.arrayContaining(['bank-1', 'bank-2', 'card-1', 'wallet-1', 'fs-1'])); expect(ctx.financialAccounts.internalWalletsIds).toHaveLength(5); }); it('derives dividend and VAT-excluded arrays from roles without any hardcoded UUIDs', () => { const ctx = provider.normalizeContext(buildRow({ dividend_payment_business_ids: ['div-1'], dividend_withholding_tax_business_id: 'wh-1', vat_excluded_business_ids: ['vat-1', 'tax-1', 'ss-1'], })); expect(ctx.dividends.dividendPaymentBusinessIds).toEqual(['div-1']); expect(ctx.dividends.dividendBusinessIds).toEqual(['wh-1', 'div-1']); expect(ctx.authorities.vatReportExcludedBusinessNames).toEqual(['vat-1', 'tax-1', 'ss-1']); // The previously hardcoded, cross-tenant dividend UUIDs must no longer leak in. expect(ctx.dividends.dividendPaymentBusinessIds).not.toContain('4bcca705-5b47-41c5-ba26-1e42c69cbf0d'); expect(ctx.dividends.dividendPaymentBusinessIds).not.toContain('909fbe3c-0419-44ed-817d-ab774e93748a'); }); it('defaults every role-derived array to empty when the aggregates are absent', () => { const ctx = provider.normalizeContext(buildRow({})); expect(ctx.financialAccounts.bankAccountIds).toEqual([]); expect(ctx.financialAccounts.creditCardIds).toEqual([]); expect(ctx.financialAccounts.internalWalletsIds).toEqual([]); expect(ctx.dividends.dividendPaymentBusinessIds).toEqual([]); expect(ctx.dividends.dividendBusinessIds).toEqual([]); expect(ctx.authorities.vatReportExcludedBusinessNames).toEqual([]); }); it('de-duplicates internal wallets when one business holds multiple roles', () => { const ctx = provider.normalizeContext(buildRow({ bank_account_business_ids: ['dup', 'bank-2'], credit_card_business_ids: ['dup'], crypto_wallet_business_ids: ['dup'], foreign_securities_business_id: 'dup', })); expect(ctx.financialAccounts.internalWalletsIds).toEqual(['dup', 'bank-2']); }); }); }); //# sourceMappingURL=admin-context.provider.test.js.map