diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-08-17 23:50:24 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-08-17 23:53:38 -0700 |
commit | 1a4fc9535a89b58e8b67c8996ade0b833116af3a (patch) | |
tree | d16f3129d7bb69f204bba8422e909354195a0042 /tst | |
parent | 157dc327e8fe63541b517cfbeeaf202a3e8553a5 (diff) | |
download | uptime-1a4fc9535a89b58e8b67c8996ade0b833116af3a.tar.gz uptime-1a4fc9535a89b58e8b67c8996ade0b833116af3a.zip |
Move to pengueno.
Diffstat (limited to 'tst')
-rw-r--r-- | tst/email.spec.ts | 158 | ||||
-rw-r--r-- | tst/email.test.ts | 100 |
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); +}); |