diff --git a/.gitignore b/.gitignore
index 2a3d21069b850111b6335939676f5ff0b42575e9..414077bebf8027acf1dc708749f21dc6f3c91ab9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,8 @@ doc/rpc.html
 
 # Ignore .python-version file from Pyenv
 .python-version
+
+# Ignore coverage report
+coverage.xml
+# Ignore pytest report
+report.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 23ed18f31af57bc784c3b3b9412483ee0192f0b9..af722d9993d40dfa9163e666403e0bec78b5c0f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -51,11 +51,12 @@ test:
     # Run sharness.
     - make -C test sh
     # Run pytest.
-    - pytest
+    - pytest --junitxml="pytest-report.xml"
     - make -C test coverage # Produce coverage reports.
-  coverage: '/TOTAL.*\s+(\d+\%)/'
+  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
   artifacts:
     reports:
+      junit: pytest-report.xml
       coverage_report:
         coverage_format: cobertura
         path: coverage.xml
diff --git a/docker/scripts/run-pytests.sh b/docker/scripts/run-pytests.sh
index d8c093d5a103f33c00abc769604256ddfb0845c0..1df432f8ec4d20dd353df8ecd47c28dccd01e8ba 100755
--- a/docker/scripts/run-pytests.sh
+++ b/docker/scripts/run-pytests.sh
@@ -25,7 +25,7 @@ rm -rf $PROMETHEUS_MULTIPROC_DIR
 mkdir -p $PROMETHEUS_MULTIPROC_DIR
 
 # Run pytest with optional targets in front of it.
-pytest
+pytest --junitxml="/data/pytest-report.xml"
 
 # By default, report coverage and move it into cache.
 if [ $COVERAGE -eq 1 ]; then
diff --git a/test/test_git_update.py b/test/test_git_update.py
index 7f1ca6ace8e3055e300ba08a57825adee1d60836..55db70b0db381f7fedb0639645363f2da7ce8469 100644
--- a/test/test_git_update.py
+++ b/test/test_git_update.py
@@ -1,8 +1,9 @@
 import json
 
+import pytest
 from srcinfo import parse
 
-from aurweb.git.update import extract_arch_fields
+from aurweb.git.update import extract_arch_fields, parse_dep, size_humanize
 
 SRCINFO = """
 pkgbase = ponies
@@ -131,3 +132,72 @@ def test_git_update_extract_arch_fields():
     assert sources[0]["value"] == "ponies.service"
 
     # add more...
+
+
+@pytest.mark.parametrize(
+    "size, expected",
+    [
+        (1024, "1024B"),
+        (1024.5, "1024.50B"),
+        (256000, "250.00KiB"),
+        (25600000, "24.41MiB"),
+        (2560000000, "2.38GiB"),
+        (2560000000000, "2.33TiB"),
+        (2560000000000000, "2.27PiB"),
+        (2560000000000000000, "2.22EiB"),
+        (2560000000000000000000, "2.17ZiB"),
+        (2560000000000000000000000, "2.12YiB"),
+    ],
+)
+def test_size_humanize(size: any, expected: str):
+    assert size_humanize(size) == expected
+
+
+@pytest.mark.parametrize(
+    "depstring, exp_depname, exp_depdesc, exp_depcond",
+    [
+        (
+            "my-little-pony: optional kids support",
+            "my-little-pony",
+            "optional kids support",
+            "",
+        ),
+        (
+            "my-little-pony>7",
+            "my-little-pony",
+            "",
+            ">7",
+        ),
+        (
+            "my-little-pony=7",
+            "my-little-pony",
+            "",
+            "=7",
+        ),
+        (
+            "my-little-pony<7",
+            "my-little-pony",
+            "",
+            "<7",
+        ),
+        (
+            "my-little-pony=<7",
+            "my-little-pony",
+            "",
+            "=<7",
+        ),
+        (
+            "my-little-pony>=7.1: optional kids support",
+            "my-little-pony",
+            "optional kids support",
+            ">=7.1",
+        ),
+    ],
+)
+def test_parse_dep(
+    depstring: str, exp_depname: str, exp_depdesc: str, exp_depcond: str
+):
+    depname, depdesc, depcond = parse_dep(depstring)
+    assert depname == exp_depname
+    assert depdesc == exp_depdesc
+    assert depcond == exp_depcond