Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1/// Snapshot tests for sortBy schema generation
2///
3/// Tests verify that the GraphQL schema is generated correctly with:
4/// - Custom SortFieldEnum for each record type
5/// - SortFieldInput InputObject type
6/// - sortBy argument on connection fields
7/// - Pagination arguments (first, after, last, before)
8///
9/// Uses birdie to capture and verify the generated schemas
10import birdie
11import gleam/dict
12import gleam/list
13import gleam/option.{None, Some}
14import gleeunit/should
15import lexicon_graphql/schema/builder as schema_builder
16import lexicon_graphql/schema/database as db_schema_builder
17import lexicon_graphql/types
18import swell/introspection
19import swell/schema
20import swell/sdl
21
22// Helper to create a test schema with a mock fetcher
23fn create_test_schema_from_lexicons(
24 lexicons: List(schema_builder.Lexicon),
25) -> schema.Schema {
26 // Mock fetcher that returns empty results (we're only testing schema generation)
27 let fetcher = fn(_collection, _params) {
28 Ok(#([], option.None, False, False, option.None))
29 }
30
31 // Mock aggregate fetcher for aggregation queries
32 let aggregate_fetcher = fn(
33 _collection: String,
34 _params: db_schema_builder.AggregateParams,
35 ) {
36 Ok([])
37 }
38
39 case
40 db_schema_builder.build_schema_with_subscriptions(
41 lexicons,
42 fetcher,
43 option.None,
44 option.None,
45 option.None,
46 option.None,
47 option.None,
48 option.None,
49 option.Some(aggregate_fetcher),
50 option.None,
51 option.None,
52 option.None,
53 option.None,
54 option.None,
55 option.None,
56 )
57 {
58 Ok(s) -> s
59 Error(_) -> panic as "Failed to build test schema"
60 }
61}
62
63// Test: Single lexicon creates connection field with sortBy
64pub fn single_lexicon_with_sorting_snapshot_test() {
65 let lexicon =
66 types.Lexicon(
67 "xyz.statusphere.status",
68 types.Defs(
69 main: Some(
70 types.RecordDef(type_: "record", key: None, properties: [
71 #(
72 "status",
73 types.Property(
74 type_: "string",
75 required: False,
76 format: None,
77 ref: None,
78 refs: None,
79 items: None,
80 ),
81 ),
82 #(
83 "createdAt",
84 types.Property(
85 type_: "string",
86 required: False,
87 format: None,
88 ref: None,
89 refs: None,
90 items: None,
91 ),
92 ),
93 ]),
94 ),
95 others: dict.new(),
96 ),
97 )
98
99 let test_schema = create_test_schema_from_lexicons([lexicon])
100 let query_type = schema.query_type(test_schema)
101
102 let serialized = sdl.print_type(query_type)
103
104 birdie.snap(
105 title: "Query type with connection field and sortBy argument",
106 content: serialized,
107 )
108}
109
110// Test: Multiple lexicons create distinct fields with separate sort enums
111pub fn multiple_lexicons_with_distinct_sort_enums_snapshot_test() {
112 let lexicon1 =
113 types.Lexicon(
114 "xyz.statusphere.status",
115 types.Defs(
116 main: Some(
117 types.RecordDef(type_: "record", key: None, properties: [
118 #(
119 "status",
120 types.Property(
121 type_: "string",
122 required: False,
123 format: None,
124 ref: None,
125 refs: None,
126 items: None,
127 ),
128 ),
129 #(
130 "createdAt",
131 types.Property(
132 type_: "string",
133 required: False,
134 format: None,
135 ref: None,
136 refs: None,
137 items: None,
138 ),
139 ),
140 ]),
141 ),
142 others: dict.new(),
143 ),
144 )
145
146 let lexicon2 =
147 types.Lexicon(
148 "app.bsky.feed.post",
149 types.Defs(
150 main: Some(
151 types.RecordDef(type_: "record", key: None, properties: [
152 #(
153 "text",
154 types.Property(
155 type_: "string",
156 required: False,
157 format: None,
158 ref: None,
159 refs: None,
160 items: None,
161 ),
162 ),
163 #(
164 "likeCount",
165 types.Property(
166 type_: "integer",
167 required: False,
168 format: None,
169 ref: None,
170 refs: None,
171 items: None,
172 ),
173 ),
174 ]),
175 ),
176 others: dict.new(),
177 ),
178 )
179
180 let test_schema = create_test_schema_from_lexicons([lexicon1, lexicon2])
181 let query_type = schema.query_type(test_schema)
182
183 let serialized = sdl.print_type(query_type)
184
185 birdie.snap(
186 title: "Query type with multiple connection fields and distinct sort enums",
187 content: serialized,
188 )
189}
190
191// Unit test: Verify sortBy argument is a list type
192pub fn sortby_argument_is_list_type_test() {
193 let lexicon =
194 types.Lexicon(
195 "xyz.statusphere.status",
196 types.Defs(
197 main: Some(
198 types.RecordDef(type_: "record", key: None, properties: [
199 #(
200 "status",
201 types.Property(
202 type_: "string",
203 required: False,
204 format: None,
205 ref: None,
206 refs: None,
207 items: None,
208 ),
209 ),
210 ]),
211 ),
212 others: dict.new(),
213 ),
214 )
215
216 let test_schema = create_test_schema_from_lexicons([lexicon])
217 let query_type = schema.query_type(test_schema)
218
219 case schema.get_field(query_type, "xyzStatusphereStatus") {
220 Some(field) -> {
221 let args = schema.field_arguments(field)
222 let sortby_arg =
223 list.find(args, fn(arg) { schema.argument_name(arg) == "sortBy" })
224
225 case sortby_arg {
226 Ok(arg) -> {
227 let arg_type = schema.argument_type(arg)
228 should.be_true(schema.is_list(arg_type))
229 }
230 Error(_) -> should.fail()
231 }
232 }
233 option.None -> should.fail()
234 }
235}
236
237// Unit test: Verify connection has all pagination arguments
238pub fn connection_has_all_pagination_arguments_test() {
239 let lexicon =
240 types.Lexicon(
241 "xyz.statusphere.status",
242 types.Defs(
243 main: Some(
244 types.RecordDef(type_: "record", key: None, properties: [
245 #(
246 "status",
247 types.Property(
248 type_: "string",
249 required: False,
250 format: None,
251 ref: None,
252 refs: None,
253 items: None,
254 ),
255 ),
256 ]),
257 ),
258 others: dict.new(),
259 ),
260 )
261
262 let test_schema = create_test_schema_from_lexicons([lexicon])
263 let query_type = schema.query_type(test_schema)
264
265 case schema.get_field(query_type, "xyzStatusphereStatus") {
266 Some(field) -> {
267 let args = schema.field_arguments(field)
268 let arg_names = list.map(args, schema.argument_name)
269
270 // Verify we have all pagination arguments
271 should.be_true(list.contains(arg_names, "first"))
272 should.be_true(list.contains(arg_names, "after"))
273 should.be_true(list.contains(arg_names, "last"))
274 should.be_true(list.contains(arg_names, "before"))
275 should.be_true(list.contains(arg_names, "sortBy"))
276 }
277 option.None -> should.fail()
278 }
279}
280
281// Comprehensive test showing ALL generated types for db_schema_builder
282pub fn db_schema_all_types_snapshot_test() {
283 let lexicon =
284 types.Lexicon(
285 "xyz.statusphere.status",
286 types.Defs(
287 main: Some(
288 types.RecordDef(type_: "record", key: None, properties: [
289 #(
290 "text",
291 types.Property(
292 type_: "string",
293 required: False,
294 format: None,
295 ref: None,
296 refs: None,
297 items: None,
298 ),
299 ),
300 #(
301 "createdAt",
302 types.Property(
303 type_: "string",
304 required: False,
305 format: None,
306 ref: None,
307 refs: None,
308 items: None,
309 ),
310 ),
311 ]),
312 ),
313 others: dict.new(),
314 ),
315 )
316
317 let test_schema = create_test_schema_from_lexicons([lexicon])
318
319 // Use introspection to get ALL types in the schema
320 let all_types = introspection.get_all_schema_types(test_schema)
321 let serialized = sdl.print_types(all_types)
322
323 birdie.snap(
324 title: "All types generated by db_schema_builder including Connection, Edge, PageInfo, SortField enum, WhereInput, etc.",
325 content: serialized,
326 )
327}
328
329// Test: Sort enum only includes primitive types (string, integer, boolean, number)
330pub fn sort_enum_excludes_blob_and_ref_types_test() {
331 let lexicon =
332 types.Lexicon(
333 "app.bsky.test.record",
334 types.Defs(
335 main: Some(
336 types.RecordDef(type_: "record", key: None, properties: [
337 #(
338 "stringField",
339 types.Property(
340 type_: "string",
341 required: False,
342 format: None,
343 ref: None,
344 refs: None,
345 items: None,
346 ),
347 ),
348 #(
349 "intField",
350 types.Property(
351 type_: "integer",
352 required: False,
353 format: None,
354 ref: None,
355 refs: None,
356 items: None,
357 ),
358 ),
359 #(
360 "boolField",
361 types.Property(
362 type_: "boolean",
363 required: False,
364 format: None,
365 ref: None,
366 refs: None,
367 items: None,
368 ),
369 ),
370 #(
371 "numberField",
372 types.Property(
373 type_: "number",
374 required: False,
375 format: None,
376 ref: None,
377 refs: None,
378 items: None,
379 ),
380 ),
381 #(
382 "uriField",
383 types.Property(
384 type_: "string",
385 required: False,
386 format: Some("at-uri"),
387 ref: None,
388 refs: None,
389 items: None,
390 ),
391 ),
392 // Non-sortable types that should be excluded
393 #(
394 "blobField",
395 types.Property(
396 type_: "blob",
397 required: False,
398 format: None,
399 ref: None,
400 refs: None,
401 items: None,
402 ),
403 ),
404 #(
405 "refField",
406 types.Property(
407 type_: "ref",
408 required: False,
409 format: None,
410 ref: Some("app.bsky.test.object"),
411 refs: None,
412 items: None,
413 ),
414 ),
415 ]),
416 ),
417 others: dict.new(),
418 ),
419 )
420
421 let test_schema = create_test_schema_from_lexicons([lexicon])
422 let all_types = introspection.get_all_schema_types(test_schema)
423
424 // Find the SortField enum
425 let sort_enum =
426 list.find(all_types, fn(t) {
427 schema.type_name(t) == "AppBskyTestRecordSortField"
428 })
429
430 case sort_enum {
431 Ok(enum_type) -> {
432 let enum_values = schema.get_enum_values(enum_type)
433 let value_names = list.map(enum_values, schema.enum_value_name)
434
435 // Should include primitive fields
436 should.be_true(list.contains(value_names, "stringField"))
437 should.be_true(list.contains(value_names, "intField"))
438 should.be_true(list.contains(value_names, "boolField"))
439 should.be_true(list.contains(value_names, "numberField"))
440 should.be_true(list.contains(value_names, "uriField"))
441
442 // Should include standard fields
443 should.be_true(list.contains(value_names, "uri"))
444 should.be_true(list.contains(value_names, "cid"))
445 should.be_true(list.contains(value_names, "did"))
446 should.be_true(list.contains(value_names, "collection"))
447 should.be_true(list.contains(value_names, "indexedAt"))
448
449 // Should NOT include blob or ref fields
450 should.be_false(list.contains(value_names, "blobField"))
451 should.be_false(list.contains(value_names, "refField"))
452
453 // Should NOT include actorHandle (it's a computed field, not sortable)
454 should.be_false(list.contains(value_names, "actorHandle"))
455 }
456 Error(_) -> should.fail()
457 }
458}
459
460// Snapshot test: Sort enum with mixed field types
461pub fn sort_enum_with_mixed_field_types_snapshot_test() {
462 let lexicon =
463 types.Lexicon(
464 "app.bsky.test.record",
465 types.Defs(
466 main: Some(
467 types.RecordDef(type_: "record", key: None, properties: [
468 // Sortable primitive types
469 #(
470 "stringField",
471 types.Property(
472 type_: "string",
473 required: False,
474 format: None,
475 ref: None,
476 refs: None,
477 items: None,
478 ),
479 ),
480 #(
481 "intField",
482 types.Property(
483 type_: "integer",
484 required: False,
485 format: None,
486 ref: None,
487 refs: None,
488 items: None,
489 ),
490 ),
491 #(
492 "boolField",
493 types.Property(
494 type_: "boolean",
495 required: False,
496 format: None,
497 ref: None,
498 refs: None,
499 items: None,
500 ),
501 ),
502 #(
503 "numberField",
504 types.Property(
505 type_: "number",
506 required: False,
507 format: None,
508 ref: None,
509 refs: None,
510 items: None,
511 ),
512 ),
513 #(
514 "datetimeField",
515 types.Property(
516 type_: "string",
517 required: False,
518 format: Some("datetime"),
519 ref: None,
520 refs: None,
521 items: None,
522 ),
523 ),
524 #(
525 "uriField",
526 types.Property(
527 type_: "string",
528 required: False,
529 format: Some("at-uri"),
530 ref: None,
531 refs: None,
532 items: None,
533 ),
534 ),
535 // Non-sortable types
536 #(
537 "blobField",
538 types.Property(
539 type_: "blob",
540 required: False,
541 format: None,
542 ref: None,
543 refs: None,
544 items: None,
545 ),
546 ),
547 #(
548 "refField",
549 types.Property(
550 type_: "ref",
551 required: False,
552 format: None,
553 ref: Some("com.atproto.repo.strongRef"),
554 refs: None,
555 items: None,
556 ),
557 ),
558 ]),
559 ),
560 others: dict.new(),
561 ),
562 )
563
564 let test_schema = create_test_schema_from_lexicons([lexicon])
565 let all_types = introspection.get_all_schema_types(test_schema)
566
567 // Find and print the SortField enum
568 let sort_enum =
569 list.find(all_types, fn(t) {
570 schema.type_name(t) == "AppBskyTestRecordSortField"
571 })
572
573 case sort_enum {
574 Ok(enum_type) -> {
575 let serialized = sdl.print_type(enum_type)
576 birdie.snap(
577 title: "SortField enum with mixed types - only includes primitives",
578 content: serialized,
579 )
580 }
581 Error(_) -> should.fail()
582 }
583}