diff options
author | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2024-12-14 23:53:26 -0800 |
---|---|---|
committer | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2024-12-14 23:55:51 -0800 |
commit | 4fd40b1f9de400a5d859789e1dad3e1a4ba6587c (patch) | |
tree | 74fbae949aa3fb9711c06e31cb6649e90a8cdb97 /tst | |
download | uptime-4fd40b1f9de400a5d859789e1dad3e1a4ba6587c.tar.gz uptime-4fd40b1f9de400a5d859789e1dad3e1a4ba6587c.zip |
initial commit
Diffstat (limited to 'tst')
-rw-r--r-- | tst/duration.spec.ts | 78 | ||||
-rw-r--r-- | tst/email.spec.ts | 153 |
2 files changed, 231 insertions, 0 deletions
diff --git a/tst/duration.spec.ts b/tst/duration.spec.ts new file mode 100644 index 0000000..bcd50f5 --- /dev/null +++ b/tst/duration.spec.ts @@ -0,0 +1,78 @@ +import { pipe } from "fp-ts/function"; +import * as E from "fp-ts/Either"; +import { describe, test, expect } from "bun:test"; +import * as D from "../src/duration"; + +describe("Duration Utility", () => { + test("get unit should convert correctly", () => { + expect(D.getMs(1000)).toBe(1000); + expect(D.getSeconds(1000)).toBe(1); + expect(D.getMinutes(60000)).toBe(1); + expect(D.getHours(3600000)).toBe(1); + }); + + test("format should format duration correctly", () => { + expect(D.format(3600000 + 237 + 5 * 60 * 1000)).toBe("01:05:00.237"); + }); +}); + +describe("DurationBuilder", () => { + test("createDurationBuilder should create a builder with zero values", () => { + const builder = D.createDurationBuilder(); + expect(builder.millis).toBe(0); + expect(builder.seconds).toBe(0); + expect(builder.minutes).toBe(0); + expect(builder.hours).toBe(0); + }); + + test("withMillis should set fields correctly and with precedence", () => { + const builder = pipe( + D.createDurationBuilder(), + D.withMillis(0), + D.withSeconds(20), + D.withMinutes(30), + D.withHours(40), + D.withMillis(10), + ); + expect(builder.millis).toBe(10); + expect(builder.seconds).toBe(20); + expect(builder.minutes).toBe(30); + expect(builder.hours).toBe(40); + }); + + test("build should calculate total duration correctly", () => { + const duration = pipe( + D.createDurationBuilder(), + D.withMillis(10), + D.withSeconds(20), + D.withMinutes(30), + D.withHours(40), + D.build, + ); + expect(duration).toBe( + 10 + 20 * 1000 + 30 * 60 * 1000 + 40 * 60 * 60 * 1000, + ); + }); +}); + +describe("parse", () => { + test("should return right for a valid duration", () => { + expect(D.parse("10 seconds 1 hr 30 min")).toEqual( + E.right(1 * 60 * 60 * 1000 + 30 * 60 * 1000 + 10 * 1000), + ); + }); + + test("should operate with order", () => { + expect(D.parse("1 hr 30 min 2 hours")).toEqual( + E.right(2 * 60 * 60 * 1000 + 30 * 60 * 1000), + ); + }); + + test("returns left for unknown duration unit", () => { + expect(D.parse("1 xyz")).toEqual(E.left("unknown duration type: xyz")); + }); + + test("return left for invalid number", () => { + expect(D.parse("abc ms")).toEqual(E.left('bad value: "abc"')); + }); +}); diff --git a/tst/email.spec.ts b/tst/email.spec.ts new file mode 100644 index 0000000..5f2aa90 --- /dev/null +++ b/tst/email.spec.ts @@ -0,0 +1,153 @@ +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)), + 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()), + )(); +}); |