diff --git a/ponos/recipe.py b/ponos/recipe.py index 42ef5b282bbeab15fa22335736b5781269cf791e..0daeff53911fac83a76af64671190e23d69d87ff 100644 --- a/ponos/recipe.py +++ b/ponos/recipe.py @@ -1,5 +1,6 @@ import re from collections import namedtuple +from uuid import UUID import yaml from django.core.validators import URLValidator @@ -56,6 +57,10 @@ def validate_task(task, top_env): if "artifact" in task: assert isinstance(task["artifact"], str), "Task artifact should be a string" + try: + UUID(task["artifact"]) + except (TypeError, ValueError): + raise AssertionError("Task artifact should be a valid UUID string") if "requires_gpu" in task: assert isinstance( diff --git a/tests/server/test_recipe.py b/tests/server/test_recipe.py new file mode 100644 index 0000000000000000000000000000000000000000..18ef2dbe9aaf879659cb2e4e71164b9248d30a45 --- /dev/null +++ b/tests/server/test_recipe.py @@ -0,0 +1,42 @@ +from textwrap import dedent + +from django.test import TestCase + +from ponos.recipe import parse_recipe + +# List of (broken recipe, expected AssertionError message) tuples +ERROR_CASES = [ + ("[]", "Recipe should be a dict"), + ("tasks: {}", "No tasks"), + ("tasks: [{image: lol}]", "Tasks should be a dict"), + ("tasks: {a: {}, '': {}}", "Tasks should have non-blank slugs"), + ("tasks: {a: []}", "Task should be a dict"), + ("tasks: {a: {}}", "Missing image"), + ( + """ + tasks: + lol: + image: blah + artifact: 42 + """, + "Task artifact should be a string", + ), + ( + """ + tasks: + lol: + image: blah + artifact: philosophers_stone + """, + "Task artifact should be a valid UUID string", + ), +] + + +class TestRecipe(TestCase): + def test_recipe_errors(self): + for recipe, expected_message in ERROR_CASES: + with self.subTest(recipe=recipe), self.assertRaisesMessage( + AssertionError, expected_message + ): + parse_recipe(dedent(recipe))