/** * mimecast-client.ts — isMimecastConfigured()/getMimecastClient() factory tests. * * Mirrors pax8-factory.test.ts's beforeEach env-var-delete + reset seam * pattern. Does not test MimecastClient's other 20+ methods — out of scope * for this phase (Phase 17, Plan 01, Task 1). */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { isMimecastConfigured, getMimecastClient, getMimecastClientForTenant, _resetMimecastClient, } from './mimecast-client'; beforeEach(() => { delete process.env.MIMECAST_CLIENT_ID; delete process.env.MIMECAST_CLIENT_SECRET; _resetMimecastClient(); }); describe('isMimecastConfigured', () => { it('returns false when neither env var is set', () => { expect(isMimecastConfigured()).toBe(false); }); it('returns false when only MIMECAST_CLIENT_ID is set', () => { process.env.MIMECAST_CLIENT_ID = 'id1'; expect(isMimecastConfigured()).toBe(false); }); it('returns false when only MIMECAST_CLIENT_SECRET is set', () => { process.env.MIMECAST_CLIENT_SECRET = 'secret1'; expect(isMimecastConfigured()).toBe(false); }); it('returns true when both env vars are set', () => { process.env.MIMECAST_CLIENT_ID = 'id1'; process.env.MIMECAST_CLIENT_SECRET = 'secret1'; expect(isMimecastConfigured()).toBe(true); }); }); describe('getMimecastClient', () => { it('throws the exact configuration error when not configured', () => { expect(() => getMimecastClient()).toThrow( 'MIMECAST_CLIENT_ID and MIMECAST_CLIENT_SECRET must be set' ); }); it('returns the same cached instance on repeated calls', () => { process.env.MIMECAST_CLIENT_ID = 'id1'; process.env.MIMECAST_CLIENT_SECRET = 'secret1'; const first = getMimecastClient(); const second = getMimecastClient(); expect(second).toBe(first); }); it('rebuilds a new instance after _resetMimecastClient()', () => { process.env.MIMECAST_CLIENT_ID = 'id1'; process.env.MIMECAST_CLIENT_SECRET = 'secret1'; const first = getMimecastClient(); _resetMimecastClient(); const second = getMimecastClient(); expect(second).not.toBe(first); }); }); describe('getMimecastClientForTenant', () => { // Fake credentials only — never real mimecast_tenants values. const FAKE_TENANT = { client_id: 'tid', client_secret: 'tsecret', base_url: 'https://tenant.example' }; it('returns a MimecastClient instance exposing the fan-out methods', () => { const client = getMimecastClientForTenant(FAKE_TENANT); expect(client).toBeTruthy(); expect(typeof client.searchDeliveredMessages).toBe('function'); expect(typeof client.getHeldMessages).toBe('function'); expect(typeof client.getThreatEvents).toBe('function'); }); it('returns a NEW instance on each call — never the cached global', () => { const first = getMimecastClientForTenant(FAKE_TENANT); const second = getMimecastClientForTenant(FAKE_TENANT); expect(second).not.toBe(first); }); it('does not populate or replace the cached global getMimecastClient() instance', () => { process.env.MIMECAST_CLIENT_ID = 'id1'; process.env.MIMECAST_CLIENT_SECRET = 'secret1'; getMimecastClientForTenant(FAKE_TENANT); const global1 = getMimecastClient(); const global2 = getMimecastClient(); // The global singleton is unaffected by tenant-client construction — // still cached and independent of the tenant instance. expect(global2).toBe(global1); expect(global1).not.toBe(getMimecastClientForTenant(FAKE_TENANT)); }); it('does not throw when base_url is omitted (defaults to the Mimecast API host)', () => { expect(() => getMimecastClientForTenant({ client_id: 'tid', client_secret: 'tsecret' })).not.toThrow(); }); }); describe('getHeldMessages date-scoping', () => { // Fake credentials only — never real mimecast_tenants values. const FAKE_TENANT = { client_id: 'tid', client_secret: 'tsecret', base_url: 'https://tenant.example' }; it('includes start/end in the POST body data[0] when provided', async () => { const client = getMimecastClientForTenant(FAKE_TENANT); const requestSpy = vi .spyOn(client as any, 'request') .mockResolvedValue({ data: [], meta: { pagination: {} } }); await client.getHeldMessages({ recipient: 'r@x.test', start: '2026-07-14T00:00:00+0000', end: '2026-07-15T00:00:00+0000', }); expect(requestSpy).toHaveBeenCalledTimes(1); const body: any = requestSpy.mock.calls[0][2]; expect(body.data[0]).toMatchObject({ admin: true, start: '2026-07-14T00:00:00+0000', end: '2026-07-15T00:00:00+0000', searchBy: { fieldName: 'recipient', value: 'r@x.test' }, }); }); it('omits start/end from the POST body data[0] when not provided', async () => { const client = getMimecastClientForTenant(FAKE_TENANT); const requestSpy = vi .spyOn(client as any, 'request') .mockResolvedValue({ data: [], meta: { pagination: {} } }); await client.getHeldMessages({ recipient: 'r@x.test' }); expect(requestSpy).toHaveBeenCalledTimes(1); const body: any = requestSpy.mock.calls[0][2]; expect(body.data[0]).not.toHaveProperty('start'); expect(body.data[0]).not.toHaveProperty('end'); }); });