|
31 | 31 | AppConfigFragmentRepository, |
32 | 32 | ) |
33 | 33 | from ai.backend.manager.repositories.app_config_fragment.types import ( |
| 34 | + AppConfigScopeArguments, |
34 | 35 | DomainAppConfigFragmentSearchScope, |
35 | 36 | UserAppConfigFragmentSearchScope, |
36 | 37 | ) |
@@ -569,3 +570,137 @@ async def test_partial_when_one_missing( |
569 | 570 | assert [f.index for f in result.failed] == [1] |
570 | 571 | with pytest.raises(AppConfigFragmentNotFound): |
571 | 572 | await repository.get_by_id(two_fragments[0].id) |
| 573 | + |
| 574 | + |
| 575 | +class TestVisibilityConditions: |
| 576 | + async def test_public_visibility_selects_only_public( |
| 577 | + self, |
| 578 | + repository: AppConfigFragmentRepository, |
| 579 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 580 | + ) -> None: |
| 581 | + result = await repository.admin_search( |
| 582 | + BatchQuerier( |
| 583 | + pagination=OffsetPagination(limit=10, offset=0), |
| 584 | + conditions=[AppConfigFragmentConditions.by_public_visibility()], |
| 585 | + ) |
| 586 | + ) |
| 587 | + # Visibility is scope-only (name-independent): every public fragment, any name. |
| 588 | + expected = { |
| 589 | + f.id for f in fragments_across_scopes if f.scope_type is AppConfigScopeType.PUBLIC |
| 590 | + } |
| 591 | + assert {item.id for item in result.items} == expected |
| 592 | + |
| 593 | + async def test_domain_visibility_selects_only_that_domain( |
| 594 | + self, |
| 595 | + repository: AppConfigFragmentRepository, |
| 596 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 597 | + ) -> None: |
| 598 | + result = await repository.admin_search( |
| 599 | + BatchQuerier( |
| 600 | + pagination=OffsetPagination(limit=10, offset=0), |
| 601 | + conditions=[AppConfigFragmentConditions.by_domain_visibility(_DOMAIN_ID)], |
| 602 | + ) |
| 603 | + ) |
| 604 | + expected = { |
| 605 | + f.id |
| 606 | + for f in fragments_across_scopes |
| 607 | + if f.scope_type is AppConfigScopeType.DOMAIN and f.scope_id == _DOMAIN_ID |
| 608 | + } |
| 609 | + assert {item.id for item in result.items} == expected |
| 610 | + |
| 611 | + async def test_user_visibility_selects_only_that_user( |
| 612 | + self, |
| 613 | + repository: AppConfigFragmentRepository, |
| 614 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 615 | + ) -> None: |
| 616 | + result = await repository.admin_search( |
| 617 | + BatchQuerier( |
| 618 | + pagination=OffsetPagination(limit=10, offset=0), |
| 619 | + conditions=[AppConfigFragmentConditions.by_user_visibility(_USER_ID)], |
| 620 | + ) |
| 621 | + ) |
| 622 | + expected = { |
| 623 | + f.id |
| 624 | + for f in fragments_across_scopes |
| 625 | + if f.scope_type is AppConfigScopeType.USER and f.scope_id == _USER_ID |
| 626 | + } |
| 627 | + assert {item.id for item in result.items} == expected |
| 628 | + |
| 629 | + |
| 630 | +class TestApplicableFragments: |
| 631 | + async def test_one_query_returns_public_domain_user_rank_ordered( |
| 632 | + self, |
| 633 | + repository: AppConfigFragmentRepository, |
| 634 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 635 | + ) -> None: |
| 636 | + applicable = await repository.list_visible_fragments_bulk( |
| 637 | + ["theme"], |
| 638 | + AppConfigScopeArguments(domain_id=DomainID(_DOMAIN_UUID), user_id=UserID(_USER_UUID)), |
| 639 | + ) |
| 640 | + # public + the caller's domain + the caller's own user fragment, ordered by the |
| 641 | + # allow-list entries' ranks (scope-type defaults: public < domain < user). |
| 642 | + expected = [ |
| 643 | + f |
| 644 | + for f in fragments_across_scopes |
| 645 | + if f.config_name == "theme" |
| 646 | + and ( |
| 647 | + f.scope_type is AppConfigScopeType.PUBLIC |
| 648 | + or (f.scope_type is AppConfigScopeType.DOMAIN and f.scope_id == _DOMAIN_ID) |
| 649 | + or (f.scope_type is AppConfigScopeType.USER and f.scope_id == _USER_ID) |
| 650 | + ) |
| 651 | + ] |
| 652 | + assert [f.id for f in applicable] == [f.id for f in expected] |
| 653 | + assert [f.scope_type for f in applicable] == [ |
| 654 | + AppConfigScopeType.PUBLIC, |
| 655 | + AppConfigScopeType.DOMAIN, |
| 656 | + AppConfigScopeType.USER, |
| 657 | + ] |
| 658 | + |
| 659 | + async def test_unknown_config_name_returns_empty( |
| 660 | + self, |
| 661 | + repository: AppConfigFragmentRepository, |
| 662 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 663 | + ) -> None: |
| 664 | + applicable = await repository.list_visible_fragments_bulk( |
| 665 | + ["unregistered"], |
| 666 | + AppConfigScopeArguments(domain_id=DomainID(_DOMAIN_UUID), user_id=UserID(_USER_UUID)), |
| 667 | + ) |
| 668 | + assert applicable == [] |
| 669 | + |
| 670 | + async def test_bulk_returns_visible_fragments_for_all_names_ordered( |
| 671 | + self, |
| 672 | + repository: AppConfigFragmentRepository, |
| 673 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 674 | + ) -> None: |
| 675 | + applicable = await repository.list_visible_fragments_bulk( |
| 676 | + ["theme", "menu"], |
| 677 | + AppConfigScopeArguments(domain_id=DomainID(_DOMAIN_UUID), user_id=UserID(_USER_UUID)), |
| 678 | + ) |
| 679 | + # public + the caller's domain + the caller's own user fragment, for both names. |
| 680 | + expected = { |
| 681 | + f.id |
| 682 | + for f in fragments_across_scopes |
| 683 | + if f.config_name in ("theme", "menu") |
| 684 | + and ( |
| 685 | + f.scope_type is AppConfigScopeType.PUBLIC |
| 686 | + or (f.scope_type is AppConfigScopeType.DOMAIN and f.scope_id == _DOMAIN_ID) |
| 687 | + or (f.scope_type is AppConfigScopeType.USER and f.scope_id == _USER_ID) |
| 688 | + ) |
| 689 | + } |
| 690 | + assert {f.id for f in applicable} == expected |
| 691 | + # Rank-ordered globally, so each config_name's subset stays rank-ordered |
| 692 | + # (public < domain < user) for the caller to group by name and deep-merge in order. |
| 693 | + for name in ("theme", "menu"): |
| 694 | + ranks = [f.scope_type.default_rank() for f in applicable if f.config_name == name] |
| 695 | + assert ranks == sorted(ranks) |
| 696 | + |
| 697 | + async def test_bulk_empty_names_returns_empty( |
| 698 | + self, |
| 699 | + repository: AppConfigFragmentRepository, |
| 700 | + fragments_across_scopes: list[AppConfigFragmentData], |
| 701 | + ) -> None: |
| 702 | + applicable = await repository.list_visible_fragments_bulk( |
| 703 | + [], |
| 704 | + AppConfigScopeArguments(domain_id=DomainID(_DOMAIN_UUID), user_id=UserID(_USER_UUID)), |
| 705 | + ) |
| 706 | + assert applicable == [] |
0 commit comments