import requests import json import os import sys import time # Add project root to sys.path for local imports sys.path.append(os.getcwd()) # --- Configuration --- BASE_URL = "https://dev-api.oneskyit.com/v3/crud" API_KEY = "PMM4n50teUCaOMMTN8qOJA" # Test Targets: (Object Type, Valid ID Random for an existing record, a field expected to be excluded from DB writes) # These IDs are placeholders or from existing test data. # Replace "temp_id_..." with actual valid IDs from your dev environment or dynamically create them. # The excluded_field_to_test should be a field that exists in the model but NOT in the base database table for that object, # or a nested object that should be ignored during PUT/PATCH. TARGETS = [ ("event", "NQ6v6X1u2c5", "event_cfg"), # event_cfg is a nested object ("event_session", "F0PZd1bNcuD", "account_id"), # account_id is excluded from event_session table writes ("event_location", "GZvFjgIIZQg", "account_id"), # account_id is excluded from event_location table writes ("event_track", "some_valid_track_id", "account_id"), # account_id is excluded from event_track table writes ("event_presentation", "some_valid_presentation_id", "account_id"), # account_id is excluded from event_presentation table writes ("event_file", "a2pPIT_W28o", "account_id"), # account_id is excluded from event_file table writes ("event_presenter", "some_valid_presenter_id", "account_id"), # account_id is excluded from event_presenter table writes ("event_abstract", "some_valid_abstract_id", "account_id"), # account_id is excluded from event_abstract table writes ("event_badge", "JPUG-87-80-88", "account_id"), # account_id is excluded from event_badge table writes ("event_person", "STI-PyWO6ODzLV8", "extended_json"), # Use a non-FK excluded field for person for better test coverage ("event_exhibit_tracking", "KVypw_xntSY", "account_id"), # account_id is excluded from event_exhibit_tracking table writes ("event_exhibit", "xK_9yEj1bQY", "event_exhibit_tracking_list"), # event_exhibit_tracking_list is a nested object ("event_device", "GZvFjgIIZQg", "account_id"), # account_id is excluded from event_device table writes ("event_cfg", "some_valid_event_cfg_id", "account_id"), # account_id is excluded from event_cfg table writes ("event_registration", "some_valid_registration_id", "cfg"), # cfg is a nested object ("event_registration_cfg", "some_valid_registration_cfg_id", "account_id"), # account_id is in table, not excluded (need to pick another field) ("event_person_profile", "some_valid_person_profile_id", "account_id"), # account_id is excluded from event_person_profile table writes ("person", "STI-PyWO6ODzLV8", "membership_person_id"), # membership_person_id is excluded from person table writes ("address", "gUpFV3CX5UI", "country_name"), # country_name is a convenience field ("contact", "dzGCDpaoYJA", "address"), # address is a nested object ("organization", "some_valid_organization_id", "contact"), # contact is a nested object ] def get_headers(ignore_extra_fields=False): headers = { "Content-Type": "application/json", "X-Aether-API-Key": API_KEY, "x-no-account-id": "bypass" } if ignore_extra_fields: headers["x-ae-ignore-extra-fields"] = "true" return headers def print_result(label, success, message=""): status = "āœ… PASS" if success else "āŒ FAIL" print(f"{status} | {label} {': ' + message if message else ''}") return success def verify_id_vision_compliance(obj_type, record_id): """ Verifies that the object returns ONLY string IDs for all ID fields (Vision Standard). """ label = f"ID Vision Compliance for {obj_type} (ID: {record_id})" url = f"{BASE_URL}/{obj_type}/{record_id}" try: response = requests.get(url, headers=get_headers()) if response.status_code == 200: data = response.json().get('data', {}) failures = [] # Check all fields ending in _id (except external_id) for key, val in data.items(): if key == "id" or (key.endswith("_id") and not key.endswith("external_id")): if val is not None and not isinstance(val, str): failures.append(f"Field '{key}' is {type(val).__name__} ({val})") elif isinstance(val, str) and len(val) < 11 and val.isdigit(): failures.append(f"Field '{key}' looks like a stringified integer: '{val}'") # Possible integer leakage if not failures: return print_result(label, True, "All ID fields are strings.") else: return print_result(label, False, f"Integer leakage detected: {', '.join(failures)}") else: return print_result(label, False, f"GET failed with status {response.status_code}: {response.text[:100]}") except Exception as e: return print_result(label, False, f"Exception during GET: {e}") def verify_excluded_fields_stripped(obj_type, record_id, excluded_field_name, excluded_field_value): """ Verifies that a PATCH operation with an excluded field (from fields_to_exclude_from_db) does not cause an error, implying the backend successfully stripped it. It also verifies that the field's value is not updated in the database. """ label = f"Excluded field '{excluded_field_name}' stripping on PATCH for {obj_type} (ID: {record_id})" url = f"{BASE_URL}/{obj_type}/{record_id}" # 1. Get current state to verify no change try: initial_response = requests.get(url, headers=get_headers()) if initial_response.status_code != 200: return print_result(label, False, f"Initial GET failed with status {initial_response.status_code} for {obj_type}") initial_data = initial_response.json().get('data', {}) initial_field_value = initial_data.get(excluded_field_name) except Exception as e: return print_result(label, False, f"Exception during initial GET for {obj_type}: {e}") # 2. Attempt PATCH with excluded field # Use 'description' as a generic update field, as most tables should have it. # If a table doesn't have 'description', this test will fail, and we'll need a more specific valid field. payload = { "description": f"Test description updated by {int(time.time())}", excluded_field_name: excluded_field_value # The field that should be excluded } try: patch_response = requests.patch(url, headers=get_headers(ignore_extra_fields=True), data=json.dumps(payload)) if patch_response.status_code == 200: # 3. Verify the field was not actually updated in the DB final_response = requests.get(url, headers=get_headers()) if final_response.status_code != 200: return print_result(label, False, f"Final GET after PATCH failed with status {final_response.status_code}") final_data = final_response.json().get('data', {}) final_field_value = final_data.get(excluded_field_name) if initial_field_value == final_field_value: return print_result(label, True, f"PATCH succeeded, and excluded field '{excluded_field_name}' was not updated.") else: return print_result(label, False, f"PATCH updated excluded field '{excluded_field_name}'. Initial: '{initial_field_value}', Final: '{final_field_value}'") else: return print_result(label, False, f"PATCH failed with status {patch_response.status_code}: {patch_response.text[:200]}") except Exception as e: return print_result(label, False, f"Exception during PATCH: {e}") # --- Main Execution --- if __name__ == "__main__": print("šŸš€ Starting Aether V3 Model Refactor Verification Suite") overall_results = [] # NOTE: Fill in valid existing IDs for the TARGETS list before running. # Otherwise, tests expecting valid IDs will fail. for obj_type, record_id, excluded_field in TARGETS: print(f"\n--- Testing Object Type: {obj_type} (ID: {record_id}) ---") # Basic check to ensure a valid record_id is being used (not a placeholder) if "some_valid_" in record_id: print_result(f"Setup for {obj_type}", False, f"Skipping tests for {obj_type} due to placeholder ID '{record_id}'. Please provide a valid existing ID.") overall_results.append(False) # Mark as failed because test wasn't run meaningfully continue # 1. Verify ID Vision Compliance overall_results.append(verify_id_vision_compliance(obj_type, record_id)) # 2. Verify excluded fields on PATCH if excluded_field: overall_results.append(verify_excluded_fields_stripped(obj_type, record_id, excluded_field, "THIS_VALUE_SHOULD_BE_IGNORED")) print("-" * 40) if all(overall_results): print("\nšŸ† ALL MODEL REFACTOR VERIFICATION TESTS PASSED!") sys.exit(0) else: print("\n🚨 SOME MODEL REFACTOR VERIFICATION TESTS FAILED!") sys.exit(1)