AtlatestRepositorysigil-jmap

sigil-jmap / tree / testtest-jmap.sgl

1;;; Tests for JMAP client library (offline, no live server needed)
2
3(import (sigil test)
4 (sigil json)
5 (sigil jmap client))
6
7;; ============================================================
8;; jmap-call
9;; ============================================================
11(test-group "jmap-call"
13 (test "builds method call triple with default call-id"
14 (let ((call (jmap-call "Mailbox/get" #{ accountId: "abc" ids: 'null })))
15 (assert-true (array? call))
16 (assert-equal 3 (array-length call))
17 (assert-equal "Mailbox/get" (array-ref call 0))
18 (assert-equal "abc" (dict-ref (array-ref call 1) accountId:))
19 (assert-equal "0" (array-ref call 2))))
21 (test "builds method call triple with custom call-id"
22 (let ((call (jmap-call "Email/query" #{ accountId: "abc" } "q0")))
23 (assert-equal "Email/query" (array-ref call 0))
24 (assert-equal "q0" (array-ref call 2))))
26 (test "preserves null values in args"
27 (let ((call (jmap-call "Mailbox/get" #{ ids: 'null })))
28 (assert-equal 'null (dict-ref (array-ref call 1) ids:)))))
31;; ============================================================
32;; jmap-response-get
33;; ============================================================
35(test-group "jmap-response-get"
37 (test "extracts result by call-id"
38 (let ((response (dict
39 methodResponses: #[
40 #["Mailbox/get" #{ accountId: "abc" list: #[] state: "s1" } "0"]])))
41 (let ((result (jmap-response-get response "0")))
42 (assert-true (dict? result))
43 (assert-equal "abc" (dict-ref result accountId:))
44 (assert-equal "s1" (dict-ref result state:)))))
46 (test "returns #f for unknown call-id"
47 (let ((response (dict
48 methodResponses: #[
49 #["Mailbox/get" #{ state: "s1" } "0"]])))
50 (assert-false (jmap-response-get response "999"))))
52 (test "finds correct result among multiple responses"
53 (let ((response (dict
54 methodResponses: #[
55 #["Email/query" #{ ids: #["id1" "id2"] } "q0"]
56 #["Email/get" #{ list: #[] } "g0"]])))
57 (let ((query-result (jmap-response-get response "q0"))
58 (get-result (jmap-response-get response "g0")))
59 (assert-true (dict? query-result))
60 (assert-true (dict-ref query-result ids:))
61 (assert-true (dict? get-result))
62 (assert-true (dict-ref get-result list:)))))
64 (test "returns #f when methodResponses is missing"
65 (assert-false (jmap-response-get #{} "0"))))
68;; ============================================================
69;; jmap-response-error?
70;; ============================================================
72(test-group "jmap-response-error?"
74 (test "detects error response"
75 (let ((response (dict
76 methodResponses: #[
77 #["error" #{ type: "unknownMethod" } "0"]])))
78 (assert-true (jmap-response-error? response "0"))))
80 (test "returns #f for success response"
81 (let ((response (dict
82 methodResponses: #[
83 #["Mailbox/get" #{ list: #[] } "0"]])))
84 (assert-false (jmap-response-error? response "0"))))
86 (test "returns #f for unknown call-id"
87 (let ((response (dict
88 methodResponses: #[
89 #["error" #{ type: "unknownMethod" } "0"]])))
90 (assert-false (jmap-response-error? response "999")))))
93;; ============================================================
94;; jmap-error-type
95;; ============================================================
97(test-group "jmap-error-type"
99 (test "extracts error type string"
100 (let ((response (dict
101 methodResponses: #[
102 #["error" #{ type: "unknownMethod" } "0"]])))
103 (assert-equal "unknownMethod" (jmap-error-type response "0"))))
105 (test "returns #f for non-error response"
106 (let ((response (dict
107 methodResponses: #[
108 #["Mailbox/get" #{ list: #[] } "0"]])))
109 (assert-false (jmap-error-type response "0"))))
111 (test "handles various error types"
112 (let ((response (dict
113 methodResponses: #[
114 #["error" #{ type: "accountNotFound" } "0"]])))
115 (assert-equal "accountNotFound" (jmap-error-type response "0")))))
118;; ============================================================
119;; Request body JSON round-trip
120;; ============================================================
122(test-group "request JSON structure"
124 (test "method call encodes to valid JSON"
125 (let* ((call (jmap-call "Mailbox/get"
126 #{ accountId: "abc123" ids: 'null }))
127 (encoded (json-encode call))
128 (decoded (json-decode encoded)))
129 (assert-true (array? decoded))
130 (assert-equal "Mailbox/get" (array-ref decoded 0))
131 (assert-equal "abc123" (dict-ref (array-ref decoded 1) accountId:))
132 ;; null encodes/decodes correctly
133 (assert-equal 'null (dict-ref (array-ref decoded 1) ids:))
134 (assert-equal "0" (array-ref decoded 2))))
136 (test "request body with using and methodCalls encodes correctly"
137 (let* ((body (dict
138 using: #["urn:ietf:params:jmap:core"
139 "urn:ietf:params:jmap:mail"]
140 methodCalls: #[
141 #["Mailbox/get"
142 #{ accountId: "abc" ids: 'null }
143 "0"]]))
144 (encoded (json-encode body))
145 (decoded (json-decode encoded)))
146 (assert-true (dict? decoded))
147 ;; Check using array
148 (let ((using (dict-ref decoded using:)))
149 (assert-equal 2 (array-length using))
150 (assert-equal "urn:ietf:params:jmap:core" (array-ref using 0)))
151 ;; Check methodCalls
152 (let ((calls (dict-ref decoded methodCalls:)))
153 (assert-equal 1 (array-length calls))
154 (assert-equal "Mailbox/get" (array-ref (array-ref calls 0) 0))))))
157;; ============================================================
158;; Session data extraction
159;; ============================================================
161(test-group "session data parsing"
163 (test "primaryAccounts URN key lookup"
164 (let ((session (dict
165 primaryAccounts:
166 (dict-set (dict-set #{}
167 (string->keyword "urn:ietf:params:jmap:mail") "account-id-123")
168 (string->keyword "urn:ietf:params:jmap:core") "account-id-123"))))
169 (let ((primary (dict-ref session primaryAccounts: #{})))
170 (assert-equal "account-id-123"
171 (dict-ref primary
172 (string->keyword "urn:ietf:params:jmap:mail") #f)))))
174 (test "capabilities with URN keys"
175 (let ((session (dict
176 capabilities:
177 (dict-set (dict-set #{}
178 (string->keyword "urn:ietf:params:jmap:core") #{ maxSizeUpload: 50000000 })
179 (string->keyword "urn:ietf:params:jmap:mail") #{}))))
180 (let ((caps (dict-ref session capabilities: #{})))
181 (assert-true
182 (dict-ref caps (string->keyword "urn:ietf:params:jmap:core") #f))
183 (assert-equal 50000000
184 (dict-ref
185 (dict-ref caps (string->keyword "urn:ietf:params:jmap:core"))
186 maxSizeUpload:))))))
189;; ============================================================
190;; Mailbox list parsing and filtering
191;; ============================================================
193(test-group "mailbox parsing"
195 (test "mailbox list from get response"
196 (let* ((response (dict
197 methodResponses: #[
198 #["Mailbox/get"
199 #{ accountId: "abc"
200 state: "s1"
201 list: #[
202 #{ id: "mb1" name: "Inbox" role: "inbox"
203 totalEmails: 42 unreadEmails: 3 }
204 #{ id: "mb2" name: "Sent" role: "sent"
205 totalEmails: 100 unreadEmails: 0 }
206 #{ id: "mb3" name: "Projects" role: #f
207 totalEmails: 10 unreadEmails: 1 }] }
208 "0"]]))
209 (result (jmap-response-get response "0"))
210 (mailboxes (array->list (dict-ref result list:))))
211 (assert-equal 3 (length mailboxes))
212 (assert-equal "Inbox" (dict-ref (car mailboxes) name:))
213 (assert-equal "inbox" (dict-ref (car mailboxes) role:))))
215 (test "filter mailboxes by role"
216 (let ((mailboxes (list
217 #{ id: "mb1" name: "Inbox" role: "inbox" }
218 #{ id: "mb2" name: "Sent" role: "sent" }
219 #{ id: "mb3" name: "Trash" role: "trash" }
220 #{ id: "mb4" name: "Projects" role: #f })))
221 ;; Find inbox
222 (let ((inbox (let loop ((mbs mailboxes))
223 (cond
224 ((null? mbs) #f)
225 ((equal? (dict-ref (car mbs) role:) "inbox") (car mbs))
226 (else (loop (cdr mbs)))))))
227 (assert-true inbox)
228 (assert-equal "mb1" (dict-ref inbox id:)))
229 ;; Find by name
230 (let ((projects (let loop ((mbs mailboxes))
231 (cond
232 ((null? mbs) #f)
233 ((equal? (dict-ref (car mbs) name:) "Projects") (car mbs))
234 (else (loop (cdr mbs)))))))
235 (assert-true projects)
236 (assert-equal "mb4" (dict-ref projects id:))))))
239;; ============================================================
240;; Email query back-reference key
241;; ============================================================
243(test-group "back-reference keys"
245 (test "#ids key construction for Email/get back-reference"
246 (let ((get-args (dict-set (dict accountId: "abc"
247 properties: #["id" "subject" "from"])
248 (string->keyword "#ids")
249 (dict resultOf: "q0"
250 name: "Email/query"
251 path: "/ids"))))
252 ;; Verify the #ids key exists and has correct structure
253 (let ((ref (dict-ref get-args (string->keyword "#ids"))))
254 (assert-true (dict? ref))
255 (assert-equal "q0" (dict-ref ref resultOf:))
256 (assert-equal "Email/query" (dict-ref ref name:))
257 (assert-equal "/ids" (dict-ref ref path:)))))
259 (test "#ids key survives JSON round-trip"
260 (let* ((args (dict-set #{}
261 (string->keyword "#ids")
262 (dict resultOf: "q0"
263 name: "Email/query"
264 path: "/ids")))
265 (encoded (json-encode args))
266 (decoded (json-decode encoded)))
267 ;; After decode, #ids becomes a keyword key
268 (let ((ref (dict-ref decoded (string->keyword "#ids") #f)))
269 (assert-true (dict? ref))
270 (assert-equal "q0" (dict-ref ref resultOf:))))))
273(run-tests)