1use async_trait::async_trait;
6use modkit_security::SecurityContext;
7use tenant_resolver_sdk::{
8 GetAncestorsOptions, GetAncestorsResponse, GetDescendantsOptions, GetDescendantsResponse,
9 GetTenantsOptions, IsAncestorOptions, TenantId, TenantInfo, TenantResolverError,
10 TenantResolverPluginClient, matches_status,
11};
12
13use super::service::Service;
14
15#[async_trait]
16impl TenantResolverPluginClient for Service {
17 async fn get_tenant(
18 &self,
19 _ctx: &SecurityContext,
20 id: TenantId,
21 ) -> Result<TenantInfo, TenantResolverError> {
22 self.tenants
23 .get(&id)
24 .cloned()
25 .ok_or(TenantResolverError::TenantNotFound { tenant_id: id })
26 }
27
28 async fn get_tenants(
29 &self,
30 _ctx: &SecurityContext,
31 ids: &[TenantId],
32 options: &GetTenantsOptions,
33 ) -> Result<Vec<TenantInfo>, TenantResolverError> {
34 let mut result = Vec::new();
35 let mut seen = std::collections::HashSet::new();
36
37 for id in ids {
38 if !seen.insert(id) {
39 continue; }
41 if let Some(tenant) = self.tenants.get(id)
42 && matches_status(tenant, &options.status)
43 {
44 result.push(tenant.clone());
45 }
46 }
48
49 Ok(result)
50 }
51
52 async fn get_ancestors(
53 &self,
54 _ctx: &SecurityContext,
55 id: TenantId,
56 options: &GetAncestorsOptions,
57 ) -> Result<GetAncestorsResponse, TenantResolverError> {
58 let tenant = self
60 .tenants
61 .get(&id)
62 .ok_or(TenantResolverError::TenantNotFound { tenant_id: id })?;
63
64 let ancestors = self.collect_ancestors(id, options.barrier_mode);
66
67 Ok(GetAncestorsResponse {
68 tenant: tenant.into(),
69 ancestors,
70 })
71 }
72
73 async fn get_descendants(
74 &self,
75 _ctx: &SecurityContext,
76 id: TenantId,
77 options: &GetDescendantsOptions,
78 ) -> Result<GetDescendantsResponse, TenantResolverError> {
79 let tenant = self
81 .tenants
82 .get(&id)
83 .ok_or(TenantResolverError::TenantNotFound { tenant_id: id })?;
84
85 let descendants =
89 self.collect_descendants(id, &options.status, options.barrier_mode, options.max_depth);
90
91 Ok(GetDescendantsResponse {
92 tenant: tenant.into(),
93 descendants,
94 })
95 }
96
97 async fn is_ancestor(
98 &self,
99 _ctx: &SecurityContext,
100 ancestor_id: TenantId,
101 descendant_id: TenantId,
102 options: &IsAncestorOptions,
103 ) -> Result<bool, TenantResolverError> {
104 self.is_ancestor_of(ancestor_id, descendant_id, options.barrier_mode)
105 }
106}
107
108#[cfg(test)]
109#[cfg_attr(coverage_nightly, coverage(off))]
110mod tests {
111 use super::*;
112 use crate::config::{StaticTrPluginConfig, TenantConfig};
113 use tenant_resolver_sdk::{BarrierMode, TenantStatus};
114 use uuid::Uuid;
115
116 fn tenant(id: &str, name: &str, status: TenantStatus) -> TenantConfig {
118 TenantConfig {
119 id: Uuid::parse_str(id).unwrap(),
120 name: name.to_owned(),
121 status,
122 tenant_type: None,
123 parent_id: None,
124 self_managed: false,
125 }
126 }
127
128 fn tenant_with_parent(id: &str, name: &str, parent: &str) -> TenantConfig {
129 TenantConfig {
130 id: Uuid::parse_str(id).unwrap(),
131 name: name.to_owned(),
132 status: TenantStatus::Active,
133 tenant_type: None,
134 parent_id: Some(Uuid::parse_str(parent).unwrap()),
135 self_managed: false,
136 }
137 }
138
139 fn tenant_barrier(id: &str, name: &str, parent: &str) -> TenantConfig {
140 TenantConfig {
141 id: Uuid::parse_str(id).unwrap(),
142 name: name.to_owned(),
143 status: TenantStatus::Active,
144 tenant_type: None,
145 parent_id: Some(Uuid::parse_str(parent).unwrap()),
146 self_managed: true,
147 }
148 }
149
150 fn ctx_for_tenant(tenant_id: &str) -> SecurityContext {
152 SecurityContext::builder()
153 .subject_id(Uuid::new_v4())
154 .subject_tenant_id(Uuid::parse_str(tenant_id).unwrap())
155 .build()
156 .unwrap()
157 }
158
159 const TENANT_A: &str = "11111111-1111-1111-1111-111111111111";
161 const TENANT_B: &str = "22222222-2222-2222-2222-222222222222";
162 const TENANT_C: &str = "33333333-3333-3333-3333-333333333333";
163 const TENANT_D: &str = "44444444-4444-4444-4444-444444444444";
164 const NONEXISTENT: &str = "99999999-9999-9999-9999-999999999999";
165
166 #[tokio::test]
169 async fn get_tenant_existing() {
170 let cfg = StaticTrPluginConfig {
171 tenants: vec![tenant(TENANT_A, "Tenant A", TenantStatus::Active)],
172 ..Default::default()
173 };
174 let service = Service::from_config(&cfg);
175 let ctx = ctx_for_tenant(TENANT_A);
176
177 let result = service
178 .get_tenant(&ctx, Uuid::parse_str(TENANT_A).unwrap())
179 .await;
180
181 assert!(result.is_ok());
182 let info = result.unwrap();
183 assert_eq!(info.name, "Tenant A");
184 assert_eq!(info.status, TenantStatus::Active);
185 }
186
187 #[tokio::test]
188 async fn get_tenant_nonexistent() {
189 let cfg = StaticTrPluginConfig {
190 tenants: vec![tenant(TENANT_A, "Tenant A", TenantStatus::Active)],
191 ..Default::default()
192 };
193 let service = Service::from_config(&cfg);
194 let ctx = ctx_for_tenant(TENANT_A);
195 let nonexistent_id = Uuid::parse_str(NONEXISTENT).unwrap();
196
197 let result = service.get_tenant(&ctx, nonexistent_id).await;
198
199 assert!(result.is_err());
200 match result.unwrap_err() {
201 TenantResolverError::TenantNotFound { tenant_id } => {
202 assert_eq!(tenant_id, nonexistent_id);
203 }
204 other => panic!("Expected TenantNotFound, got: {other:?}"),
205 }
206 }
207
208 #[tokio::test]
211 async fn get_tenants_all_found() {
212 let cfg = StaticTrPluginConfig {
213 tenants: vec![
214 tenant(TENANT_A, "A", TenantStatus::Active),
215 tenant(TENANT_B, "B", TenantStatus::Active),
216 tenant(TENANT_C, "C", TenantStatus::Suspended),
217 ],
218 ..Default::default()
219 };
220 let service = Service::from_config(&cfg);
221 let ctx = ctx_for_tenant(TENANT_A);
222
223 let ids = vec![
224 Uuid::parse_str(TENANT_A).unwrap(),
225 Uuid::parse_str(TENANT_B).unwrap(),
226 ];
227
228 let result = service
229 .get_tenants(&ctx, &ids, &GetTenantsOptions::default())
230 .await;
231 assert!(result.is_ok());
232 let tenants = result.unwrap();
233 assert_eq!(tenants.len(), 2);
234 }
235
236 #[tokio::test]
237 async fn get_tenants_some_missing() {
238 let cfg = StaticTrPluginConfig {
239 tenants: vec![tenant(TENANT_A, "A", TenantStatus::Active)],
240 ..Default::default()
241 };
242 let service = Service::from_config(&cfg);
243 let ctx = ctx_for_tenant(TENANT_A);
244
245 let ids = vec![
246 Uuid::parse_str(TENANT_A).unwrap(),
247 Uuid::parse_str(NONEXISTENT).unwrap(), ];
249
250 let result = service
251 .get_tenants(&ctx, &ids, &GetTenantsOptions::default())
252 .await;
253 assert!(result.is_ok());
254 let tenants = result.unwrap();
255 assert_eq!(tenants.len(), 1);
257 assert_eq!(tenants[0].id, Uuid::parse_str(TENANT_A).unwrap());
258 }
259
260 #[tokio::test]
261 async fn get_tenants_with_filter() {
262 let cfg = StaticTrPluginConfig {
263 tenants: vec![
264 tenant(TENANT_A, "A", TenantStatus::Active),
265 tenant(TENANT_B, "B", TenantStatus::Suspended),
266 ],
267 ..Default::default()
268 };
269 let service = Service::from_config(&cfg);
270 let ctx = ctx_for_tenant(TENANT_A);
271
272 let ids = vec![
273 Uuid::parse_str(TENANT_A).unwrap(),
274 Uuid::parse_str(TENANT_B).unwrap(),
275 ];
276
277 let opts = GetTenantsOptions {
278 status: vec![TenantStatus::Active],
279 };
280 let result = service.get_tenants(&ctx, &ids, &opts).await;
281 assert!(result.is_ok());
282 let tenants = result.unwrap();
283 assert_eq!(tenants.len(), 1);
285 assert_eq!(tenants[0].id, Uuid::parse_str(TENANT_A).unwrap());
286 }
287
288 #[tokio::test]
291 async fn get_ancestors_root_tenant() {
292 let cfg = StaticTrPluginConfig {
293 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
294 ..Default::default()
295 };
296 let service = Service::from_config(&cfg);
297 let ctx = ctx_for_tenant(TENANT_A);
298
299 let result = service
300 .get_ancestors(
301 &ctx,
302 Uuid::parse_str(TENANT_A).unwrap(),
303 &GetAncestorsOptions::default(),
304 )
305 .await;
306
307 assert!(result.is_ok());
308 let response = result.unwrap();
309 assert_eq!(response.tenant.id, Uuid::parse_str(TENANT_A).unwrap());
310 assert!(response.ancestors.is_empty());
311 }
312
313 #[tokio::test]
314 async fn get_ancestors_with_hierarchy() {
315 let cfg = StaticTrPluginConfig {
317 tenants: vec![
318 tenant(TENANT_A, "Root", TenantStatus::Active),
319 tenant_with_parent(TENANT_B, "Child", TENANT_A),
320 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
321 ],
322 ..Default::default()
323 };
324 let service = Service::from_config(&cfg);
325 let ctx = ctx_for_tenant(TENANT_C);
326
327 let result = service
328 .get_ancestors(
329 &ctx,
330 Uuid::parse_str(TENANT_C).unwrap(),
331 &GetAncestorsOptions::default(),
332 )
333 .await;
334
335 assert!(result.is_ok());
336 let response = result.unwrap();
337 assert_eq!(response.tenant.id, Uuid::parse_str(TENANT_C).unwrap());
338 assert_eq!(response.ancestors.len(), 2);
339 assert_eq!(response.ancestors[0].id, Uuid::parse_str(TENANT_B).unwrap());
340 assert_eq!(response.ancestors[1].id, Uuid::parse_str(TENANT_A).unwrap());
341 }
342
343 #[tokio::test]
344 async fn get_ancestors_with_barrier() {
345 let cfg = StaticTrPluginConfig {
347 tenants: vec![
348 tenant(TENANT_A, "Root", TenantStatus::Active),
349 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
350 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
351 ],
352 ..Default::default()
353 };
354 let service = Service::from_config(&cfg);
355 let ctx = ctx_for_tenant(TENANT_C);
356
357 let result = service
359 .get_ancestors(
360 &ctx,
361 Uuid::parse_str(TENANT_C).unwrap(),
362 &GetAncestorsOptions::default(),
363 )
364 .await;
365
366 assert!(result.is_ok());
367 let response = result.unwrap();
368 assert_eq!(response.ancestors.len(), 1);
369 assert_eq!(response.ancestors[0].id, Uuid::parse_str(TENANT_B).unwrap());
370
371 let req = GetAncestorsOptions {
373 barrier_mode: BarrierMode::Ignore,
374 };
375 let result = service
376 .get_ancestors(&ctx, Uuid::parse_str(TENANT_C).unwrap(), &req)
377 .await;
378
379 assert!(result.is_ok());
380 let response = result.unwrap();
381 assert_eq!(response.ancestors.len(), 2);
382 }
383
384 #[tokio::test]
385 async fn get_ancestors_nonexistent() {
386 let cfg = StaticTrPluginConfig::default();
387 let service = Service::from_config(&cfg);
388 let ctx = ctx_for_tenant(TENANT_A);
389
390 let result = service
391 .get_ancestors(
392 &ctx,
393 Uuid::parse_str(NONEXISTENT).unwrap(),
394 &GetAncestorsOptions::default(),
395 )
396 .await;
397
398 assert!(result.is_err());
399 match result.unwrap_err() {
400 TenantResolverError::TenantNotFound { .. } => {}
401 other => panic!("Expected TenantNotFound, got: {other:?}"),
402 }
403 }
404
405 #[tokio::test]
406 async fn get_ancestors_starting_tenant_is_barrier() {
407 let cfg = StaticTrPluginConfig {
410 tenants: vec![
411 tenant(TENANT_A, "Root", TenantStatus::Active),
412 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
413 ],
414 ..Default::default()
415 };
416 let service = Service::from_config(&cfg);
417 let ctx = ctx_for_tenant(TENANT_B);
418
419 let result = service
421 .get_ancestors(
422 &ctx,
423 Uuid::parse_str(TENANT_B).unwrap(),
424 &GetAncestorsOptions::default(),
425 )
426 .await;
427
428 assert!(result.is_ok());
429 let response = result.unwrap();
430 assert_eq!(response.tenant.id, Uuid::parse_str(TENANT_B).unwrap());
431 assert!(response.ancestors.is_empty());
432
433 let req = GetAncestorsOptions {
435 barrier_mode: BarrierMode::Ignore,
436 };
437 let result = service
438 .get_ancestors(&ctx, Uuid::parse_str(TENANT_B).unwrap(), &req)
439 .await;
440
441 assert!(result.is_ok());
442 let response = result.unwrap();
443 assert_eq!(response.ancestors.len(), 1);
444 assert_eq!(response.ancestors[0].id, Uuid::parse_str(TENANT_A).unwrap());
445 }
446
447 #[tokio::test]
450 async fn get_descendants_no_children() {
451 let cfg = StaticTrPluginConfig {
452 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
453 ..Default::default()
454 };
455 let service = Service::from_config(&cfg);
456 let ctx = ctx_for_tenant(TENANT_A);
457
458 let result = service
459 .get_descendants(
460 &ctx,
461 Uuid::parse_str(TENANT_A).unwrap(),
462 &GetDescendantsOptions::default(),
463 )
464 .await;
465
466 assert!(result.is_ok());
467 let response = result.unwrap();
468 assert_eq!(response.tenant.id, Uuid::parse_str(TENANT_A).unwrap());
469 assert!(response.descendants.is_empty());
470 }
471
472 #[tokio::test]
473 async fn get_descendants_with_hierarchy() {
474 let cfg = StaticTrPluginConfig {
476 tenants: vec![
477 tenant(TENANT_A, "Root", TenantStatus::Active),
478 tenant_with_parent(TENANT_B, "Child", TENANT_A),
479 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
480 ],
481 ..Default::default()
482 };
483 let service = Service::from_config(&cfg);
484 let ctx = ctx_for_tenant(TENANT_A);
485
486 let result = service
488 .get_descendants(
489 &ctx,
490 Uuid::parse_str(TENANT_A).unwrap(),
491 &GetDescendantsOptions::default(),
492 )
493 .await;
494
495 assert!(result.is_ok());
496 let response = result.unwrap();
497 assert_eq!(response.tenant.id, Uuid::parse_str(TENANT_A).unwrap());
498 assert_eq!(response.descendants.len(), 2);
499
500 let req = GetDescendantsOptions {
502 max_depth: Some(1),
503 ..Default::default()
504 };
505 let result = service
506 .get_descendants(&ctx, Uuid::parse_str(TENANT_A).unwrap(), &req)
507 .await;
508
509 assert!(result.is_ok());
510 let response = result.unwrap();
511 assert_eq!(response.descendants.len(), 1);
512 assert_eq!(
513 response.descendants[0].id,
514 Uuid::parse_str(TENANT_B).unwrap()
515 );
516 }
517
518 #[tokio::test]
519 async fn get_descendants_with_barrier() {
520 let cfg = StaticTrPluginConfig {
523 tenants: vec![
524 tenant(TENANT_A, "Root", TenantStatus::Active),
525 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
526 tenant_with_parent(TENANT_C, "Under Barrier", TENANT_B),
527 tenant_with_parent(TENANT_D, "Normal Child", TENANT_A),
528 ],
529 ..Default::default()
530 };
531 let service = Service::from_config(&cfg);
532 let ctx = ctx_for_tenant(TENANT_A);
533
534 let result = service
536 .get_descendants(
537 &ctx,
538 Uuid::parse_str(TENANT_A).unwrap(),
539 &GetDescendantsOptions::default(),
540 )
541 .await;
542
543 assert!(result.is_ok());
544 let response = result.unwrap();
545 assert_eq!(response.descendants.len(), 1);
546 assert_eq!(
547 response.descendants[0].id,
548 Uuid::parse_str(TENANT_D).unwrap()
549 );
550
551 let req = GetDescendantsOptions {
553 barrier_mode: BarrierMode::Ignore,
554 ..Default::default()
555 };
556 let result = service
557 .get_descendants(&ctx, Uuid::parse_str(TENANT_A).unwrap(), &req)
558 .await;
559
560 assert!(result.is_ok());
561 let response = result.unwrap();
562 assert_eq!(response.descendants.len(), 3);
563 }
564
565 #[tokio::test]
566 async fn get_descendants_nonexistent() {
567 let cfg = StaticTrPluginConfig::default();
568 let service = Service::from_config(&cfg);
569 let ctx = ctx_for_tenant(TENANT_A);
570
571 let result = service
572 .get_descendants(
573 &ctx,
574 Uuid::parse_str(NONEXISTENT).unwrap(),
575 &GetDescendantsOptions::default(),
576 )
577 .await;
578
579 assert!(result.is_err());
580 match result.unwrap_err() {
581 TenantResolverError::TenantNotFound { .. } => {}
582 other => panic!("Expected TenantNotFound, got: {other:?}"),
583 }
584 }
585
586 #[tokio::test]
587 async fn get_descendants_filter_stops_traversal() {
588 let cfg = StaticTrPluginConfig {
593 tenants: vec![
594 tenant(TENANT_A, "Root", TenantStatus::Active),
595 {
596 let mut t = tenant_with_parent(TENANT_B, "Suspended", TENANT_A);
597 t.status = TenantStatus::Suspended;
598 t
599 },
600 tenant_with_parent(TENANT_C, "Child of Suspended", TENANT_B),
601 tenant_with_parent(TENANT_D, "Active Child", TENANT_A),
602 ],
603 ..Default::default()
604 };
605 let service = Service::from_config(&cfg);
606 let ctx = ctx_for_tenant(TENANT_A);
607
608 let result = service
610 .get_descendants(
611 &ctx,
612 Uuid::parse_str(TENANT_A).unwrap(),
613 &GetDescendantsOptions::default(),
614 )
615 .await
616 .unwrap();
617 assert_eq!(result.descendants.len(), 3);
618
619 let req = GetDescendantsOptions {
621 status: vec![TenantStatus::Active],
622 ..Default::default()
623 };
624 let result = service
625 .get_descendants(&ctx, Uuid::parse_str(TENANT_A).unwrap(), &req)
626 .await
627 .unwrap();
628
629 assert_eq!(result.descendants.len(), 1);
630 assert_eq!(result.descendants[0].id, Uuid::parse_str(TENANT_D).unwrap());
631 }
632
633 #[tokio::test]
634 async fn get_descendants_pre_order() {
635 let cfg = StaticTrPluginConfig {
640 tenants: vec![
641 tenant(TENANT_A, "Root", TenantStatus::Active),
642 tenant_with_parent(TENANT_B, "Child B", TENANT_A),
643 tenant_with_parent(TENANT_C, "Grandchild C", TENANT_B),
644 ],
645 ..Default::default()
646 };
647 let service = Service::from_config(&cfg);
648 let ctx = ctx_for_tenant(TENANT_A);
649
650 let result = service
651 .get_descendants(
652 &ctx,
653 Uuid::parse_str(TENANT_A).unwrap(),
654 &GetDescendantsOptions::default(),
655 )
656 .await
657 .unwrap();
658
659 assert_eq!(result.descendants.len(), 2);
660 assert_eq!(result.descendants[0].id, Uuid::parse_str(TENANT_B).unwrap());
662 assert_eq!(result.descendants[1].id, Uuid::parse_str(TENANT_C).unwrap());
663 }
664
665 #[tokio::test]
668 async fn is_ancestor_self_returns_false() {
669 let cfg = StaticTrPluginConfig {
670 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
671 ..Default::default()
672 };
673 let service = Service::from_config(&cfg);
674 let ctx = ctx_for_tenant(TENANT_A);
675 let a_id = Uuid::parse_str(TENANT_A).unwrap();
676
677 let result = service
678 .is_ancestor(&ctx, a_id, a_id, &IsAncestorOptions::default())
679 .await;
680 assert!(result.is_ok());
681 assert!(!result.unwrap());
682 }
683
684 #[tokio::test]
685 async fn is_ancestor_direct_parent() {
686 let cfg = StaticTrPluginConfig {
687 tenants: vec![
688 tenant(TENANT_A, "Root", TenantStatus::Active),
689 tenant_with_parent(TENANT_B, "Child", TENANT_A),
690 ],
691 ..Default::default()
692 };
693 let service = Service::from_config(&cfg);
694 let ctx = ctx_for_tenant(TENANT_A);
695
696 let a_id = Uuid::parse_str(TENANT_A).unwrap();
697 let b_id = Uuid::parse_str(TENANT_B).unwrap();
698
699 let result = service
701 .is_ancestor(&ctx, a_id, b_id, &IsAncestorOptions::default())
702 .await;
703 assert!(result.is_ok());
704 assert!(result.unwrap());
705
706 let result = service
708 .is_ancestor(&ctx, b_id, a_id, &IsAncestorOptions::default())
709 .await;
710 assert!(result.is_ok());
711 assert!(!result.unwrap());
712 }
713
714 #[tokio::test]
715 async fn is_ancestor_with_barrier() {
716 let cfg = StaticTrPluginConfig {
718 tenants: vec![
719 tenant(TENANT_A, "Root", TenantStatus::Active),
720 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
721 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
722 ],
723 ..Default::default()
724 };
725 let service = Service::from_config(&cfg);
726 let ctx = ctx_for_tenant(TENANT_A);
727
728 let a_id = Uuid::parse_str(TENANT_A).unwrap();
729 let b_id = Uuid::parse_str(TENANT_B).unwrap();
730 let c_id = Uuid::parse_str(TENANT_C).unwrap();
731
732 let result = service
734 .is_ancestor(&ctx, b_id, c_id, &IsAncestorOptions::default())
735 .await;
736 assert!(result.unwrap());
737
738 let result = service
740 .is_ancestor(&ctx, a_id, c_id, &IsAncestorOptions::default())
741 .await;
742 assert!(!result.unwrap());
743
744 let req = IsAncestorOptions {
746 barrier_mode: BarrierMode::Ignore,
747 };
748 let result = service.is_ancestor(&ctx, a_id, c_id, &req).await;
749 assert!(result.unwrap());
750 }
751
752 #[tokio::test]
753 async fn is_ancestor_direct_barrier_child() {
754 let cfg = StaticTrPluginConfig {
757 tenants: vec![
758 tenant(TENANT_A, "Root", TenantStatus::Active),
759 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
760 ],
761 ..Default::default()
762 };
763 let service = Service::from_config(&cfg);
764 let ctx = ctx_for_tenant(TENANT_A);
765
766 let a_id = Uuid::parse_str(TENANT_A).unwrap();
767 let b_id = Uuid::parse_str(TENANT_B).unwrap();
768
769 let result = service
771 .is_ancestor(&ctx, a_id, b_id, &IsAncestorOptions::default())
772 .await;
773 assert!(!result.unwrap());
774
775 let req = IsAncestorOptions {
777 barrier_mode: BarrierMode::Ignore,
778 };
779 let result = service.is_ancestor(&ctx, a_id, b_id, &req).await;
780 assert!(result.unwrap());
781 }
782
783 #[tokio::test]
784 async fn is_ancestor_nonexistent() {
785 let cfg = StaticTrPluginConfig {
786 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
787 ..Default::default()
788 };
789 let service = Service::from_config(&cfg);
790 let ctx = ctx_for_tenant(TENANT_A);
791
792 let a_id = Uuid::parse_str(TENANT_A).unwrap();
793 let nonexistent = Uuid::parse_str(NONEXISTENT).unwrap();
794
795 let result = service
797 .is_ancestor(&ctx, nonexistent, a_id, &IsAncestorOptions::default())
798 .await;
799 assert!(result.is_err());
800
801 let result = service
803 .is_ancestor(&ctx, a_id, nonexistent, &IsAncestorOptions::default())
804 .await;
805 assert!(result.is_err());
806 }
807}