diff --git a/nautobot_design_builder/design_job.py b/nautobot_design_builder/design_job.py index 00840ed..2fa4724 100644 --- a/nautobot_design_builder/design_job.py +++ b/nautobot_design_builder/design_job.py @@ -269,6 +269,8 @@ def _run_in_transaction(self, dryrun: bool, **data): # pylint: disable=too-many This version of `run` is wrapped in a transaction and will roll back database changes on error. In general, this method should only be called by the `run` method. """ + sid = transaction.savepoint() + self.log_info(message=f"Building {getattr(self.Meta, 'name')}") extensions = getattr(self.Meta, "extensions", []) @@ -303,8 +305,6 @@ def _run_in_transaction(self, dryrun: bool, **data): # pylint: disable=too-many self.log_failure(message="No design template specified for design.") raise DesignImplementationError("No design template specified for design.") - sid = transaction.savepoint() - try: for design_file in design_files: self.implement_design(context, design_file, not dryrun) diff --git a/nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 b/nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 new file mode 100644 index 0000000..04f4c5e --- /dev/null +++ b/nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 @@ -0,0 +1,4 @@ +--- +manufacturers: + name: "Test Manufacturer" + wrong: "attribute" diff --git a/nautobot_design_builder/tests/designs/test_designs.py b/nautobot_design_builder/tests/designs/test_designs.py index 20db601..6936218 100644 --- a/nautobot_design_builder/tests/designs/test_designs.py +++ b/nautobot_design_builder/tests/designs/test_designs.py @@ -67,9 +67,20 @@ class Meta: # pylint: disable=too-few-public-methods ] -class MultiDesignJobWithError(DesignJob): +class DesignJobModeDeploymentWithError(DesignJob): """Design job that includes an error (for unit testing).""" + class Meta: # pylint: disable=too-few-public-methods + name = "File Design with Error" + design_files = [ + "templates/simple_design_with_error.yaml.j2", + ] + design_mode = DesignModeChoices.DEPLOYMENT + + +class MultiDesignJobWithError(DesignJob): + """Multi Design job that includes an error (for unit testing).""" + class Meta: # pylint: disable=too-few-public-methods name = "Multi File Design with Error" design_files = [ diff --git a/nautobot_design_builder/tests/test_design_job.py b/nautobot_design_builder/tests/test_design_job.py index f1bd8a0..59604c3 100644 --- a/nautobot_design_builder/tests/test_design_job.py +++ b/nautobot_design_builder/tests/test_design_job.py @@ -10,6 +10,7 @@ from nautobot.ipam.models import VRF, Prefix, IPAddress from nautobot.extras.models import Status +from nautobot_design_builder.models import Deployment from nautobot_design_builder.errors import DesignImplementationError, DesignValidationError from nautobot_design_builder.tests import DesignTestCase from nautobot_design_builder.tests.designs import test_designs @@ -51,6 +52,14 @@ def test_simple_design_with_post_implementation(self): job.run(dryrun=False, **self.data) self.assertTrue(getattr(job, "post_implementation_called")) + def test_simple_design_rollback_deployment_mode(self): + """Confirm that database changes are rolled back when an exception is raised and no Design Deployment is created.""" + self.assertEqual(0, Manufacturer.objects.all().count()) + job = self.get_mocked_job(test_designs.DesignJobModeDeploymentWithError) + job.run(data={**self.data, **{"deployment_name": "whatever"}}, commit=True) + self.assertEqual(0, Manufacturer.objects.all().count()) + self.assertEqual(0, Deployment.objects.all().count()) + def test_simple_design_report(self): job = self.get_mocked_job(test_designs.SimpleDesignReport) job.run(data={}, dryrun=False)