1use super::types::{
6 GQLBackend, GQLBatchRequest, GQLCacheControl, GQLConnection, GQLDataloader, GQLDeferDirective,
7 GQLDeprecation, GQLDirective, GQLDirectiveLocation, GQLEnumDef, GQLEnumValue, GQLError,
8 GQLFederationExtension, GQLField, GQLFragment, GQLInputField, GQLInputObject, GQLInterface,
9 GQLIntrospectionQuery, GQLLiveQueryExtension, GQLMockDataGenerator, GQLObject, GQLOperation,
10 GQLPersistedQuery, GQLPersistedQueryStore, GQLQueryComplexity, GQLRateLimitDirective,
11 GQLResolverSignature, GQLResponse, GQLScalar, GQLSchema, GQLSchemaBuilder, GQLSchemaComparator,
12 GQLSchemaExtended, GQLSchemaRegistry, GQLSdlPrinter, GQLSelectionField, GQLStreamDirective,
13 GQLType, GQLTypeNameMap, GQLTypeSystemDocument, GQLUnion,
14};
15
16#[cfg(test)]
17mod tests {
18 use super::*;
19 pub(super) fn backend() -> GQLBackend {
20 GQLBackend
21 }
22 pub(super) fn simple_object() -> GQLObject {
23 GQLObject {
24 name: "User".to_string(),
25 fields: vec![
26 GQLField {
27 name: "id".to_string(),
28 ty: GQLType::NonNull(Box::new(GQLType::Scalar("ID".to_string()))),
29 nullable: false,
30 description: None,
31 args: vec![],
32 },
33 GQLField {
34 name: "name".to_string(),
35 ty: GQLType::Scalar("String".to_string()),
36 nullable: true,
37 description: Some("The user's display name.".to_string()),
38 args: vec![],
39 },
40 ],
41 implements: vec![],
42 description: None,
43 }
44 }
45 #[test]
46 pub(super) fn test_emit_scalar_type() {
47 let b = backend();
48 assert_eq!(b.emit_type(&GQLType::Scalar("Int".to_string())), "Int");
49 assert_eq!(
50 b.emit_type(&GQLType::Scalar("String".to_string())),
51 "String"
52 );
53 }
54 #[test]
55 pub(super) fn test_emit_list_type() {
56 let b = backend();
57 let ty = GQLType::List(Box::new(GQLType::Scalar("String".to_string())));
58 assert_eq!(b.emit_type(&ty), "[String]");
59 }
60 #[test]
61 pub(super) fn test_emit_nonnull_type() {
62 let b = backend();
63 let ty = GQLType::NonNull(Box::new(GQLType::Scalar("ID".to_string())));
64 assert_eq!(b.emit_type(&ty), "ID!");
65 }
66 #[test]
67 pub(super) fn test_emit_nonnull_list_type() {
68 let b = backend();
69 let ty = GQLType::NonNull(Box::new(GQLType::List(Box::new(GQLType::NonNull(
70 Box::new(GQLType::Scalar("Int".to_string())),
71 )))));
72 assert_eq!(b.emit_type(&ty), "[Int!]!");
73 }
74 #[test]
75 pub(super) fn test_emit_field_nonnull() {
76 let b = backend();
77 let field = GQLField {
78 name: "id".to_string(),
79 ty: GQLType::NonNull(Box::new(GQLType::Scalar("ID".to_string()))),
80 nullable: false,
81 description: None,
82 args: vec![],
83 };
84 let out = b.emit_field(&field);
85 assert!(out.contains("id: ID!"), "got: {}", out);
86 }
87 #[test]
88 pub(super) fn test_emit_field_nullable_with_description() {
89 let b = backend();
90 let field = GQLField {
91 name: "bio".to_string(),
92 ty: GQLType::Scalar("String".to_string()),
93 nullable: true,
94 description: Some("Short biography.".to_string()),
95 args: vec![],
96 };
97 let out = b.emit_field(&field);
98 assert!(out.contains("\"\"\"Short biography.\"\"\""));
99 assert!(out.contains("bio: String"));
100 }
101 #[test]
102 pub(super) fn test_emit_object_no_implements() {
103 let b = backend();
104 let obj = simple_object();
105 let out = b.emit_object(&obj);
106 assert!(out.starts_with("type User {"));
107 assert!(out.contains("id: ID!"));
108 assert!(out.contains("name: String"));
109 assert!(out.ends_with('}'));
110 }
111 #[test]
112 pub(super) fn test_emit_object_with_implements() {
113 let b = backend();
114 let obj = GQLObject {
115 name: "Admin".to_string(),
116 fields: vec![GQLField {
117 name: "role".to_string(),
118 ty: GQLType::Scalar("String".to_string()),
119 nullable: true,
120 description: None,
121 args: vec![],
122 }],
123 implements: vec!["Node".to_string(), "Actor".to_string()],
124 description: None,
125 };
126 let out = b.emit_object(&obj);
127 assert!(out.contains("type Admin implements Node & Actor {"));
128 }
129 #[test]
130 pub(super) fn test_emit_enum() {
131 let b = backend();
132 let e = GQLEnumDef {
133 name: "Status".to_string(),
134 values: vec![
135 GQLEnumValue {
136 name: "ACTIVE".to_string(),
137 description: None,
138 },
139 GQLEnumValue {
140 name: "INACTIVE".to_string(),
141 description: Some("Deactivated account.".to_string()),
142 },
143 ],
144 };
145 let out = b.emit_enum(&e);
146 assert!(out.starts_with("enum Status {"));
147 assert!(out.contains("ACTIVE"));
148 assert!(out.contains("INACTIVE"));
149 assert!(out.contains("\"\"\"Deactivated account.\"\"\""));
150 }
151 #[test]
152 pub(super) fn test_emit_schema_no_mutation() {
153 let b = backend();
154 let schema = GQLSchema {
155 types: vec![simple_object()],
156 query_type: "Query".to_string(),
157 mutation_type: None,
158 };
159 let out = b.emit_schema(&schema);
160 assert!(out.contains("schema {"));
161 assert!(out.contains("query: Query"));
162 assert!(!out.contains("mutation:"));
163 assert!(out.contains("type User {"));
164 }
165 #[test]
166 pub(super) fn test_emit_schema_with_mutation() {
167 let b = backend();
168 let schema = GQLSchema {
169 types: vec![simple_object()],
170 query_type: "Query".to_string(),
171 mutation_type: Some("Mutation".to_string()),
172 };
173 let out = b.emit_schema(&schema);
174 assert!(out.contains("mutation: Mutation"));
175 }
176 #[test]
177 pub(super) fn test_generate_resolver_stubs() {
178 let b = backend();
179 let schema = GQLSchema {
180 types: vec![simple_object()],
181 query_type: "Query".to_string(),
182 mutation_type: None,
183 };
184 let out = b.generate_resolver_stubs(&schema);
185 assert!(out.contains("fn resolve_user_id"));
186 assert!(out.contains("fn resolve_user_name"));
187 assert!(out.contains("todo!()"));
188 }
189 #[test]
190 pub(super) fn test_schema_from_triples() {
191 let b = backend();
192 let schema = b.schema_from_triples(
193 "Query",
194 &[("Query", "users", "String"), ("Query", "version", "String")],
195 );
196 assert_eq!(schema.query_type, "Query");
197 assert_eq!(schema.types.len(), 1);
198 assert_eq!(schema.types[0].name, "Query");
199 assert_eq!(schema.types[0].fields.len(), 2);
200 }
201 #[test]
202 pub(super) fn test_interface_and_union_type_emit() {
203 let b = backend();
204 let iface = GQLType::Interface("Node".to_string());
205 let union_ty = GQLType::Union("SearchResult".to_string());
206 assert_eq!(b.emit_type(&iface), "Node");
207 assert_eq!(b.emit_type(&union_ty), "SearchResult");
208 }
209}
210#[allow(dead_code)]
211pub(super) fn emit_gql_type(ty: &GQLType) -> String {
212 match ty {
213 GQLType::Scalar(s) => s.clone(),
214 GQLType::Object(o) => o.clone(),
215 GQLType::Interface(i) => i.clone(),
216 GQLType::Union(u) => u.clone(),
217 GQLType::Enum(e) => e.clone(),
218 GQLType::List(inner) => format!("[{}]", emit_gql_type(inner)),
219 GQLType::NonNull(inner) => format!("{}!", emit_gql_type(inner)),
220 }
221}
222#[allow(dead_code)]
223pub(super) fn ts_type(ty: &GQLType) -> &str {
224 match ty {
225 GQLType::Scalar(s) if s == "String" => "string",
226 GQLType::Scalar(s) if s == "Int" || s == "Float" => "number",
227 GQLType::Scalar(s) if s == "Boolean" => "boolean",
228 GQLType::Scalar(s) if s == "ID" => "string",
229 GQLType::NonNull(_inner) => "any",
230 GQLType::List(_inner) => "any[]",
231 _ => "any",
232 }
233}
234#[allow(dead_code)]
235pub(super) fn rust_type(ty: &GQLType) -> &str {
236 match ty {
237 GQLType::Scalar(s) if s == "String" || s == "ID" => "String",
238 GQLType::Scalar(s) if s == "Int" => "i64",
239 GQLType::Scalar(s) if s == "Float" => "f64",
240 GQLType::Scalar(s) if s == "Boolean" => "bool",
241 GQLType::NonNull(_) => "Box<dyn std::any::Any>",
242 _ => "serde_json::Value",
243 }
244}
245#[allow(dead_code)]
246pub(super) fn go_type(ty: &GQLType) -> &str {
247 match ty {
248 GQLType::Scalar(s) if s == "String" || s == "ID" => "string",
249 GQLType::Scalar(s) if s == "Int" => "int64",
250 GQLType::Scalar(s) if s == "Float" => "float64",
251 GQLType::Scalar(s) if s == "Boolean" => "bool",
252 _ => "interface{}",
253 }
254}
255#[allow(dead_code)]
256pub(super) fn py_type(ty: &GQLType) -> &str {
257 match ty {
258 GQLType::Scalar(s) if s == "String" || s == "ID" => "str",
259 GQLType::Scalar(s) if s == "Int" => "int",
260 GQLType::Scalar(s) if s == "Float" => "float",
261 GQLType::Scalar(s) if s == "Boolean" => "bool",
262 GQLType::List(_) => "list",
263 _ => "object",
264 }
265}
266#[cfg(test)]
267mod graphql_extended_tests {
268 use super::*;
269 #[test]
270 pub(super) fn test_directive_emit() {
271 let d = GQLDirective::new("auth")
272 .with_location(GQLDirectiveLocation::Field)
273 .with_location(GQLDirectiveLocation::Object);
274 let emitted = d.emit();
275 assert!(emitted.contains("directive @auth"));
276 assert!(emitted.contains("FIELD"));
277 }
278 #[test]
279 pub(super) fn test_union_emit() {
280 let mut u = GQLUnion::new("SearchResult");
281 u.add_member("User");
282 u.add_member("Post");
283 let s = u.emit();
284 assert!(s.contains("union SearchResult = User | Post"));
285 }
286 #[test]
287 pub(super) fn test_connection_type() {
288 let conn = GQLConnection::new("User");
289 let edge = conn.emit_edge_type();
290 let connection = conn.emit_connection_type();
291 assert!(edge.contains("type UserEdge"));
292 assert!(connection.contains("type UserConnection"));
293 assert!(connection.contains("pageInfo: PageInfo!"));
294 }
295 #[test]
296 pub(super) fn test_schema_emit() {
297 let mut schema = GQLSchemaExtended::new();
298 schema.set_query_type("Query");
299 let emitted = schema.emit();
300 assert!(emitted.contains("schema {"));
301 assert!(emitted.contains("query: Query"));
302 }
303 #[test]
304 pub(super) fn test_complexity_calculation() {
305 let limits = GQLQueryComplexity::default_limits();
306 let selections = vec![
307 GQLSelectionField::new("user"),
308 GQLSelectionField::new("posts"),
309 ];
310 let complexity = limits.calculate_selection_complexity(&selections, 0);
311 assert!(complexity >= 2);
312 }
313 #[test]
314 pub(super) fn test_scalar_emit() {
315 let scalar = GQLScalar::new("DateTime");
316 let s = scalar.emit();
317 assert_eq!(s, "scalar DateTime");
318 }
319 #[test]
320 pub(super) fn test_input_object() {
321 let mut inp = GQLInputObject::new("CreateUserInput");
322 inp.add_field(GQLInputField {
323 name: "email".to_string(),
324 ty: GQLType::NonNull(Box::new(GQLType::Scalar("String".to_string()))),
325 default_value: None,
326 description: None,
327 directives: Vec::new(),
328 });
329 let s = inp.emit();
330 assert!(s.contains("input CreateUserInput"));
331 assert!(s.contains("email:"));
332 }
333 #[test]
334 pub(super) fn test_operation_emit() {
335 let op = GQLOperation::query("GetUser");
336 let s = op.emit();
337 assert!(s.contains("query GetUser"));
338 }
339 #[test]
340 pub(super) fn test_registry() {
341 let mut registry = GQLSchemaRegistry::new();
342 registry.register("v1", GQLSchemaExtended::new());
343 assert!(registry.get("v1").is_some());
344 assert_eq!(registry.list_names().len(), 1);
345 }
346 #[test]
347 pub(super) fn test_deprecation() {
348 let dep = GQLDeprecation::new("oldField", "Use newField instead");
349 let d = dep.emit_directive();
350 assert!(d.contains("@deprecated"));
351 assert!(d.contains("Use newField instead"));
352 }
353 #[test]
354 pub(super) fn test_mock_generator() {
355 let mut gen = GQLMockDataGenerator::new(42);
356 let s = gen.generate_for_type(&GQLType::Scalar("String".to_string()));
357 assert!(s.contains("mock_string"));
358 let i = gen.generate_for_type(&GQLType::Scalar("Int".to_string()));
359 assert!(!i.is_empty());
360 }
361}
362#[cfg(test)]
363mod graphql_introspection_tests {
364 use super::*;
365 #[test]
366 pub(super) fn test_schema_builder() {
367 let schema = GQLSchemaBuilder::new()
368 .scalar(GQLScalar::new("DateTime"))
369 .query_type("Query")
370 .build();
371 assert_eq!(schema.scalars.len(), 1);
372 assert_eq!(schema.query_type, Some("Query".to_string()));
373 }
374 #[test]
375 pub(super) fn test_error_json() {
376 let err = GQLError::new("Not found").with_location(1, 5);
377 let json = err.emit_json();
378 assert!(json.contains("Not found"));
379 assert!(json.contains("\"line\":1"));
380 }
381 #[test]
382 pub(super) fn test_response_success() {
383 let r = GQLResponse::success("{\"user\":{\"id\":\"1\"}}");
384 assert!(r.is_success());
385 }
386 #[test]
387 pub(super) fn test_cache_control() {
388 let cc = GQLCacheControl::public(300);
389 let d = cc.emit_directive();
390 assert!(d.contains("maxAge: 300"));
391 assert!(d.contains("PUBLIC"));
392 }
393 #[test]
394 pub(super) fn test_federation_extension() {
395 let mut fed = GQLFederationExtension::new("products");
396 fed.add_key("id");
397 let dirs = fed.emit_key_directives();
398 assert!(dirs.contains("@key(fields: \"id\")"));
399 }
400 #[test]
401 pub(super) fn test_batch_request() {
402 let mut batch = GQLBatchRequest::new(3);
403 assert!(batch.add(GQLOperation::query("Q1")));
404 assert!(batch.add(GQLOperation::query("Q2")));
405 assert!(batch.add(GQLOperation::query("Q3")));
406 assert!(!batch.add(GQLOperation::query("Q4")));
407 assert_eq!(batch.operations.len(), 3);
408 }
409 #[test]
410 pub(super) fn test_dataloader_ts_emit() {
411 let loader = GQLDataloader::new("User");
412 let ts = loader.emit_ts_loader();
413 assert!(ts.contains("DataLoader"));
414 assert!(ts.contains("maxBatchSize: 100"));
415 }
416 #[test]
417 pub(super) fn test_rate_limit_directive() {
418 let rl = GQLRateLimitDirective::new(100, 60);
419 let emitted = rl.emit();
420 assert!(emitted.contains("@rateLimit"));
421 assert!(emitted.contains("limit: 100"));
422 }
423 #[test]
424 pub(super) fn test_type_system_document() {
425 let mut doc = GQLTypeSystemDocument::new();
426 doc.extend_type("Query", &["health: Boolean!"]);
427 let emitted = doc.emit();
428 assert!(emitted.contains("extend type Query"));
429 assert!(emitted.contains("health: Boolean!"));
430 }
431 #[test]
432 pub(super) fn test_introspection_query() {
433 let q = GQLIntrospectionQuery::full_introspection_query();
434 assert!(q.contains("__schema"));
435 }
436 #[test]
437 pub(super) fn test_live_query_extension() {
438 let mut lq = GQLLiveQueryExtension::new(500);
439 lq.add_key("user:1");
440 assert_eq!(lq.invalidation_keys.len(), 1);
441 let header = lq.emit_extension_header();
442 assert!(header.contains("@live"));
443 }
444 #[test]
445 pub(super) fn test_resolver_signature() {
446 let sig = GQLResolverSignature::new(
447 "User",
448 "posts",
449 GQLType::List(Box::new(GQLType::Object("Post".to_string()))),
450 );
451 let ts = sig.emit_ts_signature();
452 assert!(ts.contains("posts"));
453 assert!(ts.contains("async"));
454 }
455 #[test]
456 pub(super) fn test_interface_emit() {
457 let mut iface = GQLInterface::new("Node");
458 iface.add_field(GQLField {
459 name: "id".to_string(),
460 ty: GQLType::NonNull(Box::new(GQLType::Scalar("ID".to_string()))),
461 nullable: false,
462 description: None,
463 args: Vec::new(),
464 });
465 let s = iface.emit();
466 assert!(s.contains("interface Node"));
467 assert!(s.contains("id:"));
468 }
469 #[test]
470 pub(super) fn test_fragment_emit() {
471 let frag = GQLFragment::new("UserFields", "User");
472 let s = frag.emit();
473 assert!(s.contains("fragment UserFields on User"));
474 }
475}
476#[cfg(test)]
477mod graphql_advanced_tests {
478 use super::*;
479 #[test]
480 pub(super) fn test_persisted_query() {
481 let pq = GQLPersistedQuery::new("{ user { id name } }");
482 assert!(!pq.hash.is_empty());
483 let ext = pq.emit_apq_extension();
484 assert!(ext.contains("persistedQuery"));
485 assert!(ext.contains("sha256Hash"));
486 }
487 #[test]
488 pub(super) fn test_persisted_query_store() {
489 let mut store = GQLPersistedQueryStore::new();
490 let pq = GQLPersistedQuery::new("{ user { id } }");
491 let hash = pq.hash.clone();
492 store.register(pq);
493 assert_eq!(store.count(), 1);
494 assert!(store.lookup(&hash).is_some());
495 }
496 #[test]
497 pub(super) fn test_defer_directive() {
498 let defer = GQLDeferDirective::new().with_label("profile");
499 let s = defer.emit();
500 assert!(s.contains("@defer"));
501 assert!(s.contains("label: \"profile\""));
502 }
503 #[test]
504 pub(super) fn test_stream_directive() {
505 let stream = GQLStreamDirective::new(2);
506 let s = stream.emit();
507 assert!(s.contains("@stream"));
508 assert!(s.contains("initialCount: 2"));
509 }
510 #[test]
511 pub(super) fn test_schema_comparator_added() {
512 let mut old = GQLSchemaExtended::new();
513 let mut new_schema = GQLSchemaExtended::new();
514 new_schema.objects.push(GQLObject {
515 name: "User".to_string(),
516 fields: Vec::new(),
517 description: None,
518 implements: Vec::new(),
519 });
520 let cmp = GQLSchemaComparator::new(old, new_schema);
521 assert_eq!(cmp.added_types(), vec!["User".to_string()]);
522 assert!(!cmp.is_breaking_change());
523 }
524 #[test]
525 pub(super) fn test_schema_comparator_removed() {
526 let mut old = GQLSchemaExtended::new();
527 old.objects.push(GQLObject {
528 name: "User".to_string(),
529 fields: Vec::new(),
530 description: None,
531 implements: Vec::new(),
532 });
533 let new_schema = GQLSchemaExtended::new();
534 let cmp = GQLSchemaComparator::new(old, new_schema);
535 assert_eq!(cmp.removed_types(), vec!["User".to_string()]);
536 assert!(cmp.is_breaking_change());
537 }
538 #[test]
539 pub(super) fn test_schema_comparator_changelog() {
540 let mut old = GQLSchemaExtended::new();
541 let mut new_schema = GQLSchemaExtended::new();
542 old.objects.push(GQLObject {
543 name: "Deleted".to_string(),
544 fields: Vec::new(),
545 description: None,
546 implements: Vec::new(),
547 });
548 new_schema.objects.push(GQLObject {
549 name: "Added".to_string(),
550 fields: Vec::new(),
551 description: None,
552 implements: Vec::new(),
553 });
554 let cmp = GQLSchemaComparator::new(old, new_schema);
555 let log = cmp.generate_changelog();
556 assert!(log.contains("+ Added type: Added"));
557 assert!(log.contains("- Removed type: Deleted"));
558 }
559}
560#[cfg(test)]
561mod graphql_sdl_tests {
562 use super::*;
563 #[test]
564 pub(super) fn test_sdl_printer_object() {
565 let obj = GQLObject {
566 name: "User".to_string(),
567 fields: vec![GQLField {
568 name: "id".to_string(),
569 ty: GQLType::Scalar("ID".to_string()),
570 nullable: false,
571 description: None,
572 args: Vec::new(),
573 }],
574 description: None,
575 implements: Vec::new(),
576 };
577 let printer = GQLSdlPrinter::new();
578 let s = printer.print_object(&obj);
579 assert!(s.contains("type User {"));
580 assert!(s.contains("id:"));
581 }
582 #[test]
583 pub(super) fn test_type_name_map() {
584 let map = GQLTypeNameMap::new();
585 assert_eq!(map.lookup("Int"), Some(&"i32".to_string()));
586 assert_eq!(map.lookup("Boolean"), Some(&"bool".to_string()));
587 assert!(map.lookup("Unknown").is_none());
588 }
589 #[test]
590 pub(super) fn test_sdl_printer_schema() {
591 let mut schema = GQLSchemaExtended::new();
592 schema.scalars.push(GQLScalar::new("Date"));
593 schema.objects.push(GQLObject {
594 name: "Post".to_string(),
595 fields: vec![GQLField {
596 name: "title".to_string(),
597 ty: GQLType::Scalar("String".to_string()),
598 nullable: true,
599 description: None,
600 args: Vec::new(),
601 }],
602 description: None,
603 implements: Vec::new(),
604 });
605 let printer = GQLSdlPrinter::new();
606 let s = printer.print_schema(&schema);
607 assert!(s.contains("scalar Date"));
608 assert!(s.contains("type Post"));
609 }
610}
611#[allow(dead_code)]
612pub fn gql_type_name(ty: &GQLType) -> String {
613 match ty {
614 GQLType::Scalar(s)
615 | GQLType::Object(s)
616 | GQLType::Interface(s)
617 | GQLType::Union(s)
618 | GQLType::Enum(s) => s.clone(),
619 GQLType::List(inner) | GQLType::NonNull(inner) => gql_type_name(inner),
620 }
621}
622#[allow(dead_code)]
623pub fn is_builtin_scalar(name: &str) -> bool {
624 matches!(name, "Int" | "Float" | "String" | "Boolean" | "ID")
625}