diff --git a/feature-builder/builder-task/orca-agent/requirements.txt b/feature-builder/builder-task/orca-agent/requirements.txt index a384969e..2c7a60e8 100644 --- a/feature-builder/builder-task/orca-agent/requirements.txt +++ b/feature-builder/builder-task/orca-agent/requirements.txt @@ -19,3 +19,4 @@ colorama>=0.4.6 pymongo>=4.11.0 PyNaCl>=1.5.0 setuptools>=78.1.0 +flask diff --git a/feature-builder/builder-task/orca-agent/src/server/routes/__init__.py b/feature-builder/builder-task/orca-agent/src/server/routes/__init__.py new file mode 100644 index 00000000..5e37eee3 --- /dev/null +++ b/feature-builder/builder-task/orca-agent/src/server/routes/__init__.py @@ -0,0 +1,23 @@ +""" +Route registration for the server application. +""" + +from .task import register_task_routes +from .submission import register_submission_routes +from .audit import register_audit_routes +from .healthz import healthz_route +from .evidence_uniqueness import register_evidence_uniqueness_routes + +def register_routes(app, *args, **kwargs): + """ + Register all routes for the application. + + Args: + app: Flask application instance + """ + # Register individual route groups + healthz_route(app) + register_task_routes(app, *args, **kwargs) + register_submission_routes(app, *args, **kwargs) + register_audit_routes(app, *args, **kwargs) + register_evidence_uniqueness_routes(app, *args, **kwargs) \ No newline at end of file diff --git a/feature-builder/builder-task/orca-agent/src/server/routes/evidence_uniqueness.py b/feature-builder/builder-task/orca-agent/src/server/routes/evidence_uniqueness.py new file mode 100644 index 00000000..c102bb74 --- /dev/null +++ b/feature-builder/builder-task/orca-agent/src/server/routes/evidence_uniqueness.py @@ -0,0 +1,110 @@ +""" +Server-side route for evidence uniqueness validation. + +This module provides server-side validation to ensure the uniqueness of evidence +in the system, preventing duplicate submissions. +""" + +from typing import Dict, Any +from flask import request, jsonify +from flask.views import MethodView + +class EvidenceUniquenessValidationRoute(MethodView): + """ + Route class for validating the uniqueness of evidence. + """ + + def __init__(self, database_service): + """ + Initialize the route with a database service. + + Args: + database_service: Service for database operations + """ + self.database_service = database_service + + def post(self) -> Dict[str, Any]: + """ + Validate the uniqueness of submitted evidence. + + Returns: + A JSON response indicating the uniqueness status of the evidence. + """ + try: + # Get the evidence data from the request + evidence_data = request.json + + if not evidence_data: + return jsonify({ + "status": "error", + "message": "No evidence data provided" + }), 400 + + # Extract unique identifiers for validation + unique_fields = evidence_data.get('unique_fields', []) + + if not unique_fields: + return jsonify({ + "status": "error", + "message": "No unique fields specified for validation" + }), 400 + + # Perform uniqueness check in the database + uniqueness_results = self._check_evidence_uniqueness(unique_fields) + + if not uniqueness_results['is_unique']: + return jsonify({ + "status": "error", + "message": "Evidence is not unique", + "duplicate_fields": uniqueness_results['duplicate_fields'] + }), 409 + + return jsonify({ + "status": "success", + "message": "Evidence is unique" + }), 200 + + except Exception as e: + return jsonify({ + "status": "error", + "message": f"Uniqueness validation failed: {str(e)}" + }), 500 + + def _check_evidence_uniqueness(self, unique_fields: Dict[str, Any]) -> Dict[str, Any]: + """ + Check the uniqueness of evidence based on specified fields. + + Args: + unique_fields (dict): Dictionary of fields to check for uniqueness + + Returns: + Dict containing uniqueness status and any duplicate fields + """ + duplicate_fields = {} + + for field, value in unique_fields.items(): + # Check if the field exists and has a value matching any existing record + existing_record = self.database_service.find_by_field(field, value) + + if existing_record: + duplicate_fields[field] = value + + return { + 'is_unique': len(duplicate_fields) == 0, + 'duplicate_fields': duplicate_fields + } + +def register_evidence_uniqueness_routes(app, database_service): + """ + Register evidence uniqueness validation routes. + + Args: + app: Flask application instance + database_service: Service for database operations + """ + evidence_route = EvidenceUniquenessValidationRoute(database_service) + app.add_url_rule( + '/validate/evidence-uniqueness', + view_func=evidence_route.post, + methods=['POST'] + ) \ No newline at end of file diff --git a/feature-builder/builder-task/orca-agent/tests/test_evidence_uniqueness.py b/feature-builder/builder-task/orca-agent/tests/test_evidence_uniqueness.py new file mode 100644 index 00000000..7c569293 --- /dev/null +++ b/feature-builder/builder-task/orca-agent/tests/test_evidence_uniqueness.py @@ -0,0 +1,107 @@ +""" +Test suite for evidence uniqueness validation. +""" + +import pytest +from unittest.mock import Mock, patch +from flask import Flask +from flask.testing import FlaskClient + +from src.server.routes.evidence_uniqueness import register_evidence_uniqueness_routes + +@pytest.fixture +def app(): + """ + Create a Flask test app with evidence uniqueness routes. + """ + test_app = Flask(__name__) + mock_database_service = Mock() + register_evidence_uniqueness_routes(test_app, mock_database_service) + return test_app + +@pytest.fixture +def client(app): + """ + Create a test client for the Flask app. + """ + return app.test_client() + +def test_evidence_uniqueness_route_requires_data(client: FlaskClient): + """ + Test that the route requires evidence data. + """ + response = client.post('/validate/evidence-uniqueness') + assert response.status_code == 400 + assert 'No evidence data provided' in response.json['message'] + +def test_evidence_uniqueness_route_requires_unique_fields(client: FlaskClient): + """ + Test that the route requires unique fields. + """ + response = client.post('/validate/evidence-uniqueness', json={}) + assert response.status_code == 400 + assert 'No unique fields specified' in response.json['message'] + +def test_evidence_uniqueness_validation_unique_evidence(client: FlaskClient): + """ + Test successful uniqueness validation. + """ + # Use patch to mock the database service + with patch('src.server.routes.evidence_uniqueness.EvidenceUniquenessValidationRoute._check_evidence_uniqueness') as mock_check: + mock_check.return_value = { + 'is_unique': True, + 'duplicate_fields': {} + } + + response = client.post('/validate/evidence-uniqueness', json={ + 'unique_fields': { + 'id': 'unique_evidence_123', + 'hash': 'abc123' + } + }) + + assert response.status_code == 200 + assert response.json['status'] == 'success' + assert 'Evidence is unique' in response.json['message'] + +def test_evidence_uniqueness_validation_duplicate_evidence(client: FlaskClient): + """ + Test uniqueness validation with duplicate evidence. + """ + # Use patch to mock the database service + with patch('src.server.routes.evidence_uniqueness.EvidenceUniquenessValidationRoute._check_evidence_uniqueness') as mock_check: + mock_check.return_value = { + 'is_unique': False, + 'duplicate_fields': { + 'id': 'duplicate_evidence_123' + } + } + + response = client.post('/validate/evidence-uniqueness', json={ + 'unique_fields': { + 'id': 'duplicate_evidence_123' + } + }) + + assert response.status_code == 409 + assert response.json['status'] == 'error' + assert 'Evidence is not unique' in response.json['message'] + assert 'duplicate_fields' in response.json + +def test_evidence_uniqueness_validation_handles_exceptions(client: FlaskClient): + """ + Test that the route handles exceptions gracefully. + """ + # Use patch to make the database service raise an exception + with patch('src.server.routes.evidence_uniqueness.EvidenceUniquenessValidationRoute._check_evidence_uniqueness') as mock_check: + mock_check.side_effect = Exception("Database error") + + response = client.post('/validate/evidence-uniqueness', json={ + 'unique_fields': { + 'id': 'test_evidence' + } + }) + + assert response.status_code == 500 + assert response.json['status'] == 'error' + assert 'Uniqueness validation failed' in response.json['message'] \ No newline at end of file