156 lines
4.3 KiB
TypeScript
156 lines
4.3 KiB
TypeScript
|
import { mock, test, expect, beforeEach } 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()),
|
||
|
)();
|
||
|
});
|