<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="5" skipped="1" tests="23" time="39.093" timestamp="2026-04-16T19:41:58.083386+00:00" hostname="maas-group-test-xjnc7-e2e-maas-openshift-pod"><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_create_api_key" time="0.143" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_list_api_keys" time="0.236" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyCRUD" name="test_revoke_api_key" time="0.168" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyAuthorization" name="test_admin_manage_other_users_keys" time="0.217" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyAuthorization" name="test_non_admin_cannot_access_other_users_keys" time="0.175" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_own_keys" time="0.447" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_other_user_forbidden" time="0.055" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyBulkOperations" name="test_bulk_revoke_admin_can_revoke_any_user" time="0.176" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_within_expiration_limit" time="0.058" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_at_expiration_limit" time="0.053" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_exceeds_expiration_limit" time="0.050" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_without_expiration" time="0.053" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyExpiration" name="test_create_key_with_short_expiration" time="0.053" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_api_key_model_access_success" time="1.933"><failure message="AssertionError: Expected 200, got 500: Internal Server Error.&#10;  &#10;assert 500 == 200&#10; +  where 500 = &lt;Response [500]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyModelInference object at 0x7fb43f07c6d0&gt;
model_completions_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
api_key_headers = {'Authorization': 'Bearer sk-oai-1Y4HICU0ZBVldktdK_oeHSnAE90NBT81KZxigRaxq7ZQYuLSSmRPF8hAqqoZS', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_api_key_model_access_success(
        self,
        model_completions_url: str,
        api_key_headers: dict,
        inference_model_name: str,
    ):
        """Test 11: Valid API key can access model endpoint - verify 200 response.
    
        Subscription is bound on the key at mint (see conftest ``api_key`` fixture).
        """
        r = requests.post(
            model_completions_url,
            headers=api_key_headers,
            json={
                "model": inference_model_name,
                "prompt": "Hello world",
                "max_tokens": 10,
            },
            timeout=60,
            verify=TLS_VERIFY,
        )
    
&gt;       assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}"
E       AssertionError: Expected 200, got 500: Internal Server Error.
E         
E       assert 500 == 200
E        +  where 500 = &lt;Response [500]&gt;.status_code

test/e2e/tests/test_api_keys.py:472: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_invalid_api_key_rejected" time="0.253"><failure message="AssertionError: Expected 403 for invalid key, got 500: Internal Server Error.&#10;  &#10;assert 500 == 403&#10; +  where 500 = &lt;Response [500]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyModelInference object at 0x7fb43f07cc10&gt;
model_completions_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
inference_model_name = 'facebook/opt-125m'

    def test_invalid_api_key_rejected(
        self,
        model_completions_url: str,
        inference_model_name: str,
    ):
        """Test 12: Invalid API key should be rejected with 403."""
        invalid_headers = {
            "Authorization": "Bearer sk-oai-invalid-key-12345",
            "Content-Type": "application/json",
        }
    
        r = requests.post(
            model_completions_url,
            headers=invalid_headers,
            json={
                "model": inference_model_name,
                "prompt": "Test",
                "max_tokens": 5,
            },
            timeout=30,
            verify=TLS_VERIFY,
        )
    
&gt;       assert r.status_code == 403, f"Expected 403 for invalid key, got {r.status_code}: {r.text}"
E       AssertionError: Expected 403 for invalid key, got 500: Internal Server Error.
E         
E       assert 500 == 403
E        +  where 500 = &lt;Response [500]&gt;.status_code

test/e2e/tests/test_api_keys.py:505: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_no_auth_header_rejected" time="0.063" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_revoked_api_key_rejected" time="2.617"><failure message="AssertionError: Expected 403 for revoked key, got 500: Internal Server Error.&#10;  &#10;assert 500 == 403&#10; +  where 500 = &lt;Response [500]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyModelInference object at 0x7fb43f074070&gt;
api_keys_base_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/maas-api/v1/api-keys'
model_completions_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
headers = {'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxendNWFRwWUh4SDRIR2dySVlHUlpsbmFPTVQ2ZHhuWnFuMDc5ckpSc0UifQ.e...XTD8EFEJJWBOngmvrpdkfoY_mSKjNZ8DmdqzE4Xq3dOZkDCXhMeyi2hxOpsDTNav9IdBzBiqh05dbsm0A', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_revoked_api_key_rejected(
        self,
        api_keys_base_url: str,
        model_completions_url: str,
        headers: dict,
        inference_model_name: str,
    ):
        """Test 14: Revoked API key should be rejected with 403."""
        # Create a new key
        designated = SIMULATOR_SUBSCRIPTION
        r_create = requests.post(
            api_keys_base_url,
            headers=headers,
            json={"name": "test-revoke-inference", "subscription": designated},
            timeout=30,
            verify=TLS_VERIFY,
        )
        assert r_create.status_code in (200, 201), f"Failed to create key: {r_create.text}"
        data = r_create.json()
        key = data["key"]
        key_id = data["id"]
    
        # Verify key works first
        key_headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
        r_test = requests.post(
            model_completions_url,
            headers=key_headers,
            json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
            timeout=60,
            verify=TLS_VERIFY,
        )
        # Key should work (200) or might fail for other reasons - we just need to test revocation
        initial_status = r_test.status_code
        print(f"[inference] Key before revoke: HTTP {initial_status}")
    
        # Revoke the key
        r_revoke = requests.delete(
            f"{api_keys_base_url}/{key_id}",
            headers=headers,
            timeout=30,
            verify=TLS_VERIFY,
        )
        assert r_revoke.status_code == 200, f"Failed to revoke: {r_revoke.text}"
        assert r_revoke.json().get("status") == "revoked"
    
        # Wait for revocation to propagate
        time.sleep(2)
    
        # Try to use revoked key
        r_revoked = requests.post(
            model_completions_url,
            headers=key_headers,
            json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
            timeout=30,
            verify=TLS_VERIFY,
        )
    
&gt;       assert r_revoked.status_code == 403, f"Expected 403 for revoked key, got {r_revoked.status_code}: {r_revoked.text}"
E       AssertionError: Expected 403 for revoked key, got 500: Internal Server Error.
E         
E       assert 500 == 403
E        +  where 500 = &lt;Response [500]&gt;.status_code

test/e2e/tests/test_api_keys.py:588: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyModelInference" name="test_api_key_chat_completions" time="0.221"><skipped type="pytest.skip" message="Chat completions returned 500">/workspace/source/test/e2e/tests/test_api_keys.py:624: Chat completions returned 500</skipped></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_double_revoke_returns_404" time="0.160" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_nonexistent_key_returns_404" time="0.064" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_then_create_new_key_works" time="30.583"><failure message="AssertionError: Revoked key A should be rejected within 30s, got 500&#10;assert 500 == 403&#10; +  where 500 = &lt;Response [500]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyRevocationE2E object at 0x7fb43f0743d0&gt;
api_keys_base_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/maas-api/v1/api-keys'
model_completions_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
headers = {'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxendNWFRwWUh4SDRIR2dySVlHUlpsbmFPTVQ2ZHhuWnFuMDc5ckpSc0UifQ.e...XTD8EFEJJWBOngmvrpdkfoY_mSKjNZ8DmdqzE4Xq3dOZkDCXhMeyi2hxOpsDTNav9IdBzBiqh05dbsm0A', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_revoke_then_create_new_key_works(
        self,
        api_keys_base_url: str,
        model_completions_url: str,
        headers: dict,
        inference_model_name: str,
    ):
        """After revoking a key, a newly created key should still work for inference."""
        designated = SIMULATOR_SUBSCRIPTION
    
        # Create key A
        r_a = requests.post(
            api_keys_base_url,
            headers=headers,
            json={"name": "test-revoke-remint-a", "subscription": designated},
            timeout=30,
            verify=TLS_VERIFY,
        )
        assert r_a.status_code in (200, 201), f"Failed to create key A: {r_a.text}"
        key_a = r_a.json()["key"]
        key_a_id = r_a.json()["id"]
    
        # Revoke key A
        r_revoke = requests.delete(f"{api_keys_base_url}/{key_a_id}", headers=headers, timeout=30, verify=TLS_VERIFY)
        assert r_revoke.status_code == 200
    
        # Create key B (new key after revocation)
        r_b = requests.post(
            api_keys_base_url,
            headers=headers,
            json={"name": "test-revoke-remint-b", "subscription": designated},
            timeout=30,
            verify=TLS_VERIFY,
        )
        assert r_b.status_code in (200, 201), f"Failed to create key B: {r_b.text}"
        key_b = r_b.json()["key"]
    
        # Poll until revoked key A is rejected (revocation may take time to propagate)
        max_wait = 30
        poll_interval = 0.5
        deadline = time.monotonic() + max_wait
        while True:
            remaining = deadline - time.monotonic()
            if remaining &lt;= 0:
                break
            r_a_inf = requests.post(
                model_completions_url,
                headers={"Authorization": f"Bearer {key_a}", "Content-Type": "application/json"},
                json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
                timeout=min(remaining, 10),
                verify=TLS_VERIFY,
            )
            if r_a_inf.status_code == 403:
                break
            time.sleep(poll_interval)
&gt;       assert r_a_inf.status_code == 403, (
            f"Revoked key A should be rejected within {max_wait}s, got {r_a_inf.status_code}"
        )
E       AssertionError: Revoked key A should be rejected within 30s, got 500
E       assert 500 == 403
E        +  where 500 = &lt;Response [500]&gt;.status_code

test/e2e/tests/test_api_keys.py:712: AssertionError</failure></testcase><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_individual_revoke_multiple_keys" time="0.329" /><testcase classname="test.e2e.tests.test_api_keys.TestAPIKeyRevocationE2E" name="test_revoke_keys_rejected_at_gateway" time="0.380"><failure message="AssertionError: Key should work before revoke, got 500: Internal Server Error.&#10;  &#10;assert 500 == 200&#10; +  where 500 = &lt;Response [500]&gt;.status_code">self = &lt;test_api_keys.TestAPIKeyRevocationE2E object at 0x7fb43f049520&gt;
api_keys_base_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/maas-api/v1/api-keys'
model_completions_url = 'https://maas.apps.b6eed26b-8fd3-48dc-8fce-4c8c0972e457.prod.konfluxeaas.com/llm/facebook-opt-125m-simulated/v1/completions'
headers = {'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxendNWFRwWUh4SDRIR2dySVlHUlpsbmFPTVQ2ZHhuWnFuMDc5ckpSc0UifQ.e...XTD8EFEJJWBOngmvrpdkfoY_mSKjNZ8DmdqzE4Xq3dOZkDCXhMeyi2hxOpsDTNav9IdBzBiqh05dbsm0A', 'Content-Type': 'application/json'}
inference_model_name = 'facebook/opt-125m'

    def test_revoke_keys_rejected_at_gateway(
        self,
        api_keys_base_url: str,
        model_completions_url: str,
        headers: dict,
        inference_model_name: str,
    ):
        """After individually revoking keys, they should be rejected at the gateway."""
        designated = SIMULATOR_SUBSCRIPTION
    
        # Create 3 keys, capturing plaintext and IDs
        keys = []
        for i in range(3):
            r = requests.post(
                api_keys_base_url,
                headers=headers,
                json={"name": f"test-revoke-gw-{i}", "subscription": designated},
                timeout=30,
                verify=TLS_VERIFY,
            )
            assert r.status_code in (200, 201), f"Failed to create key {i}: {r.text}"
            keys.append({"id": r.json()["id"], "key": r.json()["key"]})
    
        # Smoke-test: verify at least one key works before revocation
        r_smoke = requests.post(
            model_completions_url,
            headers={"Authorization": f"Bearer {keys[0]['key']}", "Content-Type": "application/json"},
            json={"model": inference_model_name, "prompt": "Test", "max_tokens": 5},
            timeout=60,
            verify=TLS_VERIFY,
        )
&gt;       assert r_smoke.status_code == 200, (
            f"Key should work before revoke, got {r_smoke.status_code}: {r_smoke.text}"
        )
E       AssertionError: Key should work before revoke, got 500: Internal Server Error.
E         
E       assert 500 == 200
E        +  where 500 = &lt;Response [500]&gt;.status_code

test/e2e/tests/test_api_keys.py:782: AssertionError</failure></testcase></testsuite></testsuites>