diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf45cdf..563f84c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added export format **protobuf**: `datacontract export --format protobuf` - Added export format **terraform**: `datacontract export --format terraform` - Added extensive linting on data contracts. `datacontract lint` will now check for a variety of possible errors in the data contract, such as missing descriptions, incorrect references to models or fields, nonsensical constraints, and more. +- Added changelog command: `datacontract changelog` will now generate a changelog based on the changes in the data contract. This will be useful for keeping track of changes in the data contract over time. ## [0.9.6-2] - 2024-03-04 diff --git a/README.md b/README.md index 23ec9379..a8a5a84c 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,10 @@ $ datacontract test --examples datacontract.yaml # find differences between to data contracts (Coming Soon) $ datacontract diff datacontract-v1.yaml datacontract-v2.yaml -# fail pipeline on breaking changes (Coming Soon) +# find differences between to data contracts categorized into error, warning, and info. +$ datacontract changelog datacontract-v1.yaml datacontract-v2.yaml + +# fail pipeline on breaking changes. Uses changelog internally and showing only error and warning. $ datacontract breaking datacontract-v1.yaml datacontract-v2.yaml # export model as jsonschema (other formats: avro, dbt, dbt-sources, dbt-staging-sql, jsonschema, odcs, rdf, sql (coming soon), sodacl, terraform) diff --git a/datacontract/breaking/breaking.py b/datacontract/breaking/breaking.py index 44c03354..11f5ae9e 100644 --- a/datacontract/breaking/breaking.py +++ b/datacontract/breaking/breaking.py @@ -1,12 +1,13 @@ from datacontract.breaking.breaking_rules import BreakingRules -from datacontract.model.breaking_change import BreakingChanges, BreakingChange, Location +from datacontract.model.breaking_change import BreakingChanges, BreakingChange, Location, Severity from datacontract.model.data_contract_specification import Field, Model, Quality def quality_breaking_changes( old_quality: Quality, new_quality: Quality, - new_path: str + new_path: str, + include_severities: [Severity], ) -> list[BreakingChange]: results = list[BreakingChange]() @@ -15,7 +16,7 @@ def quality_breaking_changes( severity = _get_rule(rule_name) description = "added quality" - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description=description, @@ -30,7 +31,7 @@ def quality_breaking_changes( severity = _get_rule(rule_name) description = "removed quality" - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description=description, @@ -47,7 +48,7 @@ def quality_breaking_changes( severity = _get_rule(rule_name) description = f"changed from `{old_quality.type}` to `{new_quality.type}`" - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description=description, @@ -62,7 +63,7 @@ def quality_breaking_changes( rule_name = "quality_specification_updated" severity = _get_rule(rule_name) description = f"changed from `{old_quality.specification}` to `{new_quality.specification}`" - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description=description, @@ -79,7 +80,8 @@ def quality_breaking_changes( def models_breaking_changes( old_models: dict[str, Model], new_models: dict[str, Model], - new_path: str + new_path: str, + include_severities: [Severity], ) -> list[BreakingChange]: composition = ["models"] results = list[BreakingChange]() @@ -88,7 +90,7 @@ def models_breaking_changes( if model_name not in old_models.keys(): rule_name = "model_added" severity = _get_rule(rule_name) - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description="added the model", @@ -103,7 +105,7 @@ def models_breaking_changes( if model_name not in new_models.keys(): rule_name = "model_removed" severity = _get_rule(rule_name) - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description="removed the model", @@ -120,7 +122,8 @@ def models_breaking_changes( old_model=old_model, new_model=new_models[model_name], new_path=new_path, - composition=composition + [model_name] + composition=composition + [model_name], + include_severities=include_severities, )) return results @@ -130,7 +133,8 @@ def model_breaking_changes( old_model: Model, new_model: Model, new_path: str, - composition: list[str] + composition: list[str], + include_severities: [Severity] ) -> list[BreakingChange]: results = list[BreakingChange]() @@ -160,7 +164,7 @@ def model_breaking_changes( if rule_name is not None: severity = _get_rule(rule_name) - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description=description, @@ -176,7 +180,8 @@ def model_breaking_changes( old_fields=old_model.fields, new_fields=new_model.fields, new_path=new_path, - composition=composition + ["fields"] + composition=composition + ["fields"], + include_severities=include_severities, )) return results @@ -186,7 +191,8 @@ def fields_breaking_changes( old_fields: dict[str, Field], new_fields: dict[str, Field], new_path: str, - composition: list[str] + composition: list[str], + include_severities: [Severity] ) -> list[BreakingChange]: results = list[BreakingChange]() @@ -194,7 +200,7 @@ def fields_breaking_changes( if field_name not in old_fields.keys(): rule_name = "field_added" severity = _get_rule(rule_name) - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description="added the field", @@ -209,7 +215,7 @@ def fields_breaking_changes( if field_name not in new_fields.keys(): rule_name = "field_removed" severity = _get_rule(rule_name) - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description='removed the field', @@ -227,6 +233,7 @@ def fields_breaking_changes( new_field=new_fields[field_name], composition=composition + [field_name], new_path=new_path, + include_severities=include_severities, )) return results @@ -236,6 +243,7 @@ def field_breaking_changes( new_field: Field, composition: list[str], new_path: str, + include_severities: [Severity], ) -> list[BreakingChange]: results = list[BreakingChange]() @@ -253,7 +261,8 @@ def field_breaking_changes( old_fields=old_field.fields, new_fields=new_field.fields, new_path=new_path, - composition=composition + [field_definition_field] + composition=composition + [field_definition_field], + include_severities=include_severities, ) ) continue @@ -290,7 +299,7 @@ def field_breaking_changes( if rule_name is not None: severity = _get_rule(rule_name) field_schema_name = "$ref" if field_definition_field == "ref" else field_definition_field - if severity != "info": + if severity in include_severities: results.append( BreakingChange( description=description, @@ -304,13 +313,13 @@ def field_breaking_changes( return results -def _get_rule(rule_name): +def _get_rule(rule_name) -> Severity: try: return getattr(BreakingRules, rule_name) except AttributeError: print(f'WARNING: Breaking Rule not found for {rule_name}!') - return 'error' + return Severity.ERROR -def _camel_to_snake(s): - return ''.join(['_'+c.lower() if c.isupper() else c for c in s]).lstrip('_') +def _camel_to_snake(s): + return ''.join(['_' + c.lower() if c.isupper() else c for c in s]).lstrip('_') diff --git a/datacontract/breaking/breaking_rules.py b/datacontract/breaking/breaking_rules.py index c47431a4..1520272a 100644 --- a/datacontract/breaking/breaking_rules.py +++ b/datacontract/breaking/breaking_rules.py @@ -1,91 +1,94 @@ +from datacontract.model.breaking_change import Severity + + class BreakingRules: # model rules - model_added = 'info' - model_removed = 'error' + model_added = Severity.INFO + model_removed = Severity.ERROR - model_description_added = 'info' - model_description_removed = 'info' - model_description_updated = 'info' + model_description_added = Severity.INFO + model_description_removed = Severity.INFO + model_description_updated = Severity.INFO - model_type_updated = 'error' + model_type_updated = Severity.ERROR # field rules - field_added = 'info' - field_removed = 'error' + field_added = Severity.INFO + field_removed = Severity.ERROR - field_ref_added = 'warning' - field_ref_removed = 'warning' - field_ref_updated = 'warning' + field_ref_added = Severity.WARNING + field_ref_removed = Severity.WARNING + field_ref_updated = Severity.WARNING - field_type_added = 'warning' - field_type_removed = 'warning' - field_type_updated = 'error' + field_type_added = Severity.WARNING + field_type_removed = Severity.WARNING + field_type_updated = Severity.ERROR - field_format_added = 'warning' - field_format_removed = 'warning' - field_format_updated = 'error' + field_format_added = Severity.WARNING + field_format_removed = Severity.WARNING + field_format_updated = Severity.ERROR - field_required_updated = 'error' + field_required_updated = Severity.ERROR - field_primary_updated = 'warning' + field_primary_updated = Severity.WARNING - field_references_added = 'warning' - field_references_removed = 'warning' - field_references_updated = 'warning' + field_references_added = Severity.WARNING + field_references_removed = Severity.WARNING + field_references_updated = Severity.WARNING - field_unique_updated = 'error' + field_unique_updated = Severity.ERROR - field_description_added = 'info' - field_description_removed = 'info' - field_description_updated = 'info' + field_description_added = Severity.INFO + field_description_removed = Severity.INFO + field_description_updated = Severity.INFO - field_pii_added = 'warning' - field_pii_removed = 'error' - field_pii_updated = 'error' + field_pii_added = Severity.WARNING + field_pii_removed = Severity.ERROR + field_pii_updated = Severity.ERROR - field_classification_added = 'warning' - field_classification_removed = 'error' - field_classification_updated = 'error' + field_classification_added = Severity.WARNING + field_classification_removed = Severity.ERROR + field_classification_updated = Severity.ERROR - field_pattern_added = 'warning' - field_pattern_removed = 'error' - field_pattern_updated = 'error' + field_pattern_added = Severity.WARNING + field_pattern_removed = Severity.ERROR + field_pattern_updated = Severity.ERROR - field_min_length_added = 'warning' - field_min_length_removed = 'warning' - field_min_length_updated = 'error' + field_min_length_added = Severity.WARNING + field_min_length_removed = Severity.WARNING + field_min_length_updated = Severity.ERROR - field_max_length_added = 'warning' - field_max_length_removed = 'warning' - field_max_length_updated = 'error' + field_max_length_added = Severity.WARNING + field_max_length_removed = Severity.WARNING + field_max_length_updated = Severity.ERROR - field_minimum_added = 'warning' - field_minimum_removed = 'warning' - field_minimum_updated = 'error' + field_minimum_added = Severity.WARNING + field_minimum_removed = Severity.WARNING + field_minimum_updated = Severity.ERROR - field_exclusive_minimum_added = 'warning' - field_exclusive_minimum_removed = 'warning' - field_exclusive_minimum_updated = 'error' + field_exclusive_minimum_added = Severity.WARNING + field_exclusive_minimum_removed = Severity.WARNING + field_exclusive_minimum_updated = Severity.ERROR - field_maximum_added = 'warning' - field_maximum_removed = 'warning' - field_maximum_updated = 'error' + field_maximum_added = Severity.WARNING + field_maximum_removed = Severity.WARNING + field_maximum_updated = Severity.ERROR - field_exclusive_maximum_added = 'warning' - field_exclusive_maximum_removed = 'warning' - field_exclusive_maximum_updated = 'error' + field_exclusive_maximum_added = Severity.WARNING + field_exclusive_maximum_removed = Severity.WARNING + field_exclusive_maximum_updated = Severity.ERROR - field_enum_added = 'warning' - field_enum_removed = 'info' - field_enum_updated = 'error' + field_enum_added = Severity.WARNING + field_enum_removed = Severity.INFO + field_enum_updated = Severity.ERROR - field_tags_added = 'info' - field_tags_removed = 'info' - field_tags_updated = 'info' + field_tags_added = Severity.INFO + field_tags_removed = Severity.INFO + field_tags_updated = Severity.INFO # quality Rules - quality_added = 'info' - quality_removed = 'warning' + quality_added = Severity.INFO + quality_removed = Severity.WARNING - quality_type_updated = 'warning' - quality_specification_updated = 'warning' + quality_type_updated = Severity.WARNING + quality_specification_updated = Severity.WARNING diff --git a/datacontract/cli.py b/datacontract/cli.py index 461cf755..5d7a2b6f 100644 --- a/datacontract/cli.py +++ b/datacontract/cli.py @@ -182,11 +182,35 @@ def breaking( data_contract_file=location_new, inline_definitions=True )) - print(str(result)) + + print(result.breaking_str()) + if not result.passed_checks(): raise typer.Exit(code=1) +@app.command() +def changelog( + location_old: Annotated[str, typer.Argument(help="The location (url or path) of the old data contract yaml.")], + location_new: Annotated[str, typer.Argument(help="The location (url or path) of the new data contract yaml.")], +): + """ + Generate a changelog between data contracts. Prints to stdout. + """ + + # TODO exception handling + result = DataContract( + data_contract_file=location_old, + inline_definitions=True + ).changelog( + DataContract( + data_contract_file=location_new, + inline_definitions=True + )) + + print(result.changelog_str()) + + def _handle_result(run): _print_table(run) if run.result == "passed": diff --git a/datacontract/data_contract.py b/datacontract/data_contract.py index 8dc7f337..616cd04e 100644 --- a/datacontract/data_contract.py +++ b/datacontract/data_contract.py @@ -25,7 +25,7 @@ publish_datamesh_manager from datacontract.lint import resolve -from datacontract.model.breaking_change import BreakingChanges, BreakingChange +from datacontract.model.breaking_change import BreakingChanges, BreakingChange, Severity from datacontract.lint.linters.description_linter import DescriptionLinter from datacontract.lint.linters.example_model_linter import ExampleModelLinter from datacontract.lint.linters.valid_constraints_linter import ValidFieldConstraintsLinter @@ -78,7 +78,7 @@ def __init__( def init(cls, template: str = "https://datacontract.com/datacontract.init.yaml") -> DataContractSpecification: return resolve.resolve_data_contract(data_contract_location=template) - def lint(self, enabled_linters: typing.Union[str, set[str]]="all") -> Run: + def lint(self, enabled_linters: typing.Union[str, set[str]] = "all") -> Run: """Lint the data contract by deserializing the contract and checking the schema, as well as calling the configured linters. enabled_linters can be either "all" or "none", or a set of linter IDs. The "schema" linter is always enabled, even with enabled_linters="none". @@ -101,7 +101,7 @@ def lint(self, enabled_linters: typing.Union[str, set[str]]="all") -> Run: linters_to_check = self.all_linters elif isinstance(enabled_linters, set): linters_to_check = {linter for linter in self.all_linters - if linter.id in enabled_linters} + if linter.id in enabled_linters} else: raise RuntimeError(f"Unknown argument enabled_linters={enabled_linters} for lint()") for linter in linters_to_check: @@ -218,6 +218,16 @@ def test(self) -> Run: return run def breaking(self, other: 'DataContract') -> BreakingChanges: + return self.changelog( + other, + include_severities=[Severity.ERROR, Severity.WARNING] + ) + + def changelog( + self, + other: 'DataContract', + include_severities: [Severity] = (Severity.ERROR, Severity.WARNING, Severity.INFO) + ) -> BreakingChanges: old = self.get_data_contract_specification() new = other.get_data_contract_specification() @@ -227,12 +237,14 @@ def breaking(self, other: 'DataContract') -> BreakingChanges: old_quality=old.quality, new_quality=new.quality, new_path=other._data_contract_file, + include_severities=include_severities, )) breaking_changes.extend(models_breaking_changes( old_models=old.models, new_models=new.models, new_path=other._data_contract_file, + include_severities=include_severities, )) return BreakingChanges(breaking_changes=breaking_changes) diff --git a/datacontract/model/breaking_change.py b/datacontract/model/breaking_change.py index f0b163c6..1fb2788b 100644 --- a/datacontract/model/breaking_change.py +++ b/datacontract/model/breaking_change.py @@ -1,6 +1,16 @@ from typing import List from pydantic import BaseModel +from enum import Enum + + +class Severity(Enum): + ERROR = "error" + WARNING = "warning" + INFO = "info" + + def __str__(self) -> str: + return self.value class Location(BaseModel): @@ -10,7 +20,7 @@ class Location(BaseModel): class BreakingChange(BaseModel): description: str - severity: str + severity: Severity check_name: str location: Location @@ -24,19 +34,30 @@ class BreakingChanges(BaseModel): breaking_changes: List[BreakingChange] def passed_checks(self) -> bool: - errors = len(list(filter(lambda x: x.severity == "error", self.breaking_changes))) + errors = len(list(filter(lambda x: x.severity == Severity.ERROR, self.breaking_changes))) return errors == 0 - def __str__(self) -> str: + def breaking_str(self) -> str: changes_amount = len(self.breaking_changes) - errors = len(list(filter(lambda x: x.severity == "error", self.breaking_changes))) - warnings = len(list(filter(lambda x: x.severity == "warning", self.breaking_changes))) + errors = len(list(filter(lambda x: x.severity == Severity.ERROR, self.breaking_changes))) + warnings = len(list(filter(lambda x: x.severity == Severity.WARNING, self.breaking_changes))) headline = f"{changes_amount} breaking changes: {errors} error, {warnings} warning\n" content = str.join("\n\n", map(lambda x: str(x), self.breaking_changes)) return headline + content + def changelog_str(self) -> str: + changes_amount = len(self.breaking_changes) + errors = len(list(filter(lambda x: x.severity == Severity.ERROR, self.breaking_changes))) + warnings = len(list(filter(lambda x: x.severity == Severity.WARNING, self.breaking_changes))) + infos = len(list(filter(lambda x: x.severity == Severity.INFO, self.breaking_changes))) + + headline = f"{changes_amount} changes: {errors} error, {warnings} warning, {infos} info\n" + content = str.join("\n\n", map(lambda x: str(x), self.breaking_changes)) + + return headline + content + # # [ # { diff --git a/tests/test_breaking.py b/tests/test_breaking.py index 9cb83f0c..a5aa154c 100644 --- a/tests/test_breaking.py +++ b/tests/test_breaking.py @@ -14,735 +14,232 @@ def test_no_breaking_changes(): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-fields-v2.yaml", "./examples/breaking/datacontract-fields-v2.yaml"]) assert result.exit_code == 0 - assert "0 breaking changes: 0 error, 0 warning" in result.stdout + assert "0 breaking changes: 0 error, 0 warning\n" in result.stdout def test_quality_added(): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-quality-v1.yaml", "./examples/breaking/datacontract-quality-v2.yaml"]) assert result.exit_code == 0 - assert "0 breaking changes: 0 error, 0 warning" in result.stdout + assert "0 breaking changes: 0 error, 0 warning\n" in result.stdout -class TestQualityRemoved: - - @pytest.fixture(scope='class') - def output(self): - result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-quality-v2.yaml", - "./examples/breaking/datacontract-quality-v1.yaml"]) - assert result.exit_code == 0 - return result.stdout - - def test_headline(self, output): - print(output) - assert "1 breaking changes: 0 error, 1 warning" in output - - def test_quality_removed(self, output): - assert r"""warning [quality_removed] at ./examples/breaking/datacontract-quality-v1.yaml - in quality - removed quality""" in output - - -class TestQualityUpdated: - - @pytest.fixture(scope='class') - def output(self): - result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-quality-v2.yaml", - "./examples/breaking/datacontract-quality-v3.yaml"]) - assert result.exit_code == 0 - return result.stdout - - def test_headline(self, output): - print(output) - assert "2 breaking changes: 0 error, 2 warning" in output +def test_quality_removed(): + result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-quality-v2.yaml", + "./examples/breaking/datacontract-quality-v1.yaml"]) + assert result.exit_code == 0 + assert "1 breaking changes: 0 error, 1 warning\n" in result.stdout - def test_type_updated(self, output): - assert r"""warning [quality_type_updated] at -./examples/breaking/datacontract-quality-v3.yaml - in quality.type - changed from `SodaCL` to `custom`""" in output - def test_specification_updated(self, output): - assert r"""warning [quality_specification_updated] at -./examples/breaking/datacontract-quality-v3.yaml - in quality.specification - changed from `checks for orders: - - freshness(column_1) < 1d` to `checks for orders: - - freshness(column_1) < 2d`""" in output +def test_quality_updated(): + result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-quality-v2.yaml", + "./examples/breaking/datacontract-quality-v3.yaml"]) + assert result.exit_code == 0 + assert "2 breaking changes: 0 error, 2 warning\n" in result.stdout class TestModelsAdded: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-models-v1.yaml", "./examples/breaking/datacontract-models-v2.yaml"]) - assert result.exit_code == 0 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout + def test_exit_code(self, result): + assert result.exit_code == 0 + def test_headline(self, output): - assert "0 breaking changes: 0 error, 0 warning" in output + assert "0 breaking changes: 0 error, 0 warning\n" in output - def test_model_added(self, output): + def test_infos_not_in_output(self, output): assert "model_added" not in output - - def test_description_added(self, output): assert "model_description_added" not in output class TestModelsRemoved: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-models-v2.yaml", "./examples/breaking/datacontract-models-v1.yaml"]) - assert result.exit_code == 1 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout - def test_headline(self, output): - assert "1 breaking changes: 1 error, 0 warning" in output + def test_exit_code(self, result): + assert result.exit_code == 1 - def test_model_removed(self, output): - assert r"""error [model_removed] at ./examples/breaking/datacontract-models-v1.yaml - in models.my_table_2 - removed the model""" in output + def test_headline(self, output): + assert "1 breaking changes: 1 error, 0 warning\n" in output - def test_description_removed(self, output): + def test_infos_not_in_output(self, output): assert "model_description_removed" not in output class TestModelsUpdated: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-models-v2.yaml", "./examples/breaking/datacontract-models-v3.yaml"]) - assert result.exit_code == 1 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout - def test_headline(self, output): - assert "1 breaking changes: 1 error, 0 warning" in output + def test_exit_code(self, result): + assert result.exit_code == 1 - def test_model_type_updated(self, output): - assert r"""error [model_type_updated] at ./examples/breaking/datacontract-models-v3.yaml - in models.my_table.type - changed from `table` to `object`""" in output + def test_headline(self, output): + assert "1 breaking changes: 1 error, 0 warning\n" in output - def test_model_description_updated(self, output): + def test_infos_not_in_output(self, output): assert "model_description_updated" not in output class TestFieldsAdded: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-fields-v1.yaml", "./examples/breaking/datacontract-fields-v2.yaml"]) - assert result.exit_code == 0 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout + def test_exit_code(self, result): + assert result.exit_code == 0 + def test_headline(self, output): - assert "14 breaking changes: 0 error, 14 warning" in output + assert "14 breaking changes: 0 error, 14 warning\n" in output - def test_field_added(self, output): + def test_infos_not_in_output(self, output): assert "field_added" not in output - - def test_references_added(self, output): - assert """warning [field_references_added] at -./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_references.references - added with value: `my_table.field_type`""" in output - - def test_type_added(self, output): - assert r"""warning [field_type_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_type.type - added with value: `string`""" in output - - def test_format_added(self, output): - assert r"""warning [field_format_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_format.format - added with value: `email`""" in output - - def test_description_added(self, output): assert "field_description_added" not in output - - def test_pii_added(self, output): - assert r"""warning [field_pii_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_pii.pii - added with value: `true`""" in output - - def test_classification_added(self, output): - assert r"""warning [field_classification_added] at -./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_classification.classification - added with value: `sensitive`""" in output - - def test_pattern_added(self, output): - assert r"""warning [field_pattern_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_pattern.pattern - added with value: `^[A-Za-z0-9]{8,14}$`""" in output - - def test_min_length_added(self, output): - assert r"""warning [field_min_length_added] at -./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_minLength.minLength - added with value: `8`""" in output - - def test_max_length_added(self, output): - assert r"""warning [field_max_length_added] at -./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_maxLength.maxLength - added with value: `14`""" in output - - def test_minimum_added(self, output): - assert r"""warning [field_minimum_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_minimum.minimum - added with value: `8`""" in output - - def test_minimum_exclusive_added(self, output): - assert r"""warning [field_exclusive_minimum_added] at -./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_exclusiveMinimum.exclusiveMinimum - added with value: `8`""" in output - - def test_maximum_added(self, output): - assert r"""warning [field_maximum_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_maximum.maximum - added with value: `14`""" in output - - def test_maximum_exclusive_added(self, output): - assert r"""warning [field_exclusive_maximum_added] at -./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_exclusiveMaximum.exclusiveMaximum - added with value: `14`""" in output - - def test_enum_added(self, output): - assert r"""warning [field_enum_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_enum.enum - added with value: `['one']`""" in output - - def test_tags_added(self, output): assert "field_tags_added" not in output - def test_ref_added(self, output): - assert r"""warning [field_ref_added] at ./examples/breaking/datacontract-fields-v2.yaml - in models.my_table.fields.field_ref.$ref - added with value: `#/definitions/my_definition`""" in output - class TestFieldsRemoved: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-fields-v2.yaml", "./examples/breaking/datacontract-fields-v1.yaml"]) - assert result.exit_code == 1 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout + def test_exit_code(self, result): + assert result.exit_code == 1 + def test_headline(self, output): - assert "15 breaking changes: 5 error, 10 warning" in output - - def test_type_removed(self, output): - assert r"""warning [field_type_removed] at ./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_type.type - removed field property""" in output - - def test_format_removed(self, output): - assert r"""warning [field_format_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_format.format - removed field property""" in output - - def test_references_removed(self, output): - assert """warning [field_references_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_references.references - removed field property""" in output - - def test_description_removed(self, output): - assert "field_description_removed" not in output + assert "15 breaking changes: 5 error, 10 warning\n" in output - def test_pii_removed(self, output): - assert r"""error [field_pii_removed] at ./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_pii.pii - removed field property""" in output - - def test_classification_removed(self, output): - assert r"""error [field_classification_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_classification.classification - removed field property""" in output - - def test_pattern_removed(self, output): - assert r"""error [field_pattern_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_pattern.pattern - removed field property""" in output - - def test_min_length_removed(self, output): - assert r"""warning [field_min_length_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_minLength.minLength - removed field property""" in output - - def test_max_length_removed(self, output): - assert r"""warning [field_max_length_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_maxLength.maxLength - removed field property""" in output - - def test_minimum_removed(self, output): - assert r"""warning [field_minimum_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_minimum.minimum - removed field property""" in output - - def test_minimum_exclusive_removed(self, output): - assert r"""warning [field_exclusive_minimum_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_exclusiveMinimum.exclusiveMinimum - removed field property""" in output - - def test_maximum_removed(self, output): - assert r"""warning [field_maximum_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_maximum.maximum - removed field property""" in output - - def test_maximum_exclusive_removed(self, output): - assert r"""warning [field_exclusive_maximum_removed] at -./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_exclusiveMaximum.exclusiveMaximum - removed field property""" in output - - def test_ref_removed(self, output): - assert r"""warning [field_ref_removed] at ./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_ref.$ref - removed field property""" in output - - def test_enum_removed(self, output): + def test_infos_not_in_output(self, output): + assert "field_description_removed" not in output assert "field_enum_removed" not in output - - def test_tags_removed(self, output): assert "field_tags_removed" not in output - def test_nested_field_removed(self, output): - assert r"""error [field_removed] at ./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.field_fields.fields.new_nested_field - removed the field""" in output - - def test_field_removed(self, output): - assert r"""error [field_removed] at ./examples/breaking/datacontract-fields-v1.yaml - in models.my_table.fields.new_field - removed the field""" in output - class TestFieldsUpdated: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-fields-v2.yaml", "./examples/breaking/datacontract-fields-v3.yaml"]) - assert result.exit_code == 1 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout + def test_exit_code(self, result): + assert result.exit_code == 1 + def test_headline(self, output): - assert "18 breaking changes: 15 error, 3 warning" in output - - def test_type_updated(self, output): - assert r"""error [field_type_updated] at ./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_type.type - changed from `string` to `integer`""" in output - - def test_format_updated(self, output): - assert """error [field_format_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_format.format - changed from `email` to `url`""" in output - - def test_required_updated(self, output): - assert """error [field_required_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_required.required - changed from `false` to `true`""" in output - - def test_primary_updated(self, output): - assert """warning [field_primary_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_primary.primary - changed from `false` to `true`""" in output - - def test_references_updated(self, output): - assert """warning [field_references_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_references.references - changed from `my_table.field_type` to `my_table.field_format`""" in output - - def test_unique_updated(self, output): - assert r"""error [field_unique_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_unique.unique - changed from `false` to `true`""" in output - - def test_description_updated(self, output): - assert "field_description_updated" not in output + assert "18 breaking changes: 15 error, 3 warning\n" in output - def test_pii_updated(self, output): - assert r"""error [field_pii_updated] at ./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_pii.pii - changed from `true` to `false`""" in output - - def test_classification_updated(self, output): - assert r"""error [field_classification_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_classification.classification - changed from `sensitive` to `restricted`""" in output - - def test_pattern_updated(self, output): - assert r"""error [field_pattern_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_pattern.pattern - changed from `^[A-Za-z0-9]{8,14}$` to `^[A-Za-z0-9]$`""" in output - - def test_min_length_updated(self, output): - assert r"""error [field_min_length_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_minLength.minLength - changed from `8` to `10`""" in output - - def test_max_length_updated(self, output): - assert r"""error [field_max_length_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_maxLength.maxLength - changed from `14` to `20`""" in output - - def test_minimum_updated(self, output): - assert r"""error [field_minimum_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_minimum.minimum - changed from `8` to `10`""" in output - - def test_minimum_exclusive_updated(self, output): - assert r"""error [field_exclusive_minimum_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_exclusiveMinimum.exclusiveMinimum - changed from `8` to `10`""" in output - - def test_maximum_updated(self, output): - assert r"""error [field_maximum_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_maximum.maximum - changed from `14` to `20`""" in output - - def test_maximum_exclusive_updated(self, output): - assert r"""error [field_exclusive_maximum_updated] at -./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_exclusiveMaximum.exclusiveMaximum - changed from `14` to `20`""" in output - - def test_enum_updated(self, output): - assert r"""error [field_enum_updated] at ./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_enum.enum - changed from `['one']` to `['one', 'two']`""" in output - - def test_ref_updated(self, output): - assert r"""warning [field_ref_updated] at ./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_ref.$ref - changed from `#/definitions/my_definition` to -`#/definitions/my_definition_2`""" in output - - def test_tags_updated(self, output): + def test_infos_not_in_output(self, output): + assert "field_description_updated" not in output assert "field_tags_updated" not in output - def test_nested_type_updated(self, output): - assert r"""error [field_type_updated] at ./examples/breaking/datacontract-fields-v3.yaml - in models.my_table.fields.field_fields.fields.nested_field_1.type - changed from `string` to `integer`""" in output - class TestDefinitionAdded: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-definitions-v1.yaml", "./examples/breaking/datacontract-definitions-v2.yaml"]) - assert result.exit_code == 0 - return result.stdout + return result - def test_headline(self, output): - assert "13 breaking changes: 0 error, 13 warning" in output + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout - def test_ref_added(self, output): - assert r"""warning [field_ref_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.$ref - added with value: `#/definitions/my_definition`""" in output + def test_exit_code(self, result): + assert result.exit_code == 0 - def test_type_added(self, output): - assert r"""warning [field_type_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.type - added with value: `string`""" in output + def test_headline(self, output): + assert "13 breaking changes: 0 error, 13 warning\n" in output - def test_description_added(self, output): + def test_infos_not_in_output(self, output): assert "field_description_added" not in output - - def test_format_added(self, output): - assert r"""warning [field_format_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.format - added with value: `uuid`""" in output - - def test_pii_added(self, output): - assert r"""warning [field_pii_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.pii - added with value: `false`""" in output - - def test_classification_added(self, output): - assert r"""warning [field_classification_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.classification - added with value: `internal`""" in output - - def test_pattern_added(self, output): - assert r"""warning [field_pattern_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.pattern - added with value: `.*`""" in output - - def test_min_length_added(self, output): - assert r"""warning [field_min_length_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.minLength - added with value: `8`""" in output - - def test_max_length_added(self, output): - assert r"""warning [field_max_length_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.maxLength - added with value: `14`""" in output - - def test_minimum_added(self, output): - assert r"""warning [field_minimum_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.minimum - added with value: `8`""" in output - - def test_minimum_exclusive_added(self, output): - assert r"""warning [field_exclusive_minimum_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.exclusiveMinimum - added with value: `14`""" in output - - def test_maximum_added(self, output): - assert r"""warning [field_maximum_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.maximum - added with value: `14`""" in output - - def test_maximum_exclusive_added(self, output): - assert r"""warning [field_exclusive_maximum_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.exclusiveMaximum - added with value: `8`""" in output - - def test_tags_added(self, output): assert "field_tags_added" not in output - def test_enum_added(self, output): - assert r"""warning [field_enum_added] at -./examples/breaking/datacontract-definitions-v2.yaml - in models.my_table.fields.my_field.enum - added with value: `['my_enum']`""" in output - class TestDefinitionRemoved: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-definitions-v2.yaml", "./examples/breaking/datacontract-definitions-v1.yaml"]) - assert result.exit_code == 1 - return result.stdout + return result - def test_headline(self, output): - assert "12 breaking changes: 3 error, 9 warning" in output + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout - def test_ref_removed(self, output): - assert r"""warning [field_ref_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.$ref - removed field property""" in output + def test_exit_code(self, result): + assert result.exit_code == 1 - def test_type_removed(self, output): - assert r"""warning [field_type_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.type - removed field property""" in output + def test_headline(self, output): + assert "12 breaking changes: 3 error, 9 warning\n" in output - def test_description_removed(self, output): + def test_infos_not_in_output(self, output): assert "field_description_removed" not in output - - def test_format_removed(self, output): - assert r"""warning [field_format_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.format - removed field property""" in output - - def test_pii_removed(self, output): - assert r"""error [field_pii_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.pii - removed field property""" in output - - def test_classification_removed(self, output): - assert r"""error [field_classification_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.classification - removed field property""" in output - - def test_pattern_removed(self, output): - assert r"""error [field_pattern_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.pattern - removed field property""" in output - - def test_min_length_removed(self, output): - assert r"""warning [field_min_length_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.minLength - removed field property""" in output - - def test_max_length_removed(self, output): - assert r"""warning [field_max_length_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.maxLength - removed field property""" in output - - def test_minimum_removed(self, output): - assert r"""warning [field_minimum_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.minimum - removed field property""" in output - - def test_minimum_exclusive_removed(self, output): - assert r"""warning [field_exclusive_minimum_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.exclusiveMinimum - removed field property""" in output - - def test_maximum_removed(self, output): - assert r"""warning [field_maximum_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.maximum - removed field property""" in output - - def test_maximum_exclusive_removed(self, output): - assert r"""warning [field_exclusive_maximum_removed] at -./examples/breaking/datacontract-definitions-v1.yaml - in models.my_table.fields.my_field.exclusiveMaximum - removed field property""" in output - - def test_tags_removed(self, output): assert "field_tags_removed" not in output - - def test_enum_removed(self, output): assert "field_enum_removed" not in output class TestDefinitionUpdated: @pytest.fixture(scope='class') - def output(self): + def result(self): result = runner.invoke(app, ["breaking", "./examples/breaking/datacontract-definitions-v2.yaml", "./examples/breaking/datacontract-definitions-v3.yaml"]) - assert result.exit_code == 1 + return result + + @pytest.fixture(scope='class') + def output(self, result): return result.stdout + def test_exit_code(self, result): + assert result.exit_code == 1 + def test_headline(self, output): - assert "13 breaking changes: 12 error, 1 warning" in output - - def test_ref_updated(self, output): - assert r"""warning [field_ref_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.$ref - changed from `#/definitions/my_definition` to -`#/definitions/my_definition_2`""" in output - - def test_type_updated(self, output): - assert r"""error [field_type_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.type - changed from `string` to `integer`""" in output - - def test_description_updated(self, output): - assert "field_description_updated" not in output + assert "13 breaking changes: 12 error, 1 warning\n" in output - def test_format_updated(self, output): - assert r"""error [field_format_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.format - changed from `uuid` to `url`""" in output - - def test_pii_updated(self, output): - assert r"""error [field_pii_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.pii - changed from `false` to `true`""" in output - - def test_classification_updated(self, output): - assert r"""error [field_classification_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.classification - changed from `internal` to `sensitive`""" in output - - def test_pattern_updated(self, output): - assert r"""error [field_pattern_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.pattern - changed from `.*` to `.*.*`""" in output - - def test_min_length_updated(self, output): - assert r"""error [field_min_length_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.minLength - changed from `8` to `10`""" in output - - def test_max_length_updated(self, output): - assert r"""error [field_max_length_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.maxLength - changed from `14` to `20`""" in output - - def test_minimum_updated(self, output): - assert r"""error [field_minimum_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.minimum - changed from `8` to `10`""" in output - - def test_minimum_exclusive_updated(self, output): - assert r"""error [field_exclusive_minimum_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.exclusiveMinimum - changed from `14` to `10`""" in output - - def test_maximum_updated(self, output): - assert r"""error [field_maximum_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.maximum - changed from `14` to `20`""" in output - - def test_maximum_exclusive_updated(self, output): - assert r"""error [field_exclusive_maximum_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.exclusiveMaximum - changed from `8` to `20`""" in output - - def test_tags_updated(self, output): + def test_infos_not_in_output(self, output): + assert "field_description_updated" not in output assert "field_tags_updated" not in output - - def test_enum_updated(self, output): - assert """error [field_enum_updated] at -./examples/breaking/datacontract-definitions-v3.yaml - in models.my_table.fields.my_field.enum - changed from `['my_enum']` to `['my_enum_2']`""" in output diff --git a/tests/test_changelog.py b/tests/test_changelog.py new file mode 100644 index 00000000..4d15870a --- /dev/null +++ b/tests/test_changelog.py @@ -0,0 +1,885 @@ +import logging + +import pytest +from typer.testing import CliRunner + +from datacontract.cli import app + +runner = CliRunner() + +logging.basicConfig(level=logging.DEBUG, force=True) + + +def test_no_changes(): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-fields-v2.yaml", + "./examples/breaking/datacontract-fields-v2.yaml"]) + assert result.exit_code == 0 + assert "0 changes: 0 error, 0 warning, 0 info\n" in result.stdout + + +class TestQualityAdded: + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-quality-v1.yaml", + "./examples/breaking/datacontract-quality-v2.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "1 changes: 0 error, 0 warning, 1 info\n" in output + + def test_quality_added(self, output): + assert """info [quality_added] at ./examples/breaking/datacontract-quality-v2.yaml + in quality + added quality""" in output + + +class TestQualityRemoved: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-quality-v2.yaml", + "./examples/breaking/datacontract-quality-v1.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "1 changes: 0 error, 1 warning, 0 info\n" in output + + def test_quality_removed(self, output): + assert r"""warning [quality_removed] at ./examples/breaking/datacontract-quality-v1.yaml + in quality + removed quality""" in output + + +class TestQualityUpdated: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-quality-v2.yaml", + "./examples/breaking/datacontract-quality-v3.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "2 changes: 0 error, 2 warning, 0 info\n" in output + + def test_type_updated(self, output): + assert r"""warning [quality_type_updated] at +./examples/breaking/datacontract-quality-v3.yaml + in quality.type + changed from `SodaCL` to `custom`""" in output + + def test_specification_updated(self, output): + assert r"""warning [quality_specification_updated] at +./examples/breaking/datacontract-quality-v3.yaml + in quality.specification + changed from `checks for orders: + - freshness(column_1) < 1d` to `checks for orders: + - freshness(column_1) < 2d`""" in output + + +class TestModelsAdded: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-models-v1.yaml", + "./examples/breaking/datacontract-models-v2.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "2 changes: 0 error, 0 warning, 2 info\n" in output + + def test_model_added(self, output): + assert """info [model_added] at ./examples/breaking/datacontract-models-v2.yaml + in models.my_table_2 + added the model""" in output + + def test_description_added(self, output): + assert """info [model_description_added] at +./examples/breaking/datacontract-models-v2.yaml + in models.my_table.description + added with value: `My Model Description`""" in output + + +class TestModelsRemoved: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-models-v2.yaml", + "./examples/breaking/datacontract-models-v1.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "2 changes: 1 error, 0 warning, 1 info\n" in output + + def test_model_removed(self, output): + assert r"""error [model_removed] at ./examples/breaking/datacontract-models-v1.yaml + in models.my_table_2 + removed the model""" in output + + def test_description_removed(self, output): + assert """info [model_description_removed] at +./examples/breaking/datacontract-models-v1.yaml + in models.my_table.description + removed model property""" in output + + +class TestModelsUpdated: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-models-v2.yaml", + "./examples/breaking/datacontract-models-v3.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "2 changes: 1 error, 0 warning, 1 info\n" in output + + def test_model_type_updated(self, output): + assert r"""error [model_type_updated] at ./examples/breaking/datacontract-models-v3.yaml + in models.my_table.type + changed from `table` to `object`""" in output + + def test_model_description_updated(self, output): + assert """info [model_description_updated] at +./examples/breaking/datacontract-models-v3.yaml + in models.my_table.description + changed from `My Model Description` to `My Updated Model +Description`""" in output + + +class TestFieldsAdded: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-fields-v1.yaml", + "./examples/breaking/datacontract-fields-v2.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "18 changes: 0 error, 14 warning, 4 info\n" in output + + def test_field_added(self, output): + assert """info [field_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.new_field + added the field""" in output + + def test_references_added(self, output): + assert """warning [field_references_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_references.references + added with value: `my_table.field_type`""" in output + + def test_type_added(self, output): + assert r"""warning [field_type_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_type.type + added with value: `string`""" in output + + def test_format_added(self, output): + assert r"""warning [field_format_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_format.format + added with value: `email`""" in output + + def test_description_added(self, output): + assert """info [field_description_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_description.description + added with value: `My Description`""" in output + + def test_pii_added(self, output): + assert r"""warning [field_pii_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_pii.pii + added with value: `true`""" in output + + def test_classification_added(self, output): + assert r"""warning [field_classification_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_classification.classification + added with value: `sensitive`""" in output + + def test_pattern_added(self, output): + assert r"""warning [field_pattern_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_pattern.pattern + added with value: `^[A-Za-z0-9]{8,14}$`""" in output + + def test_min_length_added(self, output): + assert r"""warning [field_min_length_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_minLength.minLength + added with value: `8`""" in output + + def test_max_length_added(self, output): + assert r"""warning [field_max_length_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_maxLength.maxLength + added with value: `14`""" in output + + def test_minimum_added(self, output): + assert r"""warning [field_minimum_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_minimum.minimum + added with value: `8`""" in output + + def test_minimum_exclusive_added(self, output): + assert r"""warning [field_exclusive_minimum_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_exclusiveMinimum.exclusiveMinimum + added with value: `8`""" in output + + def test_maximum_added(self, output): + assert r"""warning [field_maximum_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_maximum.maximum + added with value: `14`""" in output + + def test_maximum_exclusive_added(self, output): + assert r"""warning [field_exclusive_maximum_added] at +./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_exclusiveMaximum.exclusiveMaximum + added with value: `14`""" in output + + def test_enum_added(self, output): + assert r"""warning [field_enum_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_enum.enum + added with value: `['one']`""" in output + + def test_tags_added(self, output): + assert """info [field_tags_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_tags.tags + added with value: `['one']`""" in output + + def test_ref_added(self, output): + assert r"""warning [field_ref_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_ref.$ref + added with value: `#/definitions/my_definition`""" in output + + def test_nested_field_added(self, output): + assert """info [field_added] at ./examples/breaking/datacontract-fields-v2.yaml + in models.my_table.fields.field_fields.fields.new_nested_field + added the field""" in output + + +class TestFieldsRemoved: + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-fields-v2.yaml", + "./examples/breaking/datacontract-fields-v1.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "18 changes: 5 error, 10 warning, 3 info\n" in output + + def test_type_removed(self, output): + assert r"""warning [field_type_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_type.type + removed field property""" in output + + def test_format_removed(self, output): + assert r"""warning [field_format_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_format.format + removed field property""" in output + + def test_references_removed(self, output): + assert """warning [field_references_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_references.references + removed field property""" in output + + def test_description_removed(self, output): + assert """info [field_description_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_description.description + removed field property""" in output + + def test_pii_removed(self, output): + assert r"""error [field_pii_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_pii.pii + removed field property""" in output + + def test_classification_removed(self, output): + assert r"""error [field_classification_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_classification.classification + removed field property""" in output + + def test_pattern_removed(self, output): + assert r"""error [field_pattern_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_pattern.pattern + removed field property""" in output + + def test_min_length_removed(self, output): + assert r"""warning [field_min_length_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_minLength.minLength + removed field property""" in output + + def test_max_length_removed(self, output): + assert r"""warning [field_max_length_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_maxLength.maxLength + removed field property""" in output + + def test_minimum_removed(self, output): + assert r"""warning [field_minimum_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_minimum.minimum + removed field property""" in output + + def test_minimum_exclusive_removed(self, output): + assert r"""warning [field_exclusive_minimum_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_exclusiveMinimum.exclusiveMinimum + removed field property""" in output + + def test_maximum_removed(self, output): + assert r"""warning [field_maximum_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_maximum.maximum + removed field property""" in output + + def test_maximum_exclusive_removed(self, output): + assert r"""warning [field_exclusive_maximum_removed] at +./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_exclusiveMaximum.exclusiveMaximum + removed field property""" in output + + def test_ref_removed(self, output): + assert r"""warning [field_ref_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_ref.$ref + removed field property""" in output + + def test_enum_removed(self, output): + assert """info [field_enum_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_enum.enum + removed field property""" in output + + def test_tags_removed(self, output): + assert """info [field_tags_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_tags.tags + removed field property""" in output + + def test_nested_field_removed(self, output): + assert r"""error [field_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.field_fields.fields.new_nested_field + removed the field""" in output + + def test_field_removed(self, output): + assert r"""error [field_removed] at ./examples/breaking/datacontract-fields-v1.yaml + in models.my_table.fields.new_field + removed the field""" in output + + +class TestFieldsUpdated: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-fields-v2.yaml", + "./examples/breaking/datacontract-fields-v3.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "20 changes: 15 error, 3 warning, 2 info\n" in output + + def test_type_updated(self, output): + assert r"""error [field_type_updated] at ./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_type.type + changed from `string` to `integer`""" in output + + def test_format_updated(self, output): + assert """error [field_format_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_format.format + changed from `email` to `url`""" in output + + def test_required_updated(self, output): + assert """error [field_required_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_required.required + changed from `false` to `true`""" in output + + def test_primary_updated(self, output): + assert """warning [field_primary_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_primary.primary + changed from `false` to `true`""" in output + + def test_references_updated(self, output): + assert """warning [field_references_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_references.references + changed from `my_table.field_type` to `my_table.field_format`""" in output + + def test_unique_updated(self, output): + assert r"""error [field_unique_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_unique.unique + changed from `false` to `true`""" in output + + def test_description_updated(self, output): + assert """info [field_description_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_description.description + changed from `My Description` to `My updated Description`""" in output + + def test_pii_updated(self, output): + assert r"""error [field_pii_updated] at ./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_pii.pii + changed from `true` to `false`""" in output + + def test_classification_updated(self, output): + assert r"""error [field_classification_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_classification.classification + changed from `sensitive` to `restricted`""" in output + + def test_pattern_updated(self, output): + assert r"""error [field_pattern_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_pattern.pattern + changed from `^[A-Za-z0-9]{8,14}$` to `^[A-Za-z0-9]$`""" in output + + def test_min_length_updated(self, output): + assert r"""error [field_min_length_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_minLength.minLength + changed from `8` to `10`""" in output + + def test_max_length_updated(self, output): + assert r"""error [field_max_length_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_maxLength.maxLength + changed from `14` to `20`""" in output + + def test_minimum_updated(self, output): + assert r"""error [field_minimum_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_minimum.minimum + changed from `8` to `10`""" in output + + def test_minimum_exclusive_updated(self, output): + assert r"""error [field_exclusive_minimum_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_exclusiveMinimum.exclusiveMinimum + changed from `8` to `10`""" in output + + def test_maximum_updated(self, output): + assert r"""error [field_maximum_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_maximum.maximum + changed from `14` to `20`""" in output + + def test_maximum_exclusive_updated(self, output): + assert r"""error [field_exclusive_maximum_updated] at +./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_exclusiveMaximum.exclusiveMaximum + changed from `14` to `20`""" in output + + def test_enum_updated(self, output): + assert r"""error [field_enum_updated] at ./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_enum.enum + changed from `['one']` to `['one', 'two']`""" in output + + def test_ref_updated(self, output): + assert r"""warning [field_ref_updated] at ./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_ref.$ref + changed from `#/definitions/my_definition` to +`#/definitions/my_definition_2`""" in output + + def test_tags_updated(self, output): + assert """info [field_tags_updated] at ./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_tags.tags + changed from `['one']` to `['one', 'two']`""" in output + + def test_nested_type_updated(self, output): + assert r"""error [field_type_updated] at ./examples/breaking/datacontract-fields-v3.yaml + in models.my_table.fields.field_fields.fields.nested_field_1.type + changed from `string` to `integer`""" in output + + +class TestDefinitionAdded: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-definitions-v1.yaml", + "./examples/breaking/datacontract-definitions-v2.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "15 changes: 0 error, 13 warning, 2 info\n" in output + + def test_ref_added(self, output): + assert r"""warning [field_ref_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.$ref + added with value: `#/definitions/my_definition`""" in output + + def test_type_added(self, output): + assert r"""warning [field_type_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.type + added with value: `string`""" in output + + def test_description_added(self, output): + assert """info [field_description_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.description + added with value: `My Description`""" in output + + def test_format_added(self, output): + assert r"""warning [field_format_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.format + added with value: `uuid`""" in output + + def test_pii_added(self, output): + assert r"""warning [field_pii_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.pii + added with value: `false`""" in output + + def test_classification_added(self, output): + assert r"""warning [field_classification_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.classification + added with value: `internal`""" in output + + def test_pattern_added(self, output): + assert r"""warning [field_pattern_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.pattern + added with value: `.*`""" in output + + def test_min_length_added(self, output): + assert r"""warning [field_min_length_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.minLength + added with value: `8`""" in output + + def test_max_length_added(self, output): + assert r"""warning [field_max_length_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.maxLength + added with value: `14`""" in output + + def test_minimum_added(self, output): + assert r"""warning [field_minimum_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.minimum + added with value: `8`""" in output + + def test_minimum_exclusive_added(self, output): + assert r"""warning [field_exclusive_minimum_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.exclusiveMinimum + added with value: `14`""" in output + + def test_maximum_added(self, output): + assert r"""warning [field_maximum_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.maximum + added with value: `14`""" in output + + def test_maximum_exclusive_added(self, output): + assert r"""warning [field_exclusive_maximum_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.exclusiveMaximum + added with value: `8`""" in output + + def test_tags_added(self, output): + assert """info [field_tags_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.tags + added with value: `['my_tags']`""" in output + + def test_enum_added(self, output): + assert r"""warning [field_enum_added] at +./examples/breaking/datacontract-definitions-v2.yaml + in models.my_table.fields.my_field.enum + added with value: `['my_enum']`""" in output + + +class TestDefinitionRemoved: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-definitions-v2.yaml", + "./examples/breaking/datacontract-definitions-v1.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "15 changes: 3 error, 9 warning, 3 info\n" in output + + def test_ref_removed(self, output): + assert r"""warning [field_ref_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.$ref + removed field property""" in output + + def test_type_removed(self, output): + assert r"""warning [field_type_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.type + removed field property""" in output + + def test_description_removed(self, output): + assert """info [field_description_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.description + removed field property""" in output + + def test_format_removed(self, output): + assert r"""warning [field_format_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.format + removed field property""" in output + + def test_pii_removed(self, output): + assert r"""error [field_pii_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.pii + removed field property""" in output + + def test_classification_removed(self, output): + assert r"""error [field_classification_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.classification + removed field property""" in output + + def test_pattern_removed(self, output): + assert r"""error [field_pattern_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.pattern + removed field property""" in output + + def test_min_length_removed(self, output): + assert r"""warning [field_min_length_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.minLength + removed field property""" in output + + def test_max_length_removed(self, output): + assert r"""warning [field_max_length_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.maxLength + removed field property""" in output + + def test_minimum_removed(self, output): + assert r"""warning [field_minimum_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.minimum + removed field property""" in output + + def test_minimum_exclusive_removed(self, output): + assert r"""warning [field_exclusive_minimum_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.exclusiveMinimum + removed field property""" in output + + def test_maximum_removed(self, output): + assert r"""warning [field_maximum_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.maximum + removed field property""" in output + + def test_maximum_exclusive_removed(self, output): + assert r"""warning [field_exclusive_maximum_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.exclusiveMaximum + removed field property""" in output + + def test_tags_removed(self, output): + assert """info [field_tags_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.tags + removed field property""" in output + + def test_enum_removed(self, output): + assert """info [field_enum_removed] at +./examples/breaking/datacontract-definitions-v1.yaml + in models.my_table.fields.my_field.enum + removed field property""" in output + + +class TestDefinitionUpdated: + + @pytest.fixture(scope='class') + def result(self): + result = runner.invoke(app, ["changelog", "./examples/breaking/datacontract-definitions-v2.yaml", + "./examples/breaking/datacontract-definitions-v3.yaml"]) + return result + + @pytest.fixture(scope='class') + def output(self, result): + return result.stdout + + def test_exit_code(self, result): + assert result.exit_code == 0 + + def test_headline(self, output): + assert "15 changes: 12 error, 1 warning, 2 info\n" in output + + def test_ref_updated(self, output): + assert r"""warning [field_ref_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.$ref + changed from `#/definitions/my_definition` to +`#/definitions/my_definition_2`""" in output + + def test_type_updated(self, output): + assert r"""error [field_type_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.type + changed from `string` to `integer`""" in output + + def test_description_updated(self, output): + assert """info [field_description_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.description + changed from `My Description` to `My Description 2`""" in output + + def test_format_updated(self, output): + assert r"""error [field_format_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.format + changed from `uuid` to `url`""" in output + + def test_pii_updated(self, output): + assert r"""error [field_pii_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.pii + changed from `false` to `true`""" in output + + def test_classification_updated(self, output): + assert r"""error [field_classification_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.classification + changed from `internal` to `sensitive`""" in output + + def test_pattern_updated(self, output): + assert r"""error [field_pattern_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.pattern + changed from `.*` to `.*.*`""" in output + + def test_min_length_updated(self, output): + assert r"""error [field_min_length_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.minLength + changed from `8` to `10`""" in output + + def test_max_length_updated(self, output): + assert r"""error [field_max_length_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.maxLength + changed from `14` to `20`""" in output + + def test_minimum_updated(self, output): + assert r"""error [field_minimum_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.minimum + changed from `8` to `10`""" in output + + def test_minimum_exclusive_updated(self, output): + assert r"""error [field_exclusive_minimum_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.exclusiveMinimum + changed from `14` to `10`""" in output + + def test_maximum_updated(self, output): + assert r"""error [field_maximum_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.maximum + changed from `14` to `20`""" in output + + def test_maximum_exclusive_updated(self, output): + assert r"""error [field_exclusive_maximum_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.exclusiveMaximum + changed from `8` to `20`""" in output + + def test_tags_updated(self, output): + assert """info [field_tags_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.tags + changed from `['my_tags']` to `['my_tags_2']`""" in output + + def test_enum_updated(self, output): + assert """error [field_enum_updated] at +./examples/breaking/datacontract-definitions-v3.yaml + in models.my_table.fields.my_field.enum + changed from `['my_enum']` to `['my_enum_2']`""" in output