Skip to content

Commit 06285b3

Browse files
jrhynessclaude
andauthored
test: add E2E tests for /v1/models endpoint (opendatahub-io#509)
## Description <!--- Describe your changes in detail --> This PR adds comprehensive E2E test coverage for the `/v1/models` endpoint that validates subscription-aware model filtering and access control. **Test Coverage:** - **Single subscription auto-selection**: Users with one subscription can list models without providing `x-maas-subscription` header - **Explicit subscription header**: Users with multiple subscriptions can select which subscription to use - **Multiple subscriptions requiring header**: Validates proper 403 error when header is missing - **Subscription filtering and validation**: Ensures models are filtered by subscription access - **Model deduplication scenarios**: Tests handling of multiple modelRefs serving the same model - **Empty model lists**: Validates response when user has no accessible models - **Response schema validation**: Ensures API responses match OpenAI-compatible schema - **Error cases**: Tests 401 (unauthenticated) and 403 (forbidden) scenarios **Sample Resources:** - Added two distinct test models (`e2e-distinct-simulated`, `e2e-distinct-2-simulated`) to enable multi-model subscription testing - Added corresponding MaaSModelRef kustomize configurations for test deployment **Known Issues (marked as xfail):** Three tests expose a known bug where `/v1/models` returns all accessible models instead of filtering by the selected subscription. These are marked with `@pytest.mark.xfail` and will pass once the filtering bug is fixed: - `test_single_subscription_auto_select` - `test_explicit_subscription_header` - `test_multiple_distinct_models_in_subscription` ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> **Test Execution:** ```bash cd test/e2e ./scripts/prow_run_smoke_test.sh Results: - 12 tests passed - 3 tests xfailed (expected failures due to known bug) - 0 tests failed - All tests run in isolated namespaces with proper cleanup Environment: - OpenShift cluster with MaaS components deployed - maas-controller watching models-as-a-service namespace - Models deployed in llm namespace - Kuadrant/Authorino for authorization - PostgreSQL backend for API key storage Validation: - Verified subscription-based access control works correctly - Verified API key authentication flow - Verified OpenAI-compatible response format - Verified proper error handling for unauthorized access ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added sample Kustomize and model manifests (including MaaSModelRef) to demonstrate distinct multi-model deployments and updated top-level samples to include distinct and distinct-2 entries. * **Tests** * Added comprehensive end-to-end tests for the /v1/models endpoint covering subscription-aware filtering, multi-model scenarios, auth/error paths and deduplication checks; updated test runner to include them and added env vars to support multi-model testing. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a7739f6 commit 06285b3

File tree

23 files changed

+1762
-16
lines changed

23 files changed

+1762
-16
lines changed

docs/samples/maas-system/kustomization.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
# per tier. Deploy all at once so dependencies resolve correctly.
33
# - free: system:authenticated, 100 tokens/min
44
# - premium: premium-user, 1000 tokens/min
5-
# - unconfigured: no auth/subscription (e2e tests validate 403)
65
apiVersion: kustomize.config.k8s.io/v1beta1
76
kind: Kustomization
87

98
resources:
109
- free
1110
- premium
12-
- unconfigured
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
namespace: llm
5+
6+
resources:
7+
- model.yaml
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
apiVersion: serving.kserve.io/v1alpha1
2+
kind: LLMInferenceService
3+
metadata:
4+
name: e2e-distinct-2-simulated
5+
spec:
6+
model:
7+
uri: hf://sshleifer/tiny-gpt2 # ~2MB test model, simulator ignores it anyway
8+
name: test/e2e-distinct-model-2
9+
replicas: 1
10+
router:
11+
route: {}
12+
# Connect to MaaS-enabled gateway
13+
gateway:
14+
refs:
15+
- name: maas-default-gateway
16+
namespace: openshift-ingress
17+
template:
18+
containers:
19+
- name: main
20+
image: "ghcr.io/llm-d/llm-d-inference-sim:v0.7.1"
21+
imagePullPolicy: Always
22+
command: ["/app/llm-d-inference-sim"]
23+
args:
24+
- --port
25+
- "8000"
26+
- --model
27+
- test/e2e-distinct-model-2
28+
- --mode
29+
- random
30+
- --ssl-certfile
31+
- /var/run/kserve/tls/tls.crt
32+
- --ssl-keyfile
33+
- /var/run/kserve/tls/tls.key
34+
env:
35+
- name: POD_NAME
36+
valueFrom:
37+
fieldRef:
38+
apiVersion: v1
39+
fieldPath: metadata.name
40+
- name: POD_NAMESPACE
41+
valueFrom:
42+
fieldRef:
43+
apiVersion: v1
44+
fieldPath: metadata.namespace
45+
ports:
46+
- name: https
47+
containerPort: 8000
48+
protocol: TCP
49+
livenessProbe:
50+
httpGet:
51+
path: /health
52+
port: https
53+
scheme: HTTPS
54+
resources:
55+
requests:
56+
cpu: 100m
57+
memory: 256Mi
58+
limits:
59+
cpu: 500m
60+
memory: 512Mi
61+
readinessProbe:
62+
httpGet:
63+
path: /ready
64+
port: https
65+
scheme: HTTPS
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
namespace: llm
5+
6+
resources:
7+
- model.yaml
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
apiVersion: serving.kserve.io/v1alpha1
2+
kind: LLMInferenceService
3+
metadata:
4+
name: e2e-distinct-simulated
5+
spec:
6+
model:
7+
uri: hf://sshleifer/tiny-gpt2 # ~2MB test model, simulator ignores it anyway
8+
name: test/e2e-distinct-model
9+
replicas: 1
10+
router:
11+
route: {}
12+
# Connect to MaaS-enabled gateway
13+
gateway:
14+
refs:
15+
- name: maas-default-gateway
16+
namespace: openshift-ingress
17+
template:
18+
containers:
19+
- name: main
20+
image: "ghcr.io/llm-d/llm-d-inference-sim:v0.7.1"
21+
imagePullPolicy: Always
22+
command: ["/app/llm-d-inference-sim"]
23+
args:
24+
- --port
25+
- "8000"
26+
- --model
27+
- test/e2e-distinct-model
28+
- --mode
29+
- random
30+
- --ssl-certfile
31+
- /var/run/kserve/tls/tls.crt
32+
- --ssl-keyfile
33+
- /var/run/kserve/tls/tls.key
34+
env:
35+
- name: POD_NAME
36+
valueFrom:
37+
fieldRef:
38+
apiVersion: v1
39+
fieldPath: metadata.name
40+
- name: POD_NAMESPACE
41+
valueFrom:
42+
fieldRef:
43+
apiVersion: v1
44+
fieldPath: metadata.namespace
45+
ports:
46+
- name: https
47+
containerPort: 8000
48+
protocol: TCP
49+
livenessProbe:
50+
httpGet:
51+
path: /health
52+
port: https
53+
scheme: HTTPS
54+
resources:
55+
requests:
56+
cpu: 100m
57+
memory: 256Mi
58+
limits:
59+
cpu: 500m
60+
memory: 512Mi
61+
readinessProbe:
62+
httpGet:
63+
path: /ready
64+
port: https
65+
scheme: HTTPS

test/e2e/README.md

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,67 @@ cd test/e2e
7575

7676
Results: `test/e2e/reports/api-keys-report.html`
7777

78+
### Models Endpoint Tests
79+
80+
Tests for the `/v1/models` endpoint that validate subscription-aware model filtering:
81+
82+
```bash
83+
cd test/e2e
84+
source .venv/bin/activate
85+
86+
# Run all /v1/models tests
87+
pytest tests/test_models_endpoint.py -v
88+
89+
# Run specific test scenarios
90+
pytest tests/test_models_endpoint.py::TestModelsEndpoint::test_single_subscription_auto_select -v
91+
pytest tests/test_models_endpoint.py::TestModelsEndpoint::test_multi_subscription_without_header_403 -v
92+
```
93+
94+
**Test Coverage (15 tests):**
95+
96+
*Success Cases (HTTP 200) - 11 tests:*
97+
- ✅ Single subscription auto-select (no header required)
98+
- ✅ Explicit subscription header with multiple subscriptions
99+
- ✅ Empty subscription header value behavior
100+
- ✅ Subscription header case insensitivity (HTTP standard)
101+
- ✅ Models correctly filtered by subscription
102+
- ⚠️ Same modelRef listed twice should deduplicate (xfail - returns 2+ duplicates instead of 1)
103+
- ⚠️ Different modelRefs serving SAME model ID should deduplicate (xfail - returns 3+ duplicates instead of 1)
104+
- ✅ Different modelRefs with different IDs returns 2 entries (uses non-duplicating simulators)
105+
- ⚠️ Empty model list returns [] not null (xfail - currently returns null)
106+
- ✅ Response schema matches OpenAPI specification
107+
- ✅ Model metadata (url, ready, created, owned_by) preserved
108+
109+
*Error Cases (HTTP 403) - 3 tests:*
110+
- ✅ Multiple subscriptions without header → 403 permission_error
111+
- ✅ Invalid subscription header → 403 permission_error
112+
- ✅ Access denied to subscription → 403 permission_error
113+
114+
*Error Cases (HTTP 401) - 1 test:*
115+
- ✅ Unauthenticated request → 401 authentication_error
116+
117+
**What's Being Validated:**
118+
The `/v1/models` endpoint implements subscription-aware model filtering:
119+
- Users with a single subscription don't need to specify `x-maas-subscription` header
120+
- Users with multiple subscriptions must use `x-maas-subscription` header to select
121+
- Returns proper error responses (403/401) with `permission_error`/`authentication_error` types
122+
- Models are correctly filtered to only show those from the specified subscription
123+
- Response structure matches OpenAPI schema: `{"object": "list", "data": [...]}`
124+
- HTTP header handling follows standards (case-insensitive)
125+
- Model metadata is accurately preserved from source
126+
78127
## CI Integration
79128

80129
These tests run automatically in CI via:
81-
- **Prow**: `./test/e2e/scripts/prow_run_smoke_test.sh` (includes subscription tests)
130+
- **Prow**: `./test/e2e/scripts/prow_run_smoke_test.sh` (includes all E2E tests)
82131
- **GitHub Actions**: Can be integrated into `.github/workflows/` as needed
83132

84133
The `prow_run_smoke_test.sh` script:
85134
1. Deploys MaaS platform and dependencies
86135
2. Deploys test models (free + premium simulators)
87-
3. Runs subscription controller tests (`test_subscription.py`)
136+
3. Runs E2E tests:
137+
- API key management (`test_api_keys.py`)
138+
- Subscription controller (`test_subscription.py`)
139+
- Models endpoint (`test_models_endpoint.py`)
88140
4. Runs deployment validation and token metadata verification
89141
5. Collects artifacts (HTML/XML reports, logs) to `ARTIFACT_DIR`

test/e2e/fixtures/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# E2E Test Fixtures
2+
3+
This directory contains kustomizations for end-to-end testing that combine public samples with test-only fixtures.
4+
5+
## Contents
6+
7+
### Public Samples (from `docs/samples/maas-system/`)
8+
- **free**: `system:authenticated` group, 100 tokens/min
9+
- **premium**: `premium-user` group, 1000 tokens/min
10+
11+
### Test-Only Fixtures
12+
- **unconfigured**: Model with no MaaSAuthPolicy or MaaSSubscription (validates that gateway denies access with 403)
13+
- **distinct**: First distinct model serving `test/e2e-distinct-model` (validates multiple distinct models in subscriptions)
14+
- **distinct-2**: Second distinct model serving `test/e2e-distinct-model-2` (validates multiple distinct models in subscriptions)
15+
16+
## Usage
17+
18+
### For E2E Tests (CI)
19+
20+
```bash
21+
# Deploy all fixtures (public samples + test-only)
22+
kustomize build test/e2e/fixtures | kubectl apply -f -
23+
```
24+
25+
### For Manual Testing
26+
27+
To deploy only the public samples without test fixtures, use:
28+
29+
```bash
30+
# Public samples only (free + premium)
31+
kustomize build docs/samples/maas-system | kubectl apply -f -
32+
```
33+
34+
## Note
35+
36+
⚠️ **Do not use this kustomization for production or sample installations.** It includes test-only models that are designed to validate edge cases and should not be deployed in normal usage scenarios. For sample installations, use `docs/samples/maas-system/` instead.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
resources:
5+
- llm
6+
- maas
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
resources:
5+
- ../../../../../docs/samples/models/e2e-distinct-2-simulated

docs/samples/maas-system/unconfigured/maas/kustomization.yaml renamed to test/e2e/fixtures/distinct-2/maas/kustomization.yaml

File renamed without changes.

0 commit comments

Comments
 (0)