From 99160dd0a077270770d922a7b77fcc5486b0d4da Mon Sep 17 00:00:00 2001
From: Erwan Rouchet <rouchet@teklia.com>
Date: Tue, 10 Jan 2023 10:14:13 +0100
Subject: [PATCH] Check that artifact IDs are UUIDs in recipes

---
 ponos/recipe.py             |  5 +++++
 tests/server/test_recipe.py | 42 +++++++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)
 create mode 100644 tests/server/test_recipe.py

diff --git a/ponos/recipe.py b/ponos/recipe.py
index 42ef5b282b..0daeff5391 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 0000000000..18ef2dbe9a
--- /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))
-- 
GitLab