1use typedb_driver::TransactionType;
2use typedb_driver::concept::Concept;
3
4use super::backend::{DriverBackend, QueryResultKind};
5use super::real_driver::RealTypeDBBackend;
6use crate::config::TypeDBSection;
7use crate::error::PipelineError;
8use crate::executor::QueryExecutor;
9
10pub struct TypeDBClient {
13 backend: Box<dyn DriverBackend>,
14}
15
16impl TypeDBClient {
17 #[cfg_attr(coverage_nightly, coverage(off))]
19 pub async fn connect(config: &TypeDBSection) -> Result<Self, PipelineError> {
20 let backend = RealTypeDBBackend::connect(config).await?;
21 Ok(Self {
22 backend: Box::new(backend),
23 })
24 }
25
26 #[cfg(test)]
28 pub(crate) fn with_backend(backend: Box<dyn DriverBackend>) -> Self {
29 Self { backend }
30 }
31
32 pub async fn execute(
37 &self,
38 database: &str,
39 typeql: &str,
40 tx_type: &str,
41 ) -> Result<serde_json::Value, PipelineError> {
42 let transaction_type = parse_transaction_type(tx_type)?;
43
44 let mut tx = self
45 .backend
46 .open_transaction(database, transaction_type)
47 .await?;
48
49 let answer = tx.query(typeql).await?;
50
51 let needs_commit = matches!(
52 transaction_type,
53 TransactionType::Write | TransactionType::Schema
54 );
55
56 let results = match answer {
57 QueryResultKind::Ok => {
58 if needs_commit {
59 tx.commit().await?;
60 }
61 serde_json::json!({ "ok": true })
62 }
63 QueryResultKind::Rows(rows) => {
64 if needs_commit {
65 tx.commit().await?;
66 }
67 serde_json::Value::Array(rows)
68 }
69 QueryResultKind::Documents(docs) => {
70 if needs_commit {
71 let _ = tx.commit().await;
72 }
73 serde_json::Value::Array(docs)
74 }
75 };
76
77 Ok(results)
78 }
79
80 pub fn is_connected(&self) -> bool {
82 self.backend.is_open()
83 }
84}
85
86impl QueryExecutor for TypeDBClient {
87 fn execute<'a>(
88 &'a self,
89 database: &'a str,
90 typeql: &'a str,
91 transaction_type: &'a str,
92 ) -> std::pin::Pin<
93 Box<dyn std::future::Future<Output = Result<serde_json::Value, PipelineError>> + Send + 'a>,
94 > {
95 Box::pin(async move { self.execute(database, typeql, transaction_type).await })
96 }
97
98 fn is_connected(&self) -> bool {
99 self.is_connected()
100 }
101}
102
103pub(crate) fn parse_transaction_type(tx_type: &str) -> Result<TransactionType, PipelineError> {
105 match tx_type {
106 "read" => Ok(TransactionType::Read),
107 "write" => Ok(TransactionType::Write),
108 "schema" => Ok(TransactionType::Schema),
109 other => Err(PipelineError::QueryExecution(format!(
110 "Unknown transaction type: {other}"
111 ))),
112 }
113}
114
115pub(crate) fn concept_to_json(concept: &Concept) -> serde_json::Value {
117 let mut obj = serde_json::Map::new();
118
119 obj.insert(
120 "category".to_string(),
121 serde_json::Value::String(concept.get_category().name().to_string()),
122 );
123 obj.insert(
124 "label".to_string(),
125 serde_json::Value::String(concept.get_label().to_string()),
126 );
127
128 if let Some(iid) = concept.try_get_iid() {
129 obj.insert(
130 "iid".to_string(),
131 serde_json::Value::String(iid.to_string()),
132 );
133 }
134
135 if let Some(value) = concept.try_get_value() {
136 obj.insert("value".to_string(), value_to_json(value));
137 }
138
139 if let Some(value_type) = concept.try_get_value_type() {
140 obj.insert(
141 "value_type".to_string(),
142 serde_json::Value::String(value_type.name().to_string()),
143 );
144 }
145
146 serde_json::Value::Object(obj)
147}
148
149#[cfg_attr(coverage_nightly, coverage(off))]
151pub(crate) fn value_to_json(value: &typedb_driver::concept::Value) -> serde_json::Value {
152 if let Some(b) = value.get_boolean() {
153 return serde_json::Value::Bool(b);
154 }
155 if let Some(i) = value.get_integer() {
156 return serde_json::json!(i);
157 }
158 if let Some(d) = value.get_double() {
159 return serde_json::json!(d);
160 }
161 if let Some(s) = value.get_string() {
162 return serde_json::Value::String(s.to_string());
163 }
164 if let Some(date) = value.get_date() {
165 return serde_json::Value::String(date.to_string());
166 }
167 if let Some(dt) = value.get_datetime() {
168 return serde_json::Value::String(dt.to_string());
169 }
170 if let Some(dt_tz) = value.get_datetime_tz() {
171 return serde_json::Value::String(dt_tz.to_string());
172 }
173 if let Some(dec) = value.get_decimal() {
174 return serde_json::Value::String(dec.to_string());
175 }
176 if let Some(dur) = value.get_duration() {
177 return serde_json::Value::String(dur.to_string());
178 }
179 value_to_json_fallback(value)
181}
182
183#[cfg_attr(coverage_nightly, coverage(off))]
184fn value_to_json_fallback(value: &typedb_driver::concept::Value) -> serde_json::Value {
185 serde_json::Value::String(value.to_string())
186}
187
188#[cfg(test)]
189#[cfg_attr(coverage_nightly, coverage(off))]
190mod tests {
191 use std::future::Future;
192 use std::pin::Pin;
193 use std::sync::Arc;
194 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
195
196 use typedb_driver::IID;
197 use typedb_driver::TransactionType;
198 use typedb_driver::concept::value::{Decimal, Duration, TimeZone};
199 use typedb_driver::concept::{
200 Attribute, AttributeType, Concept, Entity, EntityType, Value, ValueType,
201 };
202
203 use super::*;
204 use crate::error::PipelineError;
205 use crate::typedb::backend::TransactionOps;
206
207 struct MockTransaction {
212 query_result: Option<QueryResultKind>,
213 query_error: Option<String>,
214 commit_error: Option<String>,
215 committed: Arc<AtomicBool>,
216 query_called: Arc<AtomicBool>,
217 }
218
219 impl MockTransaction {
220 fn new(result: QueryResultKind) -> Self {
221 Self {
222 query_result: Some(result),
223 query_error: None,
224 commit_error: None,
225 committed: Arc::new(AtomicBool::new(false)),
226 query_called: Arc::new(AtomicBool::new(false)),
227 }
228 }
229
230 fn failing_query(msg: &str) -> Self {
231 Self {
232 query_result: None,
233 query_error: Some(msg.to_string()),
234 commit_error: None,
235 committed: Arc::new(AtomicBool::new(false)),
236 query_called: Arc::new(AtomicBool::new(false)),
237 }
238 }
239
240 fn with_commit_error(mut self, msg: &str) -> Self {
241 self.commit_error = Some(msg.to_string());
242 self
243 }
244 }
245
246 impl TransactionOps for MockTransaction {
247 fn query(
248 &mut self,
249 _typeql: &str,
250 ) -> Pin<Box<dyn Future<Output = Result<QueryResultKind, PipelineError>> + Send + '_>>
251 {
252 self.query_called.store(true, Ordering::SeqCst);
253 let result = self.query_result.take();
254 let error = self.query_error.take();
255 Box::pin(async move {
256 if let Some(msg) = error {
257 return Err(PipelineError::QueryExecution(msg));
258 }
259 Ok(result.expect("MockTransaction::query called more than once"))
260 })
261 }
262
263 fn commit(
264 &mut self,
265 ) -> Pin<Box<dyn Future<Output = Result<(), PipelineError>> + Send + '_>> {
266 self.committed.store(true, Ordering::SeqCst);
267 let error = self.commit_error.take();
268 Box::pin(async move {
269 if let Some(msg) = error {
270 return Err(PipelineError::QueryExecution(msg));
271 }
272 Ok(())
273 })
274 }
275 }
276
277 struct MockBackend {
278 transaction: std::sync::Mutex<Option<MockTransaction>>,
279 open_error: Option<String>,
280 is_open: bool,
281 open_called: Arc<AtomicUsize>,
282 }
283
284 impl MockBackend {
285 fn new(tx: MockTransaction) -> Self {
286 Self {
287 transaction: std::sync::Mutex::new(Some(tx)),
288 open_error: None,
289 is_open: true,
290 open_called: Arc::new(AtomicUsize::new(0)),
291 }
292 }
293
294 fn failing(msg: &str) -> Self {
295 Self {
296 transaction: std::sync::Mutex::new(None),
297 open_error: Some(msg.to_string()),
298 is_open: true,
299 open_called: Arc::new(AtomicUsize::new(0)),
300 }
301 }
302 }
303
304 impl DriverBackend for MockBackend {
305 fn open_transaction(
306 &self,
307 _database: &str,
308 _tx_type: TransactionType,
309 ) -> Pin<Box<dyn Future<Output = Result<Box<dyn TransactionOps>, PipelineError>> + Send + '_>>
310 {
311 self.open_called.fetch_add(1, Ordering::SeqCst);
312 let tx = self.transaction.lock().unwrap().take();
313 let error = self.open_error.clone();
314 Box::pin(async move {
315 if let Some(msg) = error {
316 return Err(PipelineError::QueryExecution(msg));
317 }
318 Ok(
319 Box::new(tx.expect("MockBackend: no transaction configured"))
320 as Box<dyn TransactionOps>,
321 )
322 })
323 }
324
325 fn is_open(&self) -> bool {
326 self.is_open
327 }
328 }
329
330 fn make_client(backend: MockBackend) -> TypeDBClient {
331 TypeDBClient::with_backend(Box::new(backend))
332 }
333
334 #[test]
339 fn parse_transaction_type_read() {
340 let result = parse_transaction_type("read").unwrap();
341 assert_eq!(result, TransactionType::Read);
342 }
343
344 #[test]
345 fn parse_transaction_type_write() {
346 let result = parse_transaction_type("write").unwrap();
347 assert_eq!(result, TransactionType::Write);
348 }
349
350 #[test]
351 fn parse_transaction_type_schema() {
352 let result = parse_transaction_type("schema").unwrap();
353 assert_eq!(result, TransactionType::Schema);
354 }
355
356 #[test]
357 fn parse_transaction_type_unknown() {
358 let result = parse_transaction_type("unknown");
359 let err = result.unwrap_err();
360 assert!(
361 matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("Unknown transaction type: unknown"))
362 );
363 }
364
365 #[test]
366 fn parse_transaction_type_empty() {
367 let result = parse_transaction_type("");
368 assert!(result.is_err());
369 }
370
371 #[test]
372 fn parse_transaction_type_case_sensitive() {
373 let result = parse_transaction_type("Read");
374 assert!(result.is_err());
375 }
376
377 #[tokio::test]
382 async fn execute_ok_read_no_commit() {
383 let tx = MockTransaction::new(QueryResultKind::Ok);
384 let committed = tx.committed.clone();
385 let client = make_client(MockBackend::new(tx));
386
387 let result = client
388 .execute("db", "match $x isa thing;", "read")
389 .await
390 .unwrap();
391 assert_eq!(result, serde_json::json!({"ok": true}));
392 assert!(!committed.load(Ordering::SeqCst));
393 }
394
395 #[tokio::test]
396 async fn execute_ok_write_commits() {
397 let tx = MockTransaction::new(QueryResultKind::Ok);
398 let committed = tx.committed.clone();
399 let client = make_client(MockBackend::new(tx));
400
401 let result = client
402 .execute("db", "insert $x isa thing;", "write")
403 .await
404 .unwrap();
405 assert_eq!(result, serde_json::json!({"ok": true}));
406 assert!(committed.load(Ordering::SeqCst));
407 }
408
409 #[tokio::test]
410 async fn execute_ok_schema_commits() {
411 let tx = MockTransaction::new(QueryResultKind::Ok);
412 let committed = tx.committed.clone();
413 let client = make_client(MockBackend::new(tx));
414
415 let result = client
416 .execute("db", "define entity thing;", "schema")
417 .await
418 .unwrap();
419 assert_eq!(result, serde_json::json!({"ok": true}));
420 assert!(committed.load(Ordering::SeqCst));
421 }
422
423 #[tokio::test]
424 async fn execute_rows_read_no_commit() {
425 let rows = vec![
426 serde_json::json!({"name": "Alice"}),
427 serde_json::json!({"name": "Bob"}),
428 ];
429 let tx = MockTransaction::new(QueryResultKind::Rows(rows.clone()));
430 let committed = tx.committed.clone();
431 let client = make_client(MockBackend::new(tx));
432
433 let result = client
434 .execute("db", "match $p isa person;", "read")
435 .await
436 .unwrap();
437 assert_eq!(result, serde_json::Value::Array(rows));
438 assert!(!committed.load(Ordering::SeqCst));
439 }
440
441 #[tokio::test]
442 async fn execute_rows_write_commits() {
443 let rows = vec![serde_json::json!({"id": 1})];
444 let tx = MockTransaction::new(QueryResultKind::Rows(rows));
445 let committed = tx.committed.clone();
446 let client = make_client(MockBackend::new(tx));
447
448 let result = client
449 .execute("db", "insert $x isa thing;", "write")
450 .await
451 .unwrap();
452 assert!(result.is_array());
453 assert!(committed.load(Ordering::SeqCst));
454 }
455
456 #[tokio::test]
457 async fn execute_rows_data_preserved() {
458 let rows = vec![
459 serde_json::json!({"name": "Alice", "age": 30}),
460 serde_json::json!({"name": "Bob", "age": 25}),
461 ];
462 let tx = MockTransaction::new(QueryResultKind::Rows(rows.clone()));
463 let client = make_client(MockBackend::new(tx));
464
465 let result = client
466 .execute("db", "match $p isa person;", "read")
467 .await
468 .unwrap();
469 let arr = result.as_array().unwrap();
470 assert_eq!(arr.len(), 2);
471 assert_eq!(arr[0]["name"], "Alice");
472 assert_eq!(arr[1]["age"], 25);
473 }
474
475 #[tokio::test]
476 async fn execute_docs_read_no_commit() {
477 let docs = vec![serde_json::json!({"doc": "data"})];
478 let tx = MockTransaction::new(QueryResultKind::Documents(docs.clone()));
479 let committed = tx.committed.clone();
480 let client = make_client(MockBackend::new(tx));
481
482 let result = client
483 .execute("db", "match $p isa person; fetch {};", "read")
484 .await
485 .unwrap();
486 assert_eq!(result, serde_json::Value::Array(docs));
487 assert!(!committed.load(Ordering::SeqCst));
488 }
489
490 #[tokio::test]
491 async fn execute_docs_write_commits() {
492 let docs = vec![serde_json::json!({"doc": "data"})];
493 let tx = MockTransaction::new(QueryResultKind::Documents(docs));
494 let committed = tx.committed.clone();
495 let client = make_client(MockBackend::new(tx));
496
497 let result = client
498 .execute("db", "insert $x isa thing;", "write")
499 .await
500 .unwrap();
501 assert!(result.is_array());
502 assert!(committed.load(Ordering::SeqCst));
503 }
504
505 #[tokio::test]
506 async fn execute_docs_commit_error_ignored() {
507 let docs = vec![serde_json::json!({"doc": "data"})];
508 let tx = MockTransaction::new(QueryResultKind::Documents(docs.clone()))
509 .with_commit_error("commit failed");
510 let client = make_client(MockBackend::new(tx));
511
512 let result = client
514 .execute("db", "insert $x isa thing;", "write")
515 .await
516 .unwrap();
517 assert_eq!(result, serde_json::Value::Array(docs));
518 }
519
520 #[tokio::test]
521 async fn execute_transaction_open_failure() {
522 let client = make_client(MockBackend::failing("connection refused"));
523
524 let result = client.execute("db", "match $x isa thing;", "read").await;
525 let err = result.unwrap_err();
526 assert!(
527 matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("connection refused"))
528 );
529 }
530
531 #[tokio::test]
532 async fn execute_query_failure() {
533 let tx = MockTransaction::failing_query("syntax error");
534 let client = make_client(MockBackend::new(tx));
535
536 let result = client.execute("db", "bad query", "read").await;
537 let err = result.unwrap_err();
538 assert!(matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("syntax error")));
539 }
540
541 #[tokio::test]
542 async fn execute_commit_failure_ok_propagated() {
543 let tx = MockTransaction::new(QueryResultKind::Ok).with_commit_error("commit failed");
544 let client = make_client(MockBackend::new(tx));
545
546 let result = client.execute("db", "insert $x isa thing;", "write").await;
548 let err = result.unwrap_err();
549 assert!(
550 matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("commit failed"))
551 );
552 }
553
554 #[tokio::test]
555 async fn execute_commit_failure_rows_propagated() {
556 let tx =
557 MockTransaction::new(QueryResultKind::Rows(vec![])).with_commit_error("commit failed");
558 let client = make_client(MockBackend::new(tx));
559
560 let result = client.execute("db", "insert $x isa thing;", "write").await;
562 let err = result.unwrap_err();
563 assert!(
564 matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("commit failed"))
565 );
566 }
567
568 #[tokio::test]
569 async fn execute_invalid_transaction_type() {
570 let tx = MockTransaction::new(QueryResultKind::Ok);
571 let backend = MockBackend::new(tx);
572 let open_called = backend.open_called.clone();
573 let client = make_client(backend);
574
575 let result = client.execute("db", "match $x;", "invalid").await;
576 assert!(result.is_err());
577 assert_eq!(open_called.load(Ordering::SeqCst), 0);
579 }
580
581 #[test]
582 fn is_connected_delegates_to_backend() {
583 let mut backend = MockBackend::new(MockTransaction::new(QueryResultKind::Ok));
584 backend.is_open = true;
585 let client = make_client(backend);
586 assert!(client.is_connected());
587 }
588
589 #[test]
590 fn is_connected_false_when_backend_closed() {
591 let mut backend = MockBackend::new(MockTransaction::new(QueryResultKind::Ok));
592 backend.is_open = false;
593 let client = make_client(backend);
594 assert!(!client.is_connected());
595 }
596
597 #[test]
602 fn value_to_json_boolean_true() {
603 let value = Value::Boolean(true);
604 let json = value_to_json(&value);
605 assert_eq!(json, serde_json::Value::Bool(true));
606 }
607
608 #[test]
609 fn value_to_json_boolean_false() {
610 let value = Value::Boolean(false);
611 let json = value_to_json(&value);
612 assert_eq!(json, serde_json::Value::Bool(false));
613 }
614
615 #[test]
616 fn value_to_json_integer() {
617 let value = Value::Integer(42);
618 let json = value_to_json(&value);
619 assert_eq!(json, serde_json::json!(42));
620 }
621
622 #[test]
623 fn value_to_json_integer_negative() {
624 let value = Value::Integer(-100);
625 let json = value_to_json(&value);
626 assert_eq!(json, serde_json::json!(-100));
627 }
628
629 #[test]
630 fn value_to_json_double() {
631 let value = Value::Double(3.15);
632 let json = value_to_json(&value);
633 assert_eq!(json, serde_json::json!(3.15));
634 }
635
636 #[test]
637 fn value_to_json_string() {
638 let value = Value::String("hello".to_string());
639 let json = value_to_json(&value);
640 assert_eq!(json, serde_json::Value::String("hello".to_string()));
641 }
642
643 #[test]
644 fn value_to_json_string_empty() {
645 let value = Value::String(String::new());
646 let json = value_to_json(&value);
647 assert_eq!(json, serde_json::Value::String(String::new()));
648 }
649
650 #[test]
651 fn value_to_json_date() {
652 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
653 let value = Value::Date(date);
654 let json = value_to_json(&value);
655 assert_eq!(json, serde_json::Value::String("2024-01-15".to_string()));
656 }
657
658 #[test]
659 fn value_to_json_datetime() {
660 let dt = chrono::NaiveDate::from_ymd_opt(2024, 1, 15)
661 .unwrap()
662 .and_hms_opt(10, 30, 0)
663 .unwrap();
664 let value = Value::Datetime(dt);
665 let json = value_to_json(&value);
666 let s = json.as_str().unwrap();
667 assert!(s.contains("2024-01-15"));
668 }
669
670 #[test]
671 fn value_to_json_decimal() {
672 let dec = Decimal::new(42, 0);
673 let value = Value::Decimal(dec);
674 let json = value_to_json(&value);
675 assert!(json.is_string());
676 }
677
678 #[test]
679 fn value_to_json_duration() {
680 let dur = Duration::new(1, 2, 3_000_000_000);
681 let value = Value::Duration(dur);
682 let json = value_to_json(&value);
683 assert!(json.is_string());
684 }
685
686 #[test]
687 fn value_to_json_datetime_tz() {
688 use chrono::TimeZone as _;
689 let tz = TimeZone::Fixed(chrono::FixedOffset::east_opt(3600).unwrap());
690 let dt = tz.with_ymd_and_hms(2024, 6, 15, 12, 30, 0).unwrap();
691 let value = Value::DatetimeTZ(dt);
692 let json = value_to_json(&value);
693 let s = json.as_str().unwrap();
694 assert!(s.contains("2024"));
695 }
696
697 #[test]
702 fn concept_to_json_entity_type() {
703 let concept = Concept::EntityType(EntityType {
704 label: "person".to_string(),
705 });
706 let json = concept_to_json(&concept);
707 assert_eq!(json["category"], "EntityType");
708 assert_eq!(json["label"], "person");
709 assert!(json.get("iid").is_none());
710 assert!(json.get("value").is_none());
711 }
712
713 #[test]
714 fn concept_to_json_attribute_type() {
715 let concept = Concept::AttributeType(AttributeType {
716 label: "name".to_string(),
717 value_type: Some(ValueType::String),
718 });
719 let json = concept_to_json(&concept);
720 assert_eq!(json["category"], "AttributeType");
721 assert_eq!(json["label"], "name");
722 assert_eq!(json["value_type"], "string");
723 }
724
725 #[test]
726 fn concept_to_json_value_boolean() {
727 let concept = Concept::Value(Value::Boolean(true));
728 let json = concept_to_json(&concept);
729 assert_eq!(json["category"], "Value");
730 assert_eq!(json["value"], true);
731 }
732
733 #[test]
734 fn concept_to_json_value_integer() {
735 let concept = Concept::Value(Value::Integer(42));
736 let json = concept_to_json(&concept);
737 assert_eq!(json["value"], 42);
738 }
739
740 #[test]
741 fn concept_to_json_value_string() {
742 let concept = Concept::Value(Value::String("hello".to_string()));
743 let json = concept_to_json(&concept);
744 assert_eq!(json["value"], "hello");
745 }
746
747 #[test]
748 fn concept_to_json_entity_with_iid() {
749 let iid: IID = vec![0x01, 0x02, 0x03].into();
750 let concept = Concept::Entity(Entity {
751 iid,
752 type_: Some(EntityType {
753 label: "person".to_string(),
754 }),
755 });
756 let json = concept_to_json(&concept);
757 assert_eq!(json["category"], "Entity");
758 assert_eq!(json["label"], "person");
759 let iid_str = json["iid"].as_str().unwrap();
761 assert!(iid_str.starts_with("0x"));
762 }
763
764 #[test]
765 fn concept_to_json_attribute_with_value() {
766 let iid: IID = vec![0xAA, 0xBB].into();
767 let concept = Concept::Attribute(Attribute {
768 iid,
769 value: Value::String("hello".to_string()),
770 type_: Some(AttributeType {
771 label: "name".to_string(),
772 value_type: Some(ValueType::String),
773 }),
774 });
775 let json = concept_to_json(&concept);
776 assert_eq!(json["category"], "Attribute");
777 assert_eq!(json["label"], "name");
778 assert!(json.get("iid").is_none());
780 assert_eq!(json["value"], "hello");
781 assert_eq!(json["value_type"], "string");
782 }
783
784 #[test]
785 fn concept_to_json_attribute_type_without_value_type() {
786 let concept = Concept::AttributeType(AttributeType {
787 label: "abstract_attr".to_string(),
788 value_type: None,
789 });
790 let json = concept_to_json(&concept);
791 assert_eq!(json["label"], "abstract_attr");
792 assert!(json.get("value_type").is_none());
793 }
794
795 #[test]
800 fn type_db_client_implements_query_executor() {
801 fn assert_executor<T: QueryExecutor>() {}
802 assert_executor::<TypeDBClient>();
803 }
804
805 #[tokio::test]
806 async fn query_executor_execute_delegates_to_client() {
807 let tx = MockTransaction::new(QueryResultKind::Rows(vec![serde_json::json!({"x": 1})]));
808 let client = make_client(MockBackend::new(tx));
809 let executor: Box<dyn QueryExecutor> = Box::new(client);
810
811 let result = executor
812 .execute("db", "match $x isa thing;", "read")
813 .await
814 .unwrap();
815 assert!(result.is_array());
816 assert_eq!(result.as_array().unwrap().len(), 1);
817 }
818
819 #[test]
820 fn query_executor_is_connected_delegates_to_client() {
821 let mut backend = MockBackend::new(MockTransaction::new(QueryResultKind::Ok));
822 backend.is_open = true;
823 let client = make_client(backend);
824 let executor: Box<dyn QueryExecutor> = Box::new(client);
825 assert!(executor.is_connected());
826 }
827
828 #[tokio::test]
833 #[ignore = "requires running TypeDB server"]
834 #[cfg_attr(coverage_nightly, coverage(off))]
835 async fn integration_connect_invalid_address() {
836 let config = TypeDBSection {
837 address: "localhost:99999".to_string(),
838 database: "test".to_string(),
839 username: "admin".to_string(),
840 password: "password".to_string(),
841 };
842 let result = TypeDBClient::connect(&config).await;
843 assert!(result.is_err());
844 }
845
846 #[tokio::test]
847 #[ignore = "requires running TypeDB server"]
848 #[cfg_attr(coverage_nightly, coverage(off))]
849 async fn integration_connect_success() {
850 let config = TypeDBSection {
851 address: "localhost:1729".to_string(),
852 database: "test".to_string(),
853 username: "admin".to_string(),
854 password: "password".to_string(),
855 };
856 let result = TypeDBClient::connect(&config).await;
857 assert!(result.is_ok());
858 assert!(result.unwrap().is_connected());
859 }
860}