AtlatestRepositorysigil-websocket
sigil-websocket / tree / testtest-websocket.sgl
1
(import (sigil test)2
(sigil websocket connection)3
(sigil websocket frame))5
;; ============================================================6
;; Frame opcodes7
;; ============================================================9
(test-group "frame opcodes"10
(test "opcode values"11
(assert-equal #x0 opcode-continuation)12
(assert-equal #x1 opcode-text)13
(assert-equal #x2 opcode-binary)14
(assert-equal #x8 opcode-close)15
(assert-equal #x9 opcode-ping)16
(assert-equal #xA opcode-pong))18
(test "opcode-control? identifies control frames"19
(assert-false (opcode-control? opcode-text))20
(assert-false (opcode-control? opcode-binary))21
(assert-true (opcode-control? opcode-close))22
(assert-true (opcode-control? opcode-ping))23
(assert-true (opcode-control? opcode-pong))))25
;; ============================================================26
;; Frame encoding27
;; ============================================================29
(test-group "frame encoding"30
(test "encode-text-frame produces bytevector"31
(let ((frame (encode-text-frame "hello")))32
(assert-true (bytevector? frame))33
(assert-true (> (bytevector-length frame) 5))))35
(test "encode-ping-frame produces bytevector"36
(let ((frame (encode-ping-frame)))37
(assert-true (bytevector? frame))))39
(test "encode-close-frame produces bytevector"40
(let ((frame (encode-close-frame)))41
(assert-true (bytevector? frame)))))43
;; ============================================================44
;; Frame encode/decode roundtrip45
;; ============================================================47
(test-group "frame roundtrip"48
(test "decode from encode-text-frame"49
;; encode-text-frame generates a masked frame for client use50
(let* ((encoded (encode-text-frame "hello"))51
(result (decode-frame encoded)))52
(assert-true (frame-decode-result? result))53
(let ((frame (frame-decode-result-frame result)))54
(when frame55
(assert-equal opcode-text (ws-frame-opcode frame)))))))57
;; ============================================================58
;; Masking59
;; ============================================================61
(test-group "masking"62
(test "generate-mask-key returns 4 bytes"63
(let ((key (generate-mask-key)))64
(assert-true (bytevector? key))65
(assert-equal 4 (bytevector-length key))))67
(test "apply-mask is reversible"68
(let* ((key (generate-mask-key))69
(data (make-bytevector 5))70
(masked (apply-mask data key))71
(unmasked (apply-mask masked key)))72
(assert-equal data unmasked)))74
(test "masking changes data"75
(let* ((key (generate-mask-key))76
(data (make-bytevector 16))77
(masked (apply-mask data key)))78
;; Masked should differ (unless mask key is all zeros, extremely unlikely)79
(assert-false (equal? data masked)))))81
;; ============================================================82
;; ws-receive-nowait83
;;84
;; Every connection below is built with socket = #f ON PURPOSE. If85
;; `ws-receive-nowait` ever touched the socket these tests would raise86
;; rather than pass, so "it does not consume socket bytes" is enforced by87
;; construction instead of asserted in a comment.88
;; ============================================================90
;; `define-struct` constructors are KEYWORD-based, not positional. The first91
;; draft of these tests used positional arguments and would have failed with92
;; `keyword->symbol: expected keyword` — caught by running the shape in a93
;; scratch script, because `sigil test` cannot run in this repo at all (see the94
;; note on the closed toolchain mismatch below).95
(define (open-conn-with buffer)96
(ws-connection socket: #f state: 'open url: "ws://test/x" host: "test"97
buffer: buffer fragments: '()))99
(define (two-frames-one-segment)100
(bytevector-append (encode-server-text-frame "FIRST")101
(encode-server-text-frame "SECOND")))103
(test-group "ws-receive-nowait"104
(test "drains a SECOND frame coalesced into one segment"105
;; This is the defect in miniature: one socket read yielded two complete106
;; frames, so after the first is returned the second is buffered with107
;; nothing further on the socket. Measured against 0.10.4 over a real108
;; socket, that second frame surfaced 2997ms late — only when unrelated109
;; bytes arrived.110
(let* ((conn (open-conn-with (two-frames-one-segment)))111
(m1 (ws-receive-nowait conn))112
(m2 (ws-receive-nowait conn))113
(m3 (ws-receive-nowait conn)))114
(assert-true (ws-message? m1))115
(assert-equal "FIRST" (ws-message-data m1))116
(assert-true (ws-message? m2))117
(assert-equal "SECOND" (ws-message-data m2))118
;; and the buffer is now genuinely empty, not merely quiet119
(assert-false m3)))121
(test "returns #f cleanly on an empty buffer — no block, no error"122
(assert-false (ws-receive-nowait (open-conn-with (make-bytevector 0)))))124
(test "a PARTIAL frame is #f, not an error, and is left in the buffer"125
;; 'need-more must not be reported as failure: the caller's next socket126
;; read completes the frame. If this returned an error the caller would127
;; tear down a healthy connection mid-frame.128
(let* ((whole (encode-server-text-frame "PARTIAL-ME"))129
(head (bytevector-copy whole 0 3))130
(conn (open-conn-with head)))131
(assert-false (ws-receive-nowait conn))132
(assert-equal 3 (bytevector-length (ws-connection-buffer conn)))))134
(test "a NOT-YET-PROMOTED connection is #f, NOT 'closed"135
;; The regression that matters most here. `ws-connect` does not promote the136
;; state to 'open until a receive poll has run, so mirroring `ws-receive`'s137
;; "anything not connected is 'closed" reports a FRESH socket as dead. A138
;; non-blocking caller sees that window immediately and tears down a139
;; healthy connection; `ws-receive` hides it only because it blocks after.140
(let ((conn (ws-connection socket: #f state: 'connecting url: "ws://test/x"141
host: "test" buffer: (make-bytevector 0)142
fragments: '())))143
(assert-false (ws-receive-nowait conn))))145
(test "reports 'closed on a closed connection"146
(let ((conn (ws-connection socket: #f state: 'closed url: "ws://test/x"147
host: "test" buffer: (make-bytevector 0)148
fragments: '())))149
(assert-equal 'closed (ws-receive-nowait conn)))))151
(run-tests)