Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1/// Tests for nested forward join resolution in object_builder
2///
3/// Verifies that object types containing strongRef fields get *Resolved fields
4/// when batch_fetcher and generic_record_type are provided
5import gleam/dict
6import gleam/list
7import gleam/option.{None, Some}
8import gleeunit/should
9import lexicon_graphql/internal/graphql/object_builder
10import lexicon_graphql/internal/lexicon/registry
11import lexicon_graphql/types
12import swell/schema
13
14/// Test that nested strongRef fields get *Resolved fields when batch_fetcher is provided
15pub fn nested_strongref_gets_resolved_field_test() {
16 // Create a lexicon with an object type containing strongRef fields
17 let lexicon =
18 types.Lexicon(
19 id: "app.bsky.feed.post",
20 defs: types.Defs(
21 main: Some(
22 types.RecordDef(type_: "record", key: Some("tid"), properties: [
23 #(
24 "text",
25 types.Property(
26 type_: "string",
27 required: True,
28 format: None,
29 ref: None,
30 refs: None,
31 items: None,
32 ),
33 ),
34 ]),
35 ),
36 others: dict.from_list([
37 #(
38 "replyRef",
39 types.Object(
40 types.ObjectDef(
41 type_: "object",
42 required_fields: ["parent", "root"],
43 properties: [
44 #(
45 "parent",
46 types.Property(
47 type_: "ref",
48 required: True,
49 format: None,
50 ref: Some("com.atproto.repo.strongRef"),
51 refs: None,
52 items: None,
53 ),
54 ),
55 #(
56 "root",
57 types.Property(
58 type_: "ref",
59 required: True,
60 format: None,
61 ref: Some("com.atproto.repo.strongRef"),
62 refs: None,
63 items: None,
64 ),
65 ),
66 ],
67 ),
68 ),
69 ),
70 ]),
71 ),
72 )
73
74 // Build a registry from the lexicon
75 let reg = registry.from_lexicons([lexicon])
76
77 // Create a mock Record union type (the generic type for *Resolved fields)
78 let mock_record_type = schema.object_type("Record", "Mock record union", [])
79
80 // Create a mock batch fetcher that returns empty results
81 let mock_batch_fetcher = fn(_uris, _collection, _field) { Ok(dict.new()) }
82
83 // Build object types WITH batch_fetcher and generic_record_type
84 let object_types =
85 object_builder.build_all_object_types(
86 reg,
87 Some(mock_batch_fetcher),
88 Some(mock_record_type),
89 )
90
91 // The replyRef object type should exist
92 let assert Ok(reply_ref_type) =
93 dict.get(object_types, "app.bsky.feed.post#replyRef")
94
95 // Get the fields from the type
96 let fields = schema.get_fields(reply_ref_type)
97 let field_names = list.map(fields, schema.field_name)
98
99 // Should have parentResolved and rootResolved fields
100 list.contains(field_names, "parentResolved")
101 |> should.be_true
102
103 list.contains(field_names, "rootResolved")
104 |> should.be_true
105}
106
107/// Test that at-uri format fields in nested objects also get *Resolved fields
108pub fn nested_at_uri_gets_resolved_field_test() {
109 let lexicon =
110 types.Lexicon(
111 id: "test.record",
112 defs: types.Defs(
113 main: Some(
114 types.RecordDef(type_: "record", key: Some("tid"), properties: []),
115 ),
116 others: dict.from_list([
117 #(
118 "refObject",
119 types.Object(
120 types.ObjectDef(
121 type_: "object",
122 required_fields: ["target"],
123 properties: [
124 #(
125 "target",
126 types.Property(
127 type_: "string",
128 required: True,
129 format: Some("at-uri"),
130 ref: None,
131 refs: None,
132 items: None,
133 ),
134 ),
135 ],
136 ),
137 ),
138 ),
139 ]),
140 ),
141 )
142
143 // Build a registry from the lexicon
144 let reg = registry.from_lexicons([lexicon])
145
146 // Create mock types
147 let mock_record_type = schema.object_type("Record", "Mock record union", [])
148 let mock_batch_fetcher = fn(_uris, _collection, _field) { Ok(dict.new()) }
149
150 // Build object types WITH batch_fetcher and generic_record_type
151 let object_types =
152 object_builder.build_all_object_types(
153 reg,
154 Some(mock_batch_fetcher),
155 Some(mock_record_type),
156 )
157
158 // The refObject type should exist
159 let assert Ok(ref_object_type) =
160 dict.get(object_types, "test.record#refObject")
161
162 // Get the fields from the type
163 let fields = schema.get_fields(ref_object_type)
164 let field_names = list.map(fields, schema.field_name)
165
166 // Should have targetResolved field
167 list.contains(field_names, "targetResolved")
168 |> should.be_true
169}
170
171/// Test that *Resolved fields are NOT added when batch_fetcher is None
172pub fn no_resolved_fields_without_batch_fetcher_test() {
173 let lexicon =
174 types.Lexicon(
175 id: "app.bsky.feed.post",
176 defs: types.Defs(
177 main: Some(
178 types.RecordDef(type_: "record", key: Some("tid"), properties: []),
179 ),
180 others: dict.from_list([
181 #(
182 "replyRef",
183 types.Object(
184 types.ObjectDef(
185 type_: "object",
186 required_fields: ["parent"],
187 properties: [
188 #(
189 "parent",
190 types.Property(
191 type_: "ref",
192 required: True,
193 format: None,
194 ref: Some("com.atproto.repo.strongRef"),
195 refs: None,
196 items: None,
197 ),
198 ),
199 ],
200 ),
201 ),
202 ),
203 ]),
204 ),
205 )
206
207 let reg = registry.from_lexicons([lexicon])
208
209 // Build object types WITHOUT batch_fetcher (None, None)
210 let object_types = object_builder.build_all_object_types(reg, None, None)
211
212 let assert Ok(reply_ref_type) =
213 dict.get(object_types, "app.bsky.feed.post#replyRef")
214
215 let fields = schema.get_fields(reply_ref_type)
216 let field_names = list.map(fields, schema.field_name)
217
218 // Should NOT have parentResolved field (no batch_fetcher)
219 list.contains(field_names, "parentResolved")
220 |> should.be_false
221
222 // Should still have the regular parent field
223 list.contains(field_names, "parent")
224 |> should.be_true
225}