diff --git a/admin/development/test_v3_search.py b/admin/development/test_v3_search.py index 7287845..09385f5 100644 --- a/admin/development/test_v3_search.py +++ b/admin/development/test_v3_search.py @@ -1,26 +1,45 @@ import requests import json +import sys # Configuration BASE_URL = "https://dev-api.oneskyit.com/v3/crud" -# Valid account id_random provided by user -ACCOUNT_ID = "nqOzejLCDXM" -headers = { - "X-Account-ID": ACCOUNT_ID, - "Content-Type": "application/json" -} +# --- AUTHENTICATION CONFIG --- +# Option 1: Modern JWT (Preferred) +JWT_TOKEN = "" # PASTE YOUR JWT TOKEN HERE -def test_search(obj_type, query, description, params=None): +# 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=headers, json=query, params=params) + 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() @@ -37,6 +56,7 @@ def test_search(obj_type, query, description, params=None): 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: @@ -46,58 +66,55 @@ def test_search(obj_type, query, description, params=None): print("-" * 40 + "\n") if __name__ == "__main__": - print(f"Starting Aether V3 Search Tests against {BASE_URL}\n") + 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") - # 1. Standardized Global Search (q property) - query_q = { - "q": "%" # Standardized full-text search across indexed columns + 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_q, "Global Search (q property)") + test_search("journal", query_contains, "Operator: contains (automatically adds %%") - # 2. Hybrid Filtering (POST Body + Query Params) - query_simple = { - "and": [{"field": "name", "op": "like", "value": "%"}] + # 4. New Operator: startswith / istartswith + query_start = { + "and": [{"field": "name", "op": "startswith", "value": "A"}] } - params_hybrid = {"enabled": "disabled"} # Should find disabled journals if any - test_search("journal", query_simple, "Hybrid Filtering (Body + ?enabled=disabled)", params=params_hybrid) + test_search("journal", query_start, "Operator: startswith (automatically adds % at end)") - # 3. View Selection (view parameter) - # Testing with 'site_domain' which has 'tbl_alt' defined as 'v_site_domain_fqdn_id' - query_site = {"q": "%"} - params_view = {"view": "alt"} - test_search("site_domain", query_site, "View Selection (view=alt)", params=params_view) - - # 4. Explicit Parent Filtering - # Testing 'journal_entry' belonging to a journal (journal_id=1 exists based on previous tests) - # We'll use the 'for_obj_type' and 'for_obj_id' as query params - # Assuming id_random 'DCAV-06-35-85' exists for journal_id 1 - query_empty = {} - params_parent = { - "for_obj_type": "journal", - "for_obj_id": "DCAV-06-35-85" + # 5. New Operator: endswith / iendswith + query_end = { + "and": [{"field": "name", "op": "endswith", "value": "Test"}] } - test_search("journal_entry", query_empty, "Explicit Parent Filtering (?for_obj_type=journal)", params=params_parent) + test_search("journal", query_end, "Operator: endswith (automatically adds % at start)") - # 5. Complex Nested Logic (Recap) + # 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": "like", "value": "%Journal%"}, + {"field": "name", "op": "icontains", "value": "aether"}, {"field": "summary", "op": "is_not_null"} ] } ] } - test_search("journal", query_nested, "Nested Logic (AND + OR group)") + test_search("journal", query_nested, "Complex Logic: Nested AND/OR + icontains") - # 6. Hosted File Link Search - query_file_link = { - "and": [ - {"field": "link_to_type", "op": "eq", "value": "journal"} - ] - } - test_search("hosted_file_link", query_file_link, "Hosted File Link Search (link_to_type='journal')") - - print("Tests Complete.") + print("Tests Complete.") \ No newline at end of file diff --git a/documentation/V3_FRONTEND_API_GUIDE.md b/documentation/V3_FRONTEND_API_GUIDE.md index c5719e4..5bb0b85 100644 --- a/documentation/V3_FRONTEND_API_GUIDE.md +++ b/documentation/V3_FRONTEND_API_GUIDE.md @@ -94,6 +94,61 @@ The `op` property in a `SearchFilter` supports the following values: --- +## 3. Create, Update, & Delete (POST, PATCH, DELETE) + +V3 supports both top-level operations and nested parent/child operations. + +### A. Create (POST) +When creating objects, V3 strictly validates the incoming JSON against the `mdl_in` Pydantic model. + +```ts +// POST /v3/crud/{obj_type}/ +// POST /v3/crud/journal/ +export async function create_ae_obj_v3({ api_cfg, obj_type, data }) { + const endpoint = `/v3/crud/${obj_type}/`; + return await post_object({ api_cfg, endpoint, data }); +} + +// POST /v3/crud/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/ +// POST /v3/crud/journal/EIAC-40-76-82/journal_entry/ +// Note: Parent ID is automatically injected into the child record. +export async function create_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, data }) { + const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`; + return await post_object({ api_cfg, endpoint, data }); +} +``` + +### B. Update (PATCH) +V3 uses `PATCH` for partial updates. Only the fields provided in the body will be modified in the database. + +```ts +// PATCH /v3/crud/{obj_type}/{obj_id} +export async function update_ae_obj_v3({ api_cfg, obj_type, obj_id, data }) { + const endpoint = `/v3/crud/${obj_type}/${obj_id}`; + return await patch_object({ api_cfg, endpoint, data }); +} + +// PATCH /v3/crud/{parent_type}/{parent_id}/{child_type}/{child_id} +// Verification: The backend ensures the child actually belongs to the parent before updating. +export async function update_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, child_id, data }) { + const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/${child_id}`; + return await patch_object({ api_cfg, endpoint, data }); +} +``` + +### C. Delete (DELETE) +The `DELETE` method is used for removal. The backend may implement soft-delete (hide/disable) depending on the configuration. + +```ts +// DELETE /v3/crud/{obj_type}/{obj_id} +export async function delete_ae_obj_v3({ api_cfg, obj_type, obj_id }) { + const endpoint = `/v3/crud/${obj_type}/${obj_id}`; + return await delete_object({ api_cfg, endpoint }); +} +``` + +--- + ## 4. Authentication in V3 V3 supports multiple authentication methods. The backend resolves these automatically.