datahub/smoke-test/test_system_info.py

312 lines
14 KiB
Python
Raw Normal View History

import logging
import pytest
import requests
from tests.utils import login_as, get_gms_url, get_admin_credentials, get_frontend_url
from tests.privileges.utils import create_user, remove_user
logger = logging.getLogger(__name__)
pytestmark = pytest.mark.no_cypress_suite1
# ==============================================
# SYSTEM INFO API TESTS
# ==============================================
# These tests verify the system info API endpoints after refactoring to separate concerns:
#
# NEW API STRUCTURE:
# - /openapi/v1/system-info -> Only Spring components (no properties duplication)
# - /openapi/v1/system-info/spring-components -> Same as main endpoint
# - /openapi/v1/system-info/properties -> Detailed system properties with metadata
# - /openapi/v1/system-info/properties/simple -> Simple key-value property map
#
# CHANGES MADE:
# - Eliminated duplication where properties appeared in both main response and separate endpoint
# - Improved clarity by separating component status from configuration properties
# - Maintained backward compatibility through dedicated endpoints
# ==============================================
def test_system_info_main_endpoint(auth_session):
"""Test that main system info endpoint returns expected structure with authentication."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info")
response.raise_for_status()
system_info = response.json()
# Verify main response structure - now only contains springComponents
assert "springComponents" in system_info, "Missing springComponents in system info response"
assert "properties" not in system_info, "Properties should not be in main system info response (use /properties endpoint instead)"
logger.info("Main system info endpoint test passed")
def test_system_info_spring_components_structure(auth_session):
"""Test that spring components structure contains all expected components from main endpoint."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info")
response.raise_for_status()
system_info = response.json()
spring_components = system_info["springComponents"]
assert "gms" in spring_components, "Missing GMS component info"
assert "maeConsumer" in spring_components, "Missing MAE consumer info"
assert "mceConsumer" in spring_components, "Missing MCE consumer info"
# Verify GMS component details
gms_info = spring_components["gms"]
assert gms_info["name"] == "GMS", "GMS component name incorrect"
assert gms_info["status"] == "AVAILABLE", "GMS should be available in running system"
assert "version" in gms_info, "GMS version should be present"
assert "properties" in gms_info, "GMS properties should be present"
logger.info("Spring components structure test passed")
def test_system_info_properties_structure(auth_session):
"""Test that properties structure contains expected metadata."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/properties")
response.raise_for_status()
properties = response.json()
assert "properties" in properties, "Missing detailed properties"
assert "propertySources" in properties, "Missing property sources"
assert "totalProperties" in properties, "Missing total properties count"
assert "redactedProperties" in properties, "Missing redacted properties count"
assert isinstance(properties["totalProperties"], int), "Total properties should be integer"
assert isinstance(properties["redactedProperties"], int), "Redacted properties should be integer"
logger.info(f"Properties structure test passed - found {properties['totalProperties']} total properties, {properties['redactedProperties']} redacted")
def test_system_info_spring_components_endpoint(auth_session):
"""Test dedicated spring components endpoint."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/spring-components")
response.raise_for_status()
components_data = response.json()
assert "gms" in components_data, "Spring components endpoint missing GMS info"
assert components_data["gms"]["status"] == "AVAILABLE", "GMS should be available in components endpoint"
logger.info("Spring components endpoint test passed")
def test_system_info_properties_endpoint(auth_session):
"""Test dedicated detailed properties endpoint."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/properties")
response.raise_for_status()
properties_data = response.json()
assert "properties" in properties_data, "Properties endpoint missing detailed properties"
assert "propertySources" in properties_data, "Properties endpoint missing property sources"
logger.info("Detailed properties endpoint test passed")
def test_system_info_simple_properties_endpoint(auth_session):
"""Test simple properties endpoint returns flat key-value map."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/properties/simple")
response.raise_for_status()
simple_properties = response.json()
assert isinstance(simple_properties, dict), "Simple properties should be a dictionary"
assert len(simple_properties) > 0, "Simple properties should contain some configuration"
logger.info(f"Simple properties endpoint test passed - found {len(simple_properties)} properties")
def test_system_info_sensitive_data_redaction(auth_session):
"""Test that sensitive properties are properly redacted."""
response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/properties/simple")
response.raise_for_status()
simple_properties = response.json()
# Check for redacted sensitive properties
sensitive_found = False
redacted_properties = []
for key, value in simple_properties.items():
if any(pattern in key.lower() for pattern in ["password", "secret", "key"]):
if not key.lower().startswith("cache.client."): # Allowed prefix
if value == "***REDACTED***":
sensitive_found = True
redacted_properties.append(key)
if redacted_properties:
logger.info(f"Correctly redacted {len(redacted_properties)} sensitive properties: {redacted_properties[:3]}...")
else:
logger.info("No sensitive properties found to redact (may be valid depending on configuration)")
logger.info("Sensitive data redaction test completed")
def test_system_info_endpoint_separation(auth_session):
"""Test that endpoints are properly separated for different concerns."""
# Test main endpoint - should only contain spring components
main_response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info")
main_response.raise_for_status()
main_data = main_response.json()
assert "springComponents" in main_data, "Main endpoint should contain springComponents"
assert "properties" not in main_data, "Main endpoint should not contain properties (avoid duplication)"
# Test properties endpoint - should contain detailed properties
props_response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/properties")
props_response.raise_for_status()
props_data = props_response.json()
assert "properties" in props_data, "Properties endpoint should contain detailed properties"
assert "springComponents" not in props_data, "Properties endpoint should not contain springComponents"
# Test spring components endpoint - should match main endpoint structure
components_response = auth_session.get(f"{auth_session.gms_url()}/openapi/v1/system-info/spring-components")
components_response.raise_for_status()
components_data = components_response.json()
# Should have same structure as main endpoint's springComponents
assert main_data["springComponents"] == components_data, "Spring components endpoint should match main endpoint's springComponents"
# Verify that GMS component has properties (from SpringComponentsCollector)
gms_info = main_data["springComponents"]["gms"]
assert "properties" in gms_info, "GMS component should have properties field with system properties"
assert isinstance(gms_info["properties"], dict), "GMS properties should be a dictionary"
logger.info("Endpoint separation test passed - no duplication detected")
def extract_api_token_from_session(session):
"""Extract API token from frontend session cookies for OpenAPI authentication.
Based on TestSessionWrapper pattern in tests/utils.py
"""
# Extract actor URN from session cookies
actor_urn = session.cookies["actor"]
# Generate personal access token via GraphQL
json_payload = {
"query": """mutation createAccessToken($input: CreateAccessTokenInput!) {
createAccessToken(input: $input) {
accessToken
metadata {
id
}
}
}""",
"variables": {
"input": {
"type": "PERSONAL",
"actorUrn": actor_urn,
"duration": "ONE_HOUR",
"name": "Test API Token",
"description": "Token for smoke test authentication",
}
},
}
response = session.post(f"{get_frontend_url()}/api/v2/graphql", json=json_payload)
response.raise_for_status()
token_data = response.json()["data"]["createAccessToken"]
return token_data["accessToken"], token_data["metadata"]["id"]
2025-08-11 10:42:21 -07:00
def test_system_info_authenticated_non_admin_user_returns_403(auth_session):
"""Test that system info endpoints return HTTP 403 for authenticated users without MANAGE_SYSTEM_OPERATIONS_PRIVILEGE.
This test creates a limited-privilege user, extracts their API token, and verifies they get HTTP 403,
proving that our authorization logic correctly checks for the specific privilege.
"""
2025-08-11 10:42:21 -07:00
# Get admin session to create test user
(admin_user, admin_pass) = get_admin_credentials()
admin_session = login_as(admin_user, admin_pass)
test_user_urn = "urn:li:corpuser:limited_test_user"
token_id = None
try:
# Create a limited-privilege user (no special privileges by default)
create_user(admin_session, "limited_test_user", "testpass123")
# Login as the limited user
limited_user_session = login_as("limited_test_user", "testpass123")
# Extract API token from the session
api_token, token_id = extract_api_token_from_session(limited_user_session)
logger.info(f"✓ Successfully extracted API token for limited user")
# Test all system info endpoints with API token authentication
base_url = get_gms_url()
endpoints = [
"/openapi/v1/system-info",
"/openapi/v1/system-info/spring-components",
"/openapi/v1/system-info/properties",
"/openapi/v1/system-info/properties/simple"
]
for endpoint in endpoints:
# Make request with Authorization header (not cookies)
headers = {"Authorization": f"Bearer {api_token}"}
response = requests.get(f"{base_url}{endpoint}", headers=headers)
logger.info(f"Endpoint {endpoint}: HTTP {response.status_code}")
if response.status_code != 403:
logger.info(f"Response body: {response.text[:200]}...")
# Should return HTTP 403 FORBIDDEN for authenticated user without privilege
assert response.status_code == 403, f"Endpoint {endpoint} should return HTTP 403 for authenticated non-admin user but returned HTTP {response.status_code}. Response: {response.text}"
# Verify the error message mentions authorization
assert "not authorized for system operations" in response.text, f"Error message should mention authorization: {response.text}"
logger.info(f"✓ Endpoint {endpoint} correctly returns HTTP 403 for authenticated non-admin user")
finally:
# Clean up: revoke the API token and remove the test user
try:
if token_id:
# Revoke the API token
revoke_json = {
"query": """mutation revokeAccessToken($tokenId: String!) {
revokeAccessToken(tokenId: $tokenId)
}""",
"variables": {"tokenId": token_id},
}
limited_user_session.post(f"{get_frontend_url()}/api/v2/graphql", json=revoke_json)
# Remove the test user
admin_cleanup_session = login_as(admin_user, admin_pass)
remove_user(admin_cleanup_session, test_user_urn)
logger.info("✓ Test user and token cleaned up successfully")
except Exception as e:
logger.warning(f"Failed to clean up test user or token: {e}")
logger.info("✓ Authorization test PASSED - authenticated users without MANAGE_SYSTEM_OPERATIONS_PRIVILEGE correctly receive HTTP 403")
def test_system_info_unauthorized_access_returns_403():
"""Test that system info endpoints return 401/403 for unauthenticated users."""
# Make unauthenticated requests (no session) to verify 401/403 responses
base_url = get_gms_url()
endpoints = [
"/openapi/v1/system-info",
"/openapi/v1/system-info/spring-components",
"/openapi/v1/system-info/properties",
"/openapi/v1/system-info/properties/simple"
]
for endpoint in endpoints:
response = requests.get(f"{base_url}{endpoint}")
# Should return 403 Forbidden or 401 Unauthorized for unauthenticated requests
# Both status codes indicate that access is properly restricted
assert response.status_code in [401, 403], f"Endpoint {endpoint} should require authentication but returned {response.status_code}"
logger.info(f"✓ Endpoint {endpoint} properly restricts unauthenticated access (HTTP {response.status_code})")
logger.info("Unauthenticated access test passed - all system info endpoints require proper authentication")