uptime/tst/canary/email.spec.ts

156 lines
4.3 KiB
TypeScript

import { mock, test, expect } from "bun:test";
import * as TE from "fp-ts/lib/TaskEither";
import { perform, type EmailJobDependencies } from "../../src/canary/email";
import type {
EmailFromInstruction,
EmailToInstruction,
} from "../../src/canary";
import { constVoid, pipe } from "fp-ts/lib/function";
const from: EmailFromInstruction = {
send_port: 465,
email: "test@localhost",
username: "test",
password: "password",
server: "localhost",
};
const to: EmailToInstruction = {
read_port: 993,
email: "test@localhost",
username: "test",
password: "password",
server: "localhost",
};
const getMocks = () => {
const lock = {
path: "INBOX",
release: mock(() => constVoid()),
};
const imap = {
fetchAll: mock(() => Promise.resolve([])),
connect: mock(() => Promise.resolve()),
getMailboxLock: mock(() => Promise.resolve(lock)),
messageDelete: mock(() => Promise.resolve(true)),
close: mock(() => constVoid()),
};
const mockDependencies: Partial<EmailJobDependencies> = {
getImapImpl: () => TE.right(imap),
getSendImpl: mock(() => (email: any) => TE.right(email)),
matchesEmailImpl: mock(() => () => true),
};
return { lock, imap, mockDependencies };
};
test("retries until message is in inbox", async () => {
const { imap, mockDependencies } = getMocks();
const retry = { retries: 3, interval: 400 };
const emailJob = { from, to, readRetry: retry };
let attempts = 0;
const messageInInbox = { uid: 1 } as any;
imap.fetchAll = mock(() => {
attempts++;
if (attempts === 3) {
return Promise.resolve([messageInInbox] as any);
}
return Promise.resolve([]);
});
mockDependencies.matchesEmailImpl = mock(
(_: any) => (message: any) => message.uid === 1,
);
await pipe(
perform(emailJob, mockDependencies),
TE.map((x) => {
expect(x).toBeTruthy();
expect(attempts).toBe(3);
}),
TE.mapLeft(() => expect(false).toBeTruthy()),
)();
});
test("failure to send message goes left", async () => {
const { mockDependencies } = getMocks();
const emailJob = { from, to, readRetry: { retries: 1, interval: 1 } };
mockDependencies.getSendImpl = mock(() => () => TE.left(new Error("fail")));
await pipe(
perform(emailJob, mockDependencies),
TE.map(() => expect(false).toBeTruthy()),
TE.mapLeft((e) => {
expect(e.message).toBe("fail");
}),
)();
});
test("goes left when message not ever received", async () => {
const { imap, mockDependencies } = getMocks();
const emailJob = { from, to, readRetry: { retries: 3, interval: 1 } };
imap.fetchAll = mock(() => Promise.resolve([]));
expect(
await pipe(
perform(emailJob, mockDependencies),
TE.map(() => expect(false).toBeTruthy()),
TE.mapLeft((e) => {
expect(e.message).toBe("Email message not found");
}),
)(),
);
});
test("releases lock on left", async () => {
const { lock, imap, mockDependencies } = getMocks();
const emailJob = { from, to, readRetry: { retries: 1, interval: 1 } };
imap.fetchAll = mock(() => Promise.resolve([]));
await pipe(
perform(emailJob, mockDependencies),
TE.map(() => expect(false).toBeTruthy()),
TE.mapLeft(() => {
expect(imap.getMailboxLock).toHaveBeenCalledTimes(1);
expect(lock.release).toHaveBeenCalledTimes(1);
}),
)();
});
test("releases lock on right", async () => {
const { lock, imap, mockDependencies } = getMocks();
const emailJob = { from, to, readRetry: { retries: 1, interval: 1 } };
mockDependencies.findEmailUidInInboxImpl = () => TE.right(1);
await pipe(
perform(emailJob, mockDependencies),
TE.map(() => {
expect(imap.getMailboxLock).toHaveBeenCalledTimes(1);
expect(lock.release).toHaveBeenCalledTimes(1);
}),
TE.mapLeft(() => expect(false).toBeTruthy()),
)();
});
test("cleans up sent messages from inbox", async () => {
const { imap, mockDependencies } = getMocks();
const emailJob = { from, to, readRetry: { retries: 1, interval: 1 } };
mockDependencies.findEmailUidInInboxImpl = () => TE.right(1);
await pipe(
perform(emailJob, mockDependencies),
TE.map(() => {
expect(imap.messageDelete).toHaveBeenCalledTimes(1);
expect(imap.messageDelete).toHaveBeenCalledWith([1]);
}),
TE.mapLeft(() => expect(false).toBeTruthy()),
)();
});