2024-12-15 02:53:26 -05:00
|
|
|
import { mock, test, expect } from "bun:test";
|
|
|
|
import * as TE from "fp-ts/lib/TaskEither";
|
|
|
|
|
|
|
|
import { constVoid, pipe } from "fp-ts/lib/function";
|
|
|
|
import type { EmailFromInstruction, EmailToInstruction } from "../src/job";
|
|
|
|
import { perform, type EmailJobDependencies } from "../src/email";
|
|
|
|
|
|
|
|
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)),
|
2024-12-15 04:57:28 -05:00
|
|
|
logout: mock(() => Promise.resolve()),
|
2024-12-15 02:53:26 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
2024-12-15 04:57:28 -05:00
|
|
|
expect(imap.logout).toHaveBeenCalledTimes(1);
|
2024-12-15 02:53:26 -05:00
|
|
|
}),
|
|
|
|
)();
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
2024-12-15 04:57:28 -05:00
|
|
|
expect(imap.logout).toHaveBeenCalledTimes(1);
|
2024-12-15 02:53:26 -05:00
|
|
|
}),
|
|
|
|
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);
|
2024-12-15 04:10:24 -05:00
|
|
|
expect(imap.messageDelete).toHaveBeenCalledWith([1], { uid: true });
|
2024-12-15 02:53:26 -05:00
|
|
|
}),
|
|
|
|
TE.mapLeft(() => expect(false).toBeTruthy()),
|
|
|
|
)();
|
|
|
|
});
|