Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@ VALIDATION_RES_TOPIC=xxxx
CONTAINER_NAME=xxxx
AUTH_PERMISSION_URL=xxx # This is the URL to get the token
MAX_CONCURRENT_MESSAGES=xxx # Optional if not provided defaults to 2
MAX_GEOMETRY_VERTICES=xxx # Optional if not provided defaults to 2000
COORDINATE_PRECISION=xxx # Optional if not provided defaults to 7
ALLOW_ZERO_LENGTH_LINES=xxx # Optional if not provided defaults to False
AUTH_SIMULATE=xxx # Optional if not provided defaults to False
```

The application connect with the `STORAGECONNECTION` string provided in `.env` file and validates downloaded zipfile using `python-osw-validation` package.
`QUEUECONNECTION` is used to send out the messages and listen to messages.

`MAX_CONCURRENT_MESSAGES` is the maximum number of concurrent messages that the service can handle. If not provided, defaults to 2
`MAX_GEOMETRY_VERTICES`, `COORDINATE_PRECISION`, and `ALLOW_ZERO_LENGTH_LINES` configure `python-osw-validation` behavior. If not provided, the package defaults are used.

### How to Set up and Build
Follow the steps to install the python packages required for both building and running the application
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ python-ms-core==0.0.25
uvicorn==0.20.0
html_testRunner==1.2.1
geopandas==0.14.4
python-osw-validation==0.4.4
python-osw-validation==0.5.0
2 changes: 1 addition & 1 deletion src/assets/osw-validation.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
"success": true,
"message": ""
}
}
}
3 changes: 3 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class Settings(BaseSettings):
auth_permission_url: str = os.environ.get('AUTH_PERMISSION_URL', None)
max_concurrent_messages: int = os.environ.get('MAX_CONCURRENT_MESSAGES', 1)
max_receivable_messages: int = os.environ.get('MAX_RECEIVABLE_MESSAGES',-1) # -1 means no limit
max_geometry_vertices: int = os.environ.get('MAX_GEOMETRY_VERTICES', 2000)
coordinate_precision: int = os.environ.get('COORDINATE_PRECISION', 7)
allow_zero_length_lines: bool = os.environ.get('ALLOW_ZERO_LENGTH_LINES', True)

@property
def auth_provider(self) -> str:
Expand Down
5 changes: 3 additions & 2 deletions src/models/queue_message_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@


class ValidationResult:
is_valid: bool
validation_message: str
def __init__(self, is_valid: bool = False, validation_message: str = ''):
self.is_valid = is_valid
self.validation_message = validation_message


class Upload:
Expand Down
10 changes: 7 additions & 3 deletions src/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path
from .config import Settings
from python_osw_validation import OSWValidation
from python_osw_validation.config import ValidationConfig
from .models.queue_message_content import ValidationResult
import uuid
import json
Expand All @@ -27,6 +28,11 @@ def __init__(self, file_path=None, storage_client=None):
self.storage_client = storage_client
self.file_path = file_path
self.file_relative_path = file_path.split('/')[-1]
self.validation_config = ValidationConfig(
max_geometry_vertices=settings.max_geometry_vertices,
coordinate_precision=settings.coordinate_precision,
allow_zero_length_lines=settings.allow_zero_length_lines,
)
self.client = self.storage_client.get_container(container_name=self.container_name)
is_exists = os.path.exists(DOWNLOAD_DIR)
unique_id = self.get_unique_id()
Expand All @@ -45,14 +51,12 @@ def validate(self, max_errors=20) -> ValidationResult:
def is_osw_valid(self, max_errors) -> ValidationResult:
start_time = time.time()
result = ValidationResult()
result.is_valid = False
result.validation_message = ''
root, ext = os.path.splitext(self.file_relative_path)
if ext and ext.lower() == '.zip':
downloaded_file_path = self.download_single_file(self.file_path)
if downloaded_file_path:
logger.info(f' Downloaded file path: {downloaded_file_path}')
validator = OSWValidation(zipfile_path=downloaded_file_path)
validator = OSWValidation(zipfile_path=downloaded_file_path, config=self.validation_config)
validation_result = validator.validate(max_errors)
result.is_valid = validation_result.is_valid
if not result.is_valid:
Expand Down
1 change: 1 addition & 0 deletions tests/unit_tests/models/test_queue_message_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def test_validation_result_init(self):
result.validation_message = 'Validated'
self.assertTrue(result.is_valid)
self.assertEqual(result.validation_message, 'Validated')
self.assertFalse(hasattr(result, 'warning'))


if __name__ == '__main__':
Expand Down
9 changes: 9 additions & 0 deletions tests/unit_tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ class TestSettings(unittest.TestCase):
@patch.dict(os.environ, {
'AUTH_PERMISSION_URL': 'http://auth-url.com',
'MAX_CONCURRENT_MESSAGES': '5',
'MAX_GEOMETRY_VERTICES': '1000',
'COORDINATE_PRECISION': '6',
'ALLOW_ZERO_LENGTH_LINES': 'True',
'AUTH_SIMULATE': 'True'
}, clear=True)
def test_settings_with_simulated_auth(self):
settings = Settings()
self.assertEqual(settings.app_name, 'python-osw-validation')
self.assertEqual(settings.auth_permission_url, 'http://auth-url.com')
self.assertEqual(settings.max_concurrent_messages, 5)
self.assertEqual(settings.max_geometry_vertices, 1000)
self.assertEqual(settings.coordinate_precision, 6)
self.assertTrue(settings.allow_zero_length_lines)
self.assertEqual(settings.auth_provider, 'Simulated')

@patch.dict(os.environ, {
Expand Down Expand Up @@ -45,6 +51,9 @@ def test_default_settings(self):
self.assertEqual(settings.event_bus.container_name, 'osw')
self.assertIsNone(settings.auth_permission_url)
self.assertEqual(settings.max_concurrent_messages, 1)
self.assertEqual(settings.max_geometry_vertices, 2000)
self.assertEqual(settings.coordinate_precision, 7)
self.assertTrue(settings.allow_zero_length_lines)


if __name__ == '__main__':
Expand Down
18 changes: 18 additions & 0 deletions tests/unit_tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,24 @@ def test_send_status_success(self):

mock_publish.assert_called_once()

@patch('src.osw_validator.QueueMessage')
def test_send_status_excludes_warning_from_response(self, mock_queue_message):
validation_result = ValidationResult(is_valid=True, validation_message='')
mock_message = Upload(data={
'data': {
'user_id': '1233',
'tdei_project_group_id': '444444'
},
'message': 'test_message',
'messageType': 'message_type',
'messageId': '123'
})

self.service.send_status(result=validation_result, upload_message=mock_message)

response_payload = mock_queue_message.data_from.call_args[0][0]
self.assertNotIn('warning', response_payload['data'])

def test_send_status_failure(self):
validation_result = ValidationResult()
validation_result.is_valid = False
Expand Down
21 changes: 21 additions & 0 deletions tests/unit_tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class TestValidation(unittest.TestCase):
def setUp(self, mock_settings):
# Mock Settings and storage client to avoid actual dependencies
mock_settings.return_value.event_bus.container_name = 'test_container'
mock_settings.return_value.max_geometry_vertices = 2000
mock_settings.return_value.coordinate_precision = 7
mock_settings.return_value.allow_zero_length_lines = False

self.mock_storage_client = MagicMock()

Expand Down Expand Up @@ -65,6 +68,24 @@ def test_validate_valid_zip(self, mock_download_file, mock_clean_up):
# Ensure clean_up is called twice (once for the file, once for the folder)
self.assertEqual(mock_clean_up.call_count, 2)

@patch('src.validation.OSWValidation')
@patch('src.validation.Validation.clean_up')
@patch('src.validation.Validation.download_single_file')
def test_validate_passes_validation_config(self, mock_download_file, mock_clean_up, mock_osw_validation):
"""Test that configured validation limits are passed to the OSW validator."""
mock_download_file.return_value = f'{SAVED_FILE_PATH}/{SUCCESS_FILE_NAME}'
mock_validation_result = MagicMock()
mock_validation_result.is_valid = True
mock_osw_validation.return_value.validate.return_value = mock_validation_result

result = self.validation.validate(max_errors=10)

self.assertTrue(result.is_valid)
config = mock_osw_validation.call_args[1]['config']
self.assertEqual(config.max_geometry_vertices, 2000)
self.assertEqual(config.coordinate_precision, 7)
self.assertFalse(config.allow_zero_length_lines)
self.assertEqual(mock_clean_up.call_count, 2)

@patch('src.validation.Validation.clean_up')
@patch('src.validation.Validation.download_single_file')
Expand Down
Loading