From 83122148e8a3d11d0ac6d160c3a61e22606784ba Mon Sep 17 00:00:00 2001 From: Mike Storey Date: Tue, 24 Jun 2025 18:57:09 -0400 Subject: [PATCH] $ref loading defect corrected. --- stage0_mongodb_api/managers/schema_manager.py | 91 +++- .../managers/schema_renderer.py | 11 +- .../managers/schema_validator.py | 42 +- tests/managers/test_config_manager.py | 9 - tests/managers/test_ref_load_errors.py | 54 ++ tests/managers/test_schema_loading.py | 18 + tests/managers/test_schema_renders.py | 19 + tests/managers/test_schema_validation.py | 16 +- .../complex_refs/collections/workshop.yaml | 5 + .../complex_refs/data/enumerators.json | 50 ++ .../complex_refs/data/workshops.1.0.0.1.json | 150 ++++++ .../dictionary/observation.1.0.0.yaml | 68 +++ .../dictionary/observation_empathy.1.0.0.yaml | 23 + .../dictionary/observation_hills.1.0.0.yaml | 23 + .../observation_priority.1.0.0.yaml | 20 + .../dictionary/observation_ranking.1.0.0.yaml | 28 ++ .../observation_retrospective.1.0.0.yaml | 23 + .../observation_stakeholder.1.0.0.yaml | 26 + .../dictionary/types/appointment.yaml | 12 + .../dictionary/types/breadcrumb.yaml | 20 + .../complex_refs/dictionary/types/count.yaml | 9 + .../dictionary/types/date-time.yaml | 7 + .../complex_refs/dictionary/types/email.yaml | 5 + .../dictionary/types/identifier.yaml | 7 + .../complex_refs/dictionary/types/index.yaml | 9 + .../dictionary/types/ip_address.yaml | 5 + .../dictionary/types/markdown.yaml | 5 + .../dictionary/types/sentence.yaml | 5 + .../dictionary/types/state_code.yaml | 5 + .../dictionary/types/street_address.yaml | 18 + .../complex_refs/dictionary/types/url.yaml | 5 + .../dictionary/types/us_phone.yaml | 5 + .../complex_refs/dictionary/types/word.yaml | 6 + .../dictionary/workshop.1.0.0.yaml | 66 +++ .../bson_schema/workshop.1.0.0.1.json | 476 ++++++++++++++++++ .../json_schema/workshop.1.0.0.1.yaml | 376 ++++++++++++++ .../collections/circular_ref.yaml | 5 + .../collections/missing_ref.yaml | 5 + .../ref_load_errors/collections/test.yaml | 5 + .../ref_load_errors/data/enumerators.json | 16 + .../dictionary/circular_ref.1.0.0.yaml | 6 + .../dictionary/missing_ref.1.0.0.yaml | 6 + tests/test_cases/small_sample/simple.json | 16 - .../collections/missing_ref.yaml | 5 + .../dictionary/missing_ref.1.0.0.yaml | 7 + 45 files changed, 1717 insertions(+), 71 deletions(-) create mode 100644 tests/managers/test_ref_load_errors.py create mode 100644 tests/test_cases/complex_refs/collections/workshop.yaml create mode 100644 tests/test_cases/complex_refs/data/enumerators.json create mode 100644 tests/test_cases/complex_refs/data/workshops.1.0.0.1.json create mode 100644 tests/test_cases/complex_refs/dictionary/observation.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/observation_empathy.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/observation_hills.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/observation_priority.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/observation_ranking.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/observation_retrospective.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/observation_stakeholder.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/appointment.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/breadcrumb.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/count.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/date-time.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/email.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/identifier.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/index.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/ip_address.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/markdown.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/sentence.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/state_code.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/street_address.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/url.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/us_phone.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/types/word.yaml create mode 100644 tests/test_cases/complex_refs/dictionary/workshop.1.0.0.yaml create mode 100644 tests/test_cases/complex_refs/expected/bson_schema/workshop.1.0.0.1.json create mode 100644 tests/test_cases/complex_refs/expected/json_schema/workshop.1.0.0.1.yaml create mode 100644 tests/test_cases/ref_load_errors/collections/circular_ref.yaml create mode 100644 tests/test_cases/ref_load_errors/collections/missing_ref.yaml create mode 100644 tests/test_cases/ref_load_errors/collections/test.yaml create mode 100644 tests/test_cases/ref_load_errors/data/enumerators.json create mode 100644 tests/test_cases/ref_load_errors/dictionary/circular_ref.1.0.0.yaml create mode 100644 tests/test_cases/ref_load_errors/dictionary/missing_ref.1.0.0.yaml delete mode 100644 tests/test_cases/small_sample/simple.json create mode 100644 tests/test_cases/validation_errors/collections/missing_ref.yaml create mode 100644 tests/test_cases/validation_errors/dictionary/missing_ref.1.0.0.yaml diff --git a/stage0_mongodb_api/managers/schema_manager.py b/stage0_mongodb_api/managers/schema_manager.py index ec89a4a..dde6341 100644 --- a/stage0_mongodb_api/managers/schema_manager.py +++ b/stage0_mongodb_api/managers/schema_manager.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Set, Optional +from typing import Dict, List, Set, Optional, Any import os import re import yaml @@ -42,6 +42,10 @@ def __init__(self, collection_configs: Optional[Dict[str, Dict]] = None): # If collection_configs wasn't provided, load them if not self.collection_configs: self._load_collection_configs() + + # Resolve $ref values in dictionaries (after all dictionaries are loaded) + ref_errors = self._resolve_refs() + self.load_errors.extend(ref_errors) def _load_types(self) -> List[Dict]: """Load type definitions. @@ -165,6 +169,91 @@ def _load_dictionaries(self) -> List[Dict]: }) return errors + def _resolve_refs(self) -> List[Dict]: + """Resolve all $ref values in loaded dictionaries. + + This method recursively traverses all dictionary definitions and replaces + $ref objects with the actual referenced dictionary content. + + Returns: + List of errors encountered during resolution + """ + ref_errors = [] + + # Create a temporary copy of dictionaries for resolution + resolved = {} + + for dict_name, dict_def in self.dictionaries.items(): + resolved_def, errors = self._resolve_refs_in_object(dict_def, dict_name, set()) + resolved[dict_name] = resolved_def + ref_errors.extend(errors) + + self.dictionaries = resolved + + return ref_errors + + def _resolve_refs_in_object(self, obj: Any, dict_name: str, visited: Set[str]) -> tuple[Any, List[Dict]]: + """Recursively resolve $ref values in an object. + + Args: + obj: The object to resolve $ref values in + dict_name: The name of the dictionary being resolved + visited: Set of already visited paths (for cycle detection) + + Returns: + Tuple of (resolved_object, list_of_errors) + """ + errors = [] + if isinstance(obj, dict): + # Check if this is a $ref object + if "$ref" in obj: + ref_name = obj["$ref"] + if ref_name in visited: + errors.append({ + "error": "circular_reference", + "error_id": "SCH-013", + "dict_name": dict_name, + "ref_name": ref_name, + "message": f"Circular reference detected: {ref_name}" + }) + return obj, errors + elif ref_name not in self.dictionaries: + errors.append({ + "error": "ref_not_found", + "error_id": "SCH-014", + "dict_name": dict_name, + "ref_name": ref_name, + "message": f"Referenced dictionary not found: {ref_name}" + }) + return obj, errors + else: + # Resolve the reference - replace the entire object with the referenced content + visited.add(ref_name) + resolved, ref_errors = self._resolve_refs_in_object(self.dictionaries[ref_name], dict_name, visited) + visited.remove(ref_name) + errors.extend(ref_errors) + return resolved, errors + + # Otherwise, recursively resolve all values in the dictionary + resolved = {} + for key, value in obj.items(): + resolved_value, value_errors = self._resolve_refs_in_object(value, dict_name, visited) + resolved[key] = resolved_value + errors.extend(value_errors) + return resolved, errors + + elif isinstance(obj, list): + # Recursively resolve all items in the list + resolved_items = [] + for item in obj: + resolved_item, item_errors = self._resolve_refs_in_object(item, dict_name, visited) + resolved_items.append(resolved_item) + errors.extend(item_errors) + return resolved_items, errors + else: + # Primitive value, return as-is + return obj, errors + def _load_collection_configs(self) -> None: """Load collection configurations from the input folder. diff --git a/stage0_mongodb_api/managers/schema_renderer.py b/stage0_mongodb_api/managers/schema_renderer.py index 2bdb590..434e79a 100644 --- a/stage0_mongodb_api/managers/schema_renderer.py +++ b/stage0_mongodb_api/managers/schema_renderer.py @@ -17,21 +17,12 @@ def render_schema(version_name: str, format: SchemaFormat, context: SchemaContex @staticmethod def _render(schema: Dict, format: SchemaFormat, enumerator_version: int, context: SchemaContext) -> Dict: """ Recursively render a schema definition.""" - # Handle $ref first - replace with referenced dictionary - if "$ref" in schema: - return SchemaRenderer._render( - context["dictionaries"][schema["$ref"]], - format, - enumerator_version, - context - ) - # Handle primitive types if "schema" in schema or "json_type" in schema: return SchemaRenderer._render_primitive(schema, format) # Handle complex types - logger.info(f"Rendering schema: {schema}") + logger.debug(f"Rendering schema: {schema}") type_name = schema["type"] if type_name == SchemaType.OBJECT.value: return SchemaRenderer._render_object(schema, format, enumerator_version, context) diff --git a/stage0_mongodb_api/managers/schema_validator.py b/stage0_mongodb_api/managers/schema_validator.py index a41b69d..adf7867 100644 --- a/stage0_mongodb_api/managers/schema_validator.py +++ b/stage0_mongodb_api/managers/schema_validator.py @@ -310,13 +310,6 @@ def _validate_complex_type_properties(prop_name: str, prop_def: Dict, context: V }]) return type_errors - # Validate $ref types - if "$ref" in prop_def: - ref_errors = SchemaValidator._validate_ref_type(prop_name, prop_def["$ref"], context) - if ref_errors: - type_errors.extend(ref_errors) - return type_errors - # Validate required fields type_errors.extend(SchemaValidator._validate_required_fields(prop_name, prop_def)) if type_errors: @@ -350,19 +343,6 @@ def _validate_required_fields(prop_name: str, prop_def: Dict) -> List[Dict]: }) return errors - @staticmethod - def _validate_ref_type(prop_name: str, ref_type: str, context: ValidationContext) -> List[Dict]: - """Validate a $ref type reference.""" - if ref_type not in context["dictionaries"]: - return [{ - "error": "invalid_ref_type", - "error_id": "VLD-501", - "type": prop_name, - "ref": ref_type, - "message": f"Referenced type {ref_type} not found in dictionaries" - }] - return [] - @staticmethod def _validate_custom_type(prop_name: str, type_name: str, context: ValidationContext) -> List[Dict]: """Validate a custom type reference.""" @@ -484,20 +464,14 @@ def _validate_one_of_type(prop_name: str, one_of_def: Dict, context: ValidationC # Validate each schema in the one_of definition for schema_name, schema_def in one_of_def["schemas"].items(): - # If schema is a $ref, validate the reference - if isinstance(schema_def, dict) and "$ref" in schema_def: - ref_errors = SchemaValidator._validate_ref_type(f"{prop_name}.{schema_name}", schema_def["$ref"], context) - if ref_errors: - errors.extend(ref_errors) - else: - # Otherwise validate as a complex type - errors.extend(SchemaValidator._validate_complex_type( - f"{prop_name}.{schema_name}", - schema_def, - context, - enumerator_version, - visited - )) + # Validate as a complex type (all $ref objects will have been resolved during loading) + errors.extend(SchemaValidator._validate_complex_type( + f"{prop_name}.{schema_name}", + schema_def, + context, + enumerator_version, + visited + )) return errors \ No newline at end of file diff --git a/tests/managers/test_config_manager.py b/tests/managers/test_config_manager.py index 252ec50..fd28efd 100644 --- a/tests/managers/test_config_manager.py +++ b/tests/managers/test_config_manager.py @@ -60,15 +60,6 @@ def test_non_parsable(self): manager = ConfigManager() self.assertEqual(len(manager.load_errors), 1, f"Unexpected load errors {manager.load_errors}") - def test_validation_errors(self): - """Test loading with validation errors""" - test_case_dir = os.path.join(self.test_cases_dir, "validation_errors") - self.config.INPUT_FOLDER = test_case_dir - manager = ConfigManager() - errors = manager.validate_configs() - self.assertEqual(len(manager.load_errors), 0, f"Unexpected load errors {manager.load_errors}") - self.assertEqual(len(errors), 6, f"Unexpected number of validation errors {errors}") - def test_load_test_data_bulk_write_error(self): """Test that _load_test_data properly handles bulk write errors.""" from stage0_py_utils.mongo_utils.mongo_io import TestDataLoadError diff --git a/tests/managers/test_ref_load_errors.py b/tests/managers/test_ref_load_errors.py new file mode 100644 index 0000000..aeb9dde --- /dev/null +++ b/tests/managers/test_ref_load_errors.py @@ -0,0 +1,54 @@ +import unittest +import os +from unittest.mock import MagicMock, patch +from stage0_mongodb_api.managers.schema_manager import SchemaManager +from stage0_py_utils import Config + + +class TestRefLoadErrors(unittest.TestCase): + """Test cases for $ref load errors during schema loading.""" + + def setUp(self): + """Set up test environment.""" + self.config = Config.get_instance() + self.test_cases_dir = os.path.join(os.path.dirname(__file__), "..", "test_cases") + + @patch('stage0_py_utils.MongoIO.get_instance') + def test_ref_load_errors(self, mock_get_instance): + """Test that $ref load errors are properly caught and reported.""" + # Arrange + mock_get_instance.return_value = MagicMock() + self.config.INPUT_FOLDER = os.path.join(self.test_cases_dir, "ref_load_errors") + + # Act + schema_manager = SchemaManager() + + # Assert + # Check that we have load errors + self.assertGreater(len(schema_manager.load_errors), 0, + "Should have load errors for $ref issues") + + # Check for specific error types + error_codes = [error.get('error_id') for error in schema_manager.load_errors] + + # Should have SCH-013 (circular reference) and SCH-014 (missing reference) + self.assertIn('SCH-013', error_codes, + "Should have circular reference error (SCH-013)") + self.assertIn('SCH-014', error_codes, + "Should have missing reference error (SCH-014)") + + # Verify error details + circular_error = next((e for e in schema_manager.load_errors if e.get('error_id') == 'SCH-013'), None) + missing_error = next((e for e in schema_manager.load_errors if e.get('error_id') == 'SCH-014'), None) + + self.assertIsNotNone(circular_error, "Should have circular reference error") + self.assertEqual(circular_error['error'], 'circular_reference') + self.assertEqual(circular_error['ref_name'], 'circular_ref.1.0.0') + + self.assertIsNotNone(missing_error, "Should have missing reference error") + self.assertEqual(missing_error['error'], 'ref_not_found') + self.assertEqual(missing_error['ref_name'], 'does_not_exist.1.0.0') + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/managers/test_schema_loading.py b/tests/managers/test_schema_loading.py index 5aa11b1..d7d4377 100644 --- a/tests/managers/test_schema_loading.py +++ b/tests/managers/test_schema_loading.py @@ -114,5 +114,23 @@ def test_load_errors(self, mock_get_instance): self.assertEqual(missing_error_ids, set(), f"Missing error IDs: {missing_error_ids}") self.assertEqual(extra_error_ids, set(), f"Extra error IDs: {extra_error_ids}") + @patch('stage0_py_utils.MongoIO.get_instance') + def test_ref_resolution_errors(self, mock_get_instance): + """Test loading with $ref resolution errors.""" + # Arrange + mock_get_instance.return_value = MagicMock() + self.config.INPUT_FOLDER = os.path.join(self.test_cases_dir, "validation_errors") + + # Act + schema_manager = SchemaManager() + + # Assert + expected_error_ids = {"SCH-014"} # Only missing reference, no circular reference in this test case + actual_error_ids = {error.get('error_id') for error in schema_manager.load_errors if 'error_id' in error} + missing_error_ids = expected_error_ids - actual_error_ids + extra_error_ids = actual_error_ids - expected_error_ids + self.assertEqual(missing_error_ids, set(), f"Missing error IDs: {missing_error_ids}") + self.assertEqual(extra_error_ids, set(), f"Extra error IDs: {extra_error_ids}") + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/managers/test_schema_renders.py b/tests/managers/test_schema_renders.py index 8c074bc..2106758 100644 --- a/tests/managers/test_schema_renders.py +++ b/tests/managers/test_schema_renders.py @@ -35,6 +35,25 @@ def test_render_simple(self, mock_get_instance): self.assertEqual(rendered_bson, expected_bson, f"BSON schema mismatch, rendered: {rendered_bson}") self.assertEqual(rendered_json, expected_json, f"JSON schema mismatch, rendered: {rendered_json}") + @patch('stage0_py_utils.MongoIO.get_instance') + def test_render_nested_refs(self, mock_get_instance): + """Test rendering of nested $refs.""" + # Arrange + mock_get_instance.return_value = MagicMock() + self.config.INPUT_FOLDER = os.path.join(self.test_cases_dir, "complex_refs") + schema_manager = SchemaManager() + version_name = "workshop.1.0.0.1" + + # Act + rendered_bson = schema_manager.render_one(version_name, SchemaFormat.BSON) + rendered_json = schema_manager.render_one(version_name, SchemaFormat.JSON) + + # Assert + expected_bson = self._load_bson(version_name) + expected_json = self._load_json(version_name) + self.assertEqual(rendered_bson, expected_bson, f"BSON schema mismatch, rendered: {rendered_bson}") + self.assertEqual(rendered_json, expected_json, f"JSON schema mismatch, rendered: {rendered_json}") + @patch('stage0_py_utils.MongoIO.get_instance') def test_render_organization(self, mock_get_instance): """Test rendering with complex custom types.""" diff --git a/tests/managers/test_schema_validation.py b/tests/managers/test_schema_validation.py index 29c54df..efb00ac 100644 --- a/tests/managers/test_schema_validation.py +++ b/tests/managers/test_schema_validation.py @@ -60,6 +60,21 @@ def test_validate_large_sample(self, mock_get_instance): self.assertEqual(schema_manager.load_errors, []) self.assertEqual(errors, []) + @patch('stage0_py_utils.MongoIO.get_instance') + def test_validate_complex_refs(self, mock_get_instance): + """Test validation of complex nested $refs.""" + # Arrange + self.config.INPUT_FOLDER = os.path.join(self.test_cases_dir, "complex_refs") + mock_get_instance.return_value = MagicMock() + schema_manager = SchemaManager() + + # Act + errors = schema_manager.validate_schema() + + # Assert + self.assertEqual(schema_manager.load_errors, []) + self.assertEqual(errors, []) + @patch('stage0_py_utils.MongoIO.get_instance') def test_validation_errors(self, mock_get_instance): """Test validation with all validation errors.""" @@ -82,7 +97,6 @@ def test_validation_errors(self, mock_get_instance): "VLD-201", "VLD-202", "VLD-203", "VLD-204", # Primitive type validation errors "VLD-301", # Complex type basic validation "VLD-401", # Required fields validation - "VLD-501", # Reference type validation "VLD-601", # Custom type validation "VLD-701", # Object type validation "VLD-801", # Array type validation diff --git a/tests/test_cases/complex_refs/collections/workshop.yaml b/tests/test_cases/complex_refs/collections/workshop.yaml new file mode 100644 index 0000000..851a92e --- /dev/null +++ b/tests/test_cases/complex_refs/collections/workshop.yaml @@ -0,0 +1,5 @@ +title: Workshop Collection +description: A record of a workshop +name: user +versions: + - version: "1.0.0.1" diff --git a/tests/test_cases/complex_refs/data/enumerators.json b/tests/test_cases/complex_refs/data/enumerators.json new file mode 100644 index 0000000..b46a02c --- /dev/null +++ b/tests/test_cases/complex_refs/data/enumerators.json @@ -0,0 +1,50 @@ +[ + { + "name": "Enumerations", + "status": "Deprecated", + "version": 0, + "enumerators": {} + }, { + "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" + } + } + } +] \ No newline at end of file diff --git a/tests/test_cases/complex_refs/data/workshops.1.0.0.1.json b/tests/test_cases/complex_refs/data/workshops.1.0.0.1.json new file mode 100644 index 0000000..f1c21a9 --- /dev/null +++ b/tests/test_cases/complex_refs/data/workshops.1.0.0.1.json @@ -0,0 +1,150 @@ +[ + { + "_id": {"$oid": "000000000000000000000000"}, + "status": "pending", + "channel_id": "", + "name": "pending Empty", + "category": "", + "guild": "", + "purpose": "", + "when": { + "from": {"$date": "1/2/2025 12:00:00"}, + "to": {"$date": "1/2/2025 13:00:00"} + }, + "current_exercise": 0, + "exercises": [ + { + "exercise_id": {"$oid": "E00000000000000000000001"}, + "conversation_id": {"$oid": "C00000000000000000000001"}, + "status": "pending", + "duration": { + "from": {"$date": "1/2/2025 12:10:00"}, + "to": {"$date": "1/2/2025 12:15:00"} + }, + "observations": [ + {}, + {} + ] + }, + { + "exercise_id": {"$oid": "E00000000000000000000002"}, + "conversation_id": {"$oid": "C00000000000000000000002"}, + "status": "pending", + "duration": { + "from": {"$date": "1/2/2025 12:10:00"}, + "to": {"$date": "1/2/2025 12:15:00"} + }, + "observations": [ + {}, + {} + ] + } + ], + "last_saved": { + "fromIp": "", + "byUser": "", + "atTime": {"$date": "1/1/2025 12:34:56"}, + "correlationId": "" + } + }, + { + "_id": { + "$oid": "000000000000000000000001" + }, + "status": "pending", + "channel_id": "", + "name": "WorkshopName1", + "category": "", + "guild": "", + "purpose": "", + "when": { + "from": {"$date": "1/2/2025 12:00:00"}, + "to": {"$date": "1/2/2025 13:00:00"} + }, + "current_exercise": 0, + "exercises": [ + { + "exercise_id": {"$oid": "E00000000000000000000003"}, + "conversation_id": {"$oid": "C00000000000000000000003"}, + "status": "pending", + "duration": { + "from": {"$date": "1/2/2025 12:10:00"}, + "to": {"$date": "1/2/2025 12:15:00"} + }, + "observations": [ + {}, + {} + ] + }, + { + "exercise_id": {"$oid": "E00000000000000000000004"}, + "conversation_id": {"$oid": "C00000000000000000000004"}, + "status": "pending", + "duration": { + "from": {"$date": "1/2/2025 12:10:00"}, + "to": {"$date": "1/2/2025 12:15:00"} + }, + "observations": [ + {}, + {} + ] + } + ], + "last_saved": { + "fromIp": "", + "byUser": "", + "atTime": {"$date": "1/1/2025 12:34:56"}, + "correlationId": "" + } + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "status": "pending", + "channel_id": "", + "name": "another pending workshop", + "category": "", + "guild": "", + "purpose": "", + "when": { + "from": {"$date": "1/2/2025 12:00:00"}, + "to": {"$date": "1/2/2025 13:00:00"} + }, + "current_exercise": 0, + "exercises": [ + { + "exercise_id": {"$oid": "E00000000000000000000005"}, + "conversation_id": {"$oid": "C00000000000000000000005"}, + "status": "pending", + "duration": { + "from": {"$date": "1/2/2025 12:10:00"}, + "to": {"$date": "1/2/2025 12:15:00"} + }, + "observations": [ + {}, + {} + ] + }, + { + "exercise_id": {"$oid": "E00000000000000000000001"}, + "conversation_id": {"$oid": "C00000000000000000000006"}, + "status": "pending", + "duration": { + "from": {"$date": "1/2/2025 12:10:00"}, + "to": {"$date": "1/2/2025 12:15:00"} + }, + "observations": [ + {}, + {} + ] + } + ], + "last_saved": { + "fromIp": "", + "byUser": "", + "atTime": {"$date": "1/1/2025 12:34:56"}, + "correlationId": "" + } + } +] \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/observation.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation.1.0.0.yaml new file mode 100644 index 0000000..d6eb46b --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation.1.0.0.yaml @@ -0,0 +1,68 @@ +title: Observation +description: A workshop observation +type: object +properties: + exercise: + description: The exercise name + type: word + required: true +one_of: + type_property: exercise + schemas: + stakeholders: + description: Stakeholders Exercise + type: object + properties: + observations: + description: Stakeholder Observations + type: array + items: + $ref: observation_stakeholder.1.0.0 + empathy: + description: Empathy Mapping exercise + type: object + properties: + persona: + description: Persona information + type: object + properties: + name: + description: Name of the persona + type: word + description: + description: Description of the persona + type: sentence + observations: + description: Empathy Observations + type: array + items: + $ref: observation_empathy.1.0.0 + hills: + description: Hills of the exercise + type: object + properties: + observations: + description: Hills Observations + type: array + items: + $ref: observation_hills.1.0.0 + priority: + description: Priority of the exercise + type: object + properties: + observations: + description: Priority Observations + type: array + items: + $ref: observation_priority.1.0.0 + outcome: + $ref: observation_ranking.1.0.0 + retrospective: + description: Retrospective of the exercise + type: object + properties: + observations: + description: Retrospective Observations + type: array + items: + $ref: observation_retrospective.1.0.0 diff --git a/tests/test_cases/complex_refs/dictionary/observation_empathy.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation_empathy.1.0.0.yaml new file mode 100644 index 0000000..dc861b0 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation_empathy.1.0.0.yaml @@ -0,0 +1,23 @@ +title: Empathy Observation +description: A empathy of a Empathy Mapping exercise +type: object +properties: + users: + description: Users who made the observation + required: true + type: array + items: + description: User Name + type: word + says: + description: Things the user says + type: sentence + does: + description: Things the user does + type: sentence + thinks: + description: Things the user thinks + type: sentence + feels: + description: Things the user feels + type: sentence diff --git a/tests/test_cases/complex_refs/dictionary/observation_hills.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation_hills.1.0.0.yaml new file mode 100644 index 0000000..25db2ee --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation_hills.1.0.0.yaml @@ -0,0 +1,23 @@ +title: Hills Exercise Observation +description: A hill of a Hills exercise +type: object +properties: + name: + description: Unique Name of the hill + type: word + users: + description: Users who made the observation + required: true + type: array + items: + description: User Name + type: word + who: + description: Persona Who is the hill about + type: sentence + what: + description: What the user does + type: sentence + wow: + description: The Wow that happens when Who does What + type: sentence diff --git a/tests/test_cases/complex_refs/dictionary/observation_priority.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation_priority.1.0.0.yaml new file mode 100644 index 0000000..6b7d84a --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation_priority.1.0.0.yaml @@ -0,0 +1,20 @@ +title: Priority Exercise Observation +description: A priority of a Priority exercise +type: object +properties: + user: + description: User who made the priority observation + required: true + type: word + feasibility: + description: Hills in order of feasibility (low to high) + type: array + items: + description: Hill Name + type: word + impact: + description: Hills in order of impact (low to high) + type: array + items: + description: Hill Name + type: word diff --git a/tests/test_cases/complex_refs/dictionary/observation_ranking.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation_ranking.1.0.0.yaml new file mode 100644 index 0000000..32699ff --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation_ranking.1.0.0.yaml @@ -0,0 +1,28 @@ +title: Ranking Outcome +description: The outcome of a Priority exercise +type: object +properties: + no_brainers: + description: Hills that are high impact, and high feasibility + type: array + items: + description: Hill Name + type: word + big_bets: + description: Hills that are high impact, but low feasibility + type: array + items: + description: Hill Name + type: word + utilities: + description: Hills that are low impact, but high feasibility + type: array + items: + description: Hill Name + type: word + unwise: + description: Hills that are low impact, and low feasibility + type: array + items: + description: Hill Name + type: word diff --git a/tests/test_cases/complex_refs/dictionary/observation_retrospective.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation_retrospective.1.0.0.yaml new file mode 100644 index 0000000..ebb7d79 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation_retrospective.1.0.0.yaml @@ -0,0 +1,23 @@ +title: Retrospective Observation +description: A observation for a Retrospective exercise +type: object +properties: + users: + description: Users who made the observation + required: true + type: array + items: + description: User Name + type: word + question: + description: Question that remains + type: sentence + worked_well: + description: Something that worked well + type: sentence + could_be_improved: + description: Something that could be improved + type: sentence + ideas_to_try: + description: Something to try differently next time + type: sentence \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/observation_stakeholder.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/observation_stakeholder.1.0.0.yaml new file mode 100644 index 0000000..e7ae2ba --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/observation_stakeholder.1.0.0.yaml @@ -0,0 +1,26 @@ +title: Observation Stakeholder +description: A stakeholder of a Stakeholder exercise +type: object +properties: + users: + description: Users of the stakeholder + required: true + type: array + items: + description: User Name + type: word + text: + description: Text of the stakeholder + type: sentence + name: + description: Name of the stakeholder + type: word + title: + description: Title of the stakeholder + type: word + roles: + description: Roles of the stakeholder + type: array + items: + description: Role Name + type: word diff --git a/tests/test_cases/complex_refs/dictionary/types/appointment.yaml b/tests/test_cases/complex_refs/dictionary/types/appointment.yaml new file mode 100644 index 0000000..45e427b --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/appointment.yaml @@ -0,0 +1,12 @@ +title: Appointment +description: A date/time range +type: object +properties: + from: + description: Starting Date-Time + type: date-time + required: true + to: + description: Ending Date-Time + type: date-time + required: true diff --git a/tests/test_cases/complex_refs/dictionary/types/breadcrumb.yaml b/tests/test_cases/complex_refs/dictionary/types/breadcrumb.yaml new file mode 100644 index 0000000..f61bc50 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/breadcrumb.yaml @@ -0,0 +1,20 @@ +title: Breadcrumb +description: A tracking breadcrumb +type: object +properties: + from_ip: + description: Http Request remote IP address + type: ip_address + required: true + by_user: + description: ID Of User + type: word + required: true + at_time: + description: The date-time when last updated + type: date-time + required: true + correlation_id: + description: The logging correlation ID of the update transaction + type: word + required: true diff --git a/tests/test_cases/complex_refs/dictionary/types/count.yaml b/tests/test_cases/complex_refs/dictionary/types/count.yaml new file mode 100644 index 0000000..57bf4ea --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/count.yaml @@ -0,0 +1,9 @@ +title: Count +description: A positive integer value +json_type: + type: number + minimum: 1 + multipleOf: 1 +bson_type: + bsonType: int + minimum: 1 \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/date-time.yaml b/tests/test_cases/complex_refs/dictionary/types/date-time.yaml new file mode 100644 index 0000000..cfed5a3 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/date-time.yaml @@ -0,0 +1,7 @@ +title: DateTime +description: An ISO 8601 formatted date-time string +json_type: + type: string + format: date-time +bson_type: + bsonType: date \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/email.yaml b/tests/test_cases/complex_refs/dictionary/types/email.yaml new file mode 100644 index 0000000..53613a8 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/email.yaml @@ -0,0 +1,5 @@ +title: Email +description: A valid email address +schema: + type: string + pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/identifier.yaml b/tests/test_cases/complex_refs/dictionary/types/identifier.yaml new file mode 100644 index 0000000..fc4df48 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/identifier.yaml @@ -0,0 +1,7 @@ +title: Identifier +description: A unique identifier for a document +json_type: + type: string + pattern: "^[0-9a-fA-F]{24}$" +bson_type: + bsonType: objectId \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/index.yaml b/tests/test_cases/complex_refs/dictionary/types/index.yaml new file mode 100644 index 0000000..9eea5eb --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/index.yaml @@ -0,0 +1,9 @@ +title: Index +description: A zero-based array index +json_type: + type: number + minimum: 0 + multipleOf: 1 +bson_type: + bsonType: int + minimum: 0 \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/ip_address.yaml b/tests/test_cases/complex_refs/dictionary/types/ip_address.yaml new file mode 100644 index 0000000..80f7171 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/ip_address.yaml @@ -0,0 +1,5 @@ +title: IP Address +description: A valid IP Address +schema: + type: string + pattern: "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$" diff --git a/tests/test_cases/complex_refs/dictionary/types/markdown.yaml b/tests/test_cases/complex_refs/dictionary/types/markdown.yaml new file mode 100644 index 0000000..60fd1ee --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/markdown.yaml @@ -0,0 +1,5 @@ +title: Markdown +description: A String of text, at least 1 and no more than 4k characters. May contain markdown, newlines, and tabs. +schema: + type: string + pattern: "[\\s\\S]{1,4096}" \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/sentence.yaml b/tests/test_cases/complex_refs/dictionary/types/sentence.yaml new file mode 100644 index 0000000..0a902a6 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/sentence.yaml @@ -0,0 +1,5 @@ +title: Sentence +description: A String of text, at least 1 and no more than 255 characters with no special characters +schema: + type: string + pattern: "^[^\\t\\n\\r]{1,255}$" \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/state_code.yaml b/tests/test_cases/complex_refs/dictionary/types/state_code.yaml new file mode 100644 index 0000000..2b8b3d6 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/state_code.yaml @@ -0,0 +1,5 @@ +title: State code +description: A two character state code +schema: + type: string + pattern: "^[A-Z]{2}$" \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/street_address.yaml b/tests/test_cases/complex_refs/dictionary/types/street_address.yaml new file mode 100644 index 0000000..8ff59a9 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/street_address.yaml @@ -0,0 +1,18 @@ +title: Street Address +description: A street address +type: object +properties: + street: + description: Street address + type: sentence + required: true + city: + description: City + type: word + state: + description: State or province + type: state_code + postal_code: + description: Postal code + type: word + required: true diff --git a/tests/test_cases/complex_refs/dictionary/types/url.yaml b/tests/test_cases/complex_refs/dictionary/types/url.yaml new file mode 100644 index 0000000..1c35e86 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/url.yaml @@ -0,0 +1,5 @@ +title: URL +description: A valid URL +schema: + type: string + pattern: "^https?://[\\w\\d\\-]+(\\.[\\w\\d\\-]+)+([\\w\\d\\-._~:/?#\\[\\]@!$&'()*+,;=]*)?$" diff --git a/tests/test_cases/complex_refs/dictionary/types/us_phone.yaml b/tests/test_cases/complex_refs/dictionary/types/us_phone.yaml new file mode 100644 index 0000000..860e1fb --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/us_phone.yaml @@ -0,0 +1,5 @@ +title: US Phone +description: A US phone number in E.164 format +schema: + type: string + pattern: "^\\+1[2-9][0-9]{9}$" \ No newline at end of file diff --git a/tests/test_cases/complex_refs/dictionary/types/word.yaml b/tests/test_cases/complex_refs/dictionary/types/word.yaml new file mode 100644 index 0000000..5d05367 --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/types/word.yaml @@ -0,0 +1,6 @@ +title: Word +description: A String of text, at least 1 and no more than 40 characters with no spaces, or special characters like /t or /n +schema: + type: string + pattern: "^\\S{1,40}$" + diff --git a/tests/test_cases/complex_refs/dictionary/workshop.1.0.0.yaml b/tests/test_cases/complex_refs/dictionary/workshop.1.0.0.yaml new file mode 100644 index 0000000..f13e27e --- /dev/null +++ b/tests/test_cases/complex_refs/dictionary/workshop.1.0.0.yaml @@ -0,0 +1,66 @@ +title: Workshop +description: A record of a specific design thinking workshop. +type: object +properties: + _id: + description: The unique identifier for a Workshop + type: identifier + required: true + status: + description: Workshop Status/State + type: enum + enums: workshop_status + required: true + channel_id: + description: The discord identifier for the channel this workshop is hosted on + type: word + channel_name: + description: Workshop Name + type: word + category: + description: Discord Channel Category where this workshop channel is + type: sentence + guild: + description: Discord Server where this workshop takes place + type: sentence + purpose: + description: Workshop Purpose + type: markdown + when: + description: From-To Date/Time for the Workshop Event + type: appointment + current_exercise: + description: Index of the current exercise for Active workshops + type: index + exercises: + description: List of workshop_exercise documents + type: array + required: true + items: + description: A Workshop Exercise and observations + type: object + required: true + properties: + exercise_id: + description: The Exercise Instructions this is using + type: identifier + required: true + status: + description: The exercise status or state (Observe/Reflect/Make) + type: enum + enums: exercise_status + required: true + conversation_id: + description: The _id of the conversation for this exercise + type: identifier + required: true + observations: + description: Observations of the exercise + type: array + required: true + items: + $ref: observation.1.0.0 + last_saved: + description: Last Saved breadcrumb + type: breadcrumb + required: true diff --git a/tests/test_cases/complex_refs/expected/bson_schema/workshop.1.0.0.1.json b/tests/test_cases/complex_refs/expected/bson_schema/workshop.1.0.0.1.json new file mode 100644 index 0000000..ed0efdf --- /dev/null +++ b/tests/test_cases/complex_refs/expected/bson_schema/workshop.1.0.0.1.json @@ -0,0 +1,476 @@ +{ + "description": "A record of a specific design thinking workshop.", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "_id": { + "bsonType": "objectId", + "description": "The unique identifier for a Workshop" + }, + "status": { + "description": "Workshop Status/State", + "bsonType": "string", + "enum": [ + "pending", + "active", + "complete", + "archived" + ] + }, + "channel_id": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "The discord identifier for the channel this workshop is hosted on" + }, + "channel_name": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Workshop Name" + }, + "category": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Discord Channel Category where this workshop channel is" + }, + "guild": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Discord Server where this workshop takes place" + }, + "purpose": { + "pattern": "[\\s\\S]{1,4096}", + "bsonType": "string", + "description": "Workshop Purpose" + }, + "when": { + "description": "From-To Date/Time for the Workshop Event", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "from": { + "bsonType": "date", + "description": "Starting Date-Time" + }, + "to": { + "bsonType": "date", + "description": "Ending Date-Time" + } + }, + "title": "Appointment", + "required": ["from", "to"] + }, + "current_exercise": { + "bsonType": "int", + "minimum": 0, + "description": "Index of the current exercise for Active workshops" + }, + "exercises": { + "description": "List of workshop_exercise documents", + "bsonType": "array", + "items": { + "description": "A Workshop Exercise and observations", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "exercise_id": { + "bsonType": "objectId", + "description": "The Exercise Instructions this is using" + }, + "status": { + "description": "The exercise status or state (Observe/Reflect/Make)", + "bsonType": "string", + "enum": [ + "pending", + "observe", + "reflect", + "make", + "complete" + ] + }, + "conversation_id": { + "bsonType": "objectId", + "description": "The _id of the conversation for this exercise" + }, + "observations": { + "description": "Observations of the exercise", + "bsonType": "array", + "items": { + "description": "A workshop observation", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "exercise": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "The exercise name" + }, + "stakeholders": { + "description": "Stakeholders Exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "observations": { + "description": "Stakeholder Observations", + "bsonType": "array", + "items": { + "description": "A stakeholder of a Stakeholder exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "users": { + "description": "Users of the stakeholder", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "User Name" + } + }, + "text": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Text of the stakeholder" + }, + "name": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Name of the stakeholder" + }, + "title": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Title of the stakeholder" + }, + "roles": { + "description": "Roles of the stakeholder", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Role Name" + } + } + }, + "title": "Observation Stakeholder", + "required": [ + "users" + ] + } + } + } + }, + "empathy": { + "description": "Empathy Mapping exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "persona": { + "description": "Persona information", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "name": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Name of the persona" + }, + "description": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Description of the persona" + }, + "observations": { + "description": "Empathy Observations", + "bsonType": "array", + "items": { + "description": "A empathy of a Empathy Mapping exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "users": { + "description": "Users who made the observation", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "User Name" + } + }, + "says": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Things the user says" + }, + "does": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Things the user does" + }, + "thinks": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Things the user thinks" + }, + "feels": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Things the user feels" + } + }, + "title": "Empathy Observation", + "required": [ + "users" + ] + } + } + } + } + } + }, + "hills": { + "description": "Hills of the exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "observations": { + "description": "Hills Observations", + "bsonType": "array", + "items": { + "description": "A hill of a Hills exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "name": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Unique Name of the hill" + }, + "users": { + "description": "Users who made the observation", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "User Name" + } + }, + "who": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Persona Who is the hill about" + }, + "what": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "What the user does" + }, + "wow": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "The Wow that happens when Who does What" + } + }, + "title": "Hills Exercise Observation", + "required": [ + "users" + ] + } + } + } + }, + "priority": { + "description": "Priority of the exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "observations": { + "description": "Priority Observations", + "bsonType": "array", + "items": { + "description": "A priority of a Priority exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "user": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "User who made the priority observation" + }, + "feasibility": { + "description": "Hills in order of feasibility (low to high)", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Hill Name" + } + }, + "impact": { + "description": "Hills in order of impact (low to high)", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Hill Name" + } + } + }, + "title": "Priority Exercise Observation", + "required": [ + "user" + ] + } + }, + "outcome": { + "description": "The outcome of a Priority exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "no_brainers": { + "description": "Hills that are high impact, and high feasibility", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Hill Name" + } + }, + "big_bets": { + "description": "Hills that are high impact, but low feasibility", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Hill Name" + } + }, + "utilities": { + "description": "Hills that are low impact, but high feasibility", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Hill Name" + } + }, + "unwise": { + "description": "Hills that are low impact, and low feasibility", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "Hill Name" + } + } + }, + "title": "Ranking Outcome" + } + } + }, + "retrospective": { + "description": "Retrospective of the exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "observations": { + "description": "Retrospective Observations", + "bsonType": "array", + "items": { + "description": "A observation for a Retrospective exercise", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "users": { + "description": "Users who made the observation", + "bsonType": "array", + "items": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "User Name" + } + }, + "question": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Question that remains" + }, + "worked_well": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Something that worked well" + }, + "could_be_improved": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Something that could be improved" + }, + "ideas_to_try": { + "pattern": "^[^\\t\\n\\r]{1,255}$", + "bsonType": "string", + "description": "Something to try differently next time" + } + }, + "title": "Retrospective Observation", + "required": [ + "users" + ] + } + } + } + } + }, + "title": "Observation", + "required": ["exercise"] + } + } + }, + "required": [ + "exercise_id", + "status", + "conversation_id", + "observations" + ] + } + }, + "last_saved": { + "description": "Last Saved breadcrumb", + "bsonType": "object", + "additionalProperties": false, + "properties": { + "from_ip": { + "pattern": "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", + "bsonType": "string", + "description": "Http Request remote IP address" + }, + "by_user": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "ID Of User" + }, + "at_time": { + "bsonType": "date", + "description": "The date-time when last updated" + }, + "correlation_id": { + "pattern": "^\\S{1,40}$", + "bsonType": "string", + "description": "The logging correlation ID of the update transaction" + } + }, + "title": "Breadcrumb", + "required": [ + "from_ip", + "by_user", + "at_time", + "correlation_id" + ] + } + }, + "title": "Workshop", + "required": [ + "_id", + "status", + "exercises", + "last_saved" + ] +} diff --git a/tests/test_cases/complex_refs/expected/json_schema/workshop.1.0.0.1.yaml b/tests/test_cases/complex_refs/expected/json_schema/workshop.1.0.0.1.yaml new file mode 100644 index 0000000..70ac563 --- /dev/null +++ b/tests/test_cases/complex_refs/expected/json_schema/workshop.1.0.0.1.yaml @@ -0,0 +1,376 @@ +description: A record of a specific design thinking workshop. +type: object +additionalProperties: false +properties: + _id: + type: string + pattern: ^[0-9a-fA-F]{24}$ + description: The unique identifier for a Workshop + status: + description: Workshop Status/State + type: string + enum: + - pending + - active + - complete + - archived + channel_id: + type: string + pattern: ^\S{1,40}$ + description: The discord identifier for the channel this workshop is hosted on + channel_name: + type: string + pattern: ^\S{1,40}$ + description: Workshop Name + category: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Discord Channel Category where this workshop channel is + guild: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Discord Server where this workshop takes place + purpose: + type: string + pattern: '[\s\S]{1,4096}' + description: Workshop Purpose + when: + description: From-To Date/Time for the Workshop Event + type: object + additionalProperties: false + properties: + from: + type: string + format: date-time + description: Starting Date-Time + to: + type: string + format: date-time + description: Ending Date-Time + title: Appointment + required: + - from + - to + current_exercise: + type: number + minimum: 0 + multipleOf: 1 + description: Index of the current exercise for Active workshops + exercises: + description: List of workshop_exercise documents + type: array + items: + description: A Workshop Exercise and observations + type: object + additionalProperties: false + properties: + exercise_id: + type: string + pattern: ^[0-9a-fA-F]{24}$ + description: The Exercise Instructions this is using + status: + description: The exercise status or state (Observe/Reflect/Make) + type: string + enum: + - pending + - observe + - reflect + - make + - complete + conversation_id: + type: string + pattern: ^[0-9a-fA-F]{24}$ + description: The _id of the conversation for this exercise + observations: + description: Observations of the exercise + type: array + items: + description: A workshop observation + type: object + additionalProperties: false + properties: + exercise: + type: string + pattern: ^\S{1,40}$ + description: The exercise name + stakeholders: + description: Stakeholders Exercise + type: object + additionalProperties: false + properties: + observations: + description: Stakeholder Observations + type: array + items: + description: A stakeholder of a Stakeholder exercise + type: object + additionalProperties: false + properties: + users: + description: Users of the stakeholder + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: User Name + text: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Text of the stakeholder + name: + type: string + pattern: ^\S{1,40}$ + description: Name of the stakeholder + title: + type: string + pattern: ^\S{1,40}$ + description: Title of the stakeholder + roles: + description: Roles of the stakeholder + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Role Name + title: Observation Stakeholder + required: + - users + empathy: + description: Empathy Mapping exercise + type: object + additionalProperties: false + properties: + persona: + description: Persona information + type: object + additionalProperties: false + properties: + name: + type: string + pattern: ^\S{1,40}$ + description: Name of the persona + description: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Description of the persona + observations: + description: Empathy Observations + type: array + items: + description: A empathy of a Empathy Mapping exercise + type: object + additionalProperties: false + properties: + users: + description: Users who made the observation + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: User Name + says: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Things the user says + does: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Things the user does + thinks: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Things the user thinks + feels: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Things the user feels + title: Empathy Observation + required: + - users + hills: + description: Hills of the exercise + type: object + additionalProperties: false + properties: + observations: + description: Hills Observations + type: array + items: + description: A hill of a Hills exercise + type: object + additionalProperties: false + properties: + name: + type: string + pattern: ^\S{1,40}$ + description: Unique Name of the hill + users: + description: Users who made the observation + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: User Name + who: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Persona Who is the hill about + what: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: What the user does + wow: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: The Wow that happens when Who does What + title: Hills Exercise Observation + required: + - users + priority: + description: Priority of the exercise + type: object + additionalProperties: false + properties: + observations: + description: Priority Observations + type: array + items: + description: A priority of a Priority exercise + type: object + additionalProperties: false + properties: + user: + type: string + pattern: ^\S{1,40}$ + description: User who made the priority observation + feasibility: + description: Hills in order of feasibility (low to high) + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Hill Name + impact: + description: Hills in order of impact (low to high) + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Hill Name + title: Priority Exercise Observation + required: + - user + outcome: + description: The outcome of a Priority exercise + type: object + additionalProperties: false + properties: + no_brainers: + description: Hills that are high impact, and high feasibility + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Hill Name + big_bets: + description: Hills that are high impact, but low feasibility + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Hill Name + utilities: + description: Hills that are low impact, but high feasibility + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Hill Name + unwise: + description: Hills that are low impact, and low feasibility + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: Hill Name + title: Ranking Outcome + retrospective: + description: Retrospective of the exercise + type: object + additionalProperties: false + properties: + observations: + description: Retrospective Observations + type: array + items: + description: A observation for a Retrospective exercise + type: object + additionalProperties: false + properties: + users: + description: Users who made the observation + type: array + items: + type: string + pattern: ^\S{1,40}$ + description: User Name + question: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Question that remains + worked_well: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Something that worked well + could_be_improved: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Something that could be improved + ideas_to_try: + type: string + pattern: ^[^\t\n\r]{1,255}$ + description: Something to try differently next time + title: Retrospective Observation + required: + - users + title: Observation + required: + - exercise + required: + - exercise_id + - status + - conversation_id + - observations + last_saved: + description: Last Saved breadcrumb + type: object + additionalProperties: false + properties: + from_ip: + type: string + pattern: ^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$ + description: Http Request remote IP address + by_user: + type: string + pattern: ^\S{1,40}$ + description: ID Of User + at_time: + type: string + format: date-time + description: The date-time when last updated + correlation_id: + type: string + pattern: ^\S{1,40}$ + description: The logging correlation ID of the update transaction + title: Breadcrumb + required: + - from_ip + - by_user + - at_time + - correlation_id +title: Workshop +required: + - _id + - status + - exercises + - last_saved \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/collections/circular_ref.yaml b/tests/test_cases/ref_load_errors/collections/circular_ref.yaml new file mode 100644 index 0000000..f770238 --- /dev/null +++ b/tests/test_cases/ref_load_errors/collections/circular_ref.yaml @@ -0,0 +1,5 @@ +title: Circular Ref Collection +description: Collection for managing circular ref items +name: circular_ref +versions: + - version: "1.0.0.1" \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/collections/missing_ref.yaml b/tests/test_cases/ref_load_errors/collections/missing_ref.yaml new file mode 100644 index 0000000..b8ff788 --- /dev/null +++ b/tests/test_cases/ref_load_errors/collections/missing_ref.yaml @@ -0,0 +1,5 @@ +title: Missing Ref Collection +description: Collection for managing missing ref items +name: missing_ref +versions: + - version: "1.0.0.1" \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/collections/test.yaml b/tests/test_cases/ref_load_errors/collections/test.yaml new file mode 100644 index 0000000..01315f0 --- /dev/null +++ b/tests/test_cases/ref_load_errors/collections/test.yaml @@ -0,0 +1,5 @@ +title: Test Collection +name: test +versions: + - version: 1.0.0 + description: Test version with $ref errors \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/data/enumerators.json b/tests/test_cases/ref_load_errors/data/enumerators.json new file mode 100644 index 0000000..2bf7d41 --- /dev/null +++ b/tests/test_cases/ref_load_errors/data/enumerators.json @@ -0,0 +1,16 @@ +[ + { + "version": 0, + "enumerators": {} + } + { + "version": 1, + "enumerators": { + "test_enum": { + "value1": "Test value 1", + "value2": "Test value 2" + } + } + } + +] \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/dictionary/circular_ref.1.0.0.yaml b/tests/test_cases/ref_load_errors/dictionary/circular_ref.1.0.0.yaml new file mode 100644 index 0000000..c411a57 --- /dev/null +++ b/tests/test_cases/ref_load_errors/dictionary/circular_ref.1.0.0.yaml @@ -0,0 +1,6 @@ +title: Circular Reference Test +description: A schema with a circular reference +type: object +properties: + self_ref: + $ref: circular_ref.1.0.0 \ No newline at end of file diff --git a/tests/test_cases/ref_load_errors/dictionary/missing_ref.1.0.0.yaml b/tests/test_cases/ref_load_errors/dictionary/missing_ref.1.0.0.yaml new file mode 100644 index 0000000..d17035a --- /dev/null +++ b/tests/test_cases/ref_load_errors/dictionary/missing_ref.1.0.0.yaml @@ -0,0 +1,6 @@ +title: Missing Reference Test +description: A schema with a missing reference +type: object +properties: + missing: + $ref: does_not_exist.1.0.0 \ No newline at end of file diff --git a/tests/test_cases/small_sample/simple.json b/tests/test_cases/small_sample/simple.json deleted file mode 100644 index 02de043..0000000 --- a/tests/test_cases/small_sample/simple.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "_id": { - "$oid": "a00000000000000000000001" - }, - "name": "testDocument1", - "status": "active" - }, - { - "_id": { - "$oid": "a00000000000000000000002" - }, - "name": "testDocument2", - "status": "archived" - } -] \ No newline at end of file diff --git a/tests/test_cases/validation_errors/collections/missing_ref.yaml b/tests/test_cases/validation_errors/collections/missing_ref.yaml new file mode 100644 index 0000000..3e20706 --- /dev/null +++ b/tests/test_cases/validation_errors/collections/missing_ref.yaml @@ -0,0 +1,5 @@ +title: MissingRef +name: missing_ref +versions: + - version: 1.0.0 + description: Test version with missing $ref \ No newline at end of file diff --git a/tests/test_cases/validation_errors/dictionary/missing_ref.1.0.0.yaml b/tests/test_cases/validation_errors/dictionary/missing_ref.1.0.0.yaml new file mode 100644 index 0000000..639df57 --- /dev/null +++ b/tests/test_cases/validation_errors/dictionary/missing_ref.1.0.0.yaml @@ -0,0 +1,7 @@ +title: MissingRef +description: This schema references a missing $ref +type: object +properties: + missing: + description: This property references a missing schema + $ref: does_not_exist.1.0.0 \ No newline at end of file