AtlatestRepositorysigil-jmap
sigil-jmap / tree / testtest-jmap.sgl
1
;;; Tests for JMAP client library (offline, no live server needed)3
(import (sigil test)4
(sigil json)5
(sigil jmap client))7
;; ============================================================8
;; jmap-call9
;; ============================================================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-get33
;; ============================================================35
(test-group "jmap-response-get"37
(test "extracts result by call-id"38
(let ((response (dict39
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 (dict48
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 (dict54
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 (dict76
methodResponses: #[77
#["error" #{ type: "unknownMethod" } "0"]])))78
(assert-true (jmap-response-error? response "0"))))80
(test "returns #f for success response"81
(let ((response (dict82
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 (dict88
methodResponses: #[89
#["error" #{ type: "unknownMethod" } "0"]])))90
(assert-false (jmap-response-error? response "999")))))93
;; ============================================================94
;; jmap-error-type95
;; ============================================================97
(test-group "jmap-error-type"99
(test "extracts error type string"100
(let ((response (dict101
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 (dict107
methodResponses: #[108
#["Mailbox/get" #{ list: #[] } "0"]])))109
(assert-false (jmap-error-type response "0"))))111
(test "handles various error types"112
(let ((response (dict113
methodResponses: #[114
#["error" #{ type: "accountNotFound" } "0"]])))115
(assert-equal "accountNotFound" (jmap-error-type response "0")))))118
;; ============================================================119
;; Request body JSON round-trip120
;; ============================================================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 correctly133
(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 (dict138
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 array148
(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 methodCalls152
(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 extraction159
;; ============================================================161
(test-group "session data parsing"163
(test "primaryAccounts URN key lookup"164
(let ((session (dict165
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 primary172
(string->keyword "urn:ietf:params:jmap:mail") #f)))))174
(test "capabilities with URN keys"175
(let ((session (dict176
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-true182
(dict-ref caps (string->keyword "urn:ietf:params:jmap:core") #f))183
(assert-equal 50000000184
(dict-ref185
(dict-ref caps (string->keyword "urn:ietf:params:jmap:core"))186
maxSizeUpload:))))))189
;; ============================================================190
;; Mailbox list parsing and filtering191
;; ============================================================193
(test-group "mailbox parsing"195
(test "mailbox list from get response"196
(let* ((response (dict197
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: #f207
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 (list217
#{ 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 inbox222
(let ((inbox (let loop ((mbs mailboxes))223
(cond224
((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 name230
(let ((projects (let loop ((mbs mailboxes))231
(cond232
((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 key241
;; ============================================================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 structure253
(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 key268
(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)