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, TenantId(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 = TenantId(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 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
225 TenantId(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 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
247 TenantId(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, TenantId(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 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
274 TenantId(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, TenantId(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 TenantId(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!(
310 response.tenant.id,
311 TenantId(Uuid::parse_str(TENANT_A).unwrap())
312 );
313 assert!(response.ancestors.is_empty());
314 }
315
316 #[tokio::test]
317 async fn get_ancestors_with_hierarchy() {
318 let cfg = StaticTrPluginConfig {
320 tenants: vec![
321 tenant(TENANT_A, "Root", TenantStatus::Active),
322 tenant_with_parent(TENANT_B, "Child", TENANT_A),
323 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
324 ],
325 ..Default::default()
326 };
327 let service = Service::from_config(&cfg);
328 let ctx = ctx_for_tenant(TENANT_C);
329
330 let result = service
331 .get_ancestors(
332 &ctx,
333 TenantId(Uuid::parse_str(TENANT_C).unwrap()),
334 &GetAncestorsOptions::default(),
335 )
336 .await;
337
338 assert!(result.is_ok());
339 let response = result.unwrap();
340 assert_eq!(
341 response.tenant.id,
342 TenantId(Uuid::parse_str(TENANT_C).unwrap())
343 );
344 assert_eq!(response.ancestors.len(), 2);
345 assert_eq!(
346 response.ancestors[0].id,
347 TenantId(Uuid::parse_str(TENANT_B).unwrap())
348 );
349 assert_eq!(
350 response.ancestors[1].id,
351 TenantId(Uuid::parse_str(TENANT_A).unwrap())
352 );
353 }
354
355 #[tokio::test]
356 async fn get_ancestors_with_barrier() {
357 let cfg = StaticTrPluginConfig {
359 tenants: vec![
360 tenant(TENANT_A, "Root", TenantStatus::Active),
361 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
362 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
363 ],
364 ..Default::default()
365 };
366 let service = Service::from_config(&cfg);
367 let ctx = ctx_for_tenant(TENANT_C);
368
369 let result = service
371 .get_ancestors(
372 &ctx,
373 TenantId(Uuid::parse_str(TENANT_C).unwrap()),
374 &GetAncestorsOptions::default(),
375 )
376 .await;
377
378 assert!(result.is_ok());
379 let response = result.unwrap();
380 assert_eq!(response.ancestors.len(), 1);
381 assert_eq!(
382 response.ancestors[0].id,
383 TenantId(Uuid::parse_str(TENANT_B).unwrap())
384 );
385
386 let req = GetAncestorsOptions {
388 barrier_mode: BarrierMode::Ignore,
389 };
390 let result = service
391 .get_ancestors(&ctx, TenantId(Uuid::parse_str(TENANT_C).unwrap()), &req)
392 .await;
393
394 assert!(result.is_ok());
395 let response = result.unwrap();
396 assert_eq!(response.ancestors.len(), 2);
397 }
398
399 #[tokio::test]
400 async fn get_ancestors_nonexistent() {
401 let cfg = StaticTrPluginConfig::default();
402 let service = Service::from_config(&cfg);
403 let ctx = ctx_for_tenant(TENANT_A);
404
405 let result = service
406 .get_ancestors(
407 &ctx,
408 TenantId(Uuid::parse_str(NONEXISTENT).unwrap()),
409 &GetAncestorsOptions::default(),
410 )
411 .await;
412
413 assert!(result.is_err());
414 match result.unwrap_err() {
415 TenantResolverError::TenantNotFound { .. } => {}
416 other => panic!("Expected TenantNotFound, got: {other:?}"),
417 }
418 }
419
420 #[tokio::test]
421 async fn get_ancestors_starting_tenant_is_barrier() {
422 let cfg = StaticTrPluginConfig {
425 tenants: vec![
426 tenant(TENANT_A, "Root", TenantStatus::Active),
427 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
428 ],
429 ..Default::default()
430 };
431 let service = Service::from_config(&cfg);
432 let ctx = ctx_for_tenant(TENANT_B);
433
434 let result = service
436 .get_ancestors(
437 &ctx,
438 TenantId(Uuid::parse_str(TENANT_B).unwrap()),
439 &GetAncestorsOptions::default(),
440 )
441 .await;
442
443 assert!(result.is_ok());
444 let response = result.unwrap();
445 assert_eq!(
446 response.tenant.id,
447 TenantId(Uuid::parse_str(TENANT_B).unwrap())
448 );
449 assert!(response.ancestors.is_empty());
450
451 let req = GetAncestorsOptions {
453 barrier_mode: BarrierMode::Ignore,
454 };
455 let result = service
456 .get_ancestors(&ctx, TenantId(Uuid::parse_str(TENANT_B).unwrap()), &req)
457 .await;
458
459 assert!(result.is_ok());
460 let response = result.unwrap();
461 assert_eq!(response.ancestors.len(), 1);
462 assert_eq!(
463 response.ancestors[0].id,
464 TenantId(Uuid::parse_str(TENANT_A).unwrap())
465 );
466 }
467
468 #[tokio::test]
471 async fn get_descendants_no_children() {
472 let cfg = StaticTrPluginConfig {
473 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
474 ..Default::default()
475 };
476 let service = Service::from_config(&cfg);
477 let ctx = ctx_for_tenant(TENANT_A);
478
479 let result = service
480 .get_descendants(
481 &ctx,
482 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
483 &GetDescendantsOptions::default(),
484 )
485 .await;
486
487 assert!(result.is_ok());
488 let response = result.unwrap();
489 assert_eq!(
490 response.tenant.id,
491 TenantId(Uuid::parse_str(TENANT_A).unwrap())
492 );
493 assert!(response.descendants.is_empty());
494 }
495
496 #[tokio::test]
497 async fn get_descendants_with_hierarchy() {
498 let cfg = StaticTrPluginConfig {
500 tenants: vec![
501 tenant(TENANT_A, "Root", TenantStatus::Active),
502 tenant_with_parent(TENANT_B, "Child", TENANT_A),
503 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
504 ],
505 ..Default::default()
506 };
507 let service = Service::from_config(&cfg);
508 let ctx = ctx_for_tenant(TENANT_A);
509
510 let result = service
512 .get_descendants(
513 &ctx,
514 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
515 &GetDescendantsOptions::default(),
516 )
517 .await;
518
519 assert!(result.is_ok());
520 let response = result.unwrap();
521 assert_eq!(
522 response.tenant.id,
523 TenantId(Uuid::parse_str(TENANT_A).unwrap())
524 );
525 assert_eq!(response.descendants.len(), 2);
526
527 let req = GetDescendantsOptions {
529 max_depth: Some(1),
530 ..Default::default()
531 };
532 let result = service
533 .get_descendants(&ctx, TenantId(Uuid::parse_str(TENANT_A).unwrap()), &req)
534 .await;
535
536 assert!(result.is_ok());
537 let response = result.unwrap();
538 assert_eq!(response.descendants.len(), 1);
539 assert_eq!(
540 response.descendants[0].id,
541 TenantId(Uuid::parse_str(TENANT_B).unwrap())
542 );
543 }
544
545 #[tokio::test]
546 async fn get_descendants_with_barrier() {
547 let cfg = StaticTrPluginConfig {
550 tenants: vec![
551 tenant(TENANT_A, "Root", TenantStatus::Active),
552 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
553 tenant_with_parent(TENANT_C, "Under Barrier", TENANT_B),
554 tenant_with_parent(TENANT_D, "Normal Child", TENANT_A),
555 ],
556 ..Default::default()
557 };
558 let service = Service::from_config(&cfg);
559 let ctx = ctx_for_tenant(TENANT_A);
560
561 let result = service
563 .get_descendants(
564 &ctx,
565 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
566 &GetDescendantsOptions::default(),
567 )
568 .await;
569
570 assert!(result.is_ok());
571 let response = result.unwrap();
572 assert_eq!(response.descendants.len(), 1);
573 assert_eq!(
574 response.descendants[0].id,
575 TenantId(Uuid::parse_str(TENANT_D).unwrap())
576 );
577
578 let req = GetDescendantsOptions {
580 barrier_mode: BarrierMode::Ignore,
581 ..Default::default()
582 };
583 let result = service
584 .get_descendants(&ctx, TenantId(Uuid::parse_str(TENANT_A).unwrap()), &req)
585 .await;
586
587 assert!(result.is_ok());
588 let response = result.unwrap();
589 assert_eq!(response.descendants.len(), 3);
590 }
591
592 #[tokio::test]
593 async fn get_descendants_nonexistent() {
594 let cfg = StaticTrPluginConfig::default();
595 let service = Service::from_config(&cfg);
596 let ctx = ctx_for_tenant(TENANT_A);
597
598 let result = service
599 .get_descendants(
600 &ctx,
601 TenantId(Uuid::parse_str(NONEXISTENT).unwrap()),
602 &GetDescendantsOptions::default(),
603 )
604 .await;
605
606 assert!(result.is_err());
607 match result.unwrap_err() {
608 TenantResolverError::TenantNotFound { .. } => {}
609 other => panic!("Expected TenantNotFound, got: {other:?}"),
610 }
611 }
612
613 #[tokio::test]
614 async fn get_descendants_filter_stops_traversal() {
615 let cfg = StaticTrPluginConfig {
620 tenants: vec![
621 tenant(TENANT_A, "Root", TenantStatus::Active),
622 {
623 let mut t = tenant_with_parent(TENANT_B, "Suspended", TENANT_A);
624 t.status = TenantStatus::Suspended;
625 t
626 },
627 tenant_with_parent(TENANT_C, "Child of Suspended", TENANT_B),
628 tenant_with_parent(TENANT_D, "Active Child", TENANT_A),
629 ],
630 ..Default::default()
631 };
632 let service = Service::from_config(&cfg);
633 let ctx = ctx_for_tenant(TENANT_A);
634
635 let result = service
637 .get_descendants(
638 &ctx,
639 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
640 &GetDescendantsOptions::default(),
641 )
642 .await
643 .unwrap();
644 assert_eq!(result.descendants.len(), 3);
645
646 let req = GetDescendantsOptions {
648 status: vec![TenantStatus::Active],
649 ..Default::default()
650 };
651 let result = service
652 .get_descendants(&ctx, TenantId(Uuid::parse_str(TENANT_A).unwrap()), &req)
653 .await
654 .unwrap();
655
656 assert_eq!(result.descendants.len(), 1);
657 assert_eq!(
658 result.descendants[0].id,
659 TenantId(Uuid::parse_str(TENANT_D).unwrap())
660 );
661 }
662
663 #[tokio::test]
664 async fn get_descendants_pre_order() {
665 let cfg = StaticTrPluginConfig {
670 tenants: vec![
671 tenant(TENANT_A, "Root", TenantStatus::Active),
672 tenant_with_parent(TENANT_B, "Child B", TENANT_A),
673 tenant_with_parent(TENANT_C, "Grandchild C", TENANT_B),
674 ],
675 ..Default::default()
676 };
677 let service = Service::from_config(&cfg);
678 let ctx = ctx_for_tenant(TENANT_A);
679
680 let result = service
681 .get_descendants(
682 &ctx,
683 TenantId(Uuid::parse_str(TENANT_A).unwrap()),
684 &GetDescendantsOptions::default(),
685 )
686 .await
687 .unwrap();
688
689 assert_eq!(result.descendants.len(), 2);
690 assert_eq!(
692 result.descendants[0].id,
693 TenantId(Uuid::parse_str(TENANT_B).unwrap())
694 );
695 assert_eq!(
696 result.descendants[1].id,
697 TenantId(Uuid::parse_str(TENANT_C).unwrap())
698 );
699 }
700
701 #[tokio::test]
704 async fn is_ancestor_self_returns_false() {
705 let cfg = StaticTrPluginConfig {
706 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
707 ..Default::default()
708 };
709 let service = Service::from_config(&cfg);
710 let ctx = ctx_for_tenant(TENANT_A);
711 let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
712
713 let result = service
714 .is_ancestor(&ctx, a_id, a_id, &IsAncestorOptions::default())
715 .await;
716 assert!(result.is_ok());
717 assert!(!result.unwrap());
718 }
719
720 #[tokio::test]
721 async fn is_ancestor_direct_parent() {
722 let cfg = StaticTrPluginConfig {
723 tenants: vec![
724 tenant(TENANT_A, "Root", TenantStatus::Active),
725 tenant_with_parent(TENANT_B, "Child", TENANT_A),
726 ],
727 ..Default::default()
728 };
729 let service = Service::from_config(&cfg);
730 let ctx = ctx_for_tenant(TENANT_A);
731
732 let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
733 let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
734
735 let result = service
737 .is_ancestor(&ctx, a_id, b_id, &IsAncestorOptions::default())
738 .await;
739 assert!(result.is_ok());
740 assert!(result.unwrap());
741
742 let result = service
744 .is_ancestor(&ctx, b_id, a_id, &IsAncestorOptions::default())
745 .await;
746 assert!(result.is_ok());
747 assert!(!result.unwrap());
748 }
749
750 #[tokio::test]
751 async fn is_ancestor_with_barrier() {
752 let cfg = StaticTrPluginConfig {
754 tenants: vec![
755 tenant(TENANT_A, "Root", TenantStatus::Active),
756 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
757 tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
758 ],
759 ..Default::default()
760 };
761 let service = Service::from_config(&cfg);
762 let ctx = ctx_for_tenant(TENANT_A);
763
764 let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
765 let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
766 let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());
767
768 let result = service
770 .is_ancestor(&ctx, b_id, c_id, &IsAncestorOptions::default())
771 .await;
772 assert!(result.unwrap());
773
774 let result = service
776 .is_ancestor(&ctx, a_id, c_id, &IsAncestorOptions::default())
777 .await;
778 assert!(!result.unwrap());
779
780 let req = IsAncestorOptions {
782 barrier_mode: BarrierMode::Ignore,
783 };
784 let result = service.is_ancestor(&ctx, a_id, c_id, &req).await;
785 assert!(result.unwrap());
786 }
787
788 #[tokio::test]
789 async fn is_ancestor_direct_barrier_child() {
790 let cfg = StaticTrPluginConfig {
793 tenants: vec![
794 tenant(TENANT_A, "Root", TenantStatus::Active),
795 tenant_barrier(TENANT_B, "Barrier", TENANT_A),
796 ],
797 ..Default::default()
798 };
799 let service = Service::from_config(&cfg);
800 let ctx = ctx_for_tenant(TENANT_A);
801
802 let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
803 let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
804
805 let result = service
807 .is_ancestor(&ctx, a_id, b_id, &IsAncestorOptions::default())
808 .await;
809 assert!(!result.unwrap());
810
811 let req = IsAncestorOptions {
813 barrier_mode: BarrierMode::Ignore,
814 };
815 let result = service.is_ancestor(&ctx, a_id, b_id, &req).await;
816 assert!(result.unwrap());
817 }
818
819 #[tokio::test]
820 async fn is_ancestor_nonexistent() {
821 let cfg = StaticTrPluginConfig {
822 tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
823 ..Default::default()
824 };
825 let service = Service::from_config(&cfg);
826 let ctx = ctx_for_tenant(TENANT_A);
827
828 let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
829 let nonexistent = TenantId(Uuid::parse_str(NONEXISTENT).unwrap());
830
831 let result = service
833 .is_ancestor(&ctx, nonexistent, a_id, &IsAncestorOptions::default())
834 .await;
835 assert!(result.is_err());
836
837 let result = service
839 .is_ancestor(&ctx, a_id, nonexistent, &IsAncestorOptions::default())
840 .await;
841 assert!(result.is_err());
842 }
843}