Auto-indexing service and GraphQL API for AT Protocol Records quickslice.slices.network/
atproto gleam graphql
at main 408 lines 13 kB view raw
1/// Integration test for sorting enum validation 2/// 3/// Verifies that each Connection field uses the correct collection-specific 4/// SortFieldInput type with the appropriate enum, fixing the bug where all 5/// fields were sharing a single global enum. 6import gleam/dict 7import gleam/list 8import gleam/option 9import gleam/result 10import gleeunit/should 11import lexicon_graphql 12import lexicon_graphql/query/dataloader 13import lexicon_graphql/schema/database 14import lexicon_graphql/types 15import swell/executor 16import swell/schema 17import swell/value 18 19pub fn sorting_enum_input_types_are_unique_per_collection_test() { 20 // Test: Each collection should have its own SortFieldInput type 21 // Using introspection query to verify SocialGrainGalleryItemSortFieldInput exists 22 23 let lexicons = load_social_grain_lexicons() 24 25 // Create a stub fetcher that won't actually be called 26 let stub_fetcher = fn(_uri: String, _params: dataloader.PaginationParams) -> Result( 27 #( 28 List(#(value.Value, String)), 29 option.Option(String), 30 Bool, 31 Bool, 32 option.Option(Int), 33 ), 34 String, 35 ) { 36 Error("Not implemented for test") 37 } 38 39 let assert Ok(graphql_schema) = 40 database.build_schema_with_fetcher( 41 lexicons, 42 stub_fetcher, 43 option.None, 44 option.None, 45 option.None, 46 option.None, 47 option.None, 48 option.None, 49 ) 50 51 // Introspection query to check if SocialGrainGalleryItemSortFieldInput exists 52 let query = 53 " 54 { 55 __type(name: \"SocialGrainGalleryItemSortFieldInput\") { 56 name 57 kind 58 inputFields { 59 name 60 type { 61 name 62 kind 63 ofType { 64 name 65 kind 66 } 67 } 68 } 69 } 70 } 71 " 72 73 let ctx = schema.context(option.None) 74 let result = executor.execute(query, graphql_schema, ctx) 75 76 // Should successfully execute 77 result 78 |> should.be_ok() 79 80 // Verify the type exists and has the correct structure 81 case result { 82 Ok(executor.Response(data, errors)) -> { 83 // Should have no errors 84 errors 85 |> should.equal([]) 86 87 case data { 88 value.Object(fields) -> { 89 case list.key_find(fields, "__type") { 90 Ok(value.Object(type_fields)) -> { 91 // Verify type name 92 case list.key_find(type_fields, "name") { 93 Ok(value.String(name)) -> { 94 name 95 |> should.equal("SocialGrainGalleryItemSortFieldInput") 96 } 97 _ -> should.fail() 98 } 99 100 // Verify it's an INPUT_OBJECT 101 case list.key_find(type_fields, "kind") { 102 Ok(value.String(kind)) -> { 103 kind 104 |> should.equal("INPUT_OBJECT") 105 } 106 _ -> should.fail() 107 } 108 109 // Verify it has a "field" input field that uses the correct enum 110 case list.key_find(type_fields, "inputFields") { 111 Ok(value.List(input_fields)) -> { 112 // Find the "field" input field 113 let field_input = 114 list.find(input_fields, fn(f) { 115 case f { 116 value.Object(field_data) -> { 117 case list.key_find(field_data, "name") { 118 Ok(value.String("field")) -> True 119 _ -> False 120 } 121 } 122 _ -> False 123 } 124 }) 125 126 case field_input { 127 Ok(value.Object(field_data)) -> { 128 // Check the type is SocialGrainGalleryItemSortField (wrapped in NON_NULL) 129 case list.key_find(field_data, "type") { 130 Ok(value.Object(type_data)) -> { 131 case list.key_find(type_data, "ofType") { 132 Ok(value.Object(inner_type)) -> { 133 case list.key_find(inner_type, "name") { 134 Ok(value.String(enum_name)) -> { 135 enum_name 136 |> should.equal( 137 "SocialGrainGalleryItemSortField", 138 ) 139 } 140 _ -> should.fail() 141 } 142 } 143 _ -> should.fail() 144 } 145 } 146 _ -> should.fail() 147 } 148 } 149 _ -> should.fail() 150 } 151 } 152 _ -> should.fail() 153 } 154 } 155 _ -> should.fail() 156 } 157 } 158 _ -> should.fail() 159 } 160 } 161 _ -> should.fail() 162 } 163} 164 165pub fn did_join_uses_correct_sort_enum_test() { 166 // Test: DID join fields should use the SOURCE collection's sort enum 167 // Query SocialGrainGallery.socialGrainGalleryItemByDid's sortBy argument 168 169 let lexicons = load_social_grain_lexicons() 170 171 // Create a stub fetcher that won't actually be called 172 let stub_fetcher = fn(_uri: String, _params: dataloader.PaginationParams) -> Result( 173 #( 174 List(#(value.Value, String)), 175 option.Option(String), 176 Bool, 177 Bool, 178 option.Option(Int), 179 ), 180 String, 181 ) { 182 Error("Not implemented for test") 183 } 184 185 let assert Ok(graphql_schema) = 186 database.build_schema_with_fetcher( 187 lexicons, 188 stub_fetcher, 189 option.None, 190 option.None, 191 option.None, 192 option.None, 193 option.None, 194 option.None, 195 ) 196 197 // Introspection query to check socialGrainGalleryItemByDid's sortBy argument 198 let query = 199 " 200 { 201 __type(name: \"SocialGrainGallery\") { 202 fields { 203 name 204 args { 205 name 206 type { 207 kind 208 ofType { 209 kind 210 ofType { 211 name 212 } 213 } 214 } 215 } 216 } 217 } 218 } 219 " 220 221 let ctx = schema.context(option.None) 222 let result = executor.execute(query, graphql_schema, ctx) 223 224 result 225 |> should.be_ok() 226 227 // Find the socialGrainGalleryItemViaGallery field and verify its sortBy arg 228 case result { 229 Ok(executor.Response(data, errors)) -> { 230 errors 231 |> should.equal([]) 232 233 case data { 234 value.Object(response_fields) -> { 235 case list.key_find(response_fields, "__type") { 236 Ok(value.Object(type_fields)) -> { 237 case list.key_find(type_fields, "fields") { 238 Ok(value.List(fields)) -> { 239 // Find socialGrainGalleryItemByDid field 240 let did_join_field = 241 list.find(fields, fn(field) { 242 case field { 243 value.Object(field_data) -> { 244 case list.key_find(field_data, "name") { 245 Ok(value.String("socialGrainGalleryItemByDid")) -> 246 True 247 _ -> False 248 } 249 } 250 _ -> False 251 } 252 }) 253 254 case did_join_field { 255 Ok(value.Object(field_data)) -> { 256 case list.key_find(field_data, "args") { 257 Ok(value.List(args)) -> { 258 // Find sortBy argument 259 let sortby_arg = 260 list.find(args, fn(arg) { 261 case arg { 262 value.Object(arg_data) -> { 263 case list.key_find(arg_data, "name") { 264 Ok(value.String("sortBy")) -> True 265 _ -> False 266 } 267 } 268 _ -> False 269 } 270 }) 271 272 case sortby_arg { 273 Ok(value.Object(arg_data)) -> { 274 // Get the input type name: [SocialGrainGalleryItemSortFieldInput!] 275 case list.key_find(arg_data, "type") { 276 Ok(value.Object(type_data)) -> { 277 case list.key_find(type_data, "ofType") { 278 Ok(value.Object(non_null_data)) -> { 279 case 280 list.key_find(non_null_data, "ofType") 281 { 282 Ok(value.Object(input_type_data)) -> { 283 case 284 list.key_find( 285 input_type_data, 286 "name", 287 ) 288 { 289 Ok(value.String(input_type_name)) -> { 290 // Should use GalleryItem's input type, NOT Gallery's or Favorite's 291 input_type_name 292 |> should.equal( 293 "SocialGrainGalleryItemSortFieldInput", 294 ) 295 } 296 _ -> should.fail() 297 } 298 } 299 _ -> should.fail() 300 } 301 } 302 _ -> should.fail() 303 } 304 } 305 _ -> should.fail() 306 } 307 } 308 _ -> should.fail() 309 } 310 } 311 _ -> should.fail() 312 } 313 } 314 _ -> should.fail() 315 } 316 } 317 _ -> should.fail() 318 } 319 } 320 _ -> should.fail() 321 } 322 } 323 _ -> should.fail() 324 } 325 } 326 _ -> should.fail() 327 } 328} 329 330// Helper to load social.grain lexicons for testing 331fn load_social_grain_lexicons() -> List(types.Lexicon) { 332 let gallery_json = 333 "{ 334 \"lexicon\": 1, 335 \"id\": \"social.grain.gallery\", 336 \"defs\": { 337 \"main\": { 338 \"type\": \"record\", 339 \"key\": \"tid\", 340 \"record\": { 341 \"type\": \"object\", 342 \"required\": [\"title\"], 343 \"properties\": { 344 \"title\": {\"type\": \"string\"}, 345 \"description\": {\"type\": \"string\"} 346 } 347 } 348 } 349 } 350 }" 351 352 let gallery_item_json = 353 "{ 354 \"lexicon\": 1, 355 \"id\": \"social.grain.gallery.item\", 356 \"defs\": { 357 \"main\": { 358 \"type\": \"record\", 359 \"key\": \"tid\", 360 \"record\": { 361 \"type\": \"object\", 362 \"required\": [\"gallery\", \"item\", \"position\"], 363 \"properties\": { 364 \"gallery\": {\"type\": \"string\"}, 365 \"item\": {\"type\": \"string\"}, 366 \"position\": {\"type\": \"integer\"} 367 } 368 } 369 } 370 } 371 }" 372 373 let favorite_json = 374 "{ 375 \"lexicon\": 1, 376 \"id\": \"social.grain.favorite\", 377 \"defs\": { 378 \"main\": { 379 \"type\": \"record\", 380 \"key\": \"tid\", 381 \"record\": { 382 \"type\": \"object\", 383 \"required\": [\"subject\"], 384 \"properties\": { 385 \"subject\": {\"type\": \"string\"}, 386 \"createdAt\": {\"type\": \"string\"} 387 } 388 } 389 } 390 } 391 }" 392 393 [ 394 lexicon_graphql.parse_lexicon(gallery_json) 395 |> result.unwrap(empty_lexicon()), 396 lexicon_graphql.parse_lexicon(gallery_item_json) 397 |> result.unwrap(empty_lexicon()), 398 lexicon_graphql.parse_lexicon(favorite_json) 399 |> result.unwrap(empty_lexicon()), 400 ] 401} 402 403fn empty_lexicon() -> types.Lexicon { 404 types.Lexicon( 405 id: "empty", 406 defs: types.Defs(main: option.None, others: dict.new()), 407 ) 408}