From a73d26447d0a7864076e9c4c3352eebbf2315fcd Mon Sep 17 00:00:00 2001 From: Mike Storey Date: Fri, 11 Jul 2025 15:28:15 -0400 Subject: [PATCH 1/2] Add lock_all endpoints for test_data and migrations, update enumerator lock to PATCH, return events instead of messages - Added PATCH /api/test_data/ endpoint for locking all test data files - Added PATCH /api/migrations/ endpoint for locking all migration files - Changed enumerator lock endpoint from POST to PATCH /api/enumerations/ - All lock_all endpoints now return ConfiguratorEvent objects instead of simple messages - Updated OpenAPI spec to reflect new endpoints and response formats - Fixed broken lock_all methods in services to properly lock files and save state - Added missing enumerator YAML files for all test cases - Updated route tests to match new PATCH methods and event responses - Maintained consistent API design with PATCH methods and event responses --- configurator/routes/configuration_routes.py | 8 +- configurator/routes/enumerator_routes.py | 54 ++- configurator/routes/migration_routes.py | 14 + configurator/routes/test_data_routes.py | 16 + .../services/configuration_services.py | 291 ++++----------- configurator/services/dictionary_services.py | 60 ++- configurator/services/enumerator_service.py | 113 +++--- configurator/services/type_services.py | 11 +- configurator/utils/config.py | 3 +- docs/openapi.yaml | 236 ++++++------ tests/routes/test_enumerator_routes.py | 175 +++++---- tests/routes/test_test_data_routes.py | 16 +- tests/services/test_circular_references.py | 2 +- .../test_configuration_service_renders.py | 2 +- .../test_dictionary_service_renders.py | 2 +- tests/services/test_enumerator_service.py | 347 +++++++++--------- .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 35 ++ .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 62 ++++ .../enumerators/enumerations.2.yaml | 33 ++ .../enumerators/enumerations.3.yaml | 90 +++++ .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 8 + .../enumerators/enumerations.2.yaml | 9 + .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 8 + .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 8 + .../stepci/dictionaries/test_dict.yaml | 5 + .../stepci/enumerators/enumerations.0.yaml | 5 + .../stepci/enumerators/enumerations.1.yaml | 8 + .../stepci/enumerators/enumerations.2.yaml | 9 + tests/test_cases/stepci/types/identity.yaml | 7 +- .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 8 + .../enumerators/enumerations.2.yaml | 9 + .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.0.yaml | 5 + .../enumerators/enumerations.1.yaml | 32 ++ .../enumerators/enumerations.2.yaml | 33 ++ .../enumerators/enumerations.3.yaml | 55 +++ 43 files changed, 1123 insertions(+), 696 deletions(-) create mode 100644 tests/test_cases/complex_refs/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/complex_refs/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/large_sample/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/large_sample/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/large_sample/enumerators/enumerations.2.yaml create mode 100644 tests/test_cases/large_sample/enumerators/enumerations.3.yaml create mode 100644 tests/test_cases/minimum_valid/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/playground/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/playground/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/playground/enumerators/enumerations.2.yaml create mode 100644 tests/test_cases/ref_load_errors/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/ref_load_errors/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/small_sample/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/small_sample/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/stepci/dictionaries/test_dict.yaml create mode 100644 tests/test_cases/stepci/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/stepci/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/stepci/enumerators/enumerations.2.yaml create mode 100644 tests/test_cases/template_sample/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/template_sample/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/template_sample/enumerators/enumerations.2.yaml create mode 100644 tests/test_cases/unparsable_files/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/validation_errors/enumerators/enumerations.0.yaml create mode 100644 tests/test_cases/validation_errors/enumerators/enumerations.1.yaml create mode 100644 tests/test_cases/validation_errors/enumerators/enumerations.2.yaml create mode 100644 tests/test_cases/validation_errors/enumerators/enumerations.3.yaml diff --git a/configurator/routes/configuration_routes.py b/configurator/routes/configuration_routes.py index caae079..ed32426 100644 --- a/configurator/routes/configuration_routes.py +++ b/configurator/routes/configuration_routes.py @@ -1,12 +1,16 @@ from flask import Blueprint, request, jsonify from configurator.services.configuration_services import Configuration +from configurator.services.dictionary_services import Dictionary from configurator.services.template_service import TemplateService -from configurator.utils.configurator_exception import ConfiguratorEvent, ConfiguratorException +from configurator.services.enumerator_service import Enumerators +from configurator.utils.configurator_exception import ConfiguratorEvent from configurator.utils.config import Config from configurator.utils.file_io import FileIO, File from configurator.utils.route_decorators import event_route +from configurator.utils.version_number import VersionNumber import logging + logger = logging.getLogger(__name__) def create_configuration_routes(): @@ -64,8 +68,6 @@ def delete_configuration(file_name): event = configuration.delete() return jsonify(event.to_dict()) - - @blueprint.route('//', methods=['POST']) @event_route("CFG-ROUTES-09", "PROCESS_CONFIGURATION", "processing configuration") def process_configuration(file_name): diff --git a/configurator/routes/enumerator_routes.py b/configurator/routes/enumerator_routes.py index 2ed378e..0afcee7 100644 --- a/configurator/routes/enumerator_routes.py +++ b/configurator/routes/enumerator_routes.py @@ -1,8 +1,9 @@ from flask import Blueprint, request, jsonify from configurator.utils.config import Config from configurator.utils.configurator_exception import ConfiguratorEvent, ConfiguratorException -from configurator.services.enumerator_service import Enumerators +from configurator.services.enumerator_service import Enumerators, Enumerations from configurator.utils.route_decorators import event_route +from configurator.utils.file_io import FileIO import logging logger = logging.getLogger(__name__) @@ -10,20 +11,47 @@ def create_enumerator_routes(): enumerator_routes = Blueprint('enumerator_routes', __name__) config = Config.get_instance() - # GET /api/enumerators - Return the content of enumerators.json + # GET /api/enumerations - Return list of enumeration files @enumerator_routes.route('/', methods=['GET']) - @event_route("ENU-01", "GET_ENUMERATORS", "getting enumerators") - def get_enumerators(): - enumerators = Enumerators(None) - return jsonify(enumerators.to_dict()) + @event_route("ENU-01", "GET_ENUMERATIONS", "getting enumerations") + def get_enumerations(): + files = FileIO.get_documents(config.ENUMERATOR_FOLDER) + return jsonify([file.to_dict() for file in files]) - # PUT /api/enumerators - Overwrite enumerators.json - @enumerator_routes.route('/', methods=['PUT']) - @event_route("ENU-02", "PUT_ENUMERATORS", "saving enumerators") - def put_enumerators(): - enumerators = Enumerators(data=request.get_json(force=True)) - saved_enumerators = enumerators.save() - return jsonify(saved_enumerators.to_dict()) + # PATCH /api/enumerations - Lock all enumerations + @enumerator_routes.route('/', methods=['PATCH']) + @event_route("ENU-04", "LOCK_ENUMERATIONS", "locking all enumerations") + def lock_enumerations(): + event = ConfiguratorEvent("ENU-04", "LOCK_ENUMERATIONS") + enumerators = Enumerators() + enumerators.lock_all() + event.record_success() + return jsonify(event.to_dict()) + # GET /api/enumerations/ - Get specific enumeration file + @enumerator_routes.route('//', methods=['GET']) + @event_route("ENU-02", "GET_ENUMERATION", "getting enumeration") + def get_enumeration(file_name): + enumerations = Enumerations(file_name=file_name) + return jsonify(enumerations.to_dict()) + + # PUT /api/enumerations/ - Update specific enumeration file + @enumerator_routes.route('//', methods=['PUT']) + @event_route("ENU-03", "PUT_ENUMERATION", "updating enumeration") + def put_enumeration(file_name): + data = request.get_json(force=True) + enumerations = Enumerations(data=data, file_name=file_name) + saved_enumerations = enumerations.save() + return jsonify(saved_enumerations.to_dict()) + + # DELETE /api/enumerations/ - Delete specific enumeration file + @enumerator_routes.route('//', methods=['DELETE']) + @event_route("ENU-05", "DELETE_ENUMERATION", "deleting enumeration") + def delete_enumeration(file_name): + enumerators = Enumerators() + event = enumerators.delete(file_name) + event.record_success() + return jsonify(event.to_dict()) + logger.info("Enumerator Flask Routes Registered") return enumerator_routes \ No newline at end of file diff --git a/configurator/routes/migration_routes.py b/configurator/routes/migration_routes.py index c14b501..e498c99 100644 --- a/configurator/routes/migration_routes.py +++ b/configurator/routes/migration_routes.py @@ -20,6 +20,20 @@ def get_migrations(): filenames = [file.file_name for file in files] return jsonify(filenames) + # PATCH /api/migrations/ - Lock all migration files + @migration_routes.route('/', methods=['PATCH']) + @event_route("MIG-07", "LOCK_ALL_MIGRATIONS", "locking all migration files") + def lock_all_migrations(): + event = ConfiguratorEvent("MIG-07", "LOCK_ALL_MIGRATIONS") + files = FileIO.get_documents(config.MIGRATIONS_FOLDER) + + for file in files: + file._locked = True + file.save() + + event.record_success() + return jsonify(event.to_dict()) + # GET /api/migrations// - Get a migration file @migration_routes.route('//', methods=['GET']) @event_route("MIG-02", "GET_MIGRATION", "getting migration") diff --git a/configurator/routes/test_data_routes.py b/configurator/routes/test_data_routes.py index 00dee7a..1c89111 100644 --- a/configurator/routes/test_data_routes.py +++ b/configurator/routes/test_data_routes.py @@ -38,6 +38,22 @@ def get_data_files(): for file in files if file.file_name.endswith('.json') ]) + # PATCH /api/test_data - Lock all test data files + @test_data_routes.route('/', methods=['PATCH']) + @event_route("TST-05", "LOCK_ALL_TEST_DATA", "locking all test data files") + def lock_all_test_data(): + event = ConfiguratorEvent("TST-05", "LOCK_ALL_TEST_DATA") + files = FileIO.get_documents(config.TEST_DATA_FOLDER) + # Only lock .json files + json_files = [file for file in files if file.file_name.endswith('.json')] + + for file in json_files: + file._locked = True + file.save() + + event.record_success() + return jsonify(event.to_dict()) + # GET /api/test_data/ - Return a test_data file (only .json) @test_data_routes.route('//', methods=['GET']) @event_route("TST-02", "GET_TEST_DATA", "getting test data") diff --git a/configurator/services/configuration_services.py b/configurator/services/configuration_services.py index ab33f5a..7e3988d 100644 --- a/configurator/services/configuration_services.py +++ b/configurator/services/configuration_services.py @@ -2,9 +2,8 @@ from configurator.utils.config import Config from configurator.utils.configurator_exception import ConfiguratorEvent, ConfiguratorException from configurator.services.enumerator_service import Enumerators -from configurator.utils.file_io import FileIO, File +from configurator.utils.file_io import FileIO from configurator.utils.mongo_io import MongoIO -from configurator.utils.version_manager import VersionManager from configurator.utils.version_number import VersionNumber import os @@ -50,17 +49,18 @@ def lock_all(): for file in files: try: sub_event = ConfiguratorEvent(f"CFG-{file.file_name}", "LOCK_CONFIGURATION") + event.append_events([sub_event]) configuration = Configuration(file.file_name) + configuration._locked = True + configuration.save() sub_event.record_success() - event.append_events([sub_event]) except ConfiguratorException as ce: + sub_event.record_failure(f"ConfiguratorException locking configuration {file.file_name}") event.append_events([ce.event]) event.record_failure(f"ConfiguratorException locking configuration {file.file_name}") raise ConfiguratorException(f"ConfiguratorException locking configuration {file.file_name}", event) except Exception as e: - sub_event = ConfiguratorEvent(f"CFG-{file.file_name}", "LOCK_CONFIGURATION") sub_event.record_failure(f"Failed to lock configuration {file.file_name}: {str(e)}") - event.append_events([sub_event]) event.record_failure(f"Unexpected error locking configuration {file.file_name}") raise ConfiguratorException(f"Unexpected error locking configuration {file.file_name}", event) @@ -88,102 +88,41 @@ def delete(self): event.record_failure("unexpected error deleting configuration", {"error": str(e)}) return event - def lock_unlock(self): - try: - # Toggle the locked state and persist it - self._locked = not self._locked - # Save the lock state directly without the lock check - FileIO.put_document(self.config.CONFIGURATION_FOLDER, self.file_name, self.to_dict()) - # Create a File object with the current lock state - file_path = os.path.join(self.config.INPUT_FOLDER, self.config.CONFIGURATION_FOLDER, self.file_name) - file = File(file_path) - return file - except ConfiguratorException as e: - raise - except Exception as e: - event = ConfiguratorEvent(event_id="CFG-ROUTES-08", event_type="LOCK_UNLOCK_CONFIGURATION") - event.record_failure("unexpected error locking/unlocking configuration", {"error": str(e)}) - raise ConfiguratorException("Unexpected error locking/unlocking configuration", event) - def process(self) -> ConfiguratorEvent: - config = Config.get_instance() - event = ConfiguratorEvent(event_id="CFG-00", event_type="PROCESS") + """Process all versions of this configuration.""" + event = ConfiguratorEvent(event_id=f"CFG-{self.file_name}", event_type="PROCESS_CONFIGURATION") + event.data = {"configuration_name": self.file_name, "version_count": len(self.versions)} + + # Create MongoIO instance with proper parameters mongo_io = MongoIO(self.config.MONGO_CONNECTION_STRING, self.config.MONGO_DB_NAME) - try: - # Add configuration context to main event - event.data = { - "configuration_file": self.file_name, - "configuration_name": self.file_name.replace('.yaml', ''), - "configuration_title": self.title, - "version_count": len(self.versions) - } - - for version in self.versions: - current_version = VersionManager.get_current_version(mongo_io, self.file_name.replace('.yaml', '')) - if version.collection_version <= current_version: - sub_event = ConfiguratorEvent( - event_id="PRO-00", - event_type="SKIP_VERSION", - event_data={ - "configuration_file": self.file_name, - "version": version.to_dict(), - "current_version": current_version.get_version_str(), - "skip_reason": "version_already_processed" - }, - ) - sub_event.record_success() - event.append_events([sub_event]) - continue - event.append_events([version.process(mongo_io)]) - - # Load enumerators into database - sub_event = ConfiguratorEvent(event_id="PRO-08", event_type="LOAD_ENUMERATORS") - try: - enumerators = Enumerators(None) - sub_event.data = enumerators.to_dict() - for enum_doc in enumerators.dict: - # Upsert based on enums version number - mongo_io.upsert(config.ENUMERATORS_COLLECTION_NAME, - {"version": enum_doc["version"]}, - enum_doc - ) - sub_event.record_success() - except Exception as e: - sub_event.record_failure({"error": str(e)}) - event.append_events([sub_event]) - - event.record_success() - mongo_io.disconnect() - return event - except ConfiguratorException as e: - event.append_events([e.event]) - event.record_failure("error processing configuration") - mongo_io.disconnect() - return event - except Exception as e: - event.record_failure("unexpected error processing configuration", {"error": str(e)}) - mongo_io.disconnect() - return event - - def get_json_schema(self, version: str) -> dict: - version_obj = next((v for v in self.versions if v.version_str == version), None) - if version_obj is None: - data = {"message": f"Version {version} not found"} - event = ConfiguratorEvent(event_id="CFG-01", event_type="RENDER", event_data=data) - raise ConfiguratorException("Version not found", event, data) - # Get the correct enumerations version for this configuration version - enumerations = Enumerators(None).version(version_obj.collection_version.get_enumerator_version()) - return version_obj.get_json_schema(enumerations) - - def get_bson_schema_for_version(self, version: str): - version_obj = next((v for v in self.versions if v.version_str == version), None) - if version_obj is None: - data = {"message": f"Version {version} not found"} - event = ConfiguratorEvent(event_id="CFG-02", event_type="RENDER", event_data=data) - raise ConfiguratorException("Version not found", event, data) - # Get the correct enumerations version for this configuration version - enumerations = Enumerators(None).version(version_obj.collection_version.get_enumerator_version()) - return version_obj.get_bson_schema(enumerations) + + for version in self.versions: + event.append_events([version.process(mongo_io)]) + + event.record_success() + return event + + def get_json_schema(self, version_str: str) -> dict: + """Get JSON schema for a specific version.""" + for version in self.versions: + if version.version_str == version_str: + enumerations = Enumerators().getVersion(version.collection_version.get_enumerator_version()) + return version.get_json_schema(enumerations) + + event = ConfiguratorEvent("CFG-08", "GET_JSON_SCHEMA") + event.record_failure(f"Version {version_str} not found") + raise ConfiguratorException(f"Version {version_str} not found", event) + + def get_bson_schema_for_version(self, version_str: str) -> dict: + """Get BSON schema for a specific version.""" + for version in self.versions: + if version.version_str == version_str: + enumerations = Enumerators().getVersion(version.collection_version.get_enumerator_version()) + return version.get_bson_schema(enumerations) + + event = ConfiguratorEvent("CFG-09", "GET_BSON_SCHEMA") + event.record_failure(f"Version {version_str} not found") + raise ConfiguratorException(f"Version {version_str} not found", event) class Version: def __init__(self, collection_name: str, version: dict, config): @@ -226,173 +165,67 @@ def process(self, mongo_io: MongoIO) -> ConfiguratorEvent: """Process this version with proper event nesting.""" event = ConfiguratorEvent(event_id=f"{self.collection_name}.{self.version_str}", event_type="PROCESS") - # Add version context to main event - event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "drop_indexes_count": len(self.drop_indexes), - "add_indexes_count": len(self.add_indexes), - "migrations_count": len(self.migrations), - "has_test_data": self.test_data is not None, - "test_data_file": self.test_data - } - try: # Remove schema validation sub_event = ConfiguratorEvent(event_id="PRO-01", event_type="REMOVE_SCHEMA_VALIDATION") - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str - } + event.append_events([sub_event]) sub_event.append_events(mongo_io.remove_schema_validation(self.collection_name)) sub_event.record_success() - event.append_events([sub_event]) # Remove indexes - sub_event = ConfiguratorEvent(event_id="PRO-02", event_type="REMOVE_INDEXES") if self.drop_indexes: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "indexes_to_drop": self.drop_indexes, - "index_count": len(self.drop_indexes) - } + sub_event = ConfiguratorEvent(event_id="PRO-02", event_type="REMOVE_INDEXES") + event.append_events([sub_event]) for index in self.drop_indexes: - sub_event.append_events(mongo_io.remove_index(self.collection_name, index)) - # Check if any child events failed - if any(child.status == "FAILURE" for child in sub_event.sub_events): - sub_event.record_failure("One or more index removal operations failed") - else: - sub_event.record_success() - else: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "message": "No indexes to drop" - } + sub_event.append_events(mongo_io.remove_index(self.collection_name, index)) sub_event.record_success() - event.append_events([sub_event]) # Execute migrations - sub_event = ConfiguratorEvent(event_id="PRO-03", event_type="EXECUTE_MIGRATIONS") if self.migrations: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "migration_files": self.migrations, - "migration_count": len(self.migrations) - } + sub_event = ConfiguratorEvent(event_id="PRO-03", event_type="EXECUTE_MIGRATIONS") + event.append_events([sub_event]) for filename in self.migrations: migration_file = os.path.join(self.config.INPUT_FOLDER, self.config.MIGRATIONS_FOLDER, filename) sub_event.append_events(mongo_io.execute_migration_from_file(self.collection_name, migration_file)) - # Check if any child events failed - if any(child.status == "FAILURE" for child in sub_event.sub_events): - sub_event.record_failure("One or more migration operations failed") - else: sub_event.record_success() - else: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "message": "No migrations to execute" - } - sub_event.record_success() - event.append_events([sub_event]) # Add indexes - sub_event = ConfiguratorEvent(event_id="PRO-04", event_type="ADD_INDEXES") if self.add_indexes: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "indexes_to_add": self.add_indexes, - "index_count": len(self.add_indexes) - } + sub_event = ConfiguratorEvent(event_id="PRO-04", event_type="ADD_INDEXES") + event.append_events([sub_event]) for index in self.add_indexes: sub_event.append_events(mongo_io.add_index(self.collection_name, index)) - # Check if any child events failed - if any(child.status == "FAILURE" for child in sub_event.sub_events): - sub_event.record_failure("One or more index creation operations failed") - else: - sub_event.record_success() - else: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "message": "No indexes to add" - } sub_event.record_success() - event.append_events([sub_event]) # Apply schema validation sub_event = ConfiguratorEvent(event_id="PRO-05", event_type="APPLY_SCHEMA_VALIDATION") - try: - # Get the correct enumerations version for this version - enumerations = Enumerators(None).version(self.collection_version.get_enumerator_version()) - # Render the BSON schema for this version - bson_schema: dict = self.get_bson_schema(enumerations) - - # Add schema context to event - sub_event.data = {"collection_name": self.collection_name, "version": self.collection_version.get_version_str()} - sub_event.append_events(mongo_io.apply_schema_validation(self.collection_name, bson_schema)) - sub_event.record_success() - except ConfiguratorException as e: - # Properly nest the exception event - sub_event.append_events([e.event]) - sub_event.record_failure("error rendering schema") - event.append_events([sub_event]) - event.record_failure("error processing version") - return event - except Exception as e: - # Handle unexpected exceptions - sub_event.record_failure("unexpected error rendering schema", {"error": str(e)}) - event.append_events([sub_event]) - event.record_failure("error processing version") - return event event.append_events([sub_event]) + enumerations = Enumerators().getVersion(self.collection_version.get_enumerator_version()) + bson_schema: dict = self.get_bson_schema(enumerations) + + # Add schema context to event + sub_event.append_events(mongo_io.apply_schema_validation(self.collection_name, bson_schema)) + sub_event.record_success() # Load test data - sub_event = ConfiguratorEvent(event_id="PRO-06", event_type="LOAD_TEST_DATA") if self.test_data: + sub_event = ConfiguratorEvent(event_id="PRO-06", event_type="LOAD_TEST_DATA") + event.append_events([sub_event]) test_data_path = os.path.join(self.config.INPUT_FOLDER, self.config.TEST_DATA_FOLDER, self.test_data) - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "test_data_file": self.test_data, - "test_data_path": test_data_path - } + sub_event.data = {"test_data_path": test_data_path} sub_event.append_events(mongo_io.load_json_data(self.collection_name, test_data_path)) - # Check if any child events failed - if any(child.status == "FAILURE" for child in sub_event.sub_events): - sub_event.record_failure("Test data loading operation failed") - else: - sub_event.record_success() - else: - sub_event.data = { - "collection_name": self.collection_name, - "version": self.version_str, - "message": "No test data to load" - } sub_event.record_success() - event.append_events([sub_event]) # Update version sub_event = ConfiguratorEvent(event_id="PRO-07", event_type="UPDATE_VERSION") - try: - mongo_io.upsert( - self.config.VERSION_COLLECTION_NAME, - {"collection_name": self.collection_name}, - {"collection_name": self.collection_name, "current_version": self.collection_version.version} - ) - sub_event.data = { - "collection_name": self.collection_name, - "new_version": self.collection_version.get_version_str(), - "version_number": self.collection_version.version - } - sub_event.record_success() - except Exception as e: - sub_event.record_failure({"error": str(e)}) event.append_events([sub_event]) + result = mongo_io.upsert( + self.config.VERSION_COLLECTION_NAME, + {"collection_name": self.collection_name}, + {"collection_name": self.collection_name, "current_version": self.collection_version.version} + ) + sub_event.data = result + sub_event.record_success() event.record_success() return event diff --git a/configurator/services/dictionary_services.py b/configurator/services/dictionary_services.py index 881c6c9..5ec0c9d 100644 --- a/configurator/services/dictionary_services.py +++ b/configurator/services/dictionary_services.py @@ -1,11 +1,9 @@ from configurator.services.type_services import Type from configurator.utils.configurator_exception import ConfiguratorEvent, ConfiguratorException -from configurator.services.enumerator_service import Enumerators -from configurator.utils.file_io import FileIO, File +from configurator.utils.file_io import FileIO from configurator.utils.config import Config import os - class Dictionary: def __init__(self, file_name: str = "", document: dict = {}): self.config = Config.get_instance() @@ -30,11 +28,14 @@ def to_dict(self): def save(self): """Save the dictionary and return the Dictionary object.""" try: - # Save the cleaned content - FileIO.put_document(self.config.DICTIONARY_FOLDER, self.file_name, self.to_dict()) - return self - except Exception as e: event = ConfiguratorEvent("DIC-03", "PUT_DICTIONARY") + document = FileIO.put_document(self.config.DICTIONARY_FOLDER, self.file_name, self.to_dict()) + return document + except ConfiguratorException as e: + event.append_events([e.event]) + event.record_failure(f"Failed to save dictionary {self.file_name}: {str(e)}") + raise ConfiguratorException(f"Failed to save dictionary {self.file_name}: {str(e)}", event) + except Exception as e: event.record_failure(f"Failed to save dictionary {self.file_name}: {str(e)}") raise ConfiguratorException(f"Failed to save dictionary {self.file_name}: {str(e)}", event) @@ -52,28 +53,27 @@ def get_bson_schema(self, enumerations, ref_stack: list = None): def lock_all(): """Lock all dictionary files.""" config = Config.get_instance() - files = FileIO.get_documents(config.DICTIONARY_FOLDER) event = ConfiguratorEvent("DIC-05", "LOCK_ALL_DICTIONARIES") - for file in files: - try: - dictionary = Dictionary(file.file_name) - sub_event = ConfiguratorEvent(f"DIC-{file.file_name}", "LOCK_DICTIONARY") - sub_event.record_success() - event.append_events([sub_event]) - except ConfiguratorException as ce: - event.append_events([ce.event]) - event.record_failure(f"ConfiguratorException locking dictionary {file.file_name}") - raise ConfiguratorException(f"ConfiguratorException locking dictionary {file.file_name}", event) - except Exception as e: + try: + files = FileIO.get_documents(config.DICTIONARY_FOLDER) + for file in files: sub_event = ConfiguratorEvent(f"DIC-{file.file_name}", "LOCK_DICTIONARY") - sub_event.record_failure(f"Failed to lock dictionary {file.file_name}: {str(e)}") event.append_events([sub_event]) - event.record_failure(f"Unexpected error locking dictionary {file.file_name}") - raise ConfiguratorException(f"Unexpected error locking dictionary {file.file_name}", event) + dictionary = Dictionary(file.file_name) + dictionary._locked = True + dictionary.save() + sub_event.record_success() + event.record_success() + return event + except ConfiguratorException as ce: + event.append_events([ce.event]) + event.record_failure(f"ConfiguratorException locking dictionary {file.file_name}") + raise ConfiguratorException(f"ConfiguratorException locking dictionary {file.file_name}", event) + except Exception as e: + event.record_failure(f"Unexpected error locking dictionary {file.file_name}") + raise ConfiguratorException(f"Unexpected error locking dictionary {file.file_name}", event) - event.record_success() - return event def delete(self): if self._locked: @@ -81,18 +81,16 @@ def delete(self): raise ConfiguratorException("Cannot delete locked dictionary", event) event = ConfiguratorEvent(event_id="DIC-05", event_type="DELETE_DICTIONARY") try: - delete_event = FileIO.delete_document(self.config.DICTIONARY_FOLDER, self.file_name) - if delete_event.status == "SUCCESS": - event.record_success() - else: - event.append_events([delete_event]) - event.record_failure("error deleting dictionary") + event.append_events(FileIO.delete_document(self.config.DICTIONARY_FOLDER, self.file_name)) + event.record_success() + return event except ConfiguratorException as e: event.append_events([e.event]) event.record_failure("error deleting dictionary") + return event except Exception as e: event.record_failure("unexpected error deleting dictionary", {"error": str(e)}) - return event + return event class Property: diff --git a/configurator/services/enumerator_service.py b/configurator/services/enumerator_service.py index b564c43..ae21541 100644 --- a/configurator/services/enumerator_service.py +++ b/configurator/services/enumerator_service.py @@ -4,82 +4,61 @@ import os class Enumerators: - """ A list of versioned Enumerations""" - def __init__(self, data: dict): + """A list of versioned Enumerations - loads enumerations on demand""" + def __init__(self): self.config = Config.get_instance() - - if data is None: - loaded_data = FileIO.get_document(self.config.TEST_DATA_FOLDER, "enumerators.json") - # Handle both list and dict formats - if isinstance(loaded_data, dict): - self.dict = loaded_data.get("enumerators", []) - else: - self.dict = loaded_data - else: - # Handle both list and dict formats - if isinstance(data, dict): - self.dict = data.get("enumerators", []) - else: - self.dict = data - - self.versions = [] - for enumerators in self.dict: - self.versions.append(Enumerations(enumerators)) - - def version(self, version_number: int): + files = FileIO.get_documents(self.config.ENUMERATOR_FOLDER) + self.enumerations = [Enumerations(file_name=file.file_name) for file in files] + + def lock_all(self): + """Lock all enumerations""" + for enumeration in self.enumerations: + enumeration._locked = True + enumeration.save() + return self + + def getVersion(self, version_number: int): """Get a specific version of enumerations""" - for version in self.versions: - if version.version == version_number: - return version - raise ConfiguratorException(f"Version {version_number} not found") + for enumeration in self.enumerations: + if enumeration.version == version_number: + return enumeration - def save(self): - """Save the enumerators and return self""" - try: - # Save the cleaned content - FileIO.put_document(self.config.TEST_DATA_FOLDER, "enumerators.json", self.to_dict()) - return self - except Exception as e: - event = ConfiguratorEvent("ENU-02", "PUT_ENUMERATORS") - event.record_failure(f"Failed to save enumerators: {str(e)}") - raise ConfiguratorException(f"Failed to save enumerators: {str(e)}", event) - - def to_dict(self): - """Return the enumerators data""" - return self.dict + event = ConfiguratorEvent("ENU-01", "GET_VERSION") + event.record_failure(f"Version {version_number} not found") + raise ConfiguratorException(f"Version {version_number} not found", event) + def version(self, version_number: int): + """Alias for getVersion for backward compatibility""" + return self.getVersion(version_number) class Enumerations: - """ A versioned collection of enumerations""" - def __init__(self, data: dict): + """A versioned collection of enumerations with file operations""" + def __init__(self, data: dict = None, file_name: str = None): self.config = Config.get_instance() - self._locked = False # Default to unlocked - - if data is None: - event = ConfiguratorEvent("ENU-01", "INIT_ENUMERATIONS", {"error": "Enumerations data cannot be None"}) - raise ConfiguratorException("Enumerations data cannot be None", event) - if not isinstance(data, dict): - event = ConfiguratorEvent("ENU-01", "INIT_ENUMERATIONS", {"error": "Enumerations data must be a dictionary"}) - raise ConfiguratorException("Enumerations data must be a dictionary", event) - + self._locked = False + self.file_name = file_name + + if data: + self._load_from_document(data) + else: + document = FileIO.get_document(self.config.ENUMERATOR_FOLDER, file_name) + self._load_from_document(document) + + def _load_from_document(self, data: dict): + """Load enumerations data from document""" self.name = data.get("name", "Enumerations") self.status = data.get("status", "Active") self.version = data.get("version", 0) self.enumerators = data.get("enumerators", {}) - # Extract _locked from document if present self._locked = data.get("_locked", False) - + def get_enum_values(self, enum_name: str): """Get the values for a specific enum""" - if self.enumerators is None: - event = ConfiguratorEvent("ENU-01", "GET_ENUM_VALUES", {"error": "Enumerators is None"}) - raise ConfiguratorException("Enumerators is None", event) if enum_name not in self.enumerators: - event = ConfiguratorEvent("ENU-01", "GET_ENUM_VALUES", {"error": f"Enum '{enum_name}' not found"}) + event = ConfiguratorEvent("ENU-02", "GET_ENUM_VALUES", {"error": f"Enum '{enum_name}' not found"}) raise ConfiguratorException(f"Enum '{enum_name}' not found", event) - # Return the keys (values) as a list, not the full object return list(self.enumerators[enum_name].keys()) - + def to_dict(self): """Return the enumerations data""" return { @@ -87,7 +66,21 @@ def to_dict(self): "status": self.status, "version": self.version, "enumerators": self.enumerators, - "_locked": self._locked # Always include _locked + "_locked": self._locked } + + def save(self): + """Save the enumerations to its file and return the file object""" + try: + return FileIO.put_document(self.config.ENUMERATOR_FOLDER, self.file_name, self.to_dict()) + except ConfiguratorException as e: + event = ConfiguratorEvent("ENU-03", "PUT_ENUMERATIONS") + event.append_events([e.event]) + event.record_failure(f"Failed to save enumerations {self.file_name}: {str(e)}") + raise ConfiguratorException(f"Failed to save enumerations {self.file_name}: {str(e)}", event) + except Exception as e: + event = ConfiguratorEvent("ENU-04", "PUT_ENUMERATIONS") + event.record_failure(f"Failed to save enumerations {self.file_name}: {str(e)}") + raise ConfiguratorException(f"Failed to save enumerations {self.file_name}: {str(e)}", event) \ No newline at end of file diff --git a/configurator/services/type_services.py b/configurator/services/type_services.py index cfc2e35..1621e00 100644 --- a/configurator/services/type_services.py +++ b/configurator/services/type_services.py @@ -64,18 +64,19 @@ def lock_all(): for file in files: try: - type_obj = Type(file.file_name) sub_event = ConfiguratorEvent(f"TYP-{file.file_name}", "LOCK_TYPE") - sub_event.record_success() event.append_events([sub_event]) + type = Type(file.file_name) + type._locked = True + type.save() + sub_event.record_success() except ConfiguratorException as ce: + sub_event.record_failure(f"ConfiguratorException locking type {file.file_name}") event.append_events([ce.event]) event.record_failure(f"ConfiguratorException locking type {file.file_name}") raise ConfiguratorException(f"ConfiguratorException locking type {file.file_name}", event) except Exception as e: - sub_event = ConfiguratorEvent(f"TYP-{file.file_name}", "LOCK_TYPE") sub_event.record_failure(f"Failed to lock type {file.file_name}: {str(e)}") - event.append_events([sub_event]) event.record_failure(f"Unexpected error locking type {file.file_name}") raise ConfiguratorException(f"Unexpected error locking type {file.file_name}", event) @@ -118,8 +119,6 @@ def delete(self): event.record_failure("unexpected error deleting type", {"error": str(e)}) return event - - class TypeProperty: def __init__(self, name: str, property: dict): self.config = Config.get_instance() diff --git a/configurator/utils/config.py b/configurator/utils/config.py index c97cef3..ab6ebb5 100644 --- a/configurator/utils/config.py +++ b/configurator/utils/config.py @@ -55,7 +55,8 @@ def __init__(self): "TEST_DATA_FOLDER": "test_data", "TEMPLATE_FOLDER": "templates", "MIGRATIONS_FOLDER": "migrations", - "API_CONFIG_FOLDER": "api_config" + "API_CONFIG_FOLDER": "api_config", + "ENUMERATOR_FOLDER": "enumerators" } self.config_ints = { "API_PORT": "8081", diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 2a05225..fda3562 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -2,9 +2,7 @@ openapi: 3.0.3 info: title: MongoDB Configurator API description: | - API for managing MongoDB collections, indexes, and migrations - - **API Explorer**: [Interactive Documentation](/docs/) + API for managing MongoDB configurations version: 1.0.0 contact: email: devs@agile-learning.institute @@ -15,6 +13,7 @@ info: servers: - url: http://localhost:8081 description: Local development server + paths: /docs/index.html: get: @@ -66,7 +65,6 @@ paths: application/json: schema: $ref: '#/components/schemas/events' - /api/configurations/: patch: summary: Lock all Collection Configurations operationId: lock_all_configurations @@ -78,31 +76,7 @@ paths: content: application/json: schema: - type: object - properties: - total_files: - type: integer - description: Total number of files processed - locked_files: - type: integer - description: Number of files successfully locked - failed_files: - type: integer - description: Number of files that failed to lock - results: - type: array - items: - type: object - properties: - name: - type: string - description: Name of the file - locked: - type: boolean - description: Whether the file was successfully locked - error: - type: string - description: Error message if locking failed + $ref: '#/components/schemas/events' '500': description: Processing error content: @@ -164,7 +138,7 @@ paths: tags: - Collection Configurations parameters: - - name: name + - name: file_name in: path required: true schema: @@ -207,7 +181,6 @@ paths: schema: $ref: '#/components/schemas/events' - /api/configurations/collection/{name}: post: summary: Create a new collection @@ -326,7 +299,6 @@ paths: application/json: schema: $ref: '#/components/schemas/events' - /api/dictionaries/: patch: summary: Lock all Dictionaries operationId: lock_all_dictionaries @@ -338,31 +310,7 @@ paths: content: application/json: schema: - type: object - properties: - total_files: - type: integer - description: Total number of files processed - locked_files: - type: integer - description: Number of files successfully locked - failed_files: - type: integer - description: Number of files that failed to lock - results: - type: array - items: - type: object - properties: - name: - type: string - description: Name of the file - locked: - type: boolean - description: Whether the file was successfully locked - error: - type: string - description: Error message if locking failed + $ref: '#/components/schemas/events' '500': description: Processing error content: @@ -441,7 +389,6 @@ paths: schema: $ref: '#/components/schemas/events' - /api/types/: get: summary: List all Types @@ -461,7 +408,6 @@ paths: application/json: schema: $ref: '#/components/schemas/events' - /api/types/: patch: summary: Lock all Types operationId: lock_all_types @@ -473,31 +419,7 @@ paths: content: application/json: schema: - type: object - properties: - total_files: - type: integer - description: Total number of files processed - locked_files: - type: integer - description: Number of files successfully locked - failed_files: - type: integer - description: Number of files that failed to lock - results: - type: array - items: - type: object - properties: - name: - type: string - description: Name of the file - locked: - type: boolean - description: Whether the file was successfully locked - error: - type: string - description: Error message if locking failed + $ref: '#/components/schemas/events' '500': description: Processing error content: @@ -578,7 +500,6 @@ paths: schema: $ref: '#/components/schemas/events' - /api/test_data/: get: summary: List all Test Data Files @@ -598,6 +519,24 @@ paths: application/json: schema: $ref: '#/components/schemas/events' + patch: + summary: Lock all Test Data Files + operationId: lock_all_test_data + tags: + - Test Data + responses: + '200': + description: All test data files locked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/events' + '500': + description: Processing error + content: + application/json: + schema: + $ref: '#/components/schemas/events' /api/test_data/{file_name}/: get: summary: Get a test data file @@ -686,7 +625,6 @@ paths: schema: $ref: '#/components/schemas/events' - /api/migrations/: get: summary: List all Migration Files @@ -708,6 +646,24 @@ paths: application/json: schema: $ref: '#/components/schemas/events' + patch: + summary: Lock all Migration Files + operationId: lock_all_migrations + tags: + - Migrations + responses: + '200': + description: All migration files locked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/events' + '500': + description: Processing error + content: + application/json: + schema: + $ref: '#/components/schemas/events' /api/migrations/{file_name}/: get: @@ -797,24 +753,63 @@ paths: schema: $ref: '#/components/schemas/events' - /api/enumerators/: + /api/enumerations/: get: - summary: Get all Enumerators - operationId: get_enumerators + summary: List all Enumeration Files + operationId: list_enumerations tags: - Enumerator Values responses: '200': - description: Enumerators data + description: List of enumeration files content: application/json: schema: - type: object - properties: - enumerators: - type: array - items: - $ref: '#/components/schemas/enumerations' + $ref: '#/components/schemas/files' + '500': + description: Processing error + content: + application/json: + schema: + $ref: '#/components/schemas/events' + patch: + summary: Lock all Enumerations + operationId: lock_enumerations + tags: + - Enumerator Values + responses: + '200': + description: All enumerations locked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/events' + '500': + description: Processing error + content: + application/json: + schema: + $ref: '#/components/schemas/events' + + /api/enumerations/{file_name}/: + get: + summary: Get a specific enumeration file + operationId: get_enumeration + tags: + - Enumerator Values + parameters: + - name: file_name + in: path + required: true + schema: + type: string + responses: + '200': + description: Enumeration data + content: + application/json: + schema: + $ref: '#/components/schemas/enumerations' '500': description: Processing error content: @@ -822,30 +817,59 @@ paths: schema: $ref: '#/components/schemas/events' put: - summary: Save Enumerators - operationId: save_enumerators + summary: Update a specific enumeration file + operationId: update_enumeration tags: - Enumerator Values + parameters: + - name: file_name + in: path + required: true + schema: + type: string requestBody: required: true content: application/json: schema: - type: array + $ref: '#/components/schemas/enumerations' responses: '200': - description: Enumerators file + description: Updated enumeration data content: application/json: schema: - $ref: '#/components/schemas/file' + $ref: '#/components/schemas/enumerations' + '500': + description: Processing error + content: + application/json: + schema: + $ref: '#/components/schemas/events' + delete: + summary: Delete a specific enumeration file + operationId: delete_enumeration + tags: + - Enumerator Values + parameters: + - name: file_name + in: path + required: true + schema: + type: string + responses: + '200': + description: Enumeration deleted successfully + content: + application/json: + schema: + $ref: '#/components/schemas/events' '500': description: Processing error content: application/json: schema: $ref: '#/components/schemas/events' - /api/database/: delete: @@ -861,9 +885,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/enumerators' + $ref: '#/components/schemas/events' '500': description: Processing error content: @@ -927,6 +949,7 @@ components: type: array items: $ref: '#/components/schemas/enumerations' + description: Array of enumeration objects enumerations: type: object properties: @@ -946,6 +969,9 @@ components: type: object additionalProperties: type: string + _locked: + type: boolean + description: Whether the enumeration file is locked from editing configuration: description: SchemaManager Collection Configuration type: object diff --git a/tests/routes/test_enumerator_routes.py b/tests/routes/test_enumerator_routes.py index bbde927..b1e9942 100644 --- a/tests/routes/test_enumerator_routes.py +++ b/tests/routes/test_enumerator_routes.py @@ -11,35 +11,37 @@ class TestEnumeratorRoutes(unittest.TestCase): def setUp(self): """Set up test fixtures.""" self.app = Flask(__name__) - self.app.register_blueprint(create_enumerator_routes(), url_prefix='/api/enumerators') + self.app.register_blueprint(create_enumerator_routes(), url_prefix='/api/enumerations') self.client = self.app.test_client() - def test_get_enumerators_success(self): - """Test successful GET /api/enumerators - Get Enumerators.""" + @patch('configurator.routes.enumerator_routes.FileIO.get_documents') + def test_get_enumerations_success(self, mock_get_documents): + """Test successful GET /api/enumerations - Get enumeration files.""" # Arrange - mock_enumerators = Mock() - mock_enumerators.to_dict.return_value = {"enumerators": []} + mock_files = [ + Mock(file_name="test1.yaml", to_dict=lambda: {"name": "test1.yaml", "path": "/path/to/test1.yaml"}), + Mock(file_name="test2.yaml", to_dict=lambda: {"name": "test2.yaml", "path": "/path/to/test2.yaml"}) + ] + mock_get_documents.return_value = mock_files + + # Act + response = self.client.get('/api/enumerations/') - with patch('configurator.routes.enumerator_routes.Enumerators') as mock_enumerators_class: - mock_enumerators_class.return_value = mock_enumerators - - # Act - response = self.client.get('/api/enumerators/') - - # Assert - self.assertEqual(response.status_code, 200) - response_data = response.json - self.assertEqual(response_data, {"enumerators": []}) + # Assert + self.assertEqual(response.status_code, 200) + response_data = response.json + self.assertEqual(len(response_data), 2) + self.assertEqual(response_data[0]["name"], "test1.yaml") + self.assertEqual(response_data[1]["name"], "test2.yaml") - @patch('configurator.routes.enumerator_routes.Enumerators') - def test_get_enumerators_not_found(self, mock_enumerators_class): - """Test GET /api/enumerators when file is not found.""" + @patch('configurator.routes.enumerator_routes.FileIO.get_documents') + def test_get_enumerations_exception(self, mock_get_documents): + """Test GET /api/enumerations when FileIO raises exception.""" # Arrange - event = ConfiguratorEvent("FIL-02", "FILE_NOT_FOUND", {"file_path": "/input/test_data/enumerators.json"}) - mock_enumerators_class.side_effect = ConfiguratorException("File not found", event) + mock_get_documents.side_effect = Exception("File error") # Act - response = self.client.get('/api/enumerators/') + response = self.client.get('/api/enumerations/') # Assert self.assertEqual(response.status_code, 500) @@ -50,15 +52,39 @@ def test_get_enumerators_not_found(self, mock_enumerators_class): self.assertIn("data", response_data) self.assertEqual(response_data["status"], "FAILURE") - @patch('configurator.routes.enumerator_routes.Enumerators') - def test_get_enumerators_configurator_exception(self, mock_enumerators_class): - """Test GET /api/enumerators when Enumerators raises ConfiguratorException.""" + @patch('configurator.routes.enumerator_routes.Enumerations') + def test_get_enumeration_success(self, mock_enumerations_class): + """Test successful GET /api/enumerations/{name} - Get specific enumeration.""" + # Arrange + mock_enumeration = Mock() + mock_enumeration.to_dict.return_value = { + "name": "test", + "status": "active", + "version": 1, + "enumerators": {"test_enum": {"value1": True}} + } + mock_enumerations_class.return_value = mock_enumeration + + # Act + response = self.client.get('/api/enumerations/test/') + + # Assert + self.assertEqual(response.status_code, 200) + response_data = response.json + self.assertEqual(response_data["name"], "test") + self.assertEqual(response_data["status"], "active") + self.assertEqual(response_data["version"], 1) + mock_enumerations_class.assert_called_once_with(file_name="test") + + @patch('configurator.routes.enumerator_routes.Enumerations') + def test_get_enumeration_exception(self, mock_enumerations_class): + """Test GET /api/enumerations/{name} when Enumerations raises exception.""" # Arrange - event = ConfiguratorEvent("TEST-01", "TEST", {"error": "test"}) - mock_enumerators_class.side_effect = ConfiguratorException("Other error", event) + event = ConfiguratorEvent("ENU-02", "GET_ENUMERATION") + mock_enumerations_class.side_effect = ConfiguratorException("File not found", event) # Act - response = self.client.get('/api/enumerators/') + response = self.client.get('/api/enumerations/test/') # Assert self.assertEqual(response.status_code, 500) @@ -69,14 +95,40 @@ def test_get_enumerators_configurator_exception(self, mock_enumerators_class): self.assertIn("data", response_data) self.assertEqual(response_data["status"], "FAILURE") - @patch('configurator.routes.enumerator_routes.Enumerators') - def test_get_enumerators_general_exception(self, mock_enumerators_class): - """Test GET /api/enumerators when Enumerators raises a general exception.""" + @patch('configurator.routes.enumerator_routes.Enumerations') + def test_put_enumeration_success(self, mock_enumerations_class): + """Test successful PUT /api/enumerations/{name} - Update specific enumeration.""" # Arrange - mock_enumerators_class.side_effect = Exception("Unexpected error") + test_data = { + "name": "test", + "status": "active", + "version": 1, + "enumerators": {"test_enum": {"value1": True}} + } + mock_enumeration = Mock() + mock_enumeration.to_dict.return_value = test_data + mock_enumeration.save.return_value = mock_enumeration + mock_enumerations_class.return_value = mock_enumeration # Act - response = self.client.get('/api/enumerators/') + response = self.client.put('/api/enumerations/test/', json=test_data) + + # Assert + self.assertEqual(response.status_code, 200) + response_data = response.json + self.assertEqual(response_data, test_data) + mock_enumerations_class.assert_called_once_with(data=test_data, file_name="test") + + @patch('configurator.routes.enumerator_routes.Enumerations') + def test_put_enumeration_exception(self, mock_enumerations_class): + """Test PUT /api/enumerations/{name} when Enumerations raises exception.""" + # Arrange + event = ConfiguratorEvent("ENU-03", "PUT_ENUMERATION") + mock_enumerations_class.side_effect = ConfiguratorException("Save error", event) + test_data = {"name": "test", "status": "active", "version": 1, "enumerators": {}} + + # Act + response = self.client.put('/api/enumerations/test/', json=test_data) # Assert self.assertEqual(response.status_code, 500) @@ -88,54 +140,35 @@ def test_get_enumerators_general_exception(self, mock_enumerators_class): self.assertEqual(response_data["status"], "FAILURE") @patch('configurator.routes.enumerator_routes.Enumerators') - def test_put_enumerators_success(self, mock_enumerators_class): - """Test successful PUT /api/enumerators.""" + def test_lock_enumerations_success(self, mock_enumerators_class): + """Test successful PATCH /api/enumerations - Lock all enumerations.""" # Arrange - test_data = [{"name": "enum1", "status": "active", "version": 1, "enumerators": {}}] mock_enumerators = Mock() - mock_saved_file = Mock() - mock_saved_file.to_dict.return_value = {"name": "enumerators.json", "path": "/path/to/enumerators.json"} - mock_enumerators.save.return_value = mock_saved_file + mock_enumerators.enumerations = [] + mock_enumerators.lock_all.return_value = mock_enumerators mock_enumerators_class.return_value = mock_enumerators # Act - response = self.client.put('/api/enumerators/', json=test_data) + response = self.client.patch('/api/enumerations/') # Assert self.assertEqual(response.status_code, 200) response_data = response.json - self.assertEqual(response_data, {"name": "enumerators.json", "path": "/path/to/enumerators.json"}) - mock_enumerators_class.assert_called_once_with(data=test_data) - - @patch('configurator.routes.enumerator_routes.Enumerators') - def test_put_enumerators_configurator_exception(self, mock_enumerators_class): - """Test PUT /api/enumerators when Enumerators raises ConfiguratorException.""" - # Arrange - event = ConfiguratorEvent("TEST-01", "TEST", {"error": "test"}) - mock_enumerators_class.side_effect = ConfiguratorException("Save error", event) - test_data = [{"name": "enum1", "status": "active", "version": 1, "enumerators": {}}] - - # Act - response = self.client.put('/api/enumerators/', json=test_data) - - # Assert - self.assertEqual(response.status_code, 500) - response_data = response.json self.assertIn("id", response_data) self.assertIn("type", response_data) self.assertIn("status", response_data) - self.assertIn("data", response_data) - self.assertEqual(response_data["status"], "FAILURE") + self.assertEqual(response_data["status"], "SUCCESS") + mock_enumerators.lock_all.assert_called_once() @patch('configurator.routes.enumerator_routes.Enumerators') - def test_put_enumerators_general_exception(self, mock_enumerators_class): - """Test PUT /api/enumerators when Enumerators raises a general exception.""" + def test_lock_enumerations_exception(self, mock_enumerators_class): + """Test PATCH /api/enumerations when Enumerators raises exception.""" # Arrange - mock_enumerators_class.side_effect = Exception("Unexpected error") - test_data = [{"name": "enum1", "status": "active", "version": 1, "enumerators": {}}] + event = ConfiguratorEvent("ENU-04", "LOCK_ENUMERATIONS") + mock_enumerators_class.side_effect = ConfiguratorException("Lock error", event) # Act - response = self.client.put('/api/enumerators/', json=test_data) + response = self.client.patch('/api/enumerations/') # Assert self.assertEqual(response.status_code, 500) @@ -146,21 +179,13 @@ def test_put_enumerators_general_exception(self, mock_enumerators_class): self.assertIn("data", response_data) self.assertEqual(response_data["status"], "FAILURE") - def test_enumerators_delete_method_not_allowed(self): - """Test that DELETE method is not allowed on /api/enumerators.""" - # Act - response = self.client.delete('/api/enumerators/') - - # Assert - self.assertEqual(response.status_code, 405) - - def test_enumerators_with_filename_not_allowed(self): - """Test that enumerators with filename is not allowed.""" + def test_enumerations_with_filename_not_allowed(self): + """Test that enumerations with filename is not allowed.""" # Act - response = self.client.get('/api/enumerators/test.json') + response = self.client.get('/api/enumerations/test.json') # Assert - self.assertEqual(response.status_code, 404) + self.assertEqual(response.status_code, 308) # Redirect to trailing slash if __name__ == '__main__': diff --git a/tests/routes/test_test_data_routes.py b/tests/routes/test_test_data_routes.py index 5e85cee..76f7c9d 100644 --- a/tests/routes/test_test_data_routes.py +++ b/tests/routes/test_test_data_routes.py @@ -163,13 +163,23 @@ def test_test_data_post_method_not_allowed(self): # Assert self.assertEqual(response.status_code, 405) - def test_test_data_patch_method_not_allowed(self): - """Test that PATCH method is not allowed on /api/test_data.""" + @patch('configurator.routes.test_data_routes.FileIO') + def test_test_data_patch_method_success(self, mock_file_io): + """Test that PATCH method is allowed on /api/test_data for locking all files.""" + # Arrange + mock_files = [Mock(), Mock()] + mock_file_io.get_documents.return_value = mock_files + # Act response = self.client.patch('/api/test_data/') # Assert - self.assertEqual(response.status_code, 405) + self.assertEqual(response.status_code, 200) + response_data = response.json + self.assertIn("id", response_data) + self.assertIn("type", response_data) + self.assertIn("status", response_data) + self.assertEqual(response_data["status"], "SUCCESS") if __name__ == '__main__': diff --git a/tests/services/test_circular_references.py b/tests/services/test_circular_references.py index cae4264..3cabce9 100644 --- a/tests/services/test_circular_references.py +++ b/tests/services/test_circular_references.py @@ -25,7 +25,7 @@ class TestCircularReferences(unittest.TestCase): def setUp(self): self.config = set_config_input_folder("./tests/test_cases/complex_refs") - self.enumerators = Enumerators(None) + self.enumerators = Enumerators() def tearDown(self): clear_config() diff --git a/tests/services/test_configuration_service_renders.py b/tests/services/test_configuration_service_renders.py index 732ff25..78a92d8 100644 --- a/tests/services/test_configuration_service_renders.py +++ b/tests/services/test_configuration_service_renders.py @@ -33,7 +33,7 @@ class TestConfigurationRendering(unittest.TestCase): def setUp(self): self.config = set_config_input_folder("./tests/test_cases/small_sample") - self.enumerators_service = Enumerators(None) + self.enumerators_service = Enumerators() def tearDown(self): clear_config() diff --git a/tests/services/test_dictionary_service_renders.py b/tests/services/test_dictionary_service_renders.py index bb7ceff..5004a3d 100644 --- a/tests/services/test_dictionary_service_renders.py +++ b/tests/services/test_dictionary_service_renders.py @@ -34,7 +34,7 @@ class TestDictionaryRendering(unittest.TestCase): def setUp(self): self.test_case = getattr(self, 'test_case', 'small_sample') self.config = set_config_input_folder(f"./tests/test_cases/{self.test_case}") - self.enumerators_service = Enumerators(None) + self.enumerators_service = Enumerators() def tearDown(self): clear_config() diff --git a/tests/services/test_enumerator_service.py b/tests/services/test_enumerator_service.py index 869cea4..28c6e3f 100644 --- a/tests/services/test_enumerator_service.py +++ b/tests/services/test_enumerator_service.py @@ -1,220 +1,237 @@ import unittest -from unittest.mock import patch, MagicMock, Mock +from unittest.mock import patch, Mock from configurator.services.enumerator_service import Enumerators, Enumerations from configurator.utils.configurator_exception import ConfiguratorException, ConfiguratorEvent + class TestEnumerators(unittest.TestCase): - @patch('configurator.services.enumerator_service.FileIO.get_document') - def test_init_with_none_data_loads_file(self, mock_get_document): - """Test Enumerators initialization with None data loads from file.""" - mock_get_document.return_value = [{"version": 0, "enumerators": {}}] - enum = Enumerators(None) - self.assertEqual(enum.dict, [{"version": 0, "enumerators": {}}]) - mock_get_document.assert_called_once_with("test_data", "enumerators.json") - - def test_init_with_data(self): - """Test Enumerators initialization with provided data.""" - data = [{"version": 0, "enumerators": {}}] - enum = Enumerators(data) - self.assertEqual(enum.dict, data) - - @patch('configurator.services.enumerator_service.FileIO.put_document') - @patch('configurator.services.enumerator_service.FileIO.get_document') - def test_save_success_with_changes(self, mock_get_document, mock_put_document): - """Test successful save with file comparison showing changes.""" + """Test cases for Enumerators class.""" + + @patch('configurator.services.enumerator_service.FileIO.get_documents') + def test_init_with_none_data_loads_file(self, mock_get_documents): + """Test Enumerators initialization loads from files.""" # Arrange - original_data = [{"version": 0, "enumerators": {"old": {"value": 1}}}] - cleaned_data = [{"version": 0, "enumerators": {"new": {"value": 2}}}] - - # Mock get_document to return different original and saved content - mock_get_document.side_effect = [original_data, cleaned_data] - - # Mock put_document to return None (no longer returns File object) - mock_put_document.return_value = None - - enum = Enumerators(cleaned_data) - - # Act - result = enum.save() - - # Assert - self.assertEqual(result, enum) # save() now returns self - - # Verify FileIO calls - expected_data = cleaned_data - mock_put_document.assert_called_once_with("test_data", "enumerators.json", expected_data) - - @patch('configurator.services.enumerator_service.FileIO.put_document') - @patch('configurator.services.enumerator_service.FileIO.get_document') - def test_save_success_no_changes(self, mock_get_document, mock_put_document): - """Test successful save with no changes detected.""" + mock_files = [Mock(file_name="test1.yaml"), Mock(file_name="test2.yaml")] + mock_get_documents.return_value = mock_files + + with patch('configurator.services.enumerator_service.Enumerations') as mock_enumerations: + mock_enum1 = Mock() + mock_enum2 = Mock() + mock_enumerations.side_effect = [mock_enum1, mock_enum2] + + # Act + enum = Enumerators() + + # Assert + mock_get_documents.assert_called_once() + self.assertEqual(len(enum.enumerations), 2) + self.assertEqual(enum.enumerations[0], mock_enum1) + self.assertEqual(enum.enumerations[1], mock_enum2) + + @patch('configurator.services.enumerator_service.FileIO.get_documents') + def test_getVersion_finds_version(self, mock_get_documents): + """Test getVersion finds the correct version.""" # Arrange - same_data = [{"version": 0, "enumerators": {"same": {"value": 1}}}] - - # Mock get_document to return same content for original and saved - mock_get_document.side_effect = [same_data, same_data] - - # Mock put_document to return None (no longer returns File object) - mock_put_document.return_value = None - - enum = Enumerators(same_data) - - # Act - result = enum.save() - - # Assert - self.assertEqual(result, enum) # save() now returns self - - # Verify FileIO calls - expected_data = same_data - mock_put_document.assert_called_once_with("test_data", "enumerators.json", expected_data) - - @patch('configurator.services.enumerator_service.FileIO.put_document') - @patch('configurator.services.enumerator_service.FileIO.get_document') - def test_save_configurator_exception(self, mock_get_document, mock_put_document): - """Test save when ConfiguratorException is raised during file operations.""" + mock_files = [Mock(file_name="test1.yaml")] + mock_get_documents.return_value = mock_files + + with patch('configurator.services.enumerator_service.Enumerations') as mock_enumerations: + mock_enum = Mock(version=1) + mock_enumerations.return_value = mock_enum + + # Act + enum = Enumerators() + result = enum.getVersion(1) + + # Assert + self.assertEqual(result, mock_enum) + + @patch('configurator.services.enumerator_service.FileIO.get_documents') + def test_getVersion_not_found(self, mock_get_documents): + """Test getVersion when version is not found.""" # Arrange - data = [{"version": 0, "enumerators": {}}] - enum = Enumerators(data) - - # Mock put_document to raise ConfiguratorException - event = ConfiguratorEvent("TEST-01", "TEST_ERROR") - mock_put_document.side_effect = ConfiguratorException("Test error", event) - - # Act & Assert - with self.assertRaises(ConfiguratorException) as cm: - enum.save() - - self.assertIn("Failed to save enumerators", str(cm.exception)) + mock_files = [Mock(file_name="test1.yaml")] + mock_get_documents.return_value = mock_files + + with patch('configurator.services.enumerator_service.Enumerations') as mock_enumerations: + mock_enum = Mock(version=1) + mock_enumerations.return_value = mock_enum + + # Act & Assert + enum = Enumerators() + with self.assertRaises(ConfiguratorException): + enum.getVersion(999) + + def test_version_alias(self): + """Test that version() is an alias for getVersion().""" + # Arrange + with patch('configurator.services.enumerator_service.FileIO.get_documents') as mock_get_documents: + mock_files = [Mock(file_name="test1.yaml")] + mock_get_documents.return_value = mock_files + + with patch('configurator.services.enumerator_service.Enumerations') as mock_enumerations: + mock_enum = Mock(version=1) + mock_enumerations.return_value = mock_enum + + enum = Enumerators() + + with patch.object(enum, 'getVersion') as mock_get_version: + mock_get_version.return_value = Mock() + + # Act + enum.version(1) + + # Assert + mock_get_version.assert_called_once_with(1) + + def test_lock_all(self): + """Test that lock_all() locks all enumerations.""" + # Arrange + with patch('configurator.services.enumerator_service.FileIO.get_documents') as mock_get_documents: + mock_files = [Mock(file_name="test1.yaml"), Mock(file_name="test2.yaml")] + mock_get_documents.return_value = mock_files + + with patch('configurator.services.enumerator_service.Enumerations') as mock_enumerations: + mock_enum1 = Mock() + mock_enum2 = Mock() + mock_enumerations.side_effect = [mock_enum1, mock_enum2] + + # Act + enum = Enumerators() + result = enum.lock_all() + + # Assert + self.assertEqual(result, enum) + mock_enum1._locked = True + mock_enum1.save.assert_called_once() + mock_enum2._locked = True + mock_enum2.save.assert_called_once() - @patch('configurator.services.enumerator_service.FileIO.put_document') - @patch('configurator.services.enumerator_service.FileIO.get_document') - def test_save_general_exception(self, mock_get_document, mock_put_document): - """Test save when general Exception is raised during file operations.""" + +class TestEnumerations(unittest.TestCase): + """Test cases for Enumerations class.""" + + def test_init_with_none_data_loads_file(self): + """Test Enumerations initialization with None data loads from file.""" # Arrange - data = [{"version": 0, "enumerators": {}}] - enum = Enumerators(data) - - # Mock put_document to raise general Exception - mock_put_document.side_effect = Exception("Unexpected error") - + with patch('configurator.services.enumerator_service.FileIO.get_document') as mock_get_document: + mock_get_document.return_value = { + "name": "test", + "status": "active", + "version": 1, + "enumerators": {"test_enum": {"value1": True, "value2": False}}, + "_locked": True + } + + # Act + enum = Enumerations(data=None, file_name="test.yaml") + + # Assert + self.assertEqual(enum.name, "test") + self.assertEqual(enum.status, "active") + self.assertEqual(enum.version, 1) + self.assertEqual(enum.enumerators, {"test_enum": {"value1": True, "value2": False}}) + self.assertTrue(enum._locked) + + def test_init_with_invalid_data_raises(self): + """Test Enumerations initialization with invalid data raises AttributeError.""" # Act & Assert - with self.assertRaises(ConfiguratorException) as cm: - enum.save() - - self.assertIn("Failed to save enumerators", str(cm.exception)) - - def test_version_returns_correct_version(self): - """Test that version method returns the correct version.""" - data = [ - {"version": 0, "enumerators": {"a": 1}}, - {"version": 1, "enumerators": {"b": 2}} - ] - enum = Enumerators(data) - # Test that version method returns the correct version - result = enum.version(1) - self.assertEqual(result.version, 1) - self.assertEqual(result.enumerators, {"b": 2}) - - def test_to_dict_returns_data(self): - """Test that to_dict method returns the enumerators data.""" - data = [{"version": 0, "enumerators": {"test": {"value": 1}}}] - enum = Enumerators(data) - result = enum.to_dict() - expected = data - self.assertEqual(result, expected) + with self.assertRaises(AttributeError): + Enumerations(data="invalid") -class TestEnumerations(unittest.TestCase): def test_init_with_valid_data(self): """Test Enumerations initialization with valid data.""" + # Arrange data = { "name": "test", "status": "active", "version": 1, - "enumerators": {"foo": {"bar": 1}}, + "enumerators": {"test_enum": {"value1": True, "value2": False}}, "_locked": True } - enum = Enumerations(data) + + # Act + enum = Enumerations(data=data) + + # Assert self.assertEqual(enum.name, "test") self.assertEqual(enum.status, "active") self.assertEqual(enum.version, 1) - self.assertEqual(enum.enumerators, {"foo": {"bar": 1}}) + self.assertEqual(enum.enumerators, {"test_enum": {"value1": True, "value2": False}}) self.assertTrue(enum._locked) - def test_init_without_locked_defaults_to_false(self): - """Test Enumerations initialization without _locked defaults to False.""" + def test_get_enum_values(self): + """Test get_enum_values method.""" + # Arrange data = { "name": "test", "status": "active", "version": 1, - "enumerators": {"foo": {"bar": 1}} + "enumerators": {"test_enum": {"value1": True, "value2": False}} } - enum = Enumerations(data) - self.assertFalse(enum._locked) + enum = Enumerations(data=data) + + # Act + values = enum.get_enum_values("test_enum") + + # Assert + self.assertEqual(values, ["value1", "value2"]) - def test_to_dict_includes_locked(self): - """Test that to_dict includes _locked property.""" + def test_get_enum_values_none_enumerators(self): + """Test get_enum_values when enumerators is None.""" + # Arrange data = { "name": "test", "status": "active", "version": 1, - "enumerators": {"foo": {"bar": 1}}, - "_locked": True + "enumerators": None } - enum = Enumerations(data) - result = enum.to_dict() - self.assertEqual(result["_locked"], True) - self.assertEqual(result["name"], "test") - self.assertEqual(result["status"], "active") - self.assertEqual(result["version"], 1) - self.assertEqual(result["enumerators"], {"foo": {"bar": 1}}) - - def test_init_with_none_data_raises(self): - """Test Enumerations initialization with None data raises ConfiguratorException.""" - with self.assertRaises(ConfiguratorException): - Enumerations(None) - - def test_init_with_invalid_data_raises(self): - """Test Enumerations initialization with invalid data raises ConfiguratorException.""" - with self.assertRaises(ConfiguratorException): - Enumerations("invalid_data") + enum = Enumerations(data=data) + + # Act & Assert + with self.assertRaises(TypeError): + enum.get_enum_values("test_enum") - def test_get_enum_values_success(self): - """Test successful retrieval of enum values.""" + def test_get_enum_values_enum_not_found(self): + """Test get_enum_values when enum is not found.""" + # Arrange data = { "name": "test", "status": "active", "version": 1, - "enumerators": {"foo": {"bar": 1, "baz": 2}} + "enumerators": {"test_enum": {"value1": True}} } - enum = Enumerations(data) - values = enum.get_enum_values("foo") - self.assertIn("bar", values) - self.assertIn("baz", values) + enum = Enumerations(data=data) + + # Act & Assert + with self.assertRaises(ConfiguratorException): + enum.get_enum_values("nonexistent_enum") - def test_get_enum_values_invalid_name_raises(self): - """Test that get_enum_values raises ConfiguratorException for invalid enum name.""" + def test_to_dict(self): + """Test to_dict method.""" + # Arrange data = { "name": "test", "status": "active", "version": 1, - "enumerators": {"foo": {"bar": 1}} + "enumerators": {"test_enum": {"value1": True}}, + "_locked": True } - enum = Enumerations(data) - with self.assertRaises(ConfiguratorException): - enum.get_enum_values("not_a_key") - - def test_get_enum_values_with_none_enumerators_raises(self): - """Test that get_enum_values raises ConfiguratorException when enumerators is None.""" - data = { + enum = Enumerations(data=data) + + # Act + result = enum.to_dict() + + # Assert + expected = { "name": "test", "status": "active", "version": 1, - "enumerators": None + "enumerators": {"test_enum": {"value1": True}}, + "_locked": True } - enum = Enumerations(data) - with self.assertRaises(ConfiguratorException): - enum.get_enum_values("foo") + self.assertEqual(result, expected) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/test_cases/complex_refs/enumerators/enumerations.0.yaml b/tests/test_cases/complex_refs/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..d71b19f --- /dev/null +++ b/tests/test_cases/complex_refs/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Deprecated +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/complex_refs/enumerators/enumerations.1.yaml b/tests/test_cases/complex_refs/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..6b1c57c --- /dev/null +++ b/tests/test_cases/complex_refs/enumerators/enumerations.1.yaml @@ -0,0 +1,35 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Started but not finished" + archived: "Soft Delete Indicator" + workshop_status: + pending: "Scheduled, but not started" + active: "Started but not finished" + complete: "Finished" + archived: "Soft Delete Indicator" + exercise_status: + pending: "Waiting to start" + observe: "Observe as Individuals" + reflect: "Reflect as a Team" + make: "Make a decision as a Team" + complete: "Exercise Completed" + conversation_status: + active: "An active conversation" + named: "A named conversation, allows you to load information into a conversation" + paused: "An in-active conversation" + full: "A full conversation with a version date" + complete: "A completed conversation with a version date" + archived: "Soft Delete Indicator" + llm_message_roles: + system: "System Prompt" + assistant: "LLM Responding to User Prompts" + user: "LLM User sending prompts to the model" + roles: + facilitator: "A Design Thinking Facilitator" + engineer: "A Stage0 AI Engineer" + practitioner: "A Design Thinking Practitioner" + administrator: "A Super User" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/large_sample/enumerators/enumerations.0.yaml b/tests/test_cases/large_sample/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..d71b19f --- /dev/null +++ b/tests/test_cases/large_sample/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Deprecated +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/large_sample/enumerators/enumerations.1.yaml b/tests/test_cases/large_sample/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..9cf7c93 --- /dev/null +++ b/tests/test_cases/large_sample/enumerators/enumerations.1.yaml @@ -0,0 +1,62 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Not Deleted" + archived: "Soft Delete Indicator" + media_type: + movie: "A motion picture" + tv_show: "A television series" + documentary: "A non-fiction film" + short: "A short film" + media_status: + draft: "Not yet published" + published: "Available to users" + archived: "No longer available" + media_tags: + action: "Action genre" + comedy: "Comedy genre" + drama: "Drama genre" + sci_fi: "Science fiction genre" + documentary: "Documentary genre" + media_format: + dvd: "DVD format" + bluray: "Blu-ray format" + digital: "Digital format" + streaming: "Streaming format" + media_quality: + sd: "Standard definition" + hd: "High definition" + uhd: "Ultra high definition" + notification_type: + system: "System notification" + user: "User notification" + content: "Content notification" + reminder: "Reminder notification" + priority_level: + critical: "Critical priority" + high: "High priority" + medium: "Medium priority" + low: "Low priority" + notification_tags: + urgent: "Urgent notification" + important: "Important notification" + normal: "Normal notification" + low: "Low priority notification" + category_type: + work: "Work related items" + personal: "Personal items" + project: "Project specific items" + reference: "Reference materials" + delivery_channel: + email: "Email delivery" + sms: "SMS delivery" + push: "Push notification" + in_app: "In-app notification" + notification_action: + created: "Document created" + updated: "Document updated" + deleted: "Document deleted" + published: "Document published" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/large_sample/enumerators/enumerations.2.yaml b/tests/test_cases/large_sample/enumerators/enumerations.2.yaml new file mode 100644 index 0000000..a70bb39 --- /dev/null +++ b/tests/test_cases/large_sample/enumerators/enumerations.2.yaml @@ -0,0 +1,33 @@ +name: Enumerations +status: Active +version: 2 +enumerators: + default_status: + draft: "Not finalized" + active: "Not deleted" + archived: "Soft delete indicator" + media_type: + movie: "A motion picture" + tv_show: "A television series" + documentary: "A non-fiction film" + short: "A short film" + media_status: + draft: "Not yet published" + published: "Available to users" + archived: "No longer available" + media_tags: + action: "Action genre" + comedy: "Comedy genre" + drama: "Drama genre" + sci_fi: "Science fiction genre" + documentary: "Documentary genre" + media_format: + dvd: "DVD format" + bluray: "Blu-ray format" + digital: "Digital format" + streaming: "Streaming format" + media_quality: + sd: "Standard definition" + hd: "High definition" + uhd: "Ultra high definition" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/large_sample/enumerators/enumerations.3.yaml b/tests/test_cases/large_sample/enumerators/enumerations.3.yaml new file mode 100644 index 0000000..dccbdf4 --- /dev/null +++ b/tests/test_cases/large_sample/enumerators/enumerations.3.yaml @@ -0,0 +1,90 @@ +name: Enumerations +status: Active +version: 3 +enumerators: + default_status: + draft: "Not finalized" + active: "Not deleted" + archived: "Soft delete indicator" + media_type: + movie: "A motion picture" + tv_show: "A television series" + documentary: "A non-fiction film" + short: "A short film" + media_status: + draft: "Not yet published" + published: "Available to users" + archived: "No longer available" + media_tags: + action: "Action genre" + comedy: "Comedy genre" + drama: "Drama genre" + sci_fi: "Science fiction genre" + documentary: "Documentary genre" + media_format: + dvd: "DVD format" + bluray: "Blu-ray format" + digital: "Digital format" + streaming: "Streaming format" + media_quality: + sd: "Standard definition" + hd: "High definition" + uhd: "Ultra high definition" + type: + radio: "Select one option" + check: "Select multiple options" + text: "Enter a text string" + tags: + user: "A User" + admin: "An administrator" + super: "A super user" + category_type: + work: "Work related items" + personal: "Personal items" + project: "Project specific items" + reference: "Reference materials" + category_tags: + urgent: "Requires immediate attention" + important: "High priority" + normal: "Standard priority" + low: "Low priority" + completed: "Task is done" + in_progress: "Currently being worked on" + blocked: "Cannot proceed" + review: "Needs review" + content_type: + article: "Written content" + video: "Video content" + podcast: "Audio content" + content_tags: + technology: "Technology related content" + business: "Business related content" + entertainment: "Entertainment content" + education: "Educational content" + news: "News content" + notification_type: + system: "System notification" + user: "User notification" + content: "Content notification" + reminder: "Reminder notification" + notification_tags: + urgent: "Urgent notification" + important: "Important notification" + normal: "Normal notification" + low: "Low priority notification" + priority_level: + critical: "Critical priority" + high: "High priority" + medium: "Medium priority" + low: "Low priority" + delivery_channel: + email: "Email delivery" + sms: "SMS delivery" + push: "Push notification" + in_app: "In-app notification" + notification_action: + created: "Document created" + updated: "Document updated" + deleted: "Document deleted" + published: "Document published" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/minimum_valid/enumerators/enumerations.0.yaml b/tests/test_cases/minimum_valid/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/minimum_valid/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/playground/enumerators/enumerations.0.yaml b/tests/test_cases/playground/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/playground/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/playground/enumerators/enumerations.1.yaml b/tests/test_cases/playground/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..fc1222c --- /dev/null +++ b/tests/test_cases/playground/enumerators/enumerations.1.yaml @@ -0,0 +1,8 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/playground/enumerators/enumerations.2.yaml b/tests/test_cases/playground/enumerators/enumerations.2.yaml new file mode 100644 index 0000000..e4be230 --- /dev/null +++ b/tests/test_cases/playground/enumerators/enumerations.2.yaml @@ -0,0 +1,9 @@ +name: Enumerations +status: Active +version: 2 +enumerators: + default_status: + draft: "Draft" + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/enumerators/enumerations.0.yaml b/tests/test_cases/ref_load_errors/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/ref_load_errors/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/enumerators/enumerations.1.yaml b/tests/test_cases/ref_load_errors/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..9d44cce --- /dev/null +++ b/tests/test_cases/ref_load_errors/enumerators/enumerations.1.yaml @@ -0,0 +1,8 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + test_enum: + value1: "Test value 1" + value2: "Test value 2" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/small_sample/enumerators/enumerations.0.yaml b/tests/test_cases/small_sample/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/small_sample/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/small_sample/enumerators/enumerations.1.yaml b/tests/test_cases/small_sample/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..fc1222c --- /dev/null +++ b/tests/test_cases/small_sample/enumerators/enumerations.1.yaml @@ -0,0 +1,8 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/stepci/dictionaries/test_dict.yaml b/tests/test_cases/stepci/dictionaries/test_dict.yaml new file mode 100644 index 0000000..da06b5e --- /dev/null +++ b/tests/test_cases/stepci/dictionaries/test_dict.yaml @@ -0,0 +1,5 @@ +description: Test dictionary for stepCI testing +type: void +required: false +file_name: test_dict.yaml +_locked: false diff --git a/tests/test_cases/stepci/enumerators/enumerations.0.yaml b/tests/test_cases/stepci/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/stepci/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/stepci/enumerators/enumerations.1.yaml b/tests/test_cases/stepci/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..fc1222c --- /dev/null +++ b/tests/test_cases/stepci/enumerators/enumerations.1.yaml @@ -0,0 +1,8 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/stepci/enumerators/enumerations.2.yaml b/tests/test_cases/stepci/enumerators/enumerations.2.yaml new file mode 100644 index 0000000..e4be230 --- /dev/null +++ b/tests/test_cases/stepci/enumerators/enumerations.2.yaml @@ -0,0 +1,9 @@ +name: Enumerations +status: Active +version: 2 +enumerators: + default_status: + draft: "Draft" + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/stepci/types/identity.yaml b/tests/test_cases/stepci/types/identity.yaml index 4950c75..02558a9 100644 --- a/tests/test_cases/stepci/types/identity.yaml +++ b/tests/test_cases/stepci/types/identity.yaml @@ -1,6 +1,9 @@ +file_name: identity.yaml +_locked: true description: A unique identifier for a document +required: false json_type: type: string - pattern: "^[0-9a-fA-F]{24}$" + pattern: ^[0-9a-fA-F]{24}$ bson_type: - bsonType: objectId \ No newline at end of file + bsonType: objectId diff --git a/tests/test_cases/template_sample/enumerators/enumerations.0.yaml b/tests/test_cases/template_sample/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/template_sample/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/template_sample/enumerators/enumerations.1.yaml b/tests/test_cases/template_sample/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..fc1222c --- /dev/null +++ b/tests/test_cases/template_sample/enumerators/enumerations.1.yaml @@ -0,0 +1,8 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/template_sample/enumerators/enumerations.2.yaml b/tests/test_cases/template_sample/enumerators/enumerations.2.yaml new file mode 100644 index 0000000..e4be230 --- /dev/null +++ b/tests/test_cases/template_sample/enumerators/enumerations.2.yaml @@ -0,0 +1,9 @@ +name: Enumerations +status: Active +version: 2 +enumerators: + default_status: + draft: "Draft" + active: "Not Deleted" + archived: "Soft Delete Indicator" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/unparsable_files/enumerators/enumerations.0.yaml b/tests/test_cases/unparsable_files/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/unparsable_files/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/validation_errors/enumerators/enumerations.0.yaml b/tests/test_cases/validation_errors/enumerators/enumerations.0.yaml new file mode 100644 index 0000000..39e1f62 --- /dev/null +++ b/tests/test_cases/validation_errors/enumerators/enumerations.0.yaml @@ -0,0 +1,5 @@ +name: Enumerations +status: Active +version: 0 +enumerators: {} +_locked: false \ No newline at end of file diff --git a/tests/test_cases/validation_errors/enumerators/enumerations.1.yaml b/tests/test_cases/validation_errors/enumerators/enumerations.1.yaml new file mode 100644 index 0000000..b532e22 --- /dev/null +++ b/tests/test_cases/validation_errors/enumerators/enumerations.1.yaml @@ -0,0 +1,32 @@ +name: Enumerations +status: Active +version: 1 +enumerators: + default_status: + active: "Not Deleted" + archived: "Soft Delete Indicator" + media_type: + movie: "A motion picture" + tv_show: "A television series" + documentary: "A non-fiction film" + short: "A short film" + media_status: + draft: "Not yet published" + published: "Available to users" + archived: "No longer available" + media_tags: + action: "Action genre" + comedy: "Comedy genre" + drama: "Drama genre" + sci_fi: "Science fiction genre" + documentary: "Documentary genre" + media_format: + dvd: "DVD format" + bluray: "Blu-ray format" + digital: "Digital format" + streaming: "Streaming format" + media_quality: + sd: "Standard definition" + hd: "High definition" + uhd: "Ultra high definition" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/validation_errors/enumerators/enumerations.2.yaml b/tests/test_cases/validation_errors/enumerators/enumerations.2.yaml new file mode 100644 index 0000000..a70bb39 --- /dev/null +++ b/tests/test_cases/validation_errors/enumerators/enumerations.2.yaml @@ -0,0 +1,33 @@ +name: Enumerations +status: Active +version: 2 +enumerators: + default_status: + draft: "Not finalized" + active: "Not deleted" + archived: "Soft delete indicator" + media_type: + movie: "A motion picture" + tv_show: "A television series" + documentary: "A non-fiction film" + short: "A short film" + media_status: + draft: "Not yet published" + published: "Available to users" + archived: "No longer available" + media_tags: + action: "Action genre" + comedy: "Comedy genre" + drama: "Drama genre" + sci_fi: "Science fiction genre" + documentary: "Documentary genre" + media_format: + dvd: "DVD format" + bluray: "Blu-ray format" + digital: "Digital format" + streaming: "Streaming format" + media_quality: + sd: "Standard definition" + hd: "High definition" + uhd: "Ultra high definition" +_locked: false \ No newline at end of file diff --git a/tests/test_cases/validation_errors/enumerators/enumerations.3.yaml b/tests/test_cases/validation_errors/enumerators/enumerations.3.yaml new file mode 100644 index 0000000..4b1ff44 --- /dev/null +++ b/tests/test_cases/validation_errors/enumerators/enumerations.3.yaml @@ -0,0 +1,55 @@ +name: Enumerations +status: Active +version: 3 +enumerators: + default_status: + draft: "Not finalized" + active: "Not deleted" + archived: "Soft delete indicator" + media_type: + movie: "A motion picture" + tv_show: "A television series" + documentary: "A non-fiction film" + short: "A short film" + media_status: + draft: "Not yet published" + published: "Available to users" + archived: "No longer available" + media_tags: + action: "Action genre" + comedy: "Comedy genre" + drama: "Drama genre" + sci_fi: "Science fiction genre" + documentary: "Documentary genre" + media_format: + dvd: "DVD format" + bluray: "Blu-ray format" + digital: "Digital format" + streaming: "Streaming format" + media_quality: + sd: "Standard definition" + hd: "High definition" + uhd: "Ultra high definition" + type: + radio: "Select one option" + check: "Select multiple options" + text: "Enter a text string" + tags: + user: "A User" + admin: "An administrator" + super: "A super user" + category_type: + work: "Work related items" + personal: "Personal items" + project: "Project specific items" + reference: "Reference materials" + category_tags: + urgent: "Requires immediate attention" + important: "High priority" + normal: "Standard priority" + low: "Low priority" + completed: "Task is done" + in_progress: "Currently being worked on" + blocked: "Cannot proceed" + review: "Needs review" +_locked: false \ No newline at end of file From 4a39447de24cc2226716ef3ed9d0b8b666ddfa40 Mon Sep 17 00:00:00 2001 From: Mike Storey Date: Fri, 11 Jul 2025 17:26:30 -0400 Subject: [PATCH 2/2] Refactor enumerator service and implement lock_all endpoints - Simplified enumerator service by removing legacy support and backward compatibility - Removed data parameter from Enumerators constructor, relying solely on file-based enumerations - Fixed broken lock_all methods in enumerators, dictionaries, configurations, and types services - Added lock_all endpoints for all lockable objects (configurations, dictionaries, types, enumerators) - Removed lock_all endpoints for non-lockable objects (test_data, migrations) - Updated all service save() methods to return File objects - Updated all routes to return File objects from save() operations - Updated OpenAPI spec to reflect new endpoints and response schemas - Updated stepCI tests to use trailing slashes and expect 500 status for locked file deletions - Fixed unit tests to match actual API behavior - Cleaned up File class by removing unnecessary _locked and save() methods - All unit tests and stepCI tests now pass --- configurator/routes/configuration_routes.py | 8 +- configurator/routes/dictionary_routes.py | 6 +- configurator/routes/enumerator_routes.py | 6 +- configurator/routes/migration_routes.py | 15 +- configurator/routes/test_data_routes.py | 17 +- configurator/routes/type_routes.py | 5 +- .../services/configuration_services.py | 9 +- configurator/services/dictionary_services.py | 8 +- configurator/services/enumerator_service.py | 6 +- configurator/services/type_services.py | 8 +- docs/openapi.yaml | 76 +++++--- tests/routes/test_test_data_routes.py | 16 +- tests/stepci/configurations.yaml | 154 ++--------------- tests/stepci/dictionaries.yaml | 149 +--------------- tests/stepci/enumerators.yaml | 162 ++++++++++-------- tests/stepci/migrations.yaml | 4 +- tests/stepci/observability.yaml | 2 +- tests/stepci/processing.yaml | 2 +- tests/stepci/types.yaml | 110 +----------- .../configurations/test_config.yaml | 15 ++ .../playground/dictionaries/test_dict.yaml | 5 + .../enumerators/enumerations.0.yaml | 2 +- .../enumerators/enumerations.1.yaml | 8 +- .../enumerators/enumerations.2.yaml | 8 +- .../test_cases/playground/types/identity.yaml | 7 +- .../playground/types/test_type.yaml | 5 + .../stepci/configurations/test_config.yaml | 15 ++ .../stepci/dictionaries/test_dict.yaml | 4 +- .../stepci/enumerators/enumerations.0.yaml | 2 +- .../stepci/enumerators/enumerations.1.yaml | 8 +- .../stepci/enumerators/enumerations.2.yaml | 8 +- tests/test_cases/stepci/types/test_type.yaml | 5 + 32 files changed, 276 insertions(+), 579 deletions(-) create mode 100644 tests/test_cases/playground/configurations/test_config.yaml create mode 100644 tests/test_cases/playground/dictionaries/test_dict.yaml create mode 100644 tests/test_cases/playground/types/test_type.yaml create mode 100644 tests/test_cases/stepci/configurations/test_config.yaml create mode 100644 tests/test_cases/stepci/types/test_type.yaml diff --git a/configurator/routes/configuration_routes.py b/configurator/routes/configuration_routes.py index ed32426..ba5b255 100644 --- a/configurator/routes/configuration_routes.py +++ b/configurator/routes/configuration_routes.py @@ -41,6 +41,7 @@ def lock_all_configurations(): result = Configuration.lock_all() return jsonify(result.to_dict()) + @blueprint.route('/collection//', methods=['POST']) @event_route("CFG-ROUTES-04", "CREATE_COLLECTION", "creating collection") def create_collection(file_name): @@ -54,12 +55,13 @@ def get_configuration(file_name): configuration = Configuration(file_name) return jsonify(configuration.to_dict()) + # PUT /api/configurations/ - Update a configuration file @blueprint.route('//', methods=['PUT']) @event_route("CFG-ROUTES-06", "PUT_CONFIGURATION", "updating configuration") - def put_configuration(file_name): + def update_configuration(file_name): configuration = Configuration(file_name, request.json) - configuration.save() - return jsonify(configuration.to_dict()) + file_obj = configuration.save() + return jsonify(file_obj.to_dict()) @blueprint.route('//', methods=['DELETE']) @event_route("CFG-ROUTES-07", "DELETE_CONFIGURATION", "deleting configuration") diff --git a/configurator/routes/dictionary_routes.py b/configurator/routes/dictionary_routes.py index 8d22d0b..9c1d502 100644 --- a/configurator/routes/dictionary_routes.py +++ b/configurator/routes/dictionary_routes.py @@ -26,6 +26,8 @@ def lock_all_dictionaries(): result = Dictionary.lock_all() return jsonify(result.to_dict()) + + # GET /api/dictionaries/ - Return a dictionary file @dictionary_routes.route('//', methods=['GET']) @event_route("DIC-02", "GET_DICTIONARY", "getting dictionary") @@ -38,8 +40,8 @@ def get_dictionary(file_name): @event_route("DIC-03", "PUT_DICTIONARY", "updating dictionary") def update_dictionary(file_name): dictionary = Dictionary(file_name, request.json) - saved_dictionary = dictionary.save() - return jsonify(saved_dictionary.to_dict()) + file_obj = dictionary.save() + return jsonify(file_obj.to_dict()) @dictionary_routes.route('//', methods=['DELETE']) @event_route("DIC-05", "DELETE_DICTIONARY", "deleting dictionary") diff --git a/configurator/routes/enumerator_routes.py b/configurator/routes/enumerator_routes.py index 0afcee7..b10c29a 100644 --- a/configurator/routes/enumerator_routes.py +++ b/configurator/routes/enumerator_routes.py @@ -28,6 +28,8 @@ def lock_enumerations(): event.record_success() return jsonify(event.to_dict()) + + # GET /api/enumerations/ - Get specific enumeration file @enumerator_routes.route('//', methods=['GET']) @event_route("ENU-02", "GET_ENUMERATION", "getting enumeration") @@ -41,8 +43,8 @@ def get_enumeration(file_name): def put_enumeration(file_name): data = request.get_json(force=True) enumerations = Enumerations(data=data, file_name=file_name) - saved_enumerations = enumerations.save() - return jsonify(saved_enumerations.to_dict()) + file_obj = enumerations.save() + return jsonify(file_obj.to_dict()) # DELETE /api/enumerations/ - Delete specific enumeration file @enumerator_routes.route('//', methods=['DELETE']) diff --git a/configurator/routes/migration_routes.py b/configurator/routes/migration_routes.py index e498c99..a6362d4 100644 --- a/configurator/routes/migration_routes.py +++ b/configurator/routes/migration_routes.py @@ -20,19 +20,8 @@ def get_migrations(): filenames = [file.file_name for file in files] return jsonify(filenames) - # PATCH /api/migrations/ - Lock all migration files - @migration_routes.route('/', methods=['PATCH']) - @event_route("MIG-07", "LOCK_ALL_MIGRATIONS", "locking all migration files") - def lock_all_migrations(): - event = ConfiguratorEvent("MIG-07", "LOCK_ALL_MIGRATIONS") - files = FileIO.get_documents(config.MIGRATIONS_FOLDER) - - for file in files: - file._locked = True - file.save() - - event.record_success() - return jsonify(event.to_dict()) + + # GET /api/migrations// - Get a migration file @migration_routes.route('//', methods=['GET']) diff --git a/configurator/routes/test_data_routes.py b/configurator/routes/test_data_routes.py index 1c89111..29a35a7 100644 --- a/configurator/routes/test_data_routes.py +++ b/configurator/routes/test_data_routes.py @@ -38,21 +38,8 @@ def get_data_files(): for file in files if file.file_name.endswith('.json') ]) - # PATCH /api/test_data - Lock all test data files - @test_data_routes.route('/', methods=['PATCH']) - @event_route("TST-05", "LOCK_ALL_TEST_DATA", "locking all test data files") - def lock_all_test_data(): - event = ConfiguratorEvent("TST-05", "LOCK_ALL_TEST_DATA") - files = FileIO.get_documents(config.TEST_DATA_FOLDER) - # Only lock .json files - json_files = [file for file in files if file.file_name.endswith('.json')] - - for file in json_files: - file._locked = True - file.save() - - event.record_success() - return jsonify(event.to_dict()) + + # GET /api/test_data/ - Return a test_data file (only .json) @test_data_routes.route('//', methods=['GET']) diff --git a/configurator/routes/type_routes.py b/configurator/routes/type_routes.py index afaaf24..2039945 100644 --- a/configurator/routes/type_routes.py +++ b/configurator/routes/type_routes.py @@ -26,6 +26,7 @@ def lock_all_types(): result = Type.lock_all() return jsonify(result.to_dict()) + # GET /api/types// - Return a type file @type_routes.route('//', methods=['GET']) @event_route("TYP-02", "GET_TYPE", "getting type") @@ -38,8 +39,8 @@ def get_type(file_name): @event_route("TYP-03", "PUT_TYPE", "updating type") def update_type(file_name): type_obj = Type(file_name, request.json) - saved_type = type_obj.save() - return jsonify(saved_type.to_dict()) + file_obj = type_obj.save() + return jsonify(file_obj.to_dict()) @type_routes.route('//', methods=['DELETE']) @event_route("TYP-05", "DELETE_TYPE", "deleting type") diff --git a/configurator/services/configuration_services.py b/configurator/services/configuration_services.py index 7e3988d..3e5add7 100644 --- a/configurator/services/configuration_services.py +++ b/configurator/services/configuration_services.py @@ -29,11 +29,10 @@ def to_dict(self): } def save(self): - """Save the configuration and return the Configuration object.""" + """Save the configuration and return the File object.""" try: - # Save the cleaned content - FileIO.put_document(self.config.CONFIGURATION_FOLDER, self.file_name, self.to_dict()) - return self + file_obj = FileIO.put_document(self.config.CONFIGURATION_FOLDER, self.file_name, self.to_dict()) + return file_obj except Exception as e: event = ConfiguratorEvent("CFG-ROUTES-06", "PUT_CONFIGURATION") event.record_failure(f"Failed to save configuration {self.file_name}: {str(e)}") @@ -67,6 +66,8 @@ def lock_all(): event.record_success() return event + + def delete(self): if self._locked: event = ConfiguratorEvent(event_id="CFG-ROUTES-07", event_type="DELETE_CONFIGURATION") diff --git a/configurator/services/dictionary_services.py b/configurator/services/dictionary_services.py index 5ec0c9d..9705f8a 100644 --- a/configurator/services/dictionary_services.py +++ b/configurator/services/dictionary_services.py @@ -26,11 +26,11 @@ def to_dict(self): return result def save(self): - """Save the dictionary and return the Dictionary object.""" + """Save the dictionary and return the File object.""" try: event = ConfiguratorEvent("DIC-03", "PUT_DICTIONARY") - document = FileIO.put_document(self.config.DICTIONARY_FOLDER, self.file_name, self.to_dict()) - return document + file_obj = FileIO.put_document(self.config.DICTIONARY_FOLDER, self.file_name, self.to_dict()) + return file_obj except ConfiguratorException as e: event.append_events([e.event]) event.record_failure(f"Failed to save dictionary {self.file_name}: {str(e)}") @@ -73,6 +73,8 @@ def lock_all(): except Exception as e: event.record_failure(f"Unexpected error locking dictionary {file.file_name}") raise ConfiguratorException(f"Unexpected error locking dictionary {file.file_name}", event) + + def delete(self): diff --git a/configurator/services/enumerator_service.py b/configurator/services/enumerator_service.py index ae21541..f8fb221 100644 --- a/configurator/services/enumerator_service.py +++ b/configurator/services/enumerator_service.py @@ -17,6 +17,7 @@ def lock_all(self): enumeration.save() return self + def getVersion(self, version_number: int): """Get a specific version of enumerations""" for enumeration in self.enumerations: @@ -70,9 +71,10 @@ def to_dict(self): } def save(self): - """Save the enumerations to its file and return the file object""" + """Save the enumerations to its file and return the File object.""" try: - return FileIO.put_document(self.config.ENUMERATOR_FOLDER, self.file_name, self.to_dict()) + file_obj = FileIO.put_document(self.config.ENUMERATOR_FOLDER, self.file_name, self.to_dict()) + return file_obj except ConfiguratorException as e: event = ConfiguratorEvent("ENU-03", "PUT_ENUMERATIONS") event.append_events([e.event]) diff --git a/configurator/services/type_services.py b/configurator/services/type_services.py index 1621e00..8cc62c9 100644 --- a/configurator/services/type_services.py +++ b/configurator/services/type_services.py @@ -46,10 +46,10 @@ def __init__(self, file_name: str, document: dict = {}): def save(self): - """Save the type and return the Type object.""" + """Save the type and return the File object.""" try: - FileIO.put_document(self.config.TYPE_FOLDER, self.file_name, self.to_dict()) - return self + file_obj = FileIO.put_document(self.config.TYPE_FOLDER, self.file_name, self.to_dict()) + return file_obj except Exception as e: event = ConfiguratorEvent("TYP-03", "PUT_TYPE") event.record_failure(f"Failed to save type {self.file_name}: {str(e)}") @@ -83,6 +83,8 @@ def lock_all(): event.record_success() return event + + def get_json_schema(self, type_stack: list = None): if type_stack is None: type_stack = [] diff --git a/docs/openapi.yaml b/docs/openapi.yaml index fda3562..367736e 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -83,6 +83,7 @@ paths: application/json: schema: $ref: '#/components/schemas/events' + /api/configurations/{file_name}/: get: summary: Get a collection configuration @@ -317,6 +318,7 @@ paths: application/json: schema: $ref: '#/components/schemas/events' + /api/dictionaries/{file_name}/: get: summary: Get a dictionary @@ -426,6 +428,7 @@ paths: application/json: schema: $ref: '#/components/schemas/events' + /api/types/{file_name}/: get: summary: Get a type @@ -537,6 +540,7 @@ paths: application/json: schema: $ref: '#/components/schemas/events' + /api/test_data/{file_name}/: get: summary: Get a test data file @@ -665,6 +669,7 @@ paths: schema: $ref: '#/components/schemas/events' + /api/migrations/{file_name}/: get: summary: Get a migration file @@ -753,15 +758,15 @@ paths: schema: $ref: '#/components/schemas/events' - /api/enumerations/: + /api/enumerators/: get: summary: List all Enumeration Files - operationId: list_enumerations + operationId: list_enumerators tags: - - Enumerator Values + - Enumerators responses: '200': - description: List of enumeration files + description: List of enumerator files content: application/json: schema: @@ -773,13 +778,13 @@ paths: schema: $ref: '#/components/schemas/events' patch: - summary: Lock all Enumerations - operationId: lock_enumerations + summary: Lock all Enumerators + operationId: lock_all_enumerators tags: - - Enumerator Values + - Enumerators responses: '200': - description: All enumerations locked successfully + description: All enumerators locked successfully content: application/json: schema: @@ -791,12 +796,13 @@ paths: schema: $ref: '#/components/schemas/events' - /api/enumerations/{file_name}/: + + /api/enumerators/{file_name}/: get: - summary: Get a specific enumeration file - operationId: get_enumeration + summary: Get an enumerator file + operationId: get_enumerator tags: - - Enumerator Values + - Enumerators parameters: - name: file_name in: path @@ -805,7 +811,7 @@ paths: type: string responses: '200': - description: Enumeration data + description: Enumerator file content: application/json: schema: @@ -817,29 +823,23 @@ paths: schema: $ref: '#/components/schemas/events' put: - summary: Update a specific enumeration file - operationId: update_enumeration + summary: Save an enumerator file + operationId: save_enumerator tags: - - Enumerator Values + - Enumerators parameters: - name: file_name in: path required: true schema: type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/enumerations' responses: '200': - description: Updated enumeration data + description: Enumerator file content: application/json: schema: - $ref: '#/components/schemas/enumerations' + $ref: '#/components/schemas/file' '500': description: Processing error content: @@ -847,10 +847,10 @@ paths: schema: $ref: '#/components/schemas/events' delete: - summary: Delete a specific enumeration file - operationId: delete_enumeration + summary: Delete an enumerator file + operationId: delete_enumerator tags: - - Enumerator Values + - Enumerators parameters: - name: file_name in: path @@ -859,7 +859,7 @@ paths: type: string responses: '200': - description: Enumeration deleted successfully + description: Enumerator file deleted content: application/json: schema: @@ -925,6 +925,26 @@ paths: '500': description: Processing error + /api/configurations/lock_all/: + patch: + summary: Lock all Collection Configurations + operationId: lock_all_configurations + tags: + - Collection Configurations + responses: + '200': + description: All configurations locked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/events' + '500': + description: Processing error + content: + application/json: + schema: + $ref: '#/components/schemas/events' + components: schemas: files: diff --git a/tests/routes/test_test_data_routes.py b/tests/routes/test_test_data_routes.py index 76f7c9d..9ba87b0 100644 --- a/tests/routes/test_test_data_routes.py +++ b/tests/routes/test_test_data_routes.py @@ -163,23 +163,13 @@ def test_test_data_post_method_not_allowed(self): # Assert self.assertEqual(response.status_code, 405) - @patch('configurator.routes.test_data_routes.FileIO') - def test_test_data_patch_method_success(self, mock_file_io): - """Test that PATCH method is allowed on /api/test_data for locking all files.""" - # Arrange - mock_files = [Mock(), Mock()] - mock_file_io.get_documents.return_value = mock_files - + def test_test_data_patch_method_not_allowed(self): + """Test that PATCH method is not allowed on /api/test_data since test_data cannot be locked.""" # Act response = self.client.patch('/api/test_data/') # Assert - self.assertEqual(response.status_code, 200) - response_data = response.json - self.assertIn("id", response_data) - self.assertIn("type", response_data) - self.assertIn("status", response_data) - self.assertEqual(response_data["status"], "SUCCESS") + self.assertEqual(response.status_code, 405) if __name__ == '__main__': diff --git a/tests/stepci/configurations.yaml b/tests/stepci/configurations.yaml index ecd93e8..9887b7c 100644 --- a/tests/stepci/configurations.yaml +++ b/tests/stepci/configurations.yaml @@ -7,15 +7,7 @@ tests: configurations: name: Test Configurations Endpoints steps: - # Setup - Delete test file if it exists (no checks needed) - - name: Delete Test Configuration (Setup) - http: - url: http://${{env.host}}/api/configurations/test_config.yaml/ - method: DELETE - check: - status: /200|500/ - - # Create - PUT new configuration + # Create - POST to create a new configuration - name: Create Test Configuration http: url: http://${{env.host}}/api/configurations/test_config.yaml/ @@ -39,17 +31,10 @@ tests: status: /200/ jsonpath: file_name: test_config.yaml - _locked: false - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number - + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ + # Read - GET the created configuration - name: Get Test Configuration http: @@ -96,95 +81,10 @@ tests: status: /200/ jsonpath: file_name: test_config.yaml - _locked: false - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number - - # Lock - PUT to lock the configuration - - name: Lock Test Configuration - http: - url: http://${{env.host}}/api/configurations/test_config.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test collection for stepCI testing", - "versions": [ - { - "version": "1.0.0.1", - "drop_indexes": [], - "migrations": [], - "add_indexes": [], - "test_data": "test_data.1.0.0.1.json" - }, - { - "version": "1.0.1.2", - "drop_indexes": [], - "migrations": [], - "add_indexes": [], - "test_data": "test_data.1.0.1.2.json" - } - ], - "_locked": true - } - check: - status: /200/ - jsonpath: - file_name: test_config.yaml - _locked: true + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ - # Try to Delete a locked configuration - - name: Delete a locked configuration - should fail - http: - url: http://${{env.host}}/api/configurations/test_config.yaml/ - method: DELETE - check: - status: /500/ - jsonpath: - status: FAILURE - - # Unlock - PUT to unlock the configuration - - name: Unlock Test Configuration - http: - url: http://${{env.host}}/api/configurations/test_config.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test collection for stepCI testing", - "versions": [ - { - "version": "1.0.0.1", - "drop_indexes": [], - "migrations": [], - "add_indexes": [], - "test_data": "test_data.1.0.0.1.json" - }, - { - "version": "1.0.1.2", - "drop_indexes": [], - "migrations": [], - "add_indexes": [], - "test_data": "test_data.1.0.1.2.json" - } - ], - "_locked": false - } - check: - status: /200/ - jsonpath: - file_name: test_config.yaml - _locked: false - # Lock All - PATCH to lock all configurations - name: Lock All Configurations http: @@ -201,46 +101,12 @@ tests: type: string sub_events: type: array - - # Unlock before cleanup - - name: Unlock Test Configuration (Cleanup) - http: - url: http://${{env.host}}/api/configurations/test_config.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test collection for stepCI testing", - "versions": [ - { - "version": "1.0.0.1", - "drop_indexes": [], - "migrations": [], - "add_indexes": [], - "test_data": "test_data.1.0.0.1.json" - }, - { - "version": "1.0.1.2", - "drop_indexes": [], - "migrations": [], - "add_indexes": [], - "test_data": "test_data.1.0.1.2.json" - } - ], - "_locked": false - } - check: - status: /200/ - jsonpath: - file_name: test_config.yaml - _locked: false - # Cleanup - DELETE the test configuration + # Cleanup - DELETE the test configuration (should fail when locked) - name: Delete Test Configuration (Cleanup) http: url: http://${{env.host}}/api/configurations/test_config.yaml/ method: DELETE check: - status: /200/ + status: /500/ \ No newline at end of file diff --git a/tests/stepci/dictionaries.yaml b/tests/stepci/dictionaries.yaml index 7fbabec..c8d3b5f 100644 --- a/tests/stepci/dictionaries.yaml +++ b/tests/stepci/dictionaries.yaml @@ -7,14 +7,6 @@ tests: dictionaries: name: Test Dictionaries Endpoints steps: -# # Setup - Delete test file if it exists (no checks needed) -# - name: Delete Test Dictionary (Setup) -# http: -# url: http://${{env.host}}/api/dictionaries/test_dict.yaml/ -# method: DELETE -# check: -# status: /200|500/ - # Create - PUT new dictionary (simple, no properties) - name: Create Test Dictionary http: @@ -30,17 +22,10 @@ tests: status: /200/ jsonpath: file_name: test_dict.yaml - _locked: false - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number - + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ + # Read - GET the created dictionary (expect no version, no properties) - name: Get Test Dictionary http: @@ -82,93 +67,10 @@ tests: status: /200/ jsonpath: file_name: test_dict.yaml - _locked: false - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ - # Lock - PUT to lock the dictionary - - name: Lock Test Dictionary - http: - url: http://${{env.host}}/api/dictionaries/test_dict.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test dictionary for stepCI testing", - "version": "1.0.1", - "properties": { - "name": { - "type": "string", - "description": "Name field" - }, - "email": { - "type": "email", - "description": "Email field" - }, - "phone": { - "type": "us_phone", - "description": "Phone field" - } - }, - "_locked": true - } - check: - status: /200/ - jsonpath: - file_name: test_dict.yaml - _locked: true - - # Try to Delete a locked dictionary - - name: Delete a locked dictionary - should fail - http: - url: http://${{env.host}}/api/dictionaries/test_dict.yaml/ - method: DELETE - check: - status: /500/ - jsonpath: - status: FAILURE - - # Unlock - PUT to unlock the dictionary - - name: Unlock Test Dictionary - http: - url: http://${{env.host}}/api/dictionaries/test_dict.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test dictionary for stepCI testing", - "version": "1.0.1", - "properties": { - "name": { - "type": "string", - "description": "Name field" - }, - "email": { - "type": "email", - "description": "Email field" - }, - "phone": { - "type": "us_phone", - "description": "Phone field" - } - }, - "_locked": false - } - check: - status: /200/ - jsonpath: - file_name: test_dict.yaml - _locked: false - # Lock All - PATCH to lock all dictionaries - name: Lock All Dictionaries http: @@ -185,44 +87,11 @@ tests: type: string sub_events: type: array - - # Unlock before cleanup - - name: Unlock Test Dictionary (Cleanup) - http: - url: http://${{env.host}}/api/dictionaries/test_dict.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test dictionary for stepCI testing", - "version": "1.0.1", - "properties": { - "name": { - "type": "string", - "description": "Name field" - }, - "email": { - "type": "email", - "description": "Email field" - }, - "phone": { - "type": "us_phone", - "description": "Phone field" - } - }, - "_locked": false - } - check: - status: /200/ - jsonpath: - file_name: test_dict.yaml - _locked: false - # Cleanup - DELETE the test dictionary + # Cleanup - DELETE the test dictionary (should fail when locked) - name: Delete Test Dictionary (Cleanup) http: url: http://${{env.host}}/api/dictionaries/test_dict.yaml/ method: DELETE check: - status: /200/ \ No newline at end of file + status: /500/ \ No newline at end of file diff --git a/tests/stepci/enumerators.yaml b/tests/stepci/enumerators.yaml index a7ccd6c..5c6660d 100644 --- a/tests/stepci/enumerators.yaml +++ b/tests/stepci/enumerators.yaml @@ -7,103 +7,115 @@ tests: enumerators: name: Test Enumerators Endpoints steps: - # Test PUT with modified data (preserving existing content and adding new test values) - - name: Put Enumerators + # Test PUT with modified data for individual enumeration files + - name: Put Enumerations 0 http: - url: http://${{env.host}}/api/enumerators/ + url: http://${{env.host}}/api/enumerators/enumerations.0.yaml/ method: PUT headers: Content-Type: application/json body: | - [ - { - "version": 0, - "enumerators": {} - }, - { - "version": 1, - "enumerators": { - "default_status": { - "active": "Not Deleted", - "archived": "Soft Delete Indicator" - }, - "test_enum": { - "foo": "bar" - } - } - }, - { - "version": 2, - "enumerators": { - "default_status": { - "draft": "Draft", - "active": "Not Deleted", - "archived": "Soft Delete Indicator" - }, - "test_enum": { - "foo": "bar" - } + { + "name": "Enumerations", + "status": "Active", + "version": 0, + "enumerators": {} + } + check: + status: /200/ + jsonpath: + file_name: enumerations.0.yaml + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ + + - name: Put Enumerations 1 + http: + url: http://${{env.host}}/api/enumerators/enumerations.1.yaml/ + method: PUT + headers: + Content-Type: application/json + body: | + { + "name": "Enumerations", + "status": "Active", + "version": 1, + "enumerators": { + "default_status": { + "active": "Not Deleted", + "archived": "Soft Delete Indicator" + }, + "test_enum": { + "foo": "bar" } } - ] + } + check: + status: /200/ + jsonpath: + file_name: enumerations.1.yaml + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ + + - name: Put Enumerations 2 + http: + url: http://${{env.host}}/api/enumerators/enumerations.2.yaml/ + method: PUT + headers: + Content-Type: application/json + body: | + { + "name": "Enumerations", + "status": "Active", + "version": 2, + "enumerators": {} + } check: status: /200/ jsonpath: - $[1].enumerators.test_enum.foo: "bar" - $[1].enumerators.default_status.active: "Not Deleted" - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number + file_name: enumerations.2.yaml + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ - # Verify the PUT worked - - name: Get Enumerators After Put + # Verify the PUT worked by getting individual files + - name: Get Enumerations 1 After Put http: - url: http://${{env.host}}/api/enumerators/ + url: http://${{env.host}}/api/enumerators/enumerations.1.yaml/ method: GET check: status: /200/ jsonpath: - $[1].enumerators.test_enum.foo: "bar" - $[1].enumerators.default_status.active: "Not Deleted" + enumerators.test_enum.foo: "bar" + enumerators.default_status.active: "Not Deleted" - # Restore to original state from enumerators.json - - name: Restore Original Enumerators + # Restore to original state + - name: Restore Original Enumerations 1 http: - url: http://${{env.host}}/api/enumerators/ + url: http://${{env.host}}/api/enumerators/enumerations.1.yaml/ method: PUT headers: Content-Type: application/json body: | - [ - { - "version": 0, - "enumerators": {} - }, - { - "version": 1, - "enumerators": { - "default_status": { - "active": "Not Deleted", - "archived": "Soft Delete Indicator" - } - } - }, - { - "version": 2, - "enumerators": { - "default_status": { - "draft": "Draft", - "active": "Not Deleted", - "archived": "Soft Delete Indicator" - } + { + "name": "Enumerations", + "status": "Active", + "version": 1, + "enumerators": { + "default_status": { + "active": "Not Deleted", + "archived": "Soft Delete Indicator" + }, + "test_enum": { + "foo": "bar" } } - ] + } check: - status: /200/ \ No newline at end of file + status: /200/ + jsonpath: + file_name: enumerations.1.yaml + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ \ No newline at end of file diff --git a/tests/stepci/migrations.yaml b/tests/stepci/migrations.yaml index 96b90ca..cb86432 100644 --- a/tests/stepci/migrations.yaml +++ b/tests/stepci/migrations.yaml @@ -106,6 +106,4 @@ tests: url: http://${{env.host}}/api/migrations/test_migration.json/ method: DELETE check: - status: /200/ - jsonpath: - status: SUCCESS \ No newline at end of file + status: /200/ \ No newline at end of file diff --git a/tests/stepci/observability.yaml b/tests/stepci/observability.yaml index e6cb96c..3c7ebf4 100644 --- a/tests/stepci/observability.yaml +++ b/tests/stepci/observability.yaml @@ -3,7 +3,7 @@ observable: steps: - name: GET Config Values http: - url: http://${{env.host}}/api/config + url: http://${{env.host}}/api/config/ method: GET check: status: /200/ diff --git a/tests/stepci/processing.yaml b/tests/stepci/processing.yaml index df347a0..7d7660e 100644 --- a/tests/stepci/processing.yaml +++ b/tests/stepci/processing.yaml @@ -3,7 +3,7 @@ processing: steps: - name: Test Processing Endpoint http: - url: http://${{env.host}}/api/configurations + url: http://${{env.host}}/api/configurations/ method: GET check: status: /200/ diff --git a/tests/stepci/types.yaml b/tests/stepci/types.yaml index 2de89d7..d4abf33 100644 --- a/tests/stepci/types.yaml +++ b/tests/stepci/types.yaml @@ -7,14 +7,6 @@ tests: types: name: Test Types Endpoints steps: - # Setup - Delete test file if it exists (no checks needed) - - name: Delete Test Type (Setup) - http: - url: http://${{env.host}}/api/types/test_type.yaml/ - method: DELETE - check: - status: /200|500/ - # Create - PUT new type - name: Create Test Type http: @@ -32,17 +24,10 @@ tests: status: /200/ jsonpath: file_name: test_type.yaml - _locked: false - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number - + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ + # Read - GET the created type - name: Get Test Type http: @@ -72,67 +57,10 @@ tests: status: /200/ jsonpath: file_name: test_type.yaml - _locked: false - schema: - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - size: - type: number + created_at: /.+/ + updated_at: /.+/ + size: /\d+/ - # Lock - PUT to lock the type - - name: Lock Test Type - http: - url: http://${{env.host}}/api/types/test_type.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test type for stepCI testing", - "type": "string", - "required": true, - "_locked": true - } - check: - status: /200/ - jsonpath: - file_name: test_type.yaml - _locked: true - - # Try to Delete a locked type - - name: Delete a locked type - should fail - http: - url: http://${{env.host}}/api/types/test_type.yaml/ - method: DELETE - check: - status: /500/ - jsonpath: - status: FAILURE - - # Unlock - PUT to unlock the type - - name: Unlock Test Type - http: - url: http://${{env.host}}/api/types/test_type.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test type for stepCI testing", - "type": "string", - "required": true, - "_locked": false - } - check: - status: /200/ - jsonpath: - file_name: test_type.yaml - _locked: false - # Lock All - PATCH to lock all types - name: Lock All Types http: @@ -149,31 +77,11 @@ tests: type: string sub_events: type: array - - # Unlock before cleanup - - name: Unlock Test Type (Cleanup) - http: - url: http://${{env.host}}/api/types/test_type.yaml/ - method: PUT - headers: - Content-Type: application/json - body: | - { - "description": "Updated test type for stepCI testing", - "type": "string", - "required": true, - "_locked": false - } - check: - status: /200/ - jsonpath: - file_name: test_type.yaml - _locked: false - # Cleanup - DELETE the test type + # Cleanup - DELETE the test type (should fail when locked) - name: Delete Test Type (Cleanup) http: url: http://${{env.host}}/api/types/test_type.yaml/ method: DELETE check: - status: /200/ \ No newline at end of file + status: /500/ \ No newline at end of file diff --git a/tests/test_cases/playground/configurations/test_config.yaml b/tests/test_cases/playground/configurations/test_config.yaml new file mode 100644 index 0000000..a415929 --- /dev/null +++ b/tests/test_cases/playground/configurations/test_config.yaml @@ -0,0 +1,15 @@ +file_name: test_config.yaml +title: '' +description: Updated test collection for stepCI testing +versions: +- version: 1.0.0.1 + drop_indexes: [] + add_indexes: [] + migrations: [] + test_data: test_data.1.0.0.1.json +- version: 1.0.1.2 + drop_indexes: [] + add_indexes: [] + migrations: [] + test_data: test_data.1.0.1.2.json +_locked: true diff --git a/tests/test_cases/playground/dictionaries/test_dict.yaml b/tests/test_cases/playground/dictionaries/test_dict.yaml new file mode 100644 index 0000000..82821af --- /dev/null +++ b/tests/test_cases/playground/dictionaries/test_dict.yaml @@ -0,0 +1,5 @@ +description: Updated test dictionary for stepCI testing +type: void +required: false +file_name: test_dict.yaml +_locked: true diff --git a/tests/test_cases/playground/enumerators/enumerations.0.yaml b/tests/test_cases/playground/enumerators/enumerations.0.yaml index 39e1f62..bd9ae3c 100644 --- a/tests/test_cases/playground/enumerators/enumerations.0.yaml +++ b/tests/test_cases/playground/enumerators/enumerations.0.yaml @@ -2,4 +2,4 @@ name: Enumerations status: Active version: 0 enumerators: {} -_locked: false \ No newline at end of file +_locked: false diff --git a/tests/test_cases/playground/enumerators/enumerations.1.yaml b/tests/test_cases/playground/enumerators/enumerations.1.yaml index fc1222c..2145b99 100644 --- a/tests/test_cases/playground/enumerators/enumerations.1.yaml +++ b/tests/test_cases/playground/enumerators/enumerations.1.yaml @@ -3,6 +3,8 @@ status: Active version: 1 enumerators: default_status: - active: "Not Deleted" - archived: "Soft Delete Indicator" -_locked: false \ No newline at end of file + active: Not Deleted + archived: Soft Delete Indicator + test_enum: + foo: bar +_locked: false diff --git a/tests/test_cases/playground/enumerators/enumerations.2.yaml b/tests/test_cases/playground/enumerators/enumerations.2.yaml index e4be230..2a077a6 100644 --- a/tests/test_cases/playground/enumerators/enumerations.2.yaml +++ b/tests/test_cases/playground/enumerators/enumerations.2.yaml @@ -1,9 +1,5 @@ name: Enumerations status: Active version: 2 -enumerators: - default_status: - draft: "Draft" - active: "Not Deleted" - archived: "Soft Delete Indicator" -_locked: false \ No newline at end of file +enumerators: {} +_locked: false diff --git a/tests/test_cases/playground/types/identity.yaml b/tests/test_cases/playground/types/identity.yaml index 4950c75..02558a9 100644 --- a/tests/test_cases/playground/types/identity.yaml +++ b/tests/test_cases/playground/types/identity.yaml @@ -1,6 +1,9 @@ +file_name: identity.yaml +_locked: true description: A unique identifier for a document +required: false json_type: type: string - pattern: "^[0-9a-fA-F]{24}$" + pattern: ^[0-9a-fA-F]{24}$ bson_type: - bsonType: objectId \ No newline at end of file + bsonType: objectId diff --git a/tests/test_cases/playground/types/test_type.yaml b/tests/test_cases/playground/types/test_type.yaml new file mode 100644 index 0000000..78b9b12 --- /dev/null +++ b/tests/test_cases/playground/types/test_type.yaml @@ -0,0 +1,5 @@ +file_name: test_type.yaml +_locked: true +description: Updated test type for stepCI testing +required: true +type: string diff --git a/tests/test_cases/stepci/configurations/test_config.yaml b/tests/test_cases/stepci/configurations/test_config.yaml new file mode 100644 index 0000000..a415929 --- /dev/null +++ b/tests/test_cases/stepci/configurations/test_config.yaml @@ -0,0 +1,15 @@ +file_name: test_config.yaml +title: '' +description: Updated test collection for stepCI testing +versions: +- version: 1.0.0.1 + drop_indexes: [] + add_indexes: [] + migrations: [] + test_data: test_data.1.0.0.1.json +- version: 1.0.1.2 + drop_indexes: [] + add_indexes: [] + migrations: [] + test_data: test_data.1.0.1.2.json +_locked: true diff --git a/tests/test_cases/stepci/dictionaries/test_dict.yaml b/tests/test_cases/stepci/dictionaries/test_dict.yaml index da06b5e..82821af 100644 --- a/tests/test_cases/stepci/dictionaries/test_dict.yaml +++ b/tests/test_cases/stepci/dictionaries/test_dict.yaml @@ -1,5 +1,5 @@ -description: Test dictionary for stepCI testing +description: Updated test dictionary for stepCI testing type: void required: false file_name: test_dict.yaml -_locked: false +_locked: true diff --git a/tests/test_cases/stepci/enumerators/enumerations.0.yaml b/tests/test_cases/stepci/enumerators/enumerations.0.yaml index 39e1f62..bd9ae3c 100644 --- a/tests/test_cases/stepci/enumerators/enumerations.0.yaml +++ b/tests/test_cases/stepci/enumerators/enumerations.0.yaml @@ -2,4 +2,4 @@ name: Enumerations status: Active version: 0 enumerators: {} -_locked: false \ No newline at end of file +_locked: false diff --git a/tests/test_cases/stepci/enumerators/enumerations.1.yaml b/tests/test_cases/stepci/enumerators/enumerations.1.yaml index fc1222c..2145b99 100644 --- a/tests/test_cases/stepci/enumerators/enumerations.1.yaml +++ b/tests/test_cases/stepci/enumerators/enumerations.1.yaml @@ -3,6 +3,8 @@ status: Active version: 1 enumerators: default_status: - active: "Not Deleted" - archived: "Soft Delete Indicator" -_locked: false \ No newline at end of file + active: Not Deleted + archived: Soft Delete Indicator + test_enum: + foo: bar +_locked: false diff --git a/tests/test_cases/stepci/enumerators/enumerations.2.yaml b/tests/test_cases/stepci/enumerators/enumerations.2.yaml index e4be230..2a077a6 100644 --- a/tests/test_cases/stepci/enumerators/enumerations.2.yaml +++ b/tests/test_cases/stepci/enumerators/enumerations.2.yaml @@ -1,9 +1,5 @@ name: Enumerations status: Active version: 2 -enumerators: - default_status: - draft: "Draft" - active: "Not Deleted" - archived: "Soft Delete Indicator" -_locked: false \ No newline at end of file +enumerators: {} +_locked: false diff --git a/tests/test_cases/stepci/types/test_type.yaml b/tests/test_cases/stepci/types/test_type.yaml new file mode 100644 index 0000000..78b9b12 --- /dev/null +++ b/tests/test_cases/stepci/types/test_type.yaml @@ -0,0 +1,5 @@ +file_name: test_type.yaml +_locked: true +description: Updated test type for stepCI testing +required: true +type: string