| import pytest |
| from fastapi.testclient import TestClient |
| from uuid import uuid4, UUID |
| from datetime import datetime, timedelta |
| import json |
| from unittest.mock import patch, call, MagicMock |
| from typing import Dict |
|
|
| from tensorus.api import app |
| from tensorus.metadata.storage import InMemoryStorage |
| from tensorus.metadata.storage_abc import MetadataStorage |
| from tensorus.metadata.schemas import ( |
| TensorDescriptor, SemanticMetadata, LineageMetadata, DataType, StorageFormat, |
| ComputationalMetadata, QualityMetadata, RelationalMetadata, UsageMetadata |
| ) |
| from tensorus.metadata.schemas_iodata import TensorusExportData, TensorusExportEntry |
| from tensorus.api.dependencies import get_storage_instance |
| from tensorus.config import settings as global_settings |
| from tensorus.metadata import storage_instance as global_app_storage_instance |
|
|
| |
| |
| |
|
|
| API_KEY_FOR_MAIN_API_TESTS = "api_key_for_main_tests" |
|
|
| @pytest.fixture(autouse=True) |
| def setup_api_test_keys(monkeypatch): |
| monkeypatch.setattr(global_settings, 'VALID_API_KEYS', [API_KEY_FOR_MAIN_API_TESTS, "test_io_key_for_real"]) |
| monkeypatch.setattr(global_settings, 'API_KEY_HEADER_NAME', "X-API-KEY") |
| from tensorus.api.security import api_key_header_auth as global_api_key_header_auth |
| monkeypatch.setattr(global_api_key_header_auth.model, 'name', "X-API-KEY") |
|
|
|
|
| @pytest.fixture(scope="function") |
| def client_with_clean_storage(monkeypatch): |
| monkeypatch.setattr(global_settings, "STORAGE_BACKEND", "in_memory") |
| |
| if not isinstance(global_app_storage_instance, InMemoryStorage): |
| pytest.skip("Skipping API tests: Requires InMemoryStorage for clean state.") |
| global_app_storage_instance.clear_all_data() |
|
|
| with TestClient(app) as c: |
| yield c |
| global_app_storage_instance.clear_all_data() |
|
|
| |
| def _create_td_via_api(client: TestClient, headers: Dict, **overrides) -> Dict: |
| payload = { |
| "tensor_id": str(overrides.get("tensor_id", uuid4())), |
| "dimensionality": overrides.get("dimensionality", 1), |
| "shape": overrides.get("shape", [1]), |
| "data_type": overrides.get("data_type", DataType.FLOAT32.value), |
| "owner": overrides.get("owner", "test_audit_user"), |
| "byte_size": overrides.get("byte_size", 4) |
| } |
| payload.update(overrides) |
| response = client.post("/tensor_descriptors/", json=payload, headers=headers) |
| assert response.status_code == 201, f"Failed to create TD: {response.text}" |
| return response.json() |
|
|
| |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_create_tensor_descriptor_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_id = uuid4() |
| payload = { |
| "tensor_id": str(td_id), "dimensionality": 1, "shape": [10], |
| "data_type": "float32", "owner": "audit_log_user", "byte_size": 40 |
| } |
| response = client_with_clean_storage.post("/tensor_descriptors/", json=payload, headers=headers) |
| assert response.status_code == 201 |
|
|
| mock_log_audit_event.assert_called_with( |
| action="CREATE_TENSOR_DESCRIPTOR", |
| user=API_KEY_FOR_MAIN_API_TESTS, |
| tensor_id=str(td_id), |
| details={"owner": "audit_log_user", "data_type": "float32"} |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_update_tensor_descriptor_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers, owner="original_owner_audit") |
| td_id_str = td_data["tensor_id"] |
|
|
| update_payload = {"owner": "updated_owner_audit", "tags": ["audited"]} |
| response = client_with_clean_storage.put(f"/tensor_descriptors/{td_id_str}", json=update_payload, headers=headers) |
| assert response.status_code == 200 |
|
|
| mock_log_audit_event.assert_called_with( |
| "UPDATE_TENSOR_DESCRIPTOR", |
| API_KEY_FOR_MAIN_API_TESTS, |
| td_id_str, |
| {"updated_fields": ["owner", "tags"]} |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_delete_tensor_descriptor_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers, owner="delete_audit_user") |
| td_id_str = td_data["tensor_id"] |
|
|
| response = client_with_clean_storage.delete(f"/tensor_descriptors/{td_id_str}", headers=headers) |
| assert response.status_code == 200 |
|
|
| mock_log_audit_event.assert_called_with( |
| "DELETE_TENSOR_DESCRIPTOR", |
| API_KEY_FOR_MAIN_API_TESTS, |
| td_id_str |
| ) |
|
|
| |
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_upsert_lineage_metadata_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers) |
| td_id = UUID(td_data["tensor_id"]) |
|
|
| lineage_payload = {"tensor_id": str(td_id), "version": "vAudit"} |
| response = client_with_clean_storage.post(f"/tensor_descriptors/{td_id}/lineage", json=lineage_payload, headers=headers) |
| assert response.status_code == 201 |
|
|
| mock_log_audit_event.assert_called_with( |
| "UPSERT_Lineage_METADATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| str(td_id) |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_patch_lineage_metadata_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers) |
| td_id = UUID(td_data["tensor_id"]) |
|
|
| |
| lineage_payload = {"tensor_id": str(td_id), "version": "vInitialAudit"} |
| client_with_clean_storage.post(f"/tensor_descriptors/{td_id}/lineage", json=lineage_payload, headers=headers) |
| mock_log_audit_event.reset_mock() |
|
|
| patch_payload = {"version": "vPatchedAudit", "provenance": {"notes": "audited patch"}} |
| response = client_with_clean_storage.patch(f"/tensor_descriptors/{td_id}/lineage", json=patch_payload, headers=headers) |
| assert response.status_code == 200 |
|
|
| mock_log_audit_event.assert_called_with( |
| "PATCH_Lineage_METADATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| str(td_id), |
| {"updated_fields": list(patch_payload.keys())} |
| ) |
|
|
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_delete_lineage_metadata_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers) |
| td_id = UUID(td_data["tensor_id"]) |
|
|
| lineage_payload = {"tensor_id": str(td_id), "version": "vToDeleteAudit"} |
| client_with_clean_storage.post(f"/tensor_descriptors/{td_id}/lineage", json=lineage_payload, headers=headers) |
| mock_log_audit_event.reset_mock() |
|
|
| response = client_with_clean_storage.delete(f"/tensor_descriptors/{td_id}/lineage", headers=headers) |
| assert response.status_code == 204 |
|
|
| mock_log_audit_event.assert_called_with( |
| "DELETE_Lineage_METADATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| str(td_id) |
| ) |
|
|
| |
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_import_data_audit_log(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_id = uuid4() |
| entry = TensorusExportEntry( |
| tensor_descriptor=TensorDescriptor(tensor_id=td_id, dimensionality=0, shape=[], data_type=DataType.BOOLEAN, owner="audit_import", byte_size=0) |
| ) |
| import_payload = TensorusExportData(entries=[entry]) |
|
|
| response = client_with_clean_storage.post("/tensors/import?conflict_strategy=skip", json=import_payload.model_dump(mode="json"), headers=headers) |
| assert response.status_code == 200 |
| summary = response.json() |
|
|
| mock_log_audit_event.assert_called_with( |
| "IMPORT_DATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| details={"strategy": "skip", "summary": summary} |
| ) |
|
|
| |
| |
| |
| |
|
|
| |
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_create_semantic_metadata_audit(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers, owner="semantic_audit_user") |
| td_id = UUID(td_data["tensor_id"]) |
| mock_log_audit_event.reset_mock() |
|
|
| sm_payload = {"tensor_id": str(td_id), "name": "audit_sem_meta", "description": "testing audit"} |
| |
| response = client_with_clean_storage.post(f"/tensor_descriptors/{td_id}/semantic/", json=sm_payload, headers=headers) |
| assert response.status_code == 201 |
|
|
| mock_log_audit_event.assert_called_with( |
| "CREATE_SEMANTIC_METADATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| str(td_id), |
| {"name": "audit_sem_meta"} |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_update_semantic_metadata_audit(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers) |
| td_id = UUID(td_data["tensor_id"]) |
|
|
| |
| sm_name = "initial_semantic_name" |
| sm_payload_initial = {"tensor_id": str(td_id), "name": sm_name, "description": "Initial description"} |
| client_with_clean_storage.post(f"/tensor_descriptors/{td_id}/semantic/", json=sm_payload_initial, headers=headers) |
| mock_log_audit_event.reset_mock() |
|
|
| update_payload = {"description": "Updated description for audit"} |
| response = client_with_clean_storage.put(f"/tensor_descriptors/{td_id}/semantic/{sm_name}", json=update_payload, headers=headers) |
| assert response.status_code == 200 |
|
|
| mock_log_audit_event.assert_called_with( |
| "UPDATE_SEMANTIC_METADATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| str(td_id), |
| {"original_name": sm_name, "updated_fields": update_payload} |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_delete_semantic_metadata_audit(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| td_data = _create_td_via_api(client_with_clean_storage, headers=headers) |
| td_id = UUID(td_data["tensor_id"]) |
| sm_name = "to_delete_semantic_audit" |
| sm_payload = {"tensor_id": str(td_id), "name": sm_name, "description": "Semantic to be deleted"} |
| client_with_clean_storage.post(f"/tensor_descriptors/{td_id}/semantic/", json=sm_payload, headers=headers) |
| mock_log_audit_event.reset_mock() |
|
|
| response = client_with_clean_storage.delete(f"/tensor_descriptors/{td_id}/semantic/{sm_name}", headers=headers) |
| assert response.status_code == 204 |
|
|
| mock_log_audit_event.assert_called_with( |
| "DELETE_SEMANTIC_METADATA", |
| API_KEY_FOR_MAIN_API_TESTS, |
| str(td_id), |
| {"name": sm_name} |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_create_tensor_version_audit(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| parent_td_data = _create_td_via_api(client_with_clean_storage, headers=headers, owner="version_parent_audit") |
| parent_id_str = parent_td_data["tensor_id"] |
| mock_log_audit_event.reset_mock() |
|
|
| version_payload = {"new_version_string": "vAuditTest"} |
| response = client_with_clean_storage.post(f"/tensors/{parent_id_str}/versions", json=version_payload, headers=headers) |
| assert response.status_code == 201 |
| new_version_data = response.json() |
| new_version_id = new_version_data["tensor_id"] |
|
|
| mock_log_audit_event.assert_called_with( |
| "CREATE_TENSOR_VERSION", |
| API_KEY_FOR_MAIN_API_TESTS, |
| new_version_id, |
| { |
| "parent_id": parent_id_str, |
| "version": "vAuditTest" |
| } |
| ) |
|
|
| @patch('tensorus.api.endpoints.log_audit_event') |
| def test_create_lineage_relationship_audit(mock_log_audit_event: MagicMock, client_with_clean_storage: TestClient): |
| headers = {"X-API-KEY": API_KEY_FOR_MAIN_API_TESTS} |
| source_td_data = _create_td_via_api(client_with_clean_storage, headers=headers, owner="src_lineage_audit") |
| target_td_data = _create_td_via_api(client_with_clean_storage, headers=headers, owner="tgt_lineage_audit") |
| source_id_str = source_td_data["tensor_id"] |
| target_id_str = target_td_data["tensor_id"] |
| mock_log_audit_event.reset_mock() |
|
|
| relationship_payload = { |
| "source_tensor_id": source_id_str, |
| "target_tensor_id": target_id_str, |
| "relationship_type": "derived_for_audit" |
| } |
| response = client_with_clean_storage.post("/lineage/relationships/", json=relationship_payload, headers=headers) |
| assert response.status_code == 201 |
|
|
| |
| expected_details = { |
| "source_tensor_id": UUID(source_id_str), |
| "target_tensor_id": UUID(target_id_str), |
| "relationship_type": "derived_for_audit", |
| "details": None |
| } |
| mock_log_audit_event.assert_called_with( |
| "CREATE_LINEAGE_RELATIONSHIP", |
| API_KEY_FOR_MAIN_API_TESTS, |
| target_id_str, |
| details=expected_details |
| ) |
|
|
|
|