Auto-indexing service and GraphQL API for AT Protocol Records
at main 317 lines 8.6 kB view raw
1/// Tests for reverse join field generation 2/// 3/// Verifies that reverse join fields are discovered and added to GraphQL schemas 4import gleam/dict 5import gleam/option.{None, Some} 6import gleam/string 7import gleeunit/should 8import lexicon_graphql/schema/database as db_schema_builder 9import lexicon_graphql/types 10import swell/introspection 11import swell/schema 12import swell/sdl 13 14// Helper to create a test schema with a mock fetcher 15fn create_test_schema_from_lexicons( 16 lexicons: List(types.Lexicon), 17) -> schema.Schema { 18 // Mock fetcher that returns empty results (we're only testing schema generation) 19 let fetcher = fn(_collection, _params) { 20 Ok(#([], option.None, False, False, option.None)) 21 } 22 23 case 24 db_schema_builder.build_schema_with_fetcher( 25 lexicons, 26 fetcher, 27 option.None, 28 option.None, 29 option.None, 30 option.None, 31 option.None, 32 option.None, 33 ) 34 { 35 Ok(s) -> s 36 Error(_) -> panic as "Failed to build test schema" 37 } 38} 39 40// Test that a forward join in one collection creates a reverse join field in the target 41pub fn forward_join_creates_reverse_join_test() { 42 // Create a Post collection (target) 43 let post_lexicon = 44 types.Lexicon( 45 id: "app.bsky.feed.post", 46 defs: types.Defs( 47 main: Some( 48 types.RecordDef(type_: "record", key: None, properties: [ 49 #( 50 "text", 51 types.Property( 52 type_: "string", 53 required: True, 54 format: None, 55 ref: None, 56 refs: None, 57 items: None, 58 ), 59 ), 60 ]), 61 ), 62 others: dict.new(), 63 ), 64 ) 65 66 // Create a Like collection with a subject field that references posts 67 let like_lexicon = 68 types.Lexicon( 69 id: "app.bsky.feed.like", 70 defs: types.Defs( 71 main: Some( 72 types.RecordDef(type_: "record", key: None, properties: [ 73 #( 74 "subject", 75 types.Property( 76 type_: "string", 77 required: True, 78 format: Some("at-uri"), 79 ref: None, 80 refs: None, 81 items: None, 82 ), 83 ), 84 ]), 85 ), 86 others: dict.new(), 87 ), 88 ) 89 90 let test_schema = 91 create_test_schema_from_lexicons([post_lexicon, like_lexicon]) 92 93 // Get all types and serialize to SDL 94 let all_types = introspection.get_all_schema_types(test_schema) 95 let serialized = sdl.print_types(all_types) 96 97 // Verify that the Post type has a reverse join field for likes 98 // Field name should be: appBskyFeedLikeViaSubject (camelCase) 99 string.contains(serialized, "appBskyFeedLikeViaSubject") 100 |> should.be_true 101} 102 103// Test that strongRef fields also create reverse joins 104pub fn strong_ref_creates_reverse_join_test() { 105 // Create a Post collection (target) 106 let post_lexicon = 107 types.Lexicon( 108 id: "app.bsky.feed.post", 109 defs: types.Defs( 110 main: Some( 111 types.RecordDef(type_: "record", key: None, properties: [ 112 #( 113 "text", 114 types.Property( 115 type_: "string", 116 required: True, 117 format: None, 118 ref: None, 119 refs: None, 120 items: None, 121 ), 122 ), 123 ]), 124 ), 125 others: dict.new(), 126 ), 127 ) 128 129 // Create a Profile collection with a pinnedPost strongRef field 130 let profile_lexicon = 131 types.Lexicon( 132 id: "app.bsky.actor.profile", 133 defs: types.Defs( 134 main: Some( 135 types.RecordDef(type_: "record", key: None, properties: [ 136 #( 137 "pinnedPost", 138 types.Property( 139 type_: "ref", 140 required: False, 141 format: None, 142 ref: Some("com.atproto.repo.strongRef"), 143 refs: None, 144 items: None, 145 ), 146 ), 147 ]), 148 ), 149 others: dict.new(), 150 ), 151 ) 152 153 let test_schema = 154 create_test_schema_from_lexicons([post_lexicon, profile_lexicon]) 155 156 let all_types = introspection.get_all_schema_types(test_schema) 157 let serialized = sdl.print_types(all_types) 158 159 // Verify that the Post type has a reverse join field for pinned posts 160 // Field name should be: appBskyActorProfileViaPinnedPost (camelCase) 161 string.contains(serialized, "appBskyActorProfileViaPinnedPost") 162 |> should.be_true 163} 164 165// Test that multiple reverse joins are all generated 166pub fn multiple_reverse_joins_test() { 167 // Create a Post collection (target) 168 let post_lexicon = 169 types.Lexicon( 170 id: "app.bsky.feed.post", 171 defs: types.Defs( 172 main: Some( 173 types.RecordDef(type_: "record", key: None, properties: [ 174 #( 175 "text", 176 types.Property( 177 type_: "string", 178 required: True, 179 format: None, 180 ref: None, 181 refs: None, 182 items: None, 183 ), 184 ), 185 ]), 186 ), 187 others: dict.new(), 188 ), 189 ) 190 191 // Create a Like collection 192 let like_lexicon = 193 types.Lexicon( 194 id: "app.bsky.feed.like", 195 defs: types.Defs( 196 main: Some( 197 types.RecordDef(type_: "record", key: None, properties: [ 198 #( 199 "subject", 200 types.Property( 201 type_: "string", 202 required: True, 203 format: Some("at-uri"), 204 ref: None, 205 refs: None, 206 items: None, 207 ), 208 ), 209 ]), 210 ), 211 others: dict.new(), 212 ), 213 ) 214 215 // Create a Repost collection 216 let repost_lexicon = 217 types.Lexicon( 218 id: "app.bsky.feed.repost", 219 defs: types.Defs( 220 main: Some( 221 types.RecordDef(type_: "record", key: None, properties: [ 222 #( 223 "subject", 224 types.Property( 225 type_: "string", 226 required: True, 227 format: Some("at-uri"), 228 ref: None, 229 refs: None, 230 items: None, 231 ), 232 ), 233 ]), 234 ), 235 others: dict.new(), 236 ), 237 ) 238 239 let test_schema = 240 create_test_schema_from_lexicons([ 241 post_lexicon, 242 like_lexicon, 243 repost_lexicon, 244 ]) 245 246 let all_types = introspection.get_all_schema_types(test_schema) 247 let serialized = sdl.print_types(all_types) 248 249 // Check both reverse join fields exist on Post type (camelCase) 250 string.contains(serialized, "appBskyFeedLikeViaSubject") 251 |> should.be_true 252 253 string.contains(serialized, "appBskyFeedRepostViaSubject") 254 |> should.be_true 255} 256 257// Test that collections without forward join fields don't appear in reverse joins 258pub fn no_false_positive_reverse_joins_test() { 259 // Create a Post collection 260 let post_lexicon = 261 types.Lexicon( 262 id: "app.bsky.feed.post", 263 defs: types.Defs( 264 main: Some( 265 types.RecordDef(type_: "record", key: None, properties: [ 266 #( 267 "text", 268 types.Property( 269 type_: "string", 270 required: True, 271 format: None, 272 ref: None, 273 refs: None, 274 items: None, 275 ), 276 ), 277 ]), 278 ), 279 others: dict.new(), 280 ), 281 ) 282 283 // Create a Status collection with no join fields 284 let status_lexicon = 285 types.Lexicon( 286 id: "xyz.statusosphere.status", 287 defs: types.Defs( 288 main: Some( 289 types.RecordDef(type_: "record", key: None, properties: [ 290 #( 291 "message", 292 types.Property( 293 type_: "string", 294 required: True, 295 format: None, 296 ref: None, 297 refs: None, 298 items: None, 299 ), 300 ), 301 ]), 302 ), 303 others: dict.new(), 304 ), 305 ) 306 307 let test_schema = 308 create_test_schema_from_lexicons([post_lexicon, status_lexicon]) 309 310 let all_types = introspection.get_all_schema_types(test_schema) 311 let serialized = sdl.print_types(all_types) 312 313 // Status collection has no forward joins, so Post should not have a reverse join field 314 // that references Status. We check that the field name XyzStatusosphereStatusVia* doesn't appear 315 string.contains(serialized, "XyzStatusosphereStatusVia") 316 |> should.be_false 317}