forked from eclipse-csi/pia
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_main.py
More file actions
239 lines (188 loc) · 7.42 KB
/
Copy pathtest_main.py
File metadata and controls
239 lines (188 loc) · 7.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
"""Tests for api module."""
from unittest.mock import Mock, patch
import jwt
import pytest
from fastapi.testclient import TestClient
from pia.dependencytrack import DependencyTrackError
from pia.oidc import TokenVerificationError
@pytest.fixture
def setup_env(test_projects_file):
"""Provide temporary projects file and assign env variables."""
import os
os.environ["PIA_DEPENDENCY_TRACK_API_KEY"] = "test-secret"
os.environ["PIA_PROJECTS_PATH"] = str(test_projects_file)
yield
del os.environ["PIA_DEPENDENCY_TRACK_API_KEY"]
del os.environ["PIA_PROJECTS_PATH"]
@pytest.fixture
def client(setup_env):
"""Create FastAPI test client with projects loaded."""
from pia.main import app
with TestClient(app) as test_client:
yield test_client
@pytest.fixture
def valid_request_data():
"""Valid request data for SBOM upload."""
return {
"product_name": "test-product",
"product_version": "1.0.0",
"bom": "bom",
}
@pytest.fixture
def auth_header():
"""Valid Authorization header with Bearer token."""
return {"Authorization": "Bearer eyJhbGciOiJSUzI1NiJ9.test.token"}
class TestUploadSBOMEndpoint:
"""Tests for /v1/upload/sbom endpoint."""
@patch("pia.main.dependencytrack.upload_sbom")
@patch("pia.main.oidc.verify_token")
@patch("pia.main.jwt.decode")
def test_upload_success(
self,
mock_decode,
mock_verify,
mock_upload,
client,
valid_request_data,
auth_header,
):
"""Test successful SBOM upload."""
# Mock token decode
mock_decode.return_value = {
"iss": "https://token.actions.githubusercontent.com"
}
# Mock token verification
mock_verify.return_value = {
"iss": "https://token.actions.githubusercontent.com",
"repository": "eclipse-test/repo",
}
# Mock DT upload
mock_dt_response = Mock()
mock_dt_response.status_code = 200
mock_dt_response.content = b"content"
mock_upload.return_value = mock_dt_response
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
# Assert response was correctly crafted from dt response
assert response.status_code == 200
assert response.content == b"content"
assert response.headers["content-type"] == "application/json"
def test_upload_invalid_json(self, client, auth_header):
"""Test error with invalid JSON."""
response = client.post(
"/v1/upload/sbom",
content=b"not-json",
headers=auth_header,
)
assert response.status_code == 422
assert b"JSON" in response.content or b"json" in response.content
def test_upload_missing_field(self, client, valid_request_data, auth_header):
"""Test error with missing required field."""
del valid_request_data["product_name"]
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
assert response.status_code == 422
assert b"product_name" in response.content
def test_upload_missing_authorization_header(self, client, valid_request_data):
"""Test error when Authorization header is missing."""
response = client.post("/v1/upload/sbom", json=valid_request_data)
assert response.status_code == 422
def test_upload_invalid_authorization_header(self, client, valid_request_data):
"""Test error when Authorization header format is invalid."""
response = client.post(
"/v1/upload/sbom",
json=valid_request_data,
headers={"Authorization": "Basic invalid"},
)
assert response.status_code == 401
assert b"Invalid Authorization header format" in response.content
@patch("pia.main.jwt.decode")
def test_upload_issuer_not_allowed(
self, mock_decode, client, valid_request_data, auth_header
):
"""Test error when issuer not allowed for project."""
mock_decode.return_value = {"iss": "https://wrong-issuer.com"}
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
assert response.status_code == 401
assert b"Issuer not allowed" in response.content
@patch("pia.main.jwt.decode")
def test_upload_token_decode_fails(
self, mock_decode, client, valid_request_data, auth_header
):
"""Test error when initial token decode fails."""
mock_decode.side_effect = jwt.PyJWTError()
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
assert response.status_code == 401
assert b"Invalid token" in response.content
@patch("pia.main.jwt.decode")
@patch("pia.main.oidc.verify_token")
def test_upload_token_verification_fails(
self, mock_verify, mock_decode, client, valid_request_data, auth_header
):
"""Test error when token verification fails."""
mock_decode.return_value = {
"iss": "https://token.actions.githubusercontent.com"
}
mock_verify.side_effect = TokenVerificationError()
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
assert response.status_code == 401
assert b"Token verification failed" in response.content
@patch("pia.main.jwt.decode")
@patch("pia.main.oidc.verify_token")
def test_upload_no_matching_project(
self, mock_verify, mock_decode, client, valid_request_data, auth_header
):
"""Test error when no project matches the verified token claims."""
mock_decode.return_value = {
"iss": "https://token.actions.githubusercontent.com"
}
# Claims don't match any project's required claims
mock_verify.return_value = {
"iss": "https://token.actions.githubusercontent.com",
"repository": "wrong/repo",
}
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
assert response.status_code == 401
assert b"No matching project found for token claims" in response.content
@patch("pia.main.dependencytrack.upload_sbom")
@patch("pia.main.jwt.decode")
@patch("pia.main.oidc.verify_token")
def test_upload_dt_error(
self,
mock_verify,
mock_decode,
mock_upload,
client,
valid_request_data,
auth_header,
):
"""Test error when DependencyTrack upload fails."""
mock_decode.return_value = {
"iss": "https://token.actions.githubusercontent.com"
}
mock_verify.return_value = {
"iss": "https://token.actions.githubusercontent.com",
"repository": "eclipse-test/repo",
}
mock_upload.side_effect = DependencyTrackError()
response = client.post(
"/v1/upload/sbom", json=valid_request_data, headers=auth_header
)
assert response.status_code == 502
assert b"Failed to upload to DependencyTrack" in response.content
class TestHealthEndpoints:
"""Tests for k8s health endpoints."""
def test_liveness(self, client):
response = client.get("/livez")
assert response.status_code == 200
assert response.json() == {"status": "ok"}