@accounter/server
Version:
Accounter GraphQL server
178 lines • 7.3 kB
JavaScript
import { describe, expect, it, vi } from 'vitest';
import { GraphQLError } from 'graphql';
import { AdminContextProvider } from '../providers/admin-context.provider.js';
function createProvider(options) {
const query = vi.fn().mockImplementation((_statement, values) => {
if (options.rowsByOwnerId) {
const serializedValues = JSON.stringify(values ?? []);
for (const [ownerId, row] of Object.entries(options.rowsByOwnerId)) {
if (serializedValues.includes(ownerId)) {
return Promise.resolve({ rows: [row], rowCount: 1 });
}
}
return Promise.resolve({ rows: [], rowCount: 0 });
}
return Promise.resolve({
rows: options.row ? [options.row] : [],
rowCount: options.row ? 1 : 0,
});
});
const db = {
query,
};
const auth = {
getAuthContext: vi.fn().mockResolvedValue(options.businessId
? {
authType: 'jwt',
tenant: { businessId: options.businessId },
...(options.activeReadScopeBusinessIds
? {
activeReadScope: {
businessIds: options.activeReadScopeBusinessIds,
},
}
: {}),
}
: null),
};
return {
provider: new AdminContextProvider(auth, db),
query,
auth,
};
}
describe('AdminContext DI Integration', () => {
it('loads admin context through provider injection', async () => {
const { provider, query, auth } = createProvider({
businessId: 'owner-a',
row: {
owner_id: 'owner-a',
default_local_currency: 'USD',
default_fiat_currency_for_crypto_conversions: 'USD',
date_established: null,
initial_accounter_year: null,
},
});
const context = await provider.getVerifiedAdminContext();
expect(context.ownerId).toBe('owner-a');
expect(context.defaultLocalCurrency).toBe('USD');
expect(auth.getAuthContext).toHaveBeenCalledTimes(1);
expect(query).toHaveBeenCalledTimes(1);
});
it('uses single-business active scope as owner selection', async () => {
const primaryOwnerId = 'owner-primary';
const scopedOwnerId = 'owner-scoped';
const { provider } = createProvider({
businessId: primaryOwnerId,
activeReadScopeBusinessIds: [scopedOwnerId],
rowsByOwnerId: {
[scopedOwnerId]: {
owner_id: scopedOwnerId,
default_local_currency: 'USD',
default_fiat_currency_for_crypto_conversions: 'USD',
date_established: null,
initial_accounter_year: null,
},
},
});
const context = await provider.getVerifiedAdminContext();
expect(context.ownerId).toBe(scopedOwnerId);
});
it('prefers primary tenant business when it is inside multi-business scope', async () => {
const primaryOwnerId = 'owner-primary';
const { provider } = createProvider({
businessId: primaryOwnerId,
activeReadScopeBusinessIds: ['owner-scoped-a', primaryOwnerId, 'owner-scoped-b'],
rowsByOwnerId: {
[primaryOwnerId]: {
owner_id: primaryOwnerId,
default_local_currency: 'USD',
default_fiat_currency_for_crypto_conversions: 'USD',
date_established: null,
initial_accounter_year: null,
},
},
});
const context = await provider.getVerifiedAdminContext();
expect(context.ownerId).toBe(primaryOwnerId);
});
it('falls back to first scoped business when primary tenant business is outside scope', async () => {
const primaryOwnerId = 'owner-primary';
const firstScopedOwnerId = 'owner-scoped-a';
const { provider } = createProvider({
businessId: primaryOwnerId,
activeReadScopeBusinessIds: [firstScopedOwnerId, 'owner-scoped-b'],
rowsByOwnerId: {
[firstScopedOwnerId]: {
owner_id: firstScopedOwnerId,
default_local_currency: 'USD',
default_fiat_currency_for_crypto_conversions: 'USD',
date_established: null,
initial_accounter_year: null,
},
},
});
const context = await provider.getVerifiedAdminContext();
expect(context.ownerId).toBe(firstScopedOwnerId);
});
it('isolates admin context between concurrent requests', async () => {
const requestA = createProvider({
businessId: 'owner-a',
row: {
owner_id: 'owner-a',
default_local_currency: 'USD',
default_fiat_currency_for_crypto_conversions: 'USD',
date_established: null,
initial_accounter_year: null,
},
});
const requestB = createProvider({
businessId: 'owner-b',
row: {
owner_id: 'owner-b',
default_local_currency: 'EUR',
default_fiat_currency_for_crypto_conversions: 'EUR',
date_established: null,
initial_accounter_year: null,
},
});
const [contextA, contextB] = await Promise.all([
requestA.provider.getVerifiedAdminContext(),
requestB.provider.getVerifiedAdminContext(),
]);
expect(contextA.ownerId).toBe('owner-a');
expect(contextB.ownerId).toBe('owner-b');
expect(requestA.query).toHaveBeenCalledTimes(1);
expect(requestB.query).toHaveBeenCalledTimes(1);
});
it('caches admin context within a single request', async () => {
const { provider, query } = createProvider({
businessId: 'owner-a',
row: {
owner_id: 'owner-a',
default_local_currency: 'USD',
default_fiat_currency_for_crypto_conversions: 'USD',
date_established: null,
initial_accounter_year: null,
},
});
const [first, second] = await Promise.all([
provider.getVerifiedAdminContext(),
provider.getVerifiedAdminContext(),
]);
expect(first.ownerId).toBe('owner-a');
expect(second.ownerId).toBe('owner-a');
expect(query).toHaveBeenCalledTimes(1);
});
it('fails closed when auth context is unavailable (RLS guard path)', async () => {
const { provider, query } = createProvider({
businessId: null,
});
await expect(provider.getAdminContext()).rejects.toBeInstanceOf(GraphQLError);
await expect(provider.getAdminContext()).rejects.toMatchObject({
extensions: { code: 'UNAUTHENTICATED' },
});
expect(query).not.toHaveBeenCalled();
});
});
//# sourceMappingURL=admin-context-integration.test.js.map