summaryrefslogtreecommitdiff
path: root/tst
diff options
context:
space:
mode:
authorElizabeth Hunt <elizabeth.hunt@simponic.xyz>2024-12-14 23:53:26 -0800
committerElizabeth Hunt <elizabeth.hunt@simponic.xyz>2024-12-14 23:55:51 -0800
commit4fd40b1f9de400a5d859789e1dad3e1a4ba6587c (patch)
tree74fbae949aa3fb9711c06e31cb6649e90a8cdb97 /tst
downloaduptime-4fd40b1f9de400a5d859789e1dad3e1a4ba6587c.tar.gz
uptime-4fd40b1f9de400a5d859789e1dad3e1a4ba6587c.zip
initial commit
Diffstat (limited to 'tst')
-rw-r--r--tst/duration.spec.ts78
-rw-r--r--tst/email.spec.ts153
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()),
+ )();
+});