Auto-indexing service and GraphQL API for AT Protocol Records quickslice.slices.network/
atproto gleam graphql
at main 583 lines 16 kB view raw
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}