<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="5" skipped="7" tests="113" time="1095.315" timestamp="2026-06-12T19:39:20.581944+00:00" hostname="maas-group-test-krvww-e2e-maas-openshift-pod"><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_create_api_key" time="0.124" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_list_api_keys" time="0.153" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_revoke_api_key" time="0.114" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyAuthorization" name="test_admin_manage_other_users_keys" time="0.149" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyAuthorization" name="test_non_admin_cannot_access_other_users_keys" time="0.107" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_own_keys" time="0.289" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_other_user_forbidden" time="0.036" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_admin_can_revoke_any_user" time="0.109" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_within_expiration_limit" time="0.039" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_at_expiration_limit" time="0.037" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_exceeds_expiration_limit" time="0.046" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_without_expiration" time="0.041" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_with_short_expiration" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_api_key_model_access_success" time="0.135" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_invalid_api_key_rejected" time="0.026" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_no_auth_header_rejected" time="0.022" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_revoked_api_key_rejected" time="2.145" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_api_key_chat_completions" time="0.034" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_double_revoke_returns_404" time="0.112" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_nonexistent_key_returns_404" time="0.035" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_then_create_new_key_works" time="0.173" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_individual_revoke_multiple_keys" time="0.226" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_keys_rejected_at_gateway" time="0.329" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_cronjob_exists_and_configured" time="0.116" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_cleanup_networkpolicy_exists" time="0.122" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_create_ephemeral_key" time="0.120" /><testcase classname="test.e2e.tests.test_api_keys.TestEphemeralKeyCleanup" name="test_trigger_cleanup_preserves_active_keys" time="0.455" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_active_subscription" time="13.505" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_degraded_subscription" time="19.211" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_failed_subscription" time="19.338" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_create_key_for_pending_subscription" time="19.334" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionPhases" name="test_reject_key_for_unreconciled_subscription" time="22.830" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionFilter" name="test_search_filters_by_subscription" time="10.310" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeySubscriptionFilter" name="test_search_without_subscription_returns_all" time="0.198" /><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSAPIWatchNamespace" name="test_subscription_in_subscription_namespace_visible_to_api" time="0.000"><skipped type="pytest.skip" message="test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true">/workspace/source/test/e2e/tests/test_namespace_scoping.py:212: test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true</skipped></testcase><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSAPIWatchNamespace" name="test_subscription_in_another_namespace_not_visible_to_api" time="0.000"><skipped type="pytest.skip" message="test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true">/workspace/source/test/e2e/tests/test_namespace_scoping.py:245: test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true</skipped></testcase><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSControllerWatchNamespace" name="test_authpolicy_and_subscription_in_maas_subscription_namespace" time="0.000"><skipped type="pytest.skip" message="test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true">/workspace/source/test/e2e/tests/test_namespace_scoping.py:283: test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true</skipped></testcase><testcase classname="test.e2e.tests.test_namespace_scoping.TestMaaSControllerWatchNamespace" name="test_authpolicy_and_subscription_in_another_namespace" time="0.000"><skipped type="pytest.skip" message="test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true">/workspace/source/test/e2e/tests/test_namespace_scoping.py:321: test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true</skipped></testcase><testcase classname="test.e2e.tests.test_namespace_scoping.TestModelRef" name="test_auth_policy_model_ref" time="0.000"><skipped type="pytest.skip" message="test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true">/workspace/source/test/e2e/tests/test_namespace_scoping.py:378: test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true</skipped></testcase><testcase classname="test.e2e.tests.test_namespace_scoping.TestModelRef" name="test_subscription_model_ref" time="0.000"><skipped type="pytest.skip" message="test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true">/workspace/source/test/e2e/tests/test_namespace_scoping.py:454: test_namespace_scoping validates single-tenant dormant mode; skipped when ENABLE_TENANT_NAMESPACE_DISCOVERY=true</skipped></testcase><testcase classname="test.e2e.tests.test_negative_security.TestHeaderSpoofing" name="test_injected_identity_headers_ignored" time="0.091" /><testcase classname="test.e2e.tests.test_negative_security.TestHeaderSpoofing" name="test_duplicate_subscription_headers_ignored" time="0.070" /><testcase classname="test.e2e.tests.test_negative_security.TestExpiredKeyRejection" name="test_expired_key_rejected_at_gateway" time="5.076" /><testcase classname="test.e2e.tests.test_negative_security.TestCrossModelAccess" name="test_key_cannot_access_model_outside_subscription" time="0.076" /><testcase classname="test.e2e.tests.test_negative_security.TestAuthPolicyRemoval" name="test_authpolicy_deletion_revokes_access" time="0.810" /><testcase classname="test.e2e.tests.test_negative_security.TestMissingModelRef" name="test_subscription_with_nonexistent_model_ref" time="1.077" /><testcase classname="test.e2e.tests.test_negative_security.TestMissingModelRef" name="test_authpolicy_with_nonexistent_model_ref" time="0.693" /><testcase classname="test.e2e.tests.test_negative_security.TestHeaderAbuse" name="test_special_characters_in_subscription_header" time="0.200" /><testcase classname="test.e2e.tests.test_negative_security.TestWebhookValidation" name="test_subscription_rejected_in_unlabeled_namespace" time="6.715" /><testcase classname="test.e2e.tests.test_negative_security.TestWebhookValidation" name="test_authpolicy_rejected_in_unlabeled_namespace" time="6.608" /><testcase classname="test.e2e.tests.test_subscription.TestAuthEnforcement" name="test_authorized_user_gets_200" time="0.090" /><testcase classname="test.e2e.tests.test_subscription.TestAuthEnforcement" name="test_no_auth_gets_401" time="0.025" /><testcase classname="test.e2e.tests.test_subscription.TestAuthEnforcement" name="test_invalid_token_gets_403" time="0.048" /><testcase classname="test.e2e.tests.test_subscription.TestAuthEnforcement" name="test_wrong_group_gets_403" time="0.029" /><testcase classname="test.e2e.tests.test_subscription.TestAPIKeySubscriptionBinding" name="test_create_api_key_uses_highest_priority_subscription" time="0.319" /><testcase classname="test.e2e.tests.test_subscription.TestAPIKeySubscriptionBinding" name="test_create_api_key_with_explicit_simulator_subscription" time="0.078" /><testcase classname="test.e2e.tests.test_subscription.TestAPIKeySubscriptionBinding" name="test_create_api_key_nonexistent_subscription_errors" time="0.253" /><testcase classname="test.e2e.tests.test_subscription.TestSubscriptionEnforcement" name="test_subscribed_user_gets_200" time="0.040" /><testcase classname="test.e2e.tests.test_subscription.TestSubscriptionEnforcement" name="test_auth_pass_no_subscription_gets_403" time="16.424" /><testcase classname="test.e2e.tests.test_subscription.TestSubscriptionEnforcement" name="test_rate_limit_exhaustion_gets_429" time="25.646" /><testcase classname="test.e2e.tests.test_subscription.TestSubscriptionEnforcement" name="test_models_endpoint_exempt_from_rate_limiting" time="25.272" /><testcase classname="test.e2e.tests.test_subscription.TestMultipleSubscriptionsPerModel" name="test_user_in_one_of_two_subscriptions_gets_200" time="8.399" /><testcase classname="test.e2e.tests.test_subscription.TestMultipleAuthPoliciesPerModel" name="test_two_auth_policies_or_logic" time="16.821" /><testcase classname="test.e2e.tests.test_subscription.TestMultipleAuthPoliciesPerModel" name="test_delete_one_auth_policy_other_still_works" time="24.549" /><testcase classname="test.e2e.tests.test_subscription.TestCascadeDeletion" name="test_delete_subscription_rebuilds_trlp" time="8.535" /><testcase classname="test.e2e.tests.test_subscription.TestCascadeDeletion" name="test_trlp_persists_during_multi_subscription_deletion" time="33.345" /><testcase classname="test.e2e.tests.test_subscription.TestCascadeDeletion" name="test_delete_last_subscription_denies_access" time="8.630" /><testcase classname="test.e2e.tests.test_subscription.TestCascadeDeletion" name="test_unconfigured_model_denied_by_gateway_auth" time="0.482" /><testcase classname="test.e2e.tests.test_subscription.TestOrderingEdgeCases" name="test_subscription_before_auth_policy" time="27.685" /><testcase classname="test.e2e.tests.test_subscription.TestManagedAnnotation" name="test_authpolicy_managed_false_prevents_update" time="8.249"><skipped type="pytest.skip" message="gateway-only mode: per-model AuthPolicy is not created">/workspace/source/test/e2e/tests/test_subscription.py:1040: gateway-only mode: per-model AuthPolicy is not created</skipped></testcase><testcase classname="test.e2e.tests.test_subscription.TestManagedAnnotation" name="test_trlp_managed_false_prevents_update" time="20.810" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_with_both_access_and_subscription_gets_200" time="9.664" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_with_access_but_no_subscription_gets_403" time="17.212" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_with_subscription_but_no_access_gets_403" time="9.645" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_single_subscription_auto_selects" time="17.570" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_multiple_subscriptions_separate_keys_gets_200" time="17.527" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_mint_api_key_denied_for_inaccessible_subscription" time="17.738" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_group_based_access_gets_200" time="17.101" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_group_based_auth_but_no_subscription_gets_403" time="17.231" /><testcase classname="test.e2e.tests.test_subscription.TestE2ESubscriptionFlow" name="test_e2e_group_based_subscription_but_no_auth_gets_403" time="9.310" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_subscription_active_status_with_valid_model" time="9.215" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_subscription_failed_status_with_missing_model" time="8.781" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_authpolicy_active_status_with_valid_model" time="8.768" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_authpolicy_failed_status_with_missing_model" time="8.785" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_subscription_degraded_status_with_partial_models" time="9.140" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_subscription_degraded_trlp_blocks_inference" time="93.553" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_authpolicy_degraded_status_with_partial_models" time="8.788" /><testcase classname="test.e2e.tests.test_subscription.TestStatusReporting" name="test_subscription_status_transitions_on_model_deletion" time="22.220" /><testcase classname="test.e2e.tests.test_subscription.TestDegradedSubscriptionFiltering" name="test_degraded_healthy_model_allows_inference" time="19.234" /><testcase classname="test.e2e.tests.test_subscription.TestDegradedSubscriptionFiltering" name="test_failed_subscription_blocks_inference" time="19.502" /><testcase classname="test.e2e.tests.test_subscription.TestDegradedSubscriptionFiltering" name="test_models_endpoint_with_degraded_subscription_api_key" time="19.252" /><testcase classname="test.e2e.tests.test_subscription.TestDegradedSubscriptionFiltering" name="test_models_endpoint_with_degraded_subscription_kube_token" time="19.249" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_single_subscription_auto_select" time="42.233" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_explicit_subscription_header" time="16.768" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_empty_subscription_header_value" time="8.433" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_models_filtered_by_subscription" time="8.833" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_deduplication_same_model_multiple_refs" time="17.242" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_different_modelrefs_same_model_id" time="17.274" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_multiple_distinct_models_in_subscription" time="21.528"><failure message="AssertionError: Expected to find both distinct models {'test/e2e-distinct-model', 'test/e2e-distinct-model-2'}, but got {'test/e2e-distinct-model'}&#10;assert {'test/e2e-distinct-model'} == {'test/e2e-di...inct-model-2'}&#10;  &#10;  Extra items in the right set:&#10;  'test/e2e-distinct-model-2'&#10;  &#10;  Full diff:&#10;    {&#10;        'test/e2e-distinct-model',&#10;  -     'test/e2e-distinct-model-2',&#10;    }">self = &lt;test_models_endpoint.TestModelsEndpoint object at 0x7f6f379f4130&gt;

    def test_multiple_distinct_models_in_subscription(self):
        """
        Test 8: Multiple distinct models should return exactly 2 entries (1 per unique ID).
    
        Uses pre-deployed models (both known to not have backend duplication issues):
        - DISTINCT_MODEL_REF (simulated-distinct) serving "test/e2e-distinct-model"
        - DISTINCT_MODEL_2_REF (simulated-distinct-2) serving "test/e2e-distinct-model-2"
    
        Creates a subscription with both models. The API should return exactly 2 entries
        (one for each distinct model ID), with no duplicates.
    
        This test validates that when backend models don't have duplication bugs, the
        API correctly returns one entry per distinct model ID.
        """
        log.info("Test 8: Multiple distinct models should return 2 entries")
    
        sa_name = "e2e-models-distinct-sa"
        sa_ns = "default"
        maas_ns = _ns()
        subscription_name = "e2e-distinct-models-subscription"
        auth_policy_name = "e2e-distinct-models-auth"
        api_key = None
    
        try:
            # Create SA
            sa_token = _create_sa_token(sa_name, namespace=sa_ns)
            sa_user = _sa_to_user(sa_name, namespace=sa_ns)
    
            # Create auth policy with both distinct models
            log.info(f"Creating auth policy with {DISTINCT_MODEL_REF} and {DISTINCT_MODEL_2_REF}")
            auth_policy_cr = {
                "apiVersion": "maas.opendatahub.io/v1alpha1",
                "kind": "MaaSAuthPolicy",
                "metadata": {
                    "name": auth_policy_name,
                    "namespace": maas_ns,
                },
                "spec": {
                    "modelRefs": [
                        {"name": DISTINCT_MODEL_REF, "namespace": MODEL_NAMESPACE},
                        {"name": DISTINCT_MODEL_2_REF, "namespace": MODEL_NAMESPACE},
                    ],
                    "subjects": {
                        "users": [sa_user],
                        "groups": [],
                    },
                },
            }
            subprocess.run(
                ["oc", "apply", "-f", "-"],
                input=json.dumps(auth_policy_cr),
                text=True,
                check=True,
            )
    
            # Create subscription with both distinct models
            log.info(f"Creating subscription with {DISTINCT_MODEL_REF} and {DISTINCT_MODEL_2_REF}")
            subscription_cr = {
                "apiVersion": "maas.opendatahub.io/v1alpha1",
                "kind": "MaaSSubscription",
                "metadata": {
                    "name": subscription_name,
                    "namespace": maas_ns,
                },
                "spec": {
                    "owner": {
                        "users": [sa_user],
                        "groups": [],
                    },
                    "modelRefs": [
                        {
                            "name": DISTINCT_MODEL_REF,
                            "namespace": MODEL_NAMESPACE,
                            "tokenRateLimits": [{"limit": 100, "window": "1m"}],
                        },
                        {
                            "name": DISTINCT_MODEL_2_REF,
                            "namespace": MODEL_NAMESPACE,
                            "tokenRateLimits": [{"limit": 100, "window": "1m"}],
                        },
                    ],
                },
            }
            subprocess.run(
                ["oc", "apply", "-f", "-"],
                input=json.dumps(subscription_cr),
                text=True,
                check=True,
            )
    
            # Wait for subscription to reconcile before creating API key
            _wait_for_maas_subscription_phase(subscription_name, namespace=maas_ns)
    
            # Create API key bound to our test subscription
            api_key = _create_api_key(sa_token, name="e2e-distinct-models-test-key", subscription=subscription_name)
    
            _wait_reconcile()
    
            # Query /v1/models
            log.info(f"Querying /v1/models with subscription: {subscription_name}")
            r = requests.get(
                f"{_maas_api_url()}/v1/models",
                headers={
                    "Authorization": f"Bearer {api_key}",
                    "x-maas-subscription": subscription_name,
                },
                timeout=TIMEOUT,
                verify=TLS_VERIFY,
            )
    
            assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}"
            data = r.json()
            models = data.get("data") or []
    
            assert isinstance(models, list), "Models should be a list"
    
            # Get model IDs from response
            model_ids = [m["id"] for m in models]
            unique_ids = set(model_ids)
    
            log.info(f"#x1F4CA API Response: {len(models)} total model(s), {len(unique_ids)} unique ID(s)")
            log.info(f"   Model IDs: {model_ids}")
            log.info(f"   Unique IDs: {unique_ids}")
            log.info(f"   Subscription had: 2 modelRefs ({DISTINCT_MODEL_REF}, {DISTINCT_MODEL_2_REF})")
    
            # Verify we got BOTH expected model IDs
            expected_ids = {DISTINCT_MODEL_ID, DISTINCT_MODEL_2_ID}
&gt;           assert unique_ids == expected_ids, \
                f"Expected to find both distinct models {expected_ids}, but got {unique_ids}"
E               AssertionError: Expected to find both distinct models {'test/e2e-distinct-model', 'test/e2e-distinct-model-2'}, but got {'test/e2e-distinct-model'}
E               assert {'test/e2e-distinct-model'} == {'test/e2e-di...inct-model-2'}
E                 
E                 Extra items in the right set:
E                 'test/e2e-distinct-model-2'
E                 
E                 Full diff:
E                   {
E                       'test/e2e-distinct-model',
E                 -     'test/e2e-distinct-model-2',
E                   }

test/e2e/tests/test_models_endpoint.py:1048: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_user_token_returns_all_models" time="14.453" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_user_token_with_subscription_header_filters" time="17.093" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_empty_model_list" time="10.990" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_response_schema_matches_openapi" time="8.425" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_model_metadata_preserved" time="8.417" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_api_key_scoped_to_subscription" time="17.232" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_api_key_with_deleted_subscription_403" time="25.259"><failure message="AssertionError: Expected 403 for API key with deleted subscription, got 500: {&quot;error&quot;:&quot;Exception thrown while generating token&quot;,&quot;exceptionCode&quot;:&quot;AUTH_FAILURE&quot;,&quot;refId&quot;:&quot;001&quot;}&#10;assert 500 == 403&#10; +  where 500 = &lt;Response [500]&gt;.status_code">self = &lt;test_models_endpoint.TestModelsEndpoint object at 0x7f6f3752f700&gt;

    def test_api_key_with_deleted_subscription_403(self):
        """
        Test: API key bound to a subscription that was deleted after key creation.
    
        This tests an edge case where an API key was minted with a subscription,
        but that subscription is later deleted. The gateway injects X-MaaS-Subscription
        from the key, but the subscription no longer exists.
    
        Expected: HTTP 403 with error type: permission_error
        """
        ns = _ns()
        auth_policy_name = "e2e-api-key-deleted-sub-auth"
        subscription_name = "e2e-api-key-deleted-sub"
        sa_name = "e2e-api-key-deleted-sub-sa"
        api_key = None
    
        try:
            # Create service account and token
            oc_token = _create_sa_token(sa_name, namespace=ns)
            sa_user = _sa_to_user(sa_name, namespace=ns)
    
            # Create test resources
            _create_test_auth_policy(auth_policy_name, MODEL_REF, users=[sa_user])
            _create_test_subscription(subscription_name, MODEL_REF, users=[sa_user])
    
            # Wait for subscription to reconcile before creating API key
            _wait_for_maas_subscription_phase(subscription_name, namespace=ns)
    
            # Create API key bound to subscription
            api_key = _create_api_key(oc_token, name=f"{sa_name}-key", subscription=subscription_name)
    
            _wait_reconcile()
    
            # Delete the subscription (simulating deletion after key creation)
            log.info(f"Deleting subscription {subscription_name} after API key creation")
            _delete_cr("maassubscription", subscription_name, namespace=ns)
            _wait_reconcile()
    
            # Query with API key (gateway injects deleted subscription name)
            log.info("Querying /v1/models with API key bound to deleted subscription")
            r = requests.get(
                f"{_maas_api_url()}/v1/models",
                headers={
                    "Authorization": f"Bearer {api_key}",
                },
                timeout=TIMEOUT,
                verify=TLS_VERIFY,
            )
    
            # Should return 403 because subscription doesn't exist
&gt;           assert r.status_code == 403, \
                f"Expected 403 for API key with deleted subscription, got {r.status_code}: {r.text}"
E               AssertionError: Expected 403 for API key with deleted subscription, got 500: {"error":"Exception thrown while generating token","exceptionCode":"AUTH_FAILURE","refId":"001"}
E               assert 500 == 403
E                +  where 500 = &lt;Response [500]&gt;.status_code

test/e2e/tests/test_models_endpoint.py:1529: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_api_key_with_inaccessible_subscription_403" time="17.395" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_invalid_subscription_header_403" time="17.089" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_access_denied_to_subscription_403" time="17.755" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_api_key_ignores_subscription_header" time="22.274" /><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_multiple_api_keys_different_subscriptions" time="22.308"><failure message="AssertionError: Key2 should see test/e2e-distinct-model-2 from e2e-multi-keys-sub2&#10;assert 'test/e2e-distinct-model-2' in set()">self = &lt;test_models_endpoint.TestModelsEndpoint object at 0x7f6f3752f2b0&gt;

    def test_multiple_api_keys_different_subscriptions(self):
        """
        Test: Multiple API keys each bound to different subscriptions.
    
        Creates two API keys from the same user, each explicitly bound to a different
        subscription. Verifies each key returns only its bound subscription's models.
    
        Expected: Each API key returns models only from its bound subscription.
        """
        sa_name = "e2e-multi-keys-sa"
        sa_ns = "default"
        maas_ns = _ns()
        sub1_name = "e2e-multi-keys-sub1"
        sub2_name = "e2e-multi-keys-sub2"
        auth1_name = "e2e-multi-keys-auth1"
        auth2_name = "e2e-multi-keys-auth2"
        api_key1 = None
        api_key2 = None
    
        try:
            # Create SA
            sa_token = _create_sa_token(sa_name, namespace=sa_ns)
            sa_user = _sa_to_user(sa_name, namespace=sa_ns)
    
            # Create two subscriptions with different models
            log.info(f"Creating subscription 1 with {DISTINCT_MODEL_REF}")
            _create_test_auth_policy(auth1_name, DISTINCT_MODEL_REF, users=[sa_user])
            _create_test_subscription(sub1_name, DISTINCT_MODEL_REF, users=[sa_user])
    
            log.info(f"Creating subscription 2 with {DISTINCT_MODEL_2_REF}")
            _create_test_auth_policy(auth2_name, DISTINCT_MODEL_2_REF, users=[sa_user])
            _create_test_subscription(sub2_name, DISTINCT_MODEL_2_REF, users=[sa_user])
    
            # Wait for both subscriptions to reconcile before creating API keys
            _wait_for_maas_subscription_phase(sub1_name, namespace=maas_ns)
            _wait_for_maas_subscription_phase(sub2_name, namespace=maas_ns)
    
            # Create two API keys, each bound to a different subscription
            log.info(f"Creating API key 1 bound to {sub1_name}")
            api_key1_response = _request_with_gateway_retry(
                requests.post,
                f"{_maas_api_url()}/v1/api-keys",
                headers={"Authorization": f"Bearer {sa_token}", "Content-Type": "application/json"},
                json={"name": "key1", "subscription": sub1_name},
            )
            assert api_key1_response.status_code in (200, 201)
            api_key1 = api_key1_response.json().get("key")
            bound_sub1 = api_key1_response.json().get("subscription")
            assert bound_sub1 == sub1_name, f"Key 1 should be bound to {sub1_name}, got {bound_sub1}"
    
            log.info(f"Creating API key 2 bound to {sub2_name}")
            api_key2_response = _request_with_gateway_retry(
                requests.post,
                f"{_maas_api_url()}/v1/api-keys",
                headers={"Authorization": f"Bearer {sa_token}", "Content-Type": "application/json"},
                json={"name": "key2", "subscription": sub2_name},
            )
            assert api_key2_response.status_code in (200, 201)
            api_key2 = api_key2_response.json().get("key")
            bound_sub2 = api_key2_response.json().get("subscription")
            assert bound_sub2 == sub2_name, f"Key 2 should be bound to {sub2_name}, got {bound_sub2}"
    
            _wait_reconcile()
    
            # Test key1 - should return models from sub1 only
            log.info(f"Testing API key 1 (bound to {sub1_name})")
            r1 = requests.get(
                f"{_maas_api_url()}/v1/models",
                headers={"Authorization": f"Bearer {api_key1}"},
                timeout=TIMEOUT,
                verify=TLS_VERIFY,
            )
            assert r1.status_code == 200, f"Expected 200 for key1, got {r1.status_code}: {r1.text}"
            models1 = r1.json().get("data") or []
            model_ids1 = {m["id"] for m in models1}
    
            assert DISTINCT_MODEL_ID in model_ids1, f"Key1 should see {DISTINCT_MODEL_ID} from {sub1_name}"
            assert DISTINCT_MODEL_2_ID not in model_ids1, f"Key1 should NOT see {DISTINCT_MODEL_2_ID} from {sub2_name}"
    
            # Test key2 - should return models from sub2 only
            log.info(f"Testing API key 2 (bound to {sub2_name})")
            r2 = requests.get(
                f"{_maas_api_url()}/v1/models",
                headers={"Authorization": f"Bearer {api_key2}"},
                timeout=TIMEOUT,
                verify=TLS_VERIFY,
            )
            assert r2.status_code == 200, f"Expected 200 for key2, got {r2.status_code}: {r2.text}"
            models2 = r2.json().get("data") or []
            model_ids2 = {m["id"] for m in models2}
    
&gt;           assert DISTINCT_MODEL_2_ID in model_ids2, f"Key2 should see {DISTINCT_MODEL_2_ID} from {sub2_name}"
E           AssertionError: Key2 should see test/e2e-distinct-model-2 from e2e-multi-keys-sub2
E           assert 'test/e2e-distinct-model-2' in set()

test/e2e/tests/test_models_endpoint.py:1913: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_service_account_token_multiple_subs_no_header" time="12.362"><failure message="AssertionError: Should see test/e2e-distinct-model-2 from e2e-sa-multi-no-hdr-sub2 (user access)&#10;assert 'test/e2e-distinct-model-2' in {'facebook/opt-125m', 'test/e2e-distinct-model'}">self = &lt;test_models_endpoint.TestModelsEndpoint object at 0x7f6f379f4be0&gt;

    def test_service_account_token_multiple_subs_no_header(self):
        """
        Test: K8s token with access to multiple subscriptions returns all models (no header).
    
        Creates a service account with access to two subscriptions (via group and user).
        When querying without x-maas-subscription header, should return models from
        all accessible subscriptions.
    
        Expected: HTTP 200 with models from both subscriptions.
        """
        sa_name = "e2e-sa-multi-subs-no-header"
        sa_ns = "default"
        maas_ns = _ns()
        sub1_name = "e2e-sa-multi-no-hdr-sub1"
        sub2_name = "e2e-sa-multi-no-hdr-sub2"
        auth1_name = "e2e-sa-multi-no-hdr-auth1"
        auth2_name = "e2e-sa-multi-no-hdr-auth2"
    
        try:
            # Create SA
            sa_token = _create_sa_token(sa_name, namespace=sa_ns)
            sa_user = _sa_to_user(sa_name, namespace=sa_ns)
    
            # Create two subscriptions with different models
            # Sub1: Access via system:authenticated group
            log.info(f"Creating subscription 1 with {DISTINCT_MODEL_REF} (group: system:authenticated)")
            _create_test_auth_policy(auth1_name, DISTINCT_MODEL_REF, groups=["system:authenticated"])
            _create_test_subscription(sub1_name, DISTINCT_MODEL_REF, groups=["system:authenticated"])
    
            # Sub2: Access via specific user
            log.info(f"Creating subscription 2 with {DISTINCT_MODEL_2_REF} (user: {sa_user})")
            _create_test_auth_policy(auth2_name, DISTINCT_MODEL_2_REF, users=[sa_user])
            _create_test_subscription(sub2_name, DISTINCT_MODEL_2_REF, users=[sa_user])
    
            _wait_for_maas_auth_policy_phase(auth1_name)
            _wait_for_maas_auth_policy_phase(auth2_name)
            _wait_for_maas_subscription_phase(sub1_name)
            _wait_for_maas_subscription_phase(sub2_name)
    
            # Query with K8s token (no header)
            log.info("Querying /v1/models with K8s token (no header) - should return models from both subscriptions")
            r = _request_with_gateway_retry(
                requests.get,
                f"{_maas_api_url()}/v1/models",
                headers={"Authorization": f"Bearer {sa_token}"},
            )
    
            assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}"
            data = r.json()
            models = data.get("data") or []
            model_ids = {m["id"] for m in models}
    
            # Should see models from BOTH subscriptions
            assert DISTINCT_MODEL_ID in model_ids, \
                f"Should see {DISTINCT_MODEL_ID} from {sub1_name} (group access)"
&gt;           assert DISTINCT_MODEL_2_ID in model_ids, \
                f"Should see {DISTINCT_MODEL_2_ID} from {sub2_name} (user access)"
E               AssertionError: Should see test/e2e-distinct-model-2 from e2e-sa-multi-no-hdr-sub2 (user access)
E               assert 'test/e2e-distinct-model-2' in {'facebook/opt-125m', 'test/e2e-distinct-model'}

test/e2e/tests/test_models_endpoint.py:1981: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_models_endpoint.TestModelsEndpoint" name="test_service_account_token_multiple_subs_with_header" time="14.442"><failure message="AssertionError: Should see test/e2e-distinct-model-2 from e2e-sa-multi-hdr-sub2&#10;assert 'test/e2e-distinct-model-2' in set()">self = &lt;test_models_endpoint.TestModelsEndpoint object at 0x7f6f379f4370&gt;

    def test_service_account_token_multiple_subs_with_header(self):
        """
        Test: K8s token with access to multiple subscriptions filters by header.
    
        Creates a service account with access to two subscriptions. When querying
        with x-maas-subscription header, should return models from only the specified
        subscription.
    
        Expected: HTTP 200 with models from only the specified subscription.
        """
        sa_name = "e2e-sa-multi-subs-with-header"
        sa_ns = "default"
        maas_ns = _ns()
        sub1_name = "e2e-sa-multi-hdr-sub1"
        sub2_name = "e2e-sa-multi-hdr-sub2"
        auth1_name = "e2e-sa-multi-hdr-auth1"
        auth2_name = "e2e-sa-multi-hdr-auth2"
    
        try:
            # Create SA
            sa_token = _create_sa_token(sa_name, namespace=sa_ns)
            sa_user = _sa_to_user(sa_name, namespace=sa_ns)
    
            # Create two subscriptions with different models
            log.info(f"Creating subscription 1 with {DISTINCT_MODEL_REF}")
            _create_test_auth_policy(auth1_name, DISTINCT_MODEL_REF, users=[sa_user])
            _create_test_subscription(sub1_name, DISTINCT_MODEL_REF, users=[sa_user])
    
            log.info(f"Creating subscription 2 with {DISTINCT_MODEL_2_REF}")
            _create_test_auth_policy(auth2_name, DISTINCT_MODEL_2_REF, users=[sa_user])
            _create_test_subscription(sub2_name, DISTINCT_MODEL_2_REF, users=[sa_user])
    
            _wait_for_maas_auth_policy_phase(auth1_name)
            _wait_for_maas_auth_policy_phase(auth2_name)
            _wait_for_maas_subscription_phase(sub1_name)
            _wait_for_maas_subscription_phase(sub2_name)
    
            # Query with K8s token and header specifying sub1
            log.info(f"Querying /v1/models with K8s token and header: {sub1_name}")
            r1 = _request_with_gateway_retry(
                requests.get,
                f"{_maas_api_url()}/v1/models",
                headers={
                    "Authorization": f"Bearer {sa_token}",
                    "x-maas-subscription": sub1_name,
                },
            )
    
            assert r1.status_code == 200, f"Expected 200, got {r1.status_code}: {r1.text}"
            models1 = r1.json().get("data") or []
            model_ids1 = {m["id"] for m in models1}
    
            # Should see only models from sub1
            assert DISTINCT_MODEL_ID in model_ids1, f"Should see {DISTINCT_MODEL_ID} from {sub1_name}"
            assert DISTINCT_MODEL_2_ID not in model_ids1, f"Should NOT see {DISTINCT_MODEL_2_ID} from {sub2_name}"
    
            # Query with K8s token and header specifying sub2
            log.info(f"Querying /v1/models with K8s token and header: {sub2_name}")
            r2 = _request_with_gateway_retry(
                requests.get,
                f"{_maas_api_url()}/v1/models",
                headers={
                    "Authorization": f"Bearer {sa_token}",
                    "x-maas-subscription": sub2_name,
                },
            )
    
            assert r2.status_code == 200, f"Expected 200, got {r2.status_code}: {r2.text}"
            models2 = r2.json().get("data") or []
            model_ids2 = {m["id"] for m in models2}
    
            # Should see only models from sub2
&gt;           assert DISTINCT_MODEL_2_ID in model_ids2, f"Should see {DISTINCT_MODEL_2_ID} from {sub2_name}"
E           AssertionError: Should see test/e2e-distinct-model-2 from e2e-sa-multi-hdr-sub2
E           assert 'test/e2e-distinct-model-2' in set()

test/e2e/tests/test_models_endpoint.py:2066: AssertionError</failure></testcase></testsuite></testsuites>