A Gleam WebSocket consumer for AT Protocol Jetstream events.
1import gleam/option
2import gleeunit
3import goose
4
5pub fn main() -> Nil {
6 gleeunit.main()
7}
8
9// Test build_url with default config (no filters)
10pub fn build_url_default_test() {
11 let config = goose.default_config()
12 let url = goose.build_url(config)
13
14 assert url
15 == "wss://jetstream2.us-east.bsky.network/subscribe?compress=false&requireHello=false"
16}
17
18// Test build_url with wanted collections
19pub fn build_url_with_collections_test() {
20 let config =
21 goose.JetstreamConfig(
22 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
23 wanted_collections: ["app.bsky.feed.post", "app.bsky.feed.like"],
24 wanted_dids: [],
25 cursor: option.None,
26 max_message_size_bytes: option.None,
27 compress: False,
28 require_hello: False,
29 )
30 let url = goose.build_url(config)
31
32 assert url
33 == "wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.like&wantedCollections=app.bsky.feed.post&compress=false&requireHello=false"
34}
35
36// Test build_url with wanted DIDs
37pub fn build_url_with_dids_test() {
38 let config =
39 goose.JetstreamConfig(
40 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
41 wanted_collections: [],
42 wanted_dids: ["did:plc:example123", "did:plc:example456"],
43 cursor: option.None,
44 max_message_size_bytes: option.None,
45 compress: False,
46 require_hello: False,
47 )
48 let url = goose.build_url(config)
49
50 assert url
51 == "wss://jetstream2.us-east.bsky.network/subscribe?wantedDids=did:plc:example456&wantedDids=did:plc:example123&compress=false&requireHello=false"
52}
53
54// Test build_url with both collections and DIDs
55pub fn build_url_with_both_test() {
56 let config =
57 goose.JetstreamConfig(
58 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
59 wanted_collections: ["app.bsky.feed.post"],
60 wanted_dids: ["did:plc:example123"],
61 cursor: option.None,
62 max_message_size_bytes: option.None,
63 compress: False,
64 require_hello: False,
65 )
66 let url = goose.build_url(config)
67
68 assert url
69 == "wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post&wantedDids=did:plc:example123&compress=false&requireHello=false"
70}
71
72// Test build_url with cursor
73pub fn build_url_with_cursor_test() {
74 let config =
75 goose.JetstreamConfig(
76 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
77 wanted_collections: [],
78 wanted_dids: [],
79 cursor: option.Some(1_234_567_890_123_456),
80 max_message_size_bytes: option.None,
81 compress: False,
82 require_hello: False,
83 )
84 let url = goose.build_url(config)
85
86 assert url
87 == "wss://jetstream2.us-east.bsky.network/subscribe?cursor=1234567890123456&compress=false&requireHello=false"
88}
89
90// Test build_url with max_message_size_bytes
91pub fn build_url_with_max_size_test() {
92 let config =
93 goose.JetstreamConfig(
94 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
95 wanted_collections: [],
96 wanted_dids: [],
97 cursor: option.None,
98 max_message_size_bytes: option.Some(1_048_576),
99 compress: False,
100 require_hello: False,
101 )
102 let url = goose.build_url(config)
103
104 assert url
105 == "wss://jetstream2.us-east.bsky.network/subscribe?maxMessageSizeBytes=1048576&compress=false&requireHello=false"
106}
107
108// Test build_url with compress enabled
109pub fn build_url_with_compress_test() {
110 let config =
111 goose.JetstreamConfig(
112 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
113 wanted_collections: [],
114 wanted_dids: [],
115 cursor: option.None,
116 max_message_size_bytes: option.None,
117 compress: True,
118 require_hello: False,
119 )
120 let url = goose.build_url(config)
121
122 assert url
123 == "wss://jetstream2.us-east.bsky.network/subscribe?compress=true&requireHello=false"
124}
125
126// Test build_url with require_hello enabled
127pub fn build_url_with_require_hello_test() {
128 let config =
129 goose.JetstreamConfig(
130 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
131 wanted_collections: [],
132 wanted_dids: [],
133 cursor: option.None,
134 max_message_size_bytes: option.None,
135 compress: False,
136 require_hello: True,
137 )
138 let url = goose.build_url(config)
139
140 assert url
141 == "wss://jetstream2.us-east.bsky.network/subscribe?compress=false&requireHello=true"
142}
143
144// Test build_url with all options combined
145pub fn build_url_with_all_options_test() {
146 let config =
147 goose.JetstreamConfig(
148 endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
149 wanted_collections: ["app.bsky.feed.post"],
150 wanted_dids: ["did:plc:example123"],
151 cursor: option.Some(9_876_543_210),
152 max_message_size_bytes: option.Some(2_097_152),
153 compress: True,
154 require_hello: True,
155 )
156 let url = goose.build_url(config)
157
158 assert url
159 == "wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post&wantedDids=did:plc:example123&cursor=9876543210&maxMessageSizeBytes=2097152&compress=true&requireHello=true"
160}
161
162// Test parsing a commit event (create operation with record)
163pub fn parse_commit_event_create_test() {
164 let json =
165 "{\"did\":\"did:plc:test123\",\"time_us\":1234567890,\"commit\":{\"rev\":\"abc123\",\"operation\":\"create\",\"collection\":\"app.bsky.feed.post\",\"rkey\":\"post123\",\"record\":{\"text\":\"Hello world\"},\"cid\":\"cid123\"}}"
166
167 let event = goose.parse_event(json)
168
169 case event {
170 goose.CommitEvent(did, time_us, commit) -> {
171 assert did == "did:plc:test123"
172 assert time_us == 1_234_567_890
173 assert commit.rev == "abc123"
174 assert commit.operation == "create"
175 assert commit.collection == "app.bsky.feed.post"
176 assert commit.rkey == "post123"
177 }
178 _ -> panic as "Expected CommitEvent"
179 }
180}
181
182// Test parsing a commit event (delete operation without record)
183pub fn parse_commit_event_delete_test() {
184 let json =
185 "{\"did\":\"did:plc:test456\",\"time_us\":9876543210,\"commit\":{\"rev\":\"xyz789\",\"operation\":\"delete\",\"collection\":\"app.bsky.feed.like\",\"rkey\":\"like456\"}}"
186
187 let event = goose.parse_event(json)
188
189 case event {
190 goose.CommitEvent(did, time_us, commit) -> {
191 assert did == "did:plc:test456"
192 assert time_us == 9_876_543_210
193 assert commit.rev == "xyz789"
194 assert commit.operation == "delete"
195 assert commit.collection == "app.bsky.feed.like"
196 assert commit.rkey == "like456"
197 }
198 _ -> panic as "Expected CommitEvent"
199 }
200}
201
202// Test parsing an identity event
203pub fn parse_identity_event_test() {
204 let json =
205 "{\"did\":\"did:plc:identity123\",\"time_us\":1111111111,\"identity\":{\"did\":\"did:plc:identity123\",\"handle\":\"alice.bsky.social\",\"seq\":42,\"time\":\"2024-01-01T00:00:00Z\"}}"
206
207 let event = goose.parse_event(json)
208
209 case event {
210 goose.IdentityEvent(did, time_us, identity) -> {
211 assert did == "did:plc:identity123"
212 assert time_us == 1_111_111_111
213 assert identity.did == "did:plc:identity123"
214 assert identity.handle == "alice.bsky.social"
215 assert identity.seq == 42
216 assert identity.time == "2024-01-01T00:00:00Z"
217 }
218 _ -> panic as "Expected IdentityEvent"
219 }
220}
221
222// Test parsing an account event
223pub fn parse_account_event_test() {
224 let json =
225 "{\"did\":\"did:plc:account789\",\"time_us\":2222222222,\"account\":{\"active\":true,\"did\":\"did:plc:account789\",\"seq\":99,\"time\":\"2024-01-02T00:00:00Z\"}}"
226
227 let event = goose.parse_event(json)
228
229 case event {
230 goose.AccountEvent(did, time_us, account) -> {
231 assert did == "did:plc:account789"
232 assert time_us == 2_222_222_222
233 assert account.active == True
234 assert account.did == "did:plc:account789"
235 assert account.seq == 99
236 assert account.time == "2024-01-02T00:00:00Z"
237 }
238 _ -> panic as "Expected AccountEvent"
239 }
240}
241
242// Test parsing unknown/invalid JSON
243pub fn parse_unknown_event_test() {
244 let json = "{\"unknown\":\"event\",\"type\":\"something\"}"
245
246 let event = goose.parse_event(json)
247
248 case event {
249 goose.UnknownEvent(raw) -> {
250 assert raw == json
251 }
252 _ -> panic as "Expected UnknownEvent"
253 }
254}