Tests: Reorganize test suite into functional subdirectories
- Categorized scripts into tests/unit/, tests/integration/, tests/e2e/, and tests/tools/. - Adopted consistent naming prefixes (test_unit_*, test_int_*, test_e2e_*, tool_*). - Renamed conftest_mock.py to mock_config_helper.py for clarity. - Updated test_int_boot_diagnosis.py with sys.path setup for root-level execution.
This commit is contained in:
55
tests/e2e/test_e2e_agent_bridge.py
Normal file
55
tests/e2e/test_e2e_agent_bridge.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com/agent"
|
||||
|
||||
def get_headers():
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-No-Account-ID": "testing-bypass"
|
||||
}
|
||||
return headers
|
||||
|
||||
def test_endpoint(method, path, description, params=None):
|
||||
"""
|
||||
Helper to run a test and print results.
|
||||
"""
|
||||
print(f"--- Testing: {description} ---")
|
||||
url = f"{BASE_URL}{path}"
|
||||
|
||||
request_headers = get_headers()
|
||||
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(url, headers=request_headers, params=params)
|
||||
|
||||
print(f"URL: {response.url}")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
|
||||
# Check if the result is a list or single object
|
||||
result_data = data.get('data')
|
||||
if isinstance(result_data, dict):
|
||||
print(f"Result Data: {json.dumps(result_data, indent=2)}")
|
||||
else:
|
||||
print(f"Result Data (truncated): {str(result_data)[:200]}...")
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error Message: {data.get('status_message')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during test: {e}")
|
||||
print("-" * 40 + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Starting Aether Agent Bridge Tests against {BASE_URL}\n")
|
||||
|
||||
# 1. Get Status
|
||||
test_endpoint("GET", "/status", "Get Container Status")
|
||||
|
||||
# 2. Get Logs (last 5 lines)
|
||||
test_endpoint("GET", "/logs", "Get Latest Logs", params={"lines": 5})
|
||||
|
||||
print("Tests Complete.")
|
||||
25
tests/e2e/test_e2e_legacy_remote_schema.py
Normal file
25
tests/e2e/test_e2e_legacy_remote_schema.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
BASE_URL = "https://dev-api.oneskyit.com/v3/crud"
|
||||
ACCOUNT_ID = "nqOzejLCDXM"
|
||||
|
||||
def get_headers():
|
||||
return {"X-Account-ID": ACCOUNT_ID}
|
||||
|
||||
def check_schema(obj):
|
||||
print(f"--- Schema for {obj} ---")
|
||||
url = f"{BASE_URL}/{obj}/schema"
|
||||
r = requests.get(url, headers=get_headers())
|
||||
if r.status_code == 200:
|
||||
data = r.json()['data']
|
||||
cols = [c['field'] for col in [data['database']['columns']] for c in col]
|
||||
print(f"Columns: {cols}")
|
||||
fields = list(data['model']['fields'].keys())
|
||||
print(f"Pydantic Fields: {fields}")
|
||||
else:
|
||||
print(f"Error {r.status_code}: {r.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_schema("site")
|
||||
check_schema("site_domain")
|
||||
47
tests/e2e/test_e2e_site_bootstrap.py
Normal file
47
tests/e2e/test_e2e_site_bootstrap.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com/v3/crud"
|
||||
FQDN_TO_TEST = "dev-app.oneskyit.com" # Example domain that should exist
|
||||
|
||||
def test_site_domain_lookup():
|
||||
print(f"--- Testing Public Site Domain Lookup: {FQDN_TO_TEST} ---")
|
||||
url = f"{BASE_URL}/site_domain/search"
|
||||
|
||||
# Payload: Search for FQDN, explicitly WITHOUT authentication
|
||||
# The frontend typically sends this to bootstrap
|
||||
query = {
|
||||
"and": [{"field": "fqdn", "op": "eq", "value": FQDN_TO_TEST}]
|
||||
}
|
||||
|
||||
# NO AUTH HEADERS (Simulating unauthenticated bootstrapping)
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=query)
|
||||
print(f"URL: {response.url}")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
|
||||
if response.status_code == 200:
|
||||
result_data = data.get('data')
|
||||
if isinstance(result_data, list):
|
||||
print(f"Success! Found {len(result_data)} records.")
|
||||
if len(result_data) > 0:
|
||||
print(f"Data: {json.dumps(result_data[0], indent=2)}")
|
||||
else:
|
||||
print(f"Unexpected data format: {type(result_data)}")
|
||||
else:
|
||||
print(f"Failure (Expected before fix). Message: {data.get('status_message')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during test: {e}")
|
||||
print("-" * 40 + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_site_domain_lookup()
|
||||
67
tests/e2e/test_e2e_v3_accounts.py
Normal file
67
tests/e2e/test_e2e_v3_accounts.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com/v3/crud"
|
||||
|
||||
# --- AUTHENTICATION CONFIG ---
|
||||
ACCOUNT_ID = "nqOzejLCDXM" # Legacy Header Fallback
|
||||
|
||||
def get_headers():
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Account-ID": ACCOUNT_ID
|
||||
}
|
||||
return headers
|
||||
|
||||
def test_endpoint(method, path, description, query=None, params=None):
|
||||
"""
|
||||
Helper to run a test and print results.
|
||||
"""
|
||||
print(f"--- Testing: {description} ---")
|
||||
url = f"{BASE_URL}{path}"
|
||||
|
||||
request_headers = get_headers()
|
||||
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(url, headers=request_headers, params=params)
|
||||
elif method == "POST":
|
||||
response = requests.post(url, headers=request_headers, json=query, params=params)
|
||||
|
||||
print(f"URL: {response.url}")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
|
||||
# Check if the result is a list or single object
|
||||
result_data = data.get('data')
|
||||
if isinstance(result_data, list):
|
||||
print(f"Result Count: {len(result_data)}")
|
||||
if len(result_data) > 0:
|
||||
print(f"First Item Example: {json.dumps(result_data[0], indent=2)}")
|
||||
else:
|
||||
print(f"Result Type: {type(result_data)}")
|
||||
print(f"Result Data: {json.dumps(result_data, indent=2)}")
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error Message: {data.get('status_message')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during test: {e}")
|
||||
print("-" * 40 + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Starting Aether V3 Account Tests against {BASE_URL}\n")
|
||||
|
||||
# 1. List Accounts
|
||||
test_endpoint("GET", "/account/", "List Accounts (GET)")
|
||||
|
||||
# 2. Search Accounts (Full Text)
|
||||
test_endpoint("POST", "/account/search", "Search Accounts (POST - All)", query={"q": "%"})
|
||||
|
||||
# 3. Search Accounts (Specific Name)
|
||||
test_endpoint("POST", "/account/search", "Search Accounts (POST - Specific)", query={"and": [{"field": "name", "op": "icontains", "value": "Sky"}]})
|
||||
|
||||
print("Tests Complete.")
|
||||
55
tests/e2e/test_e2e_v3_extra_filters.py
Normal file
55
tests/e2e/test_e2e_v3_extra_filters.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com/v3/crud"
|
||||
ACCOUNT_ID = "nqOzejLCDXM" # Legacy Header Fallback
|
||||
|
||||
def get_headers(include_account=True):
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
if include_account:
|
||||
headers["X-Account-ID"] = ACCOUNT_ID
|
||||
return headers
|
||||
|
||||
def test_endpoint(path, description, include_account=True):
|
||||
print(f"--- Testing: {description} ---")
|
||||
url = f"{BASE_URL}{path}"
|
||||
try:
|
||||
response = requests.get(url, headers=get_headers(include_account))
|
||||
print(f"URL: {response.url}")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
result_data = data.get('data')
|
||||
|
||||
if isinstance(result_data, list):
|
||||
print(f"Result Count: {len(result_data)}")
|
||||
if len(result_data) > 0:
|
||||
print(f"First Item: {json.dumps(result_data[0], indent=2)[:300]}...")
|
||||
else:
|
||||
print(f"Result Data: {json.dumps(result_data, indent=2)}")
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error Message: {data.get('status_message')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during test: {e}")
|
||||
print("-" * 40 + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test Users
|
||||
test_endpoint("/user/", "List Users (Default filter)")
|
||||
test_endpoint("/user/?enabled=all&hidden=all", "List Users (Bypass filter)")
|
||||
|
||||
# Test Sites
|
||||
test_endpoint(f"/account/{ACCOUNT_ID}/site/", "List Sites (Account Filter)")
|
||||
|
||||
# Test Site Domains
|
||||
test_endpoint("/site_domain/", "List Site Domains (Default filter)")
|
||||
test_endpoint("/site_domain/?enabled=all&hidden=all", "List Site Domains (Bypass filter)")
|
||||
|
||||
# Test Legacy Site Domain Lookup (Initial frontend request)
|
||||
# This route is in api_crud.py (v1) and needs to work without an account ID header
|
||||
test_endpoint("/../../crud/site/domain/scott.localhost:5173?use_alt_table=true&use_alt_base=true", "Legacy Site Domain Lookup", include_account=False)
|
||||
49
tests/e2e/test_e2e_v3_schema.py
Normal file
49
tests/e2e/test_e2e_v3_schema.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com/v3/crud"
|
||||
ACCOUNT_ID = "nqOzejLCDXM" # Legacy Header Fallback
|
||||
|
||||
def get_headers():
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"X-Account-ID": ACCOUNT_ID
|
||||
}
|
||||
|
||||
def test_schema(obj_type):
|
||||
print(f"--- Testing Schema: {obj_type} ---")
|
||||
url = f"{BASE_URL}/{obj_type}/schema"
|
||||
try:
|
||||
response = requests.get(url, headers=get_headers())
|
||||
print(f"URL: {response.url}")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
if response.status_code == 200:
|
||||
print(f"Object Type: {data['data']['object_type']}")
|
||||
print(f"Database Table: {data['data']['database']['table_name']}")
|
||||
print(f"Column Count: {len(data['data']['database']['columns'])}")
|
||||
print(f"Model Name: {data['data']['model']['name']}")
|
||||
print(f"Model Field Count: {len(data['data']['model']['fields'])}")
|
||||
|
||||
# Print a few columns and fields as example
|
||||
print("\nExample Columns:")
|
||||
for col in data['data']['database']['columns'][:3]:
|
||||
print(f" - {col['field']} ({col['type']})")
|
||||
|
||||
print("\nExample Model Fields:")
|
||||
fields = list(data['data']['model']['fields'].keys())
|
||||
for field in fields[:3]:
|
||||
f_info = data['data']['model']['fields'][field]
|
||||
print(f" - {field} (alias: {f_info['alias']}, type: {f_info['type']})")
|
||||
else:
|
||||
print(f"Error: {data.get('status_message')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during test: {e}")
|
||||
print("-" * 40 + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_schema("account")
|
||||
test_schema("event_badge")
|
||||
120
tests/e2e/test_e2e_v3_search.py
Normal file
120
tests/e2e/test_e2e_v3_search.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com/v3/crud"
|
||||
|
||||
# --- AUTHENTICATION CONFIG ---
|
||||
# Option 1: Modern JWT (Preferred)
|
||||
JWT_TOKEN = "" # PASTE YOUR JWT TOKEN HERE
|
||||
|
||||
# Option 2: Legacy Header Fallback
|
||||
ACCOUNT_ID = "nqOzejLCDXM"
|
||||
|
||||
def get_headers(use_jwt=True):
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
if use_jwt and JWT_TOKEN:
|
||||
headers["Authorization"] = f"Bearer {JWT_TOKEN}"
|
||||
else:
|
||||
headers["X-Account-ID"] = ACCOUNT_ID
|
||||
return headers
|
||||
|
||||
def test_search(obj_type, query, description, params=None, use_jwt_query=False):
|
||||
"""
|
||||
Helper to run a search test and print results.
|
||||
"""
|
||||
print(f"--- Testing: {description} ---")
|
||||
url = f"{BASE_URL}/{obj_type}/search"
|
||||
|
||||
# Handle JWT in query parameter if requested
|
||||
request_params = params.copy() if params else {}
|
||||
request_headers = get_headers(use_jwt=not use_jwt_query)
|
||||
|
||||
if use_jwt_query and JWT_TOKEN:
|
||||
request_params["jwt"] = JWT_TOKEN
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=request_headers, json=query, params=request_params)
|
||||
print(f"URL: {response.url}")
|
||||
print(f"Auth Method: {'JWT Query' if use_jwt_query else ('JWT Header' if JWT_TOKEN else 'Legacy Header')}")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
|
||||
# Check if the result is a list or single object
|
||||
result_data = data.get('data')
|
||||
if isinstance(result_data, list):
|
||||
print(f"Result Count: {len(result_data)}")
|
||||
if len(result_data) > 0:
|
||||
# Print first item as example
|
||||
print(f"First Item Example: {json.dumps(result_data[0], indent=2)[:200]}...")
|
||||
else:
|
||||
print(f"Result Type: {type(result_data)}")
|
||||
print(f"Result Data: {json.dumps(result_data, indent=2)}")
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error Message: {data.get('status_message')}")
|
||||
print(f"Meta Details: {json.dumps(data.get('meta', {}), indent=2)}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f"Error: Could not connect to {BASE_URL}. Is the API running?")
|
||||
except Exception as e:
|
||||
print(f"Error during test: {e}")
|
||||
print("-" * 40 + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not JWT_TOKEN:
|
||||
print("WARNING: JWT_TOKEN is empty. Falling back to Legacy ACCOUNT_ID auth.")
|
||||
print("To test JWT, please paste a token into the JWT_TOKEN variable.\n")
|
||||
|
||||
print(f"Starting Aether V3 Search & JWT Tests against {BASE_URL}\n")
|
||||
|
||||
# 1. JWT Header Authentication Test
|
||||
test_search("journal", {"q": "%"}, "Auth: JWT via Authorization Header")
|
||||
|
||||
# 2. JWT Query Parameter Authentication Test
|
||||
if JWT_TOKEN:
|
||||
test_search("journal", {"q": "%"}, "Auth: JWT via 'jwt' Query Parameter", use_jwt_query=True)
|
||||
|
||||
# 3. New Operator: contains / icontains
|
||||
query_contains = {
|
||||
"and": [{"field": "name", "op": "contains", "value": "Journal"}]
|
||||
}
|
||||
test_search("journal", query_contains, "Operator: contains (automatically adds %%")
|
||||
|
||||
# 4. New Operator: startswith / istartswith
|
||||
query_start = {
|
||||
"and": [{"field": "name", "op": "startswith", "value": "A"}]
|
||||
}
|
||||
test_search("journal", query_start, "Operator: startswith (automatically adds % at end)")
|
||||
|
||||
# 5. New Operator: endswith / iendswith
|
||||
query_end = {
|
||||
"and": [{"field": "name", "op": "endswith", "value": "Test"}]
|
||||
}
|
||||
test_search("journal", query_end, "Operator: endswith (automatically adds % at start)")
|
||||
|
||||
# 6. Error Handling: Unsupported Operator
|
||||
query_bad = {
|
||||
"and": [{"field": "name", "op": "invalid_op", "value": "test"}]
|
||||
}
|
||||
test_search("journal", query_bad, "Error Handling: Unsupported Operator (Should return 400)")
|
||||
|
||||
# 7. Complex Nested Logic (Recap)
|
||||
query_nested = {
|
||||
"and": [
|
||||
{"field": "enable", "op": "eq", "value": True},
|
||||
{
|
||||
"or": [
|
||||
{"field": "name", "op": "icontains", "value": "aether"},
|
||||
{"field": "summary", "op": "is_not_null"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
test_search("journal", query_nested, "Complex Logic: Nested AND/OR + icontains")
|
||||
|
||||
print("Tests Complete.")
|
||||
50
tests/e2e/test_e2e_v3_security_exceptions.py
Normal file
50
tests/e2e/test_e2e_v3_security_exceptions.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "https://dev-api.oneskyit.com"
|
||||
SEARCH_ENDPOINT = f"{BASE_URL}/v3/crud/site_domain/search"
|
||||
RESTRICTED_ENDPOINT = f"{BASE_URL}/v3/crud/journal/search"
|
||||
|
||||
def test_site_domain_exception():
|
||||
print("--- Testing site_domain guest access (Exception) ---")
|
||||
search_query = {
|
||||
"q": "%", # Match all for testing
|
||||
"and": []
|
||||
}
|
||||
|
||||
try:
|
||||
# No Authorization or X-Account-ID headers provided
|
||||
response = requests.post(SEARCH_ENDPOINT, json=search_query)
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("SUCCESS: site_domain search allowed without authentication.")
|
||||
print(f"Result count: {len(data.get('data', []))}")
|
||||
else:
|
||||
print(f"FAILED: site_domain search returned {response.status_code}")
|
||||
print(response.text)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during site_domain test: {e}")
|
||||
|
||||
def test_restricted_search():
|
||||
print("\n--- Testing restricted search (Should fail) ---")
|
||||
search_query = {"q": "%"}
|
||||
|
||||
try:
|
||||
response = requests.post(RESTRICTED_ENDPOINT, json=search_query)
|
||||
print(f"Status Code: {response.status_code}")
|
||||
|
||||
if response.status_code == 403:
|
||||
print("SUCCESS: Restricted search was correctly blocked (403 Forbidden).")
|
||||
else:
|
||||
print(f"FAILED: Restricted search returned {response.status_code} instead of 403.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during restricted test: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_site_domain_exception()
|
||||
test_restricted_search()
|
||||
Reference in New Issue
Block a user