test(quick-260718-7v8): cover held-message date-scoping and sender-relevance guard
- blast-radius: getHeldMessages called with same start/end window as searchDeliveredMessages - blast-radius: unrelated-sender held row excluded from held/matched and perRecipient; matching-sender held row still counts and overrides - client: getHeldMessages threads start/end into POST body data[0] when provided, omits them when not
This commit is contained in:
parent
94f7dad29c
commit
67ee680105
2 changed files with 167 additions and 1 deletions
|
|
@ -283,4 +283,129 @@ describe('getBlastRadius', () => {
|
|||
});
|
||||
expect(setCachedDataMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls getHeldMessages with the SAME start/end window passed to searchDeliveredMessages', async () => {
|
||||
await getBlastRadius(BASE_INPUT);
|
||||
|
||||
const expectedStart = BASE_INPUT.dateWindow.start.toISOString().replace(/\.\d{3}Z$/, '+0000');
|
||||
const expectedEnd = BASE_INPUT.dateWindow.end.toISOString().replace(/\.\d{3}Z$/, '+0000');
|
||||
|
||||
expect(searchDeliveredMessagesMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ start: expectedStart, end: expectedEnd })
|
||||
);
|
||||
expect(getHeldMessagesMock).toHaveBeenCalledWith({
|
||||
recipient: BASE_INPUT.recipient,
|
||||
start: expectedStart,
|
||||
end: expectedEnd,
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes an in-window held row from an UNRELATED sender domain from held/matched and perRecipient', async () => {
|
||||
searchDeliveredMessagesMock.mockResolvedValue({
|
||||
messages: [
|
||||
{
|
||||
id: 'd1',
|
||||
status: 'Delivered',
|
||||
subject: BASE_INPUT.subject,
|
||||
from: BASE_INPUT.sender,
|
||||
fromEnv: BASE_INPUT.sender,
|
||||
to: BASE_INPUT.recipient,
|
||||
toDisplay: 'Reporter',
|
||||
received: '2026-07-14T12:00:00+0000',
|
||||
senderIP: '1.2.3.4',
|
||||
spamScore: 0,
|
||||
detectionLevel: 'none',
|
||||
attachments: false,
|
||||
route: 'inbound',
|
||||
info: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
getHeldMessagesMock.mockResolvedValue({
|
||||
messages: [
|
||||
{
|
||||
id: 'h1',
|
||||
subject: 'Weekly promo',
|
||||
from: 'promo@paulfredrick.test', // unrelated domain vs. evil.test
|
||||
fromDisplay: 'Paul Fredrick',
|
||||
to: BASE_INPUT.recipient,
|
||||
toDisplay: 'Reporter',
|
||||
dateReceived: '2026-07-14T13:00:00+0000',
|
||||
reason: 'spam',
|
||||
reasonCode: 'SPAM',
|
||||
policyInfo: '',
|
||||
route: 'inbound',
|
||||
hasAttachments: false,
|
||||
size: 100,
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
const result = await getBlastRadius(BASE_INPUT);
|
||||
|
||||
expect(result).toEqual({
|
||||
status: 'ok',
|
||||
matched: 1,
|
||||
delivered: 1,
|
||||
held: 0,
|
||||
rejected: 0,
|
||||
clicked: 0,
|
||||
perRecipient: [{ recipient: BASE_INPUT.recipient, status: 'delivered' }],
|
||||
source: 'fan-out',
|
||||
});
|
||||
});
|
||||
|
||||
it('still counts and overrides when a held row sender domain MATCHES input.sender', async () => {
|
||||
searchDeliveredMessagesMock.mockResolvedValue({
|
||||
messages: [
|
||||
{
|
||||
id: 'd1',
|
||||
status: 'Delivered',
|
||||
subject: BASE_INPUT.subject,
|
||||
from: BASE_INPUT.sender,
|
||||
fromEnv: BASE_INPUT.sender,
|
||||
to: BASE_INPUT.recipient,
|
||||
toDisplay: 'Reporter',
|
||||
received: '2026-07-14T12:00:00+0000',
|
||||
senderIP: '1.2.3.4',
|
||||
spamScore: 0,
|
||||
detectionLevel: 'none',
|
||||
attachments: false,
|
||||
route: 'inbound',
|
||||
info: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
getHeldMessagesMock.mockResolvedValue({
|
||||
messages: [
|
||||
{
|
||||
id: 'h1',
|
||||
subject: BASE_INPUT.subject,
|
||||
from: 'queue@evil.test', // same domain as BASE_INPUT.sender (evil.test)
|
||||
fromDisplay: 'Attacker Queue',
|
||||
to: BASE_INPUT.recipient,
|
||||
toDisplay: 'Reporter',
|
||||
dateReceived: '2026-07-14T13:00:00+0000',
|
||||
reason: 'spam',
|
||||
reasonCode: 'SPAM',
|
||||
policyInfo: '',
|
||||
route: 'inbound',
|
||||
hasAttachments: false,
|
||||
size: 100,
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
const result = await getBlastRadius(BASE_INPUT);
|
||||
|
||||
expect(result.status).toBe('ok');
|
||||
if (result.status === 'ok') {
|
||||
expect(result.held).toBe(1);
|
||||
expect(result.delivered).toBe(1);
|
||||
expect(result.matched).toBe(2); // 1 non-rejected delivered + 1 held (counts are independent of the perRecipient override)
|
||||
expect(result.perRecipient).toEqual([{ recipient: BASE_INPUT.recipient, status: 'held' }]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* for this phase (Phase 17, Plan 01, Task 1).
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import {
|
||||
isMimecastConfigured,
|
||||
getMimecastClient,
|
||||
|
|
@ -103,3 +103,44 @@ describe('getMimecastClientForTenant', () => {
|
|||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue