109 lines
3.7 KiB
JavaScript

require('dotenv').config();
const axios = require('axios');
const { getBusToken, createBusRequest } = require('../../utils/busAuth');
jest.mock('axios');
const BUS_URL = process.env.BUS_URL;
const BUS_JWT_SECRET = process.env.BUS_JWT_SECRET;
const BUS_ISSUER = process.env.BUS_ISSUER;
const BUS_SCOPE = process.env.MPI_SCOPE;
beforeEach(() => {
jest.clearAllMocks();
});
/**
* Decodes a JWT without verifying the signature.
*/
function decodeJwt(jwt) {
const [header, payload] = jwt.split('.');
return {
header: JSON.parse(Buffer.from(header, 'base64').toString()),
payload: JSON.parse(Buffer.from(payload, 'base64').toString()),
};
}
describe('getBusToken', () => {
it('POSTs to /bus-auth/v2/auth on the given busURL', async () => {
axios.post.mockResolvedValue({ data: 'access-token' });
await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE);
expect(axios.post).toHaveBeenCalledWith(
`${BUS_URL}/bus-auth/v2/auth`,
expect.any(Object)
);
});
it('sends the correct grantType, scope and clientAssertionType', async () => {
axios.post.mockResolvedValue({ data: 'access-token' });
await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE);
const body = axios.post.mock.calls[0][1];
expect(body.grantType).toBe('client_credentials');
expect(body.scope).toBe(BUS_SCOPE);
expect(body.clientAssertionType).toBe(
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
);
});
it('sends a HS256 JWT signed with the secret as clientAssertion', async () => {
axios.post.mockResolvedValue({ data: 'access-token' });
await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE);
const { clientAssertion } = axios.post.mock.calls[0][1];
expect(clientAssertion.split('.')).toHaveLength(3);
const { header } = decodeJwt(clientAssertion);
expect(header.alg).toBe('HS256');
expect(header.typ).toBe('JWT');
});
it('JWT payload contains the correct issuer and expiration fields', async () => {
axios.post.mockResolvedValue({ data: 'access-token' });
const before = Math.floor(Date.now() / 1000);
await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE);
const after = Math.floor(Date.now() / 1000);
const { clientAssertion } = axios.post.mock.calls[0][1];
const { payload } = decodeJwt(clientAssertion);
expect(payload.iss).toBe(BUS_ISSUER);
expect(payload.iat).toBeGreaterThanOrEqual(before);
expect(payload.iat).toBeLessThanOrEqual(after);
expect(payload.exp).toBeGreaterThan(payload.iat);
});
it('returns the accessToken field from the bus auth response', async () => {
const tokenResponse = { accessToken: 'abc123', tokenType: 'bearer', expiresIn: 180000 };
axios.post.mockResolvedValue({ data: tokenResponse });
const result = await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE);
expect(result).toBe('abc123');
});
});
describe('createBusRequest', () => {
it('creates an axios instance with Authorization and Content-Type headers', () => {
const mockInstance = { get: jest.fn(), post: jest.fn() };
axios.create.mockReturnValue(mockInstance);
const token = 'my-token';
createBusRequest(token);
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
'Content-Type': 'application/fhir+json',
'Authorization': `Bearer ${token}`,
},
})
);
});
});