summaryrefslogtreecommitdiff
path: root/tst
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-08-17 23:50:24 -0700
committerElizabeth Hunt <me@liz.coffee>2025-08-17 23:53:38 -0700
commit1a4fc9535a89b58e8b67c8996ade0b833116af3a (patch)
treed16f3129d7bb69f204bba8422e909354195a0042 /tst
parent157dc327e8fe63541b517cfbeeaf202a3e8553a5 (diff)
downloaduptime-1a4fc9535a89b58e8b67c8996ade0b833116af3a.tar.gz
uptime-1a4fc9535a89b58e8b67c8996ade0b833116af3a.zip
Move to pengueno.
Diffstat (limited to 'tst')
-rw-r--r--tst/email.spec.ts158
-rw-r--r--tst/email.test.ts100
2 files changed, 100 insertions, 158 deletions
diff --git a/tst/email.spec.ts b/tst/email.spec.ts
deleted file mode 100644
index 79c21cc..0000000
--- a/tst/email.spec.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-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)),
- logout: mock(() => Promise.resolve()),
- mailboxClose: mock(() => Promise.resolve()),
- };
-
- 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);
- expect(imap.mailboxClose).toHaveBeenCalledTimes(1);
- expect(imap.logout).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);
- expect(imap.logout).toHaveBeenCalledTimes(1);
- expect(imap.mailboxClose).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], { uid: true });
- }),
- TE.mapLeft(() => expect(false).toBeTruthy()),
- )();
-});
diff --git a/tst/email.test.ts b/tst/email.test.ts
new file mode 100644
index 0000000..dfab0ff
--- /dev/null
+++ b/tst/email.test.ts
@@ -0,0 +1,100 @@
+import { test, expect, jest } from '@jest/globals';
+import { Email, Outbox, Inbox, IRecv, ISend } from '@emprespresso/uptime';
+import { LogMetricTraceable, Either } from '@emprespresso/pengueno';
+import { FetchMessageObject, MailboxLockObject } from 'imapflow';
+
+const createMockRecv = (): IRecv => ({
+ connect: jest.fn(),
+ logout: jest.fn(),
+ getMailboxLock: jest.fn(),
+ mailboxClose: jest.fn(),
+ fetchAll: jest.fn(),
+ messageDelete: jest.fn(),
+});
+
+const createMockSend = (): ISend => ({
+ sendMail: jest.fn(),
+});
+
+const from = {
+ send_port: 465,
+ email: 'test@localhost',
+ username: 'test',
+ password: 'password',
+ send_host: 'localhost',
+};
+
+const to = {
+ read_port: 993,
+ email: 'test@localhost',
+ username: 'test',
+ password: 'password',
+ read_host: 'localhost',
+};
+
+test('failure to send message goes left', async () => {
+ const send = createMockSend();
+
+ send.sendMail.mockRejectedValue(new Error('fail'));
+
+ const outbox = new Outbox(send);
+
+ const email = LogMetricTraceable.of<Email>({
+ from: from.email,
+ to: to.email,
+ subject: 'test',
+ text: 'test',
+ });
+
+ const result = await outbox.send(email);
+
+ expect(result.left().present()).toBe(true);
+ expect(result.left().get().message).toBe('fail');
+});
+
+test('goes left when message not ever received', async () => {
+ const recv = createMockRecv();
+
+ recv.fetchAll.mockResolvedValue([]);
+ const lock: MailboxLockObject = { path: 'INBOX', release: jest.fn() };
+
+ const inbox = new Inbox(recv, LogMetricTraceable.of(lock));
+
+ const email = LogMetricTraceable.of<Email>({
+ from: from.email,
+ to: to.email,
+ subject: 'test',
+ text: 'test',
+ });
+
+ const result = await inbox.find(email);
+
+ expect(result.left().present()).toBe(true);
+});
+
+test('releases lock on failure', async () => {
+ const recv = createMockRecv();
+
+ recv.fetchAll.mockResolvedValue([]);
+ const lock: MailboxLockObject = { path: 'INBOX', release: jest.fn() };
+
+ // Mock mailboxClose and logout to resolve properly
+ recv.mailboxClose.mockResolvedValue(true);
+ recv.logout.mockResolvedValue();
+
+ const inbox = new Inbox(recv, LogMetricTraceable.of(lock));
+
+ const email = LogMetricTraceable.of<Email>({
+ from: from.email,
+ to: to.email,
+ subject: 'test',
+ text: 'test',
+ });
+
+ await inbox.find(email);
+ await inbox.close();
+
+ expect(lock.release).toHaveBeenCalledTimes(1);
+ expect(recv.mailboxClose).toHaveBeenCalledTimes(1);
+ expect(recv.logout).toHaveBeenCalledTimes(1);
+});