mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 00:10:57 +00:00
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import { Logger } from '@nestjs/common';
|
|
import { ClientProxy } from '@nestjs/microservices';
|
|
import { NEVER, Observable, throwError } from 'rxjs';
|
|
|
|
import { isBrokerConnected, publishConfirmed } from './broker.util';
|
|
|
|
/**
|
|
* `ClientProxy.emit()` returns a cold Observable that, for RMQ, completes without
|
|
* emitting once `dispatchEvent` settles — and rejects if the publish fails. These
|
|
* fakes reproduce each of those three shapes.
|
|
*/
|
|
function clientEmitting(source: Observable<unknown>): ClientProxy {
|
|
return { emit: jest.fn().mockReturnValue(source) } as unknown as ClientProxy;
|
|
}
|
|
|
|
describe('publishConfirmed', () => {
|
|
const logger = { error: jest.fn() } as unknown as Logger;
|
|
|
|
beforeEach(() => jest.clearAllMocks());
|
|
|
|
it('is true when the publish completes (broker confirmed)', async () => {
|
|
// Completes with no value — the success shape, and the case that throws
|
|
// EmptyError without a defaultIfEmpty.
|
|
const client = clientEmitting(new Observable<never>((s) => s.complete()));
|
|
await expect(publishConfirmed(client, 'send-sms', {}, logger)).resolves.toBe(true);
|
|
});
|
|
|
|
it('is false when the publish never settles, rather than hanging', async () => {
|
|
// A broker that is down: amqp-connection-manager buffers the publish and the
|
|
// promise would never resolve. The timeout is what stops one dead broker from
|
|
// hanging every caller of sendSms/sendEmail.
|
|
const client = clientEmitting(NEVER);
|
|
await expect(publishConfirmed(client, 'send-sms', {}, logger, 20)).resolves.toBe(
|
|
false,
|
|
);
|
|
expect(logger.error).toHaveBeenCalled();
|
|
});
|
|
|
|
it('is false when the publish errors', async () => {
|
|
const client = clientEmitting(throwError(() => new Error('channel closed')));
|
|
await expect(publishConfirmed(client, 'send-email', {}, logger)).resolves.toBe(
|
|
false,
|
|
);
|
|
expect(logger.error).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('isBrokerConnected', () => {
|
|
/** Stands in for `ClientProxy.unwrap()`, which returns the AmqpConnectionManager. */
|
|
function clientUnwrapping(manager: unknown): ClientProxy {
|
|
return { unwrap: () => manager } as unknown as ClientProxy;
|
|
}
|
|
|
|
it('reports the connection manager state', () => {
|
|
expect(isBrokerConnected(clientUnwrapping({ isConnected: () => true }))).toBe(
|
|
true,
|
|
);
|
|
expect(isBrokerConnected(clientUnwrapping({ isConnected: () => false }))).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it('is false when unwrap throws — the client never connected', () => {
|
|
// ClientRMQ.unwrap() throws "Not initialized" while its internal client is
|
|
// null, which is what a failed boot-time connect leaves behind. That is a
|
|
// real down signal and must not be softened to "unknown".
|
|
const uninitialised = {
|
|
unwrap: () => {
|
|
throw new Error('Not initialized. Please call the "connect" method first.');
|
|
},
|
|
} as unknown as ClientProxy;
|
|
expect(isBrokerConnected(uninitialised)).toBe(false);
|
|
});
|
|
|
|
it('is null — not a guess — when the manager lacks isConnected or it throws', () => {
|
|
// Guards the health endpoint against reporting "ok" if amqp-connection-manager
|
|
// or Nest changes shape and the accessor we rely on disappears.
|
|
expect(isBrokerConnected(clientUnwrapping(null))).toBeNull();
|
|
expect(isBrokerConnected(clientUnwrapping({}))).toBeNull();
|
|
expect(
|
|
isBrokerConnected(
|
|
clientUnwrapping({
|
|
isConnected: () => {
|
|
throw new Error('boom');
|
|
},
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|