Something went wrong. Try again.
CLI for FastMail via JMAP
Something went wrong. Try again.
15 kB · 499 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500/** * Tests for JMAP Mailbox methods. */
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";import { JmapClient } from "./client.ts";import { getMailboxes, queryMailboxes, setMailboxes } from "./mailbox.ts";
// Mock sessionconst mockSession = { capabilities: { "urn:ietf:params:jmap:core": { maxSizeUpload: 50000000, maxConcurrentUpload: 8, maxSizeRequest: 10000000, maxConcurrentRequests: 8, maxCallsInRequest: 32, maxObjectsInGet: 500, maxObjectsInSet: 500, collationAlgorithms: ["i;ascii-casemap", "i;unicode-casemap"], }, "urn:ietf:params:jmap:mail": { maxMailboxesPerEmail: 1000, maxMailboxDepth: 10, maxSizeMailboxName: 490, maxSizeAttachmentsPerEmail: 50000000, emailQuerySortOptions: [ "receivedAt", "sentAt", "size", "from", "to", "subject", ], mayCreateTopLevelMailbox: true, }, }, accounts: { "account-123": { name: "test@example.com", isPersonal: true, isReadOnly: false, accountCapabilities: { "urn:ietf:params:jmap:core": {}, "urn:ietf:params:jmap:mail": {}, }, }, }, primaryAccounts: { "urn:ietf:params:jmap:core": "account-123", "urn:ietf:params:jmap:mail": "account-123", }, username: "test@example.com", apiUrl: "https://api.fastmail.com/jmap/api/", downloadUrl: "https://api.fastmail.com/jmap/download/{accountId}/{blobId}/{name}", uploadUrl: "https://api.fastmail.com/jmap/upload/{accountId}/", eventSourceUrl: "https://api.fastmail.com/jmap/eventsource/?types={types}&closeafter={closeafter}&ping={ping}", state: "session-state-123",};
function createMockResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" }, });}
let fetchSpy: ReturnType<typeof spyOn>;
beforeEach(() => { // Reset any previous spies fetchSpy?.mockRestore?.();});
afterEach(() => { fetchSpy?.mockRestore?.();});
describe("Mailbox methods", () => { describe("getMailboxes", () => { test("fetches all mailboxes", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/get", { accountId: "account-123", state: "78540", list: [ { id: "MB23cfa8094c0f41e6", name: "Inbox", parentId: null, role: "inbox", sortOrder: 10, totalEmails: 16307, unreadEmails: 13905, totalThreads: 5833, unreadThreads: 5128, myRights: { mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true, maySetKeywords: true, mayCreateChild: true, mayRename: false, mayDelete: false, maySubmit: true, }, isSubscribed: true, }, { id: "MB674cc24095db49ce", name: "Sent", parentId: null, role: "sent", sortOrder: 20, totalEmails: 1234, unreadEmails: 0, totalThreads: 1234, unreadThreads: 0, myRights: { mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true, maySetKeywords: true, mayCreateChild: true, mayRename: false, mayDelete: false, maySubmit: true, }, isSubscribed: true, }, ], notFound: [], }, callId, ], ], sessionState: "75128aab4b1b", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await getMailboxes(client);
expect(result.accountId).toBe("account-123"); expect(result.state).toBe("78540"); expect(result.list).toHaveLength(2); expect(result.list[0]?.name).toBe("Inbox"); expect(result.list[0]?.role).toBe("inbox"); expect(result.list[0]?.totalEmails).toBe(16307); expect(result.list[0]?.unreadEmails).toBe(13905); });
test("fetches specific mailboxes by IDs", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/get", { accountId: "account-123", state: "78540", list: [ { id: "MB23cfa8094c0f41e6", name: "Inbox", parentId: null, role: "inbox", sortOrder: 10, totalEmails: 100, unreadEmails: 5, totalThreads: 80, unreadThreads: 3, myRights: { mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true, maySetKeywords: true, mayCreateChild: true, mayRename: false, mayDelete: false, maySubmit: true, }, isSubscribed: true, }, ], notFound: [], }, callId, ], ], sessionState: "75128aab4b1b", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await getMailboxes(client, { ids: ["MB23cfa8094c0f41e6"], });
expect(result.list).toHaveLength(1); expect(result.list[0]?.id).toBe("MB23cfa8094c0f41e6"); }); });
describe("queryMailboxes", () => { test("queries mailboxes with filter", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/query", { accountId: "account-123", queryState: "780545", canCalculateChanges: true, position: 0, total: 2, ids: ["MB23cfa8094c0f41e6", "MB674cc24095db49ce"], }, callId, ], ], sessionState: "75128aab4b1b", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await queryMailboxes(client, { filter: { isSubscribed: true }, });
expect(result.accountId).toBe("account-123"); expect(result.queryState).toBe("780545"); expect(result.ids).toHaveLength(2); });
test("queries mailboxes with sort", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/query", { accountId: "account-123", queryState: "780546", canCalculateChanges: true, position: 0, ids: ["MB674cc24095db49ce", "MB23cfa8094c0f41e6"], }, callId, ], ], sessionState: "75128aab4b1b", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await queryMailboxes(client, { sort: [{ property: "name", isAscending: true }], });
expect(result.ids).toHaveLength(2); }); });
describe("setMailboxes", () => { test("creates a mailbox", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/set", { accountId: "account-123", oldState: "78542", newState: "78549", created: { newMailbox: { id: "MB99newid123", }, }, notCreated: null, notUpdated: null, notDestroyed: null, }, callId, ], ], sessionState: "75128aab4b1c", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await setMailboxes(client, { create: { newMailbox: { name: "Project X", sortOrder: 20, }, }, });
expect(result.created?.newMailbox?.id).toBe("MB99newid123"); expect(result.newState).toBe("78549"); });
test("updates a mailbox", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/set", { accountId: "account-123", oldState: "78542", newState: "78543", updated: { MB674cc24095db49ce: null, }, notCreated: null, notUpdated: null, notDestroyed: null, }, callId, ], ], sessionState: "75128aab4b1d", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await setMailboxes(client, { update: { MB674cc24095db49ce: { name: "Updated Name", }, }, });
expect(result.updated?.MB674cc24095db49ce).toBeDefined(); expect(result.newState).toBe("78543"); });
test("destroys a mailbox", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/set", { accountId: "account-123", oldState: "78542", newState: "78544", destroyed: ["MB674cc24095db49ce"], notCreated: null, notUpdated: null, notDestroyed: null, }, callId, ], ], sessionState: "75128aab4b1e", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await setMailboxes(client, { destroy: ["MB674cc24095db49ce"], });
expect(result.destroyed).toContain("MB674cc24095db49ce"); expect(result.newState).toBe("78544"); });
test("handles mailboxHasEmail error", async () => { let callCount = 0; fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async ( _url: unknown, options: RequestInit | undefined, ) => { callCount++; if (callCount === 1) { return createMockResponse(mockSession); } const body = JSON.parse(options?.body as string); const callId = body.methodCalls[0][2]; return createMockResponse({ methodResponses: [ [ "Mailbox/set", { accountId: "account-123", oldState: "78542", newState: "78542", notDestroyed: { MB23cfa8094c0f41e6: { type: "mailboxHasEmail", description: "Mailbox contains emails", }, }, }, callId, ], ], sessionState: "75128aab4b1f", }); }) as unknown as typeof fetch);
const client = new JmapClient({ token: "test-token" }); const result = await setMailboxes(client, { destroy: ["MB23cfa8094c0f41e6"], onDestroyRemoveEmails: false, });
expect(result.notDestroyed?.MB23cfa8094c0f41e6?.type).toBe( "mailboxHasEmail", ); }); });});