diff --git a/tests/pylorax/blueprints/atlas.toml b/tests/pylorax/blueprints/atlas.toml
new file mode 100644
index 00000000..1cbb0c24
--- /dev/null
+++ b/tests/pylorax/blueprints/atlas.toml
@@ -0,0 +1,10 @@
+name = "atlas"
+description = "Automatically Tuned Linear Algebra Software"
+
+[[modules]]
+name = "atlas"
+version = "3.10.*"
+
+[[modules]]
+name = "numpy"
+version = "1.7.*"
diff --git a/tests/pylorax/blueprints/development.toml b/tests/pylorax/blueprints/development.toml
new file mode 100644
index 00000000..8eece1df
--- /dev/null
+++ b/tests/pylorax/blueprints/development.toml
@@ -0,0 +1,82 @@
+name = "development"
+description = "A general purpose development image"
+
+[[packages]]
+name = "cmake"
+version = "*"
+
+[[packages]]
+name = "curl"
+version = "*"
+
+[[packages]]
+name = "file"
+version = "*"
+
+[[packages]]
+name = "gcc"
+version = "*"
+
+[[packages]]
+name = "gcc-c++"
+version = "*"
+
+[[packages]]
+name = "gdb"
+version = "*"
+
+[[packages]]
+name = "git"
+version = "*"
+
+[[packages]]
+name = "glibc-devel"
+version = "*"
+
+[[packages]]
+name = "gnupg2"
+version = "*"
+
+[[packages]]
+name = "libcurl-devel"
+version = "*"
+
+[[packages]]
+name = "make"
+version = "*"
+
+[[packages]]
+name = "openssl-devel"
+version = "*"
+
+[[packages]]
+name = "openssl-devel"
+version = "*"
+
+[[packages]]
+name = "sqlite"
+version = "*"
+
+[[packages]]
+name = "sqlite-devel"
+version = "*"
+
+[[packages]]
+name = "sudo"
+version = "*"
+
+[[packages]]
+name = "tar"
+version = "*"
+
+[[packages]]
+name = "xz"
+version = "*"
+
+[[packages]]
+name = "xz-devel"
+version = "*"
+
+[[packages]]
+name = "zlib-devel"
+version = "*"
diff --git a/tests/pylorax/blueprints/glusterfs.toml b/tests/pylorax/blueprints/glusterfs.toml
new file mode 100644
index 00000000..b64fd883
--- /dev/null
+++ b/tests/pylorax/blueprints/glusterfs.toml
@@ -0,0 +1,14 @@
+name = "glusterfs"
+description = "An example GlusterFS server with samba"
+
+[[modules]]
+name = "glusterfs"
+version = "3.7.*"
+
+[[modules]]
+name = "glusterfs-cli"
+version = "3.7.*"
+
+[[packages]]
+name = "samba"
+version = "4.2.*"
diff --git a/tests/pylorax/blueprints/http-server.toml b/tests/pylorax/blueprints/http-server.toml
new file mode 100644
index 00000000..c6d74b1c
--- /dev/null
+++ b/tests/pylorax/blueprints/http-server.toml
@@ -0,0 +1,35 @@
+name = "http-server"
+description = "An example http server with PHP and MySQL support."
+version = "0.0.1"
+
+[[modules]]
+name = "httpd"
+version = "2.4.*"
+
+[[modules]]
+name = "mod_auth_kerb"
+version = "5.4"
+
+[[modules]]
+name = "mod_ssl"
+version = "2.4.*"
+
+[[modules]]
+name = "php"
+version = "5.4.*"
+
+[[modules]]
+name = "php-mysql"
+version = "5.4.*"
+
+[[packages]]
+name = "tmux"
+version = "2.2"
+
+[[packages]]
+name = "openssh-server"
+version = "6.6.*"
+
+[[packages]]
+name = "rsync"
+version = "3.0.*"
diff --git a/tests/pylorax/blueprints/jboss.toml b/tests/pylorax/blueprints/jboss.toml
new file mode 100644
index 00000000..9c7090fb
--- /dev/null
+++ b/tests/pylorax/blueprints/jboss.toml
@@ -0,0 +1,14 @@
+name = "jboss"
+description = "An example jboss server"
+
+[[modules]]
+name = "jboss-servlet-3.0-api"
+version = "1.0.*"
+
+[[modules]]
+name = "jboss-interceptors-1.1-api"
+version = "1.0.*"
+
+[[modules]]
+name = "java-1.8.0-openjdk"
+version = "1.8.0.*"
diff --git a/tests/pylorax/blueprints/kubernetes.toml b/tests/pylorax/blueprints/kubernetes.toml
new file mode 100644
index 00000000..08e20bb7
--- /dev/null
+++ b/tests/pylorax/blueprints/kubernetes.toml
@@ -0,0 +1,26 @@
+name = "kubernetes"
+description = "An example kubernetes master"
+
+[[modules]]
+name = "kubernetes"
+version = "1.2.*"
+
+[[modules]]
+name = "docker"
+version = "1.10.*"
+
+[[modules]]
+name = "docker-lvm-plugin"
+version = "1.10.*"
+
+[[modules]]
+name = "etcd"
+version = "2.3.*"
+
+[[modules]]
+name = "flannel"
+version = "0.5.*"
+
+[[packages]]
+name = "oci-systemd-hook"
+version = "0.1.*"
diff --git a/tests/pylorax/results/full-recipe.dict b/tests/pylorax/results/full-recipe.dict
new file mode 100644
index 00000000..082d6de4
--- /dev/null
+++ b/tests/pylorax/results/full-recipe.dict
@@ -0,0 +1 @@
+{'description': u'An example http server with PHP and MySQL support.', 'packages': [{'version': u'6.6.*', 'name': u'openssh-server'}, {'version': u'3.0.*', 'name': u'rsync'}, {'version': u'2.2', 'name': u'tmux'}], 'modules': [{'version': u'2.4.*', 'name': u'httpd'}, {'version': u'5.4', 'name': u'mod_auth_kerb'}, {'version': u'2.4.*', 'name': u'mod_ssl'}, {'version': u'5.4.*', 'name': u'php'}, {'version': u'5.4.*', 'name': u'php-mysql'}], 'version': u'0.0.1', 'name': u'http-server'}
diff --git a/tests/pylorax/results/full-recipe.toml b/tests/pylorax/results/full-recipe.toml
new file mode 100644
index 00000000..c6d74b1c
--- /dev/null
+++ b/tests/pylorax/results/full-recipe.toml
@@ -0,0 +1,35 @@
+name = "http-server"
+description = "An example http server with PHP and MySQL support."
+version = "0.0.1"
+
+[[modules]]
+name = "httpd"
+version = "2.4.*"
+
+[[modules]]
+name = "mod_auth_kerb"
+version = "5.4"
+
+[[modules]]
+name = "mod_ssl"
+version = "2.4.*"
+
+[[modules]]
+name = "php"
+version = "5.4.*"
+
+[[modules]]
+name = "php-mysql"
+version = "5.4.*"
+
+[[packages]]
+name = "tmux"
+version = "2.2"
+
+[[packages]]
+name = "openssh-server"
+version = "6.6.*"
+
+[[packages]]
+name = "rsync"
+version = "3.0.*"
diff --git a/tests/pylorax/results/minimal.dict b/tests/pylorax/results/minimal.dict
new file mode 100644
index 00000000..4af1c108
--- /dev/null
+++ b/tests/pylorax/results/minimal.dict
@@ -0,0 +1 @@
+{'description': u'An example http server with PHP and MySQL support.', 'packages': [], 'modules': [], 'version': u'0.0.1', 'name': u'http-server'}
diff --git a/tests/pylorax/results/minimal.toml b/tests/pylorax/results/minimal.toml
new file mode 100644
index 00000000..bb71b650
--- /dev/null
+++ b/tests/pylorax/results/minimal.toml
@@ -0,0 +1,3 @@
+name = "http-server"
+description = "An example http server with PHP and MySQL support."
+version = "0.0.1"
diff --git a/tests/pylorax/results/modules-only.dict b/tests/pylorax/results/modules-only.dict
new file mode 100644
index 00000000..64b74b31
--- /dev/null
+++ b/tests/pylorax/results/modules-only.dict
@@ -0,0 +1 @@
+{'description': u'An example http server with PHP and MySQL support.', 'packages': [], 'modules': [{'version': u'2.4.*', 'name': u'httpd'}, {'version': u'5.4', 'name': u'mod_auth_kerb'}, {'version': u'2.4.*', 'name': u'mod_ssl'}, {'version': u'5.4.*', 'name': u'php'}, {'version': u'5.4.*', 'name': u'php-mysql'}], 'version': u'0.0.1', 'name': u'http-server'}
diff --git a/tests/pylorax/results/modules-only.toml b/tests/pylorax/results/modules-only.toml
new file mode 100644
index 00000000..3457797d
--- /dev/null
+++ b/tests/pylorax/results/modules-only.toml
@@ -0,0 +1,23 @@
+name = "http-server"
+description = "An example http server with PHP and MySQL support."
+version = "0.0.1"
+
+[[modules]]
+name = "httpd"
+version = "2.4.*"
+
+[[modules]]
+name = "mod_auth_kerb"
+version = "5.4"
+
+[[modules]]
+name = "mod_ssl"
+version = "2.4.*"
+
+[[modules]]
+name = "php"
+version = "5.4.*"
+
+[[modules]]
+name = "php-mysql"
+version = "5.4.*"
diff --git a/tests/pylorax/results/packages-only.dict b/tests/pylorax/results/packages-only.dict
new file mode 100644
index 00000000..8ad5412c
--- /dev/null
+++ b/tests/pylorax/results/packages-only.dict
@@ -0,0 +1 @@
+{'description': u'An example http server with PHP and MySQL support.', 'packages': [{'version': u'6.6.*', 'name': u'openssh-server'}, {'version': u'3.0.*', 'name': u'rsync'}, {'version': u'2.2', 'name': u'tmux'}], 'modules': [], 'version': u'0.0.1', 'name': u'http-server'}
diff --git a/tests/pylorax/results/packages-only.toml b/tests/pylorax/results/packages-only.toml
new file mode 100644
index 00000000..2aa507f3
--- /dev/null
+++ b/tests/pylorax/results/packages-only.toml
@@ -0,0 +1,15 @@
+name = "http-server"
+description = "An example http server with PHP and MySQL support."
+version = "0.0.1"
+
+[[packages]]
+name = "tmux"
+version = "2.2"
+
+[[packages]]
+name = "openssh-server"
+version = "6.6.*"
+
+[[packages]]
+name = "rsync"
+version = "3.0.*"
diff --git a/tests/pylorax/test_crossdomain.py b/tests/pylorax/test_crossdomain.py
new file mode 100644
index 00000000..d3a585b1
--- /dev/null
+++ b/tests/pylorax/test_crossdomain.py
@@ -0,0 +1,118 @@
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import unittest
+from flask import Flask
+from datetime import timedelta
+
+from pylorax.api.crossdomain import crossdomain
+
+
+server = Flask(__name__)
+
+@server.route('/01')
+@crossdomain(origin='*', methods=['GET'])
+def hello_world_01():
+ return 'Hello, World!'
+
+
+@server.route('/02')
+@crossdomain(origin='*', headers=['TESTING'])
+def hello_world_02():
+ return 'Hello, World!'
+
+@server.route('/03')
+@crossdomain(origin='*', max_age=timedelta(days=7))
+def hello_world_03():
+ return 'Hello, World!'
+
+
+@server.route('/04')
+@crossdomain(origin='*', attach_to_all=False)
+def hello_world_04():
+ return 'Hello, World!'
+
+
+@server.route('/05')
+@crossdomain(origin='*', automatic_options=False)
+def hello_world_05():
+ return 'Hello, World!'
+
+
+@server.route('/06')
+@crossdomain(origin=['https://redhat.com', 'http://weldr.io'])
+def hello_world_06():
+ return 'Hello, World!'
+
+
+class CrossdomainTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.server = server.test_client()
+
+ def test_01_with_methods_specified(self):
+ # first send a preflight request to check what methods are allowed
+ response = self.server.options("/01")
+ self.assertEqual(200, response.status_code)
+ self.assertIn('GET', response.headers['Access-Control-Allow-Methods'])
+
+ # then try to issue a POST request which isn't allowed
+ response = self.server.post("/01")
+ self.assertEqual(405, response.status_code)
+
+ def test_02_with_headers_specified(self):
+ response = self.server.get("/02")
+ self.assertEqual(200, response.status_code)
+ self.assertEqual('Hello, World!', response.data)
+
+ self.assertEqual('TESTING', response.headers['Access-Control-Allow-Headers'])
+
+ def test_03_with_max_age_as_timedelta(self):
+ response = self.server.get("/03")
+ self.assertEqual(200, response.status_code)
+ self.assertEqual('Hello, World!', response.data)
+
+ expected_max_age = int(timedelta(days=7).total_seconds())
+ actual_max_age = int(response.headers['Access-Control-Max-Age'])
+ self.assertEqual(expected_max_age, actual_max_age)
+
+ def test_04_attach_to_all_false(self):
+ response = self.server.get("/04")
+ self.assertEqual(200, response.status_code)
+ self.assertEqual('Hello, World!', response.data)
+
+ # when attach_to_all is False the decorator will not assign
+ # the Access-Control-* headers to the response
+ for header, _ in response.headers:
+ self.assertFalse(header.startswith('Access-Control-'))
+
+
+ def test_05_options_request(self):
+ response = self.server.options("/05")
+ self.assertEqual(200, response.status_code)
+ self.assertEqual('Hello, World!', response.data)
+
+ self.assertEqual(response.headers['Access-Control-Allow-Methods'], 'HEAD, OPTIONS, GET')
+
+
+ def test_06_with_origin_as_list(self):
+ response = self.server.get("/06")
+ self.assertEqual(200, response.status_code)
+ self.assertEqual('Hello, World!', response.data)
+
+ for header, value in response.headers:
+ if header == 'Access-Control-Allow-Origin':
+ self.assertIn(value, ['https://redhat.com', 'http://weldr.io'])
diff --git a/tests/pylorax/test_projects.py b/tests/pylorax/test_projects.py
new file mode 100644
index 00000000..67cc207a
--- /dev/null
+++ b/tests/pylorax/test_projects.py
@@ -0,0 +1,233 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import os
+import mock
+import time
+import shutil
+import tempfile
+import unittest
+
+from yum.Errors import YumBaseError
+
+from pylorax.api.config import configure, make_yum_dirs
+from pylorax.api.projects import api_time, api_changelog, yaps_to_project, yaps_to_project_info
+from pylorax.api.projects import tm_to_dep, yaps_to_module, projects_list, projects_info, projects_depsolve
+from pylorax.api.projects import modules_list, modules_info, ProjectsError, dep_evra, dep_nevra
+from pylorax.api.yumbase import get_base_object
+
+
+class Yaps(object):
+ """Test class for yaps tests"""
+ name = "name"
+ summary = "summary"
+ description = "description"
+ url = "url"
+ epoch = 1
+ release = "release"
+ arch = "arch"
+ buildtime = 499222800
+ license = "license"
+ version = "version"
+
+ def returnChangelog(self):
+ return [[0,1,"Heavy!"]]
+
+
+class TM(object):
+ """Test class for tm test"""
+ name = "name"
+ epoch = 1
+ version = "version"
+ release = "release"
+ arch = "arch"
+
+
+class ProjectsTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.tmp_dir = tempfile.mkdtemp(prefix="lorax.test.repo.")
+ self.config = configure(root_dir=self.tmp_dir, test_config=True)
+ make_yum_dirs(self.config)
+ self.yb = get_base_object(self.config)
+ os.environ["TZ"] = "UTC"
+ time.tzset()
+
+ @classmethod
+ def tearDownClass(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def test_api_time(self):
+ self.assertEqual(api_time(499222800), "1985-10-27T01:00:00")
+
+ def test_api_changelog(self):
+ self.assertEqual(api_changelog([[0,1,"Heavy!"], [0, 1, "Light!"]]), "Heavy!")
+
+ def test_api_changelog_empty_list(self):
+ self.assertEqual(api_changelog([]), '')
+
+ def test_api_changelog_missing_text_entry(self):
+ self.assertEqual(api_changelog([('now', 'atodorov')]), '')
+
+ def test_yaps_to_project(self):
+ result = {"name":"name",
+ "summary":"summary",
+ "description":"description",
+ "homepage":"url",
+ "upstream_vcs":"UPSTREAM_VCS"}
+
+ y = Yaps()
+ self.assertEqual(yaps_to_project(y), result)
+
+ def test_yaps_to_project_info(self):
+ build = {"epoch":1,
+ "release":"release",
+ "arch":"arch",
+ "build_time":"1985-10-27T01:00:00",
+ "changelog":"Heavy!",
+ "build_config_ref": "BUILD_CONFIG_REF",
+ "build_env_ref": "BUILD_ENV_REF",
+ "metadata": {},
+ "source": {"license":"license",
+ "version":"version",
+ "source_ref": "SOURCE_REF",
+ "metadata": {}}}
+
+ result = {"name":"name",
+ "summary":"summary",
+ "description":"description",
+ "homepage":"url",
+ "upstream_vcs":"UPSTREAM_VCS",
+ "builds": [build]}
+
+ y = Yaps()
+ self.assertEqual(yaps_to_project_info(y), result)
+
+ def test_tm_to_dep(self):
+ result = {"name":"name",
+ "epoch":1,
+ "version":"version",
+ "release":"release",
+ "arch":"arch"}
+
+ tm = TM()
+ self.assertEqual(tm_to_dep(tm), result)
+
+ def test_yaps_to_module(self):
+ result = {"name":"name",
+ "group_type":"rpm"}
+
+ y = Yaps()
+ self.assertEqual(yaps_to_module(y), result)
+
+ def test_dep_evra(self):
+ dep = {"arch": "noarch",
+ "epoch": 0,
+ "name": "basesystem",
+ "release": "7.el7",
+ "version": "10.0"}
+ self.assertEqual(dep_evra(dep), "10.0-7.el7.noarch")
+
+ def test_dep_evra_with_epoch_not_zero(self):
+ dep = {"arch": "x86_64",
+ "epoch": 2,
+ "name": "tog-pegasus-libs",
+ "release": "3.el7",
+ "version": "2.14.1"}
+ self.assertEqual(dep_evra(dep), "2:2.14.1-3.el7.x86_64")
+
+ def test_dep_nevra(self):
+ dep = {"arch": "noarch",
+ "epoch": 0,
+ "name": "basesystem",
+ "release": "7.el7",
+ "version": "10.0"}
+ self.assertEqual(dep_nevra(dep), "basesystem-10.0-7.el7.noarch")
+
+ def test_projects_list(self):
+ projects = projects_list(self.yb)
+ self.assertEqual(len(projects) > 10, True)
+
+ def test_projects_list_yum_raises_exception(self):
+ with self.assertRaises(ProjectsError):
+ with mock.patch.object(self.yb, 'doPackageLists', side_effect=YumBaseError('TESTING')):
+ projects_list(self.yb)
+
+ def test_projects_info(self):
+ projects = projects_info(self.yb, ["bash"])
+
+ self.assertEqual(projects[0]["name"], "bash")
+ self.assertEqual(projects[0]["builds"][0]["source"]["license"], "GPLv3+")
+
+ def test_projects_info_yum_raises_exception(self):
+ with self.assertRaises(ProjectsError):
+ with mock.patch.object(self.yb, 'doPackageLists', side_effect=YumBaseError('TESTING')):
+ projects_info(self.yb, ["bash"])
+
+ def test_projects_depsolve(self):
+ deps = projects_depsolve(self.yb, ["bash"])
+
+ self.assertEqual(deps[0]["name"], "basesystem")
+
+ def test_projects_depsolve_fail(self):
+ with self.assertRaises(ProjectsError):
+ projects_depsolve(self.yb, ["nada-package"])
+
+ def test_modules_list(self):
+ modules = modules_list(self.yb, None)
+
+ self.assertEqual(len(modules) > 10, True)
+ self.assertEqual(modules[0]["group_type"], "rpm")
+
+ modules = modules_list(self.yb, ["g*"])
+ self.assertEqual(modules[0]["name"].startswith("g"), True)
+
+ def test_modules_list_yum_raises_exception(self):
+ with self.assertRaises(ProjectsError):
+ with mock.patch.object(self.yb, 'doPackageLists', side_effect=YumBaseError('TESTING')):
+ modules_list(self.yb, None)
+
+ def test_modules_info(self):
+ modules = modules_info(self.yb, ["bash"])
+
+ print(modules)
+ self.assertEqual(modules[0]["name"], "bash")
+ self.assertEqual(modules[0]["dependencies"][0]["name"], "basesystem")
+
+ def test_modules_info_yum_raises_exception(self):
+ with self.assertRaises(ProjectsError):
+ with mock.patch.object(self.yb, 'doPackageLists', side_effect=YumBaseError('TESTING')):
+ modules_info(self.yb, ["bash"])
+
+
+class ConfigureTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.tmp_dir = tempfile.mkdtemp(prefix="lorax.test.configure.")
+ self.conf_file = os.path.join(self.tmp_dir, 'test.conf')
+ open(self.conf_file, 'w').write("[composer]\ncache_dir = /tmp/cache-test")
+
+ @classmethod
+ def tearDownClass(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def test_configure_reads_existing_file(self):
+ config = configure(conf_file=self.conf_file)
+ self.assertEqual(config.get('composer', 'cache_dir'), '/tmp/cache-test')
+
+ def test_configure_reads_non_existing_file(self):
+ config = configure(conf_file=self.conf_file + '.non-existing')
+ self.assertEqual(config.get('composer', 'cache_dir'), '/var/tmp/composer/cache')
diff --git a/tests/pylorax/test_recipes.py b/tests/pylorax/test_recipes.py
new file mode 100644
index 00000000..40144dfd
--- /dev/null
+++ b/tests/pylorax/test_recipes.py
@@ -0,0 +1,343 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import os
+import mock
+from pytoml import TomlError
+import shutil
+import tempfile
+import unittest
+
+import pylorax.api.recipes as recipes
+from pylorax.sysutils import joinpaths
+
+class BasicRecipeTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ # Input toml is in .toml and python dict string is in .dict
+ input_recipes = [("full-recipe.toml", "full-recipe.dict"),
+ ("minimal.toml", "minimal.dict"),
+ ("modules-only.toml", "modules-only.dict"),
+ ("packages-only.toml", "packages-only.dict")]
+ results_path = "./tests/pylorax/results/"
+ self.input_toml = []
+ for (recipe_toml, recipe_dict) in input_recipes:
+ with open(joinpaths(results_path, recipe_toml)) as f_toml:
+ with open(joinpaths(results_path, recipe_dict)) as f_dict:
+ # XXX Warning, can run arbitrary code
+ result_dict = eval(f_dict.read())
+ self.input_toml.append((f_toml.read(), result_dict))
+
+ self.old_modules = [recipes.RecipeModule("toml", "2.1"),
+ recipes.RecipeModule("bash", "4.*"),
+ recipes.RecipeModule("httpd", "3.7.*")]
+ self.old_packages = [recipes.RecipePackage("python", "2.7.*"),
+ recipes.RecipePackage("parted", "3.2")]
+ self.new_modules = [recipes.RecipeModule("toml", "2.1"),
+ recipes.RecipeModule("httpd", "3.8.*"),
+ recipes.RecipeModule("openssh", "2.8.1")]
+ self.new_packages = [recipes.RecipePackage("python", "2.7.*"),
+ recipes.RecipePackage("parted", "3.2"),
+ recipes.RecipePackage("git", "2.13.*")]
+ self.modules_result = [{"new": {"Modules": {"version": "2.8.1", "name": "openssh"}},
+ "old": None},
+ {"new": None,
+ "old": {"Modules": {"name": "bash", "version": "4.*"}}},
+ {"new": {"Modules": {"version": "3.8.*", "name": "httpd"}},
+ "old": {"Modules": {"version": "3.7.*", "name": "httpd"}}}]
+ self.packages_result = [{"new": {"Packages": {"name": "git", "version": "2.13.*"}}, "old": None}]
+
+ @classmethod
+ def tearDownClass(self):
+ pass
+
+ def toml_to_recipe_test(self):
+ """Test converting the TOML string to a Recipe object"""
+ for (toml_str, recipe_dict) in self.input_toml:
+ result = recipes.recipe_from_toml(toml_str)
+ self.assertEqual(result, recipe_dict)
+
+ def toml_to_recipe_fail_test(self):
+ """Test trying to convert a non-TOML string to a Recipe"""
+ with self.assertRaises(TomlError):
+ recipes.recipe_from_toml("This is not a TOML string\n")
+
+ with self.assertRaises(recipes.RecipeError):
+ recipes.recipe_from_toml('name = "a failed toml string"\n')
+
+ def recipe_to_toml_test(self):
+ """Test converting a Recipe object to a TOML string"""
+ # In order to avoid problems from matching strings we convert to TOML and
+ # then back so compare the Recipes.
+ for (toml_str, _recipe_dict) in self.input_toml:
+ # This is tested in toml_to_recipe
+ recipe_1 = recipes.recipe_from_toml(toml_str)
+ # Convert the Recipe to TOML and then back to a Recipe
+ toml_2 = recipe_1.toml()
+ recipe_2 = recipes.recipe_from_toml(toml_2)
+ self.assertEqual(recipe_1, recipe_2)
+
+ def recipe_bump_version_test(self):
+ """Test the Recipe's version bump function"""
+
+ # Neither have a version
+ recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None)
+ new_version = recipe.bump_version(None)
+ self.assertEqual(new_version, "0.0.1")
+
+ # Original has a version, new does not
+ recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None)
+ new_version = recipe.bump_version("0.0.1")
+ self.assertEqual(new_version, "0.0.2")
+
+ # Original has no version, new does
+ recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.0", None, None)
+ new_version = recipe.bump_version(None)
+ self.assertEqual(new_version, "0.1.0")
+
+ # New and Original are the same
+ recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.0.1", None, None)
+ new_version = recipe.bump_version("0.0.1")
+ self.assertEqual(new_version, "0.0.2")
+
+ # New is different from Original
+ recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", None, None)
+ new_version = recipe.bump_version("0.0.1")
+ self.assertEqual(new_version, "0.1.1")
+
+ def find_name_test(self):
+ """Test the find_name function"""
+ test_list = [{"name":"dog"}, {"name":"cat"}, {"name":"squirrel"}]
+
+ self.assertEqual(recipes.find_name("dog", test_list), {"name":"dog"})
+ self.assertEqual(recipes.find_name("cat", test_list), {"name":"cat"})
+ self.assertEqual(recipes.find_name("squirrel", test_list), {"name":"squirrel"})
+
+ self.assertIsNone(recipes.find_name("alien", test_list))
+
+ def diff_items_test(self):
+ """Test the diff_items function"""
+ self.assertEqual(recipes.diff_items("Modules", self.old_modules, self.new_modules), self.modules_result)
+ self.assertEqual(recipes.diff_items("Packages", self.old_packages, self.new_packages), self.packages_result)
+
+ def recipe_diff_test(self):
+ """Test the recipe_diff function"""
+ old_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", self.old_modules, self.old_packages)
+ new_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.3.1", self.new_modules, self.new_packages)
+ result = [{'new': {'Version': '0.3.1'}, 'old': {'Version': '0.1.1'}},
+ {'new': {'Module': {'name': 'openssh', 'version': '2.8.1'}}, 'old': None},
+ {'new': None, 'old': {'Module': {'name': 'bash', 'version': '4.*'}}},
+ {'new': {'Module': {'name': 'httpd', 'version': '3.8.*'}},
+ 'old': {'Module': {'name': 'httpd', 'version': '3.7.*'}}},
+ {'new': {'Package': {'name': 'git', 'version': '2.13.*'}}, 'old': None}]
+ self.assertEqual(recipes.recipe_diff(old_recipe, new_recipe), result)
+
+class GitRecipesTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.repo_dir = tempfile.mkdtemp(prefix="lorax.test.repo.")
+ self.repo = recipes.open_or_create_repo(self.repo_dir)
+
+ self.results_path = "./tests/pylorax/results/"
+ self.examples_path = "./tests/pylorax/blueprints/"
+ self.new_recipe = os.path.join(self.examples_path, 'python-testing.toml')
+
+ @classmethod
+ def tearDownClass(self):
+ if self.repo is not None:
+ del self.repo
+ shutil.rmtree(self.repo_dir)
+
+ def tearDown(self):
+ if os.path.exists(self.new_recipe):
+ os.remove(self.new_recipe)
+
+ def _create_another_recipe(self):
+ open(self.new_recipe, 'w').write("""name = "python-testing"
+description = "A recipe used during testing."
+version = "0.0.1"
+
+[[packages]]
+name = "python"
+version = "2.7.*"
+""")
+
+ def test_01_repo_creation(self):
+ """Test that creating the repository succeeded"""
+ self.assertNotEqual(self.repo, None)
+
+ def test_02_commit_recipe(self):
+ """Test committing a Recipe object"""
+ recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None)
+ oid = recipes.commit_recipe(self.repo, "master", recipe)
+ self.assertNotEqual(oid, None)
+
+ def test_03_list_recipe(self):
+ """Test listing recipe commits"""
+ commits = recipes.list_commits(self.repo, "master", "test-recipe.toml")
+ self.assertEqual(len(commits), 1, "Wrong number of commits.")
+ self.assertEqual(commits[0].message, "Recipe test-recipe, version 0.0.1 saved.")
+ self.assertNotEqual(commits[0].timestamp, None, "Timestamp is None")
+ self.assertEqual(len(commits[0].commit), 40, "Commit hash isn't 40 characters")
+ self.assertEqual(commits[0].revision, None, "revision is not None")
+
+ def test_03_list_commits_commit_time_val_error(self):
+ """Test listing recipe commits which raise CommitTimeValError"""
+ with mock.patch('pylorax.api.recipes.GLib.DateTime.to_timeval', return_value=False):
+ commits = recipes.list_commits(self.repo, "master", "test-recipe.toml")
+ self.assertEqual(len(commits), 0, "Wrong number of commits.")
+
+ def test_04_commit_recipe_file(self):
+ """Test committing a TOML file"""
+ recipe_path = joinpaths(self.results_path, "full-recipe.toml")
+ oid = recipes.commit_recipe_file(self.repo, "master", recipe_path)
+ self.assertNotEqual(oid, None)
+
+ commits = recipes.list_commits(self.repo, "master", "http-server.toml")
+ self.assertEqual(len(commits), 1, "Wrong number of commits: %s" % commits)
+
+ def test_04_commit_recipe_file_handles_internal_ioerror(self):
+ """Test committing a TOML raises RecipeFileError on internal IOError"""
+ recipe_path = joinpaths(self.results_path, "non-existing-file.toml")
+ with self.assertRaises(recipes.RecipeFileError):
+ recipes.commit_recipe_file(self.repo, "master", recipe_path)
+
+ def test_05_commit_toml_dir(self):
+ """Test committing a directory of TOML files"""
+ # first verify that the newly created file isn't present
+ old_commits = recipes.list_commits(self.repo, "master", "python-testing.toml")
+ self.assertEqual(len(old_commits), 0, "Wrong number of commits: %s" % old_commits)
+
+ # then create it and commit the entire directory
+ self._create_another_recipe()
+ recipes.commit_recipe_directory(self.repo, "master", self.examples_path)
+
+ # verify that the newly created file is already in the repository
+ new_commits = recipes.list_commits(self.repo, "master", "python-testing.toml")
+ self.assertEqual(len(new_commits), 1, "Wrong number of commits: %s" % new_commits)
+ # again make sure new_commits != old_commits
+ self.assertGreater(len(new_commits), len(old_commits),
+ "New commits shoud differ from old commits")
+
+ def test_05_commit_recipe_directory_handling_internal_exceptions(self):
+ """Test committing a directory of TOML files while handling internal exceptions"""
+ # first verify that the newly created file isn't present
+ old_commits = recipes.list_commits(self.repo, "master", "python-testing.toml")
+ self.assertEqual(len(old_commits), 0, "Wrong number of commits: %s" % old_commits)
+
+ # then create it and commit the entire directory
+ self._create_another_recipe()
+
+ # try to commit while raising RecipeFileError
+ with mock.patch('pylorax.api.recipes.commit_recipe_file', side_effect=recipes.RecipeFileError('TESTING')):
+ recipes.commit_recipe_directory(self.repo, "master", self.examples_path)
+
+ # try to commit while raising TomlError
+ with mock.patch('pylorax.api.recipes.commit_recipe_file', side_effect=TomlError('TESTING', 0, 0, '__test__')):
+ recipes.commit_recipe_directory(self.repo, "master", self.examples_path)
+
+ # verify again that the newly created file isn't present b/c we raised an exception
+ new_commits = recipes.list_commits(self.repo, "master", "python-testing.toml")
+ self.assertEqual(len(new_commits), 0, "Wrong number of commits: %s" % new_commits)
+
+ def test_06_read_recipe(self):
+ """Test reading a recipe from a commit"""
+ commits = recipes.list_commits(self.repo, "master", "http-server.toml")
+ self.assertEqual(len(commits), 1, "Wrong number of commits: %s" % commits)
+
+ recipe = recipes.read_recipe_commit(self.repo, "master", "http-server")
+ self.assertNotEqual(recipe, None)
+ self.assertEqual(recipe["name"], "http-server")
+
+ # Read by commit id
+ recipe = recipes.read_recipe_commit(self.repo, "master", "http-server", commits[0].commit)
+ self.assertNotEqual(recipe, None)
+ self.assertEqual(recipe["name"], "http-server")
+
+ # Read the recipe and its commit id
+ (commit_id, recipe) = recipes.read_recipe_and_id(self.repo, "master", "http-server", commits[0].commit)
+ self.assertEqual(commit_id, commits[0].commit)
+
+ def test_07_tag_commit(self):
+ """Test tagging the most recent commit of a recipe"""
+ result = recipes.tag_file_commit(self.repo, "master", "not-a-file")
+ self.assertEqual(result, None)
+
+ result = recipes.tag_recipe_commit(self.repo, "master", "http-server")
+ self.assertNotEqual(result, None)
+
+ commits = recipes.list_commits(self.repo, "master", "http-server.toml")
+ self.assertEqual(len(commits), 1, "Wrong number of commits: %s" % commits)
+ self.assertEqual(commits[0].revision, 1)
+
+ def test_08_delete_recipe(self):
+ """Test deleting a file from a branch"""
+ oid = recipes.delete_recipe(self.repo, "master", "http-server")
+ self.assertNotEqual(oid, None)
+
+ master_files = recipes.list_branch_files(self.repo, "master")
+ self.assertEqual("http-server.toml" in master_files, False)
+
+ def test_09_revert_commit(self):
+ """Test reverting a file on a branch"""
+ commits = recipes.list_commits(self.repo, "master", "http-server.toml")
+ revert_to = commits[0].commit
+ oid = recipes.revert_recipe(self.repo, "master", "http-server", revert_to)
+ self.assertNotEqual(oid, None)
+
+ commits = recipes.list_commits(self.repo, "master", "http-server.toml")
+ self.assertEqual(len(commits), 2, "Wrong number of commits: %s" % commits)
+ self.assertEqual(commits[0].message, "http-server.toml reverted to commit %s" % revert_to)
+
+ def test_10_tag_new_commit(self):
+ """Test tagging a newer commit of a recipe"""
+ recipe = recipes.read_recipe_commit(self.repo, "master", "http-server")
+ recipe["description"] = "A modified description"
+ oid = recipes.commit_recipe(self.repo, "master", recipe)
+ self.assertNotEqual(oid, None)
+
+ # Tag the new commit
+ result = recipes.tag_recipe_commit(self.repo, "master", "http-server")
+ self.assertNotEqual(result, None)
+
+ commits = recipes.list_commits(self.repo, "master", "http-server.toml")
+ self.assertEqual(len(commits), 3, "Wrong number of commits: %s" % commits)
+ self.assertEqual(commits[0].revision, 2)
+
+
+class ExistingGitRepoRecipesTest(GitRecipesTest):
+ @classmethod
+ def setUpClass(self):
+ # will initialize the git repository in the parent class
+ super(ExistingGitRepoRecipesTest, self).setUpClass()
+
+ # reopen the repository again so that tests are executed
+ # against the existing repo one more time.
+ self.repo = recipes.open_or_create_repo(self.repo_dir)
+
+
+class GetRevisionFromTagTests(unittest.TestCase):
+ def test_01_valid_tag(self):
+ revision = recipes.get_revision_from_tag('branch/filename/r123')
+ self.assertEqual(123, revision)
+
+ def test_02_invalid_tag_not_a_number(self):
+ revision = recipes.get_revision_from_tag('branch/filename/rABC')
+ self.assertIsNone(revision)
+
+ def test_02_invalid_tag_missing_revision_string(self):
+ revision = recipes.get_revision_from_tag('branch/filename/mybranch')
+ self.assertIsNone(revision)
diff --git a/tests/pylorax/test_server.py b/tests/pylorax/test_server.py
new file mode 100644
index 00000000..b463c39f
--- /dev/null
+++ b/tests/pylorax/test_server.py
@@ -0,0 +1,810 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import os
+from glob import glob
+import shutil
+import tempfile
+import time
+from threading import Lock
+import unittest
+
+from flask import json
+import pytoml as toml
+from pylorax.api.config import configure, make_yum_dirs, make_queue_dirs
+from pylorax.api.queue import start_queue_monitor
+from pylorax.api.recipes import open_or_create_repo, commit_recipe_directory
+from pylorax.api.server import server, GitLock, YumLock
+from pylorax.api.yumbase import get_base_object
+from pylorax.sysutils import joinpaths
+
+class ServerTestCase(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(self):
+ repo_dir = tempfile.mkdtemp(prefix="lorax.test.repo.")
+ server.config["REPO_DIR"] = repo_dir
+ repo = open_or_create_repo(server.config["REPO_DIR"])
+ server.config["GITLOCK"] = GitLock(repo=repo, lock=Lock(), dir=repo_dir)
+
+ server.config["COMPOSER_CFG"] = configure(root_dir=repo_dir, test_config=True)
+ os.makedirs(joinpaths(server.config["COMPOSER_CFG"].get("composer", "share_dir"), "composer"))
+ errors = make_queue_dirs(server.config["COMPOSER_CFG"], 0)
+ if errors:
+ raise RuntimeError("\n".join(errors))
+
+ make_yum_dirs(server.config["COMPOSER_CFG"])
+ yb = get_base_object(server.config["COMPOSER_CFG"])
+ server.config["YUMLOCK"] = YumLock(yb=yb, lock=Lock())
+
+ server.config['TESTING'] = True
+ self.server = server.test_client()
+
+ self.examples_path = "./tests/pylorax/blueprints/"
+
+ # Copy the shared files over to the directory tree we are using
+ share_path = "./share/composer/"
+ for f in glob(joinpaths(share_path, "*")):
+ shutil.copy(f, joinpaths(server.config["COMPOSER_CFG"].get("composer", "share_dir"), "composer"))
+
+ # Import the example blueprints
+ commit_recipe_directory(server.config["GITLOCK"].repo, "master", self.examples_path)
+
+ start_queue_monitor(server.config["COMPOSER_CFG"], 0, 0)
+
+ @classmethod
+ def tearDownClass(self):
+ shutil.rmtree(server.config["REPO_DIR"])
+
+ def test_01_status(self):
+ """Test the /api/status route"""
+ status_fields = ["build", "api", "db_version", "schema_version", "db_supported", "backend"]
+ resp = self.server.get("/api/status")
+ data = json.loads(resp.data)
+ # Just make sure the fields are present
+ self.assertEqual(sorted(data.keys()), sorted(status_fields))
+
+ def test_02_blueprints_list(self):
+ """Test the /api/v0/blueprints/list route"""
+ list_dict = {"blueprints":["atlas", "development", "glusterfs", "http-server", "jboss", "kubernetes"],
+ "limit":20, "offset":0, "total":6}
+ resp = self.server.get("/api/v0/blueprints/list")
+ data = json.loads(resp.data)
+ self.assertEqual(data, list_dict)
+
+ def test_03_blueprints_info(self):
+ """Test the /api/v0/blueprints/info route"""
+ info_dict_1 = {"changes":[{"changed":False, "name":"http-server"}],
+ "errors":[],
+ "blueprints":[{"description":"An example http server with PHP and MySQL support.",
+ "modules":[{"name":"httpd", "version":"2.4.*"},
+ {"name":"mod_auth_kerb", "version":"5.4"},
+ {"name":"mod_ssl", "version":"2.4.*"},
+ {"name":"php", "version":"5.4.*"},
+ {"name": "php-mysql", "version":"5.4.*"}],
+ "name":"http-server",
+ "packages": [{"name":"openssh-server", "version": "6.6.*"},
+ {"name": "rsync", "version": "3.0.*"},
+ {"name": "tmux", "version": "2.2"}],
+ "version": "0.0.1"}]}
+ resp = self.server.get("/api/v0/blueprints/info/http-server")
+ data = json.loads(resp.data)
+ self.assertEqual(data, info_dict_1)
+
+ info_dict_2 = {"changes":[{"changed":False, "name":"glusterfs"},
+ {"changed":False, "name":"http-server"}],
+ "errors":[],
+ "blueprints":[{"description": "An example GlusterFS server with samba",
+ "modules":[{"name":"glusterfs", "version":"3.7.*"},
+ {"name":"glusterfs-cli", "version":"3.7.*"}],
+ "name":"glusterfs",
+ "packages":[{"name":"samba", "version":"4.2.*"}],
+ "version": "0.0.1"},
+ {"description":"An example http server with PHP and MySQL support.",
+ "modules":[{"name":"httpd", "version":"2.4.*"},
+ {"name":"mod_auth_kerb", "version":"5.4"},
+ {"name":"mod_ssl", "version":"2.4.*"},
+ {"name":"php", "version":"5.4.*"},
+ {"name": "php-mysql", "version":"5.4.*"}],
+ "name":"http-server",
+ "packages": [{"name":"openssh-server", "version": "6.6.*"},
+ {"name": "rsync", "version": "3.0.*"},
+ {"name": "tmux", "version": "2.2"}],
+ "version": "0.0.1"},
+ ]}
+ resp = self.server.get("/api/v0/blueprints/info/http-server,glusterfs")
+ data = json.loads(resp.data)
+ self.assertEqual(data, info_dict_2)
+
+ info_dict_3 = {"changes":[],
+ "errors":["missing-blueprint: No commits for missing-blueprint.toml on the master branch."],
+ "blueprints":[]
+ }
+ resp = self.server.get("/api/v0/blueprints/info/missing-blueprint")
+ data = json.loads(resp.data)
+ self.assertEqual(data, info_dict_3)
+
+ def test_04_blueprints_changes(self):
+ """Test the /api/v0/blueprints/changes route"""
+ resp = self.server.get("/api/v0/blueprints/changes/http-server")
+ data = json.loads(resp.data)
+
+ # Can't compare a whole dict since commit hash and timestamps will change.
+ # Should have 1 commit (for now), with a matching message.
+ self.assertEqual(data["limit"], 20)
+ self.assertEqual(data["offset"], 0)
+ self.assertEqual(len(data["errors"]), 0)
+ self.assertEqual(len(data["blueprints"]), 1)
+ self.assertEqual(data["blueprints"][0]["name"], "http-server")
+ self.assertEqual(len(data["blueprints"][0]["changes"]), 1)
+
+ def test_04a_blueprints_diff_empty_ws(self):
+ """Test the /api/v0/diff/NEWEST/WORKSPACE with empty workspace"""
+ resp = self.server.get("/api/v0/blueprints/diff/glusterfs/NEWEST/WORKSPACE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data, {"diff": []})
+
+ def test_05_blueprints_new_json(self):
+ """Test the /api/v0/blueprints/new route with json blueprint"""
+ test_blueprint = {"description": "An example GlusterFS server with samba",
+ "name":"glusterfs",
+ "version": "0.2.0",
+ "modules":[{"name":"glusterfs", "version":"3.7.*"},
+ {"name":"glusterfs-cli", "version":"3.7.*"}],
+ "packages":[{"name":"samba", "version":"4.2.*"},
+ {"name":"tmux", "version":"2.2"}]}
+
+ resp = self.server.post("/api/v0/blueprints/new",
+ data=json.dumps(test_blueprint),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/info/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0], test_blueprint)
+
+ def test_06_blueprints_new_toml(self):
+ """Test the /api/v0/blueprints/new route with toml blueprint"""
+ test_blueprint = open(joinpaths(self.examples_path, "glusterfs.toml"), "rb").read()
+ resp = self.server.post("/api/v0/blueprints/new",
+ data=test_blueprint,
+ content_type="text/x-toml")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/info/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual(len(blueprints), 1)
+
+ # Returned blueprint has had its version bumped to 0.2.1
+ test_blueprint = toml.loads(test_blueprint)
+ test_blueprint["version"] = "0.2.1"
+
+ self.assertEqual(blueprints[0], test_blueprint)
+
+ def test_07_blueprints_ws_json(self):
+ """Test the /api/v0/blueprints/workspace route with json blueprint"""
+ test_blueprint = {"description": "An example GlusterFS server with samba, ws version",
+ "name":"glusterfs",
+ "version": "0.3.0",
+ "modules":[{"name":"glusterfs", "version":"3.7.*"},
+ {"name":"glusterfs-cli", "version":"3.7.*"}],
+ "packages":[{"name":"samba", "version":"4.2.*"},
+ {"name":"tmux", "version":"2.2"}]}
+
+ resp = self.server.post("/api/v0/blueprints/workspace",
+ data=json.dumps(test_blueprint),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/info/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0], test_blueprint)
+ changes = data.get("changes")
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0], {"name":"glusterfs", "changed":True})
+
+ def test_08_blueprints_ws_toml(self):
+ """Test the /api/v0/blueprints/workspace route with toml blueprint"""
+ test_blueprint = {"description": "An example GlusterFS server with samba, ws version",
+ "name":"glusterfs",
+ "version": "0.4.0",
+ "modules":[{"name":"glusterfs", "version":"3.7.*"},
+ {"name":"glusterfs-cli", "version":"3.7.*"}],
+ "packages":[{"name":"samba", "version":"4.2.*"},
+ {"name":"tmux", "version":"2.2"}]}
+
+ resp = self.server.post("/api/v0/blueprints/workspace",
+ data=json.dumps(test_blueprint),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/info/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0], test_blueprint)
+ changes = data.get("changes")
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0], {"name":"glusterfs", "changed":True})
+
+ def test_09_blueprints_ws_delete(self):
+ """Test DELETE /api/v0/blueprints/workspace/"""
+ # Write to the workspace first, just use the test_blueprints_ws_json test for this
+ self.test_07_blueprints_ws_json()
+
+ # Delete it
+ resp = self.server.delete("/api/v0/blueprints/workspace/glusterfs")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ # Make sure it isn't the workspace copy and that changed is False
+ resp = self.server.get("/api/v0/blueprints/info/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0]["version"], "0.2.1")
+ changes = data.get("changes")
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0], {"name":"glusterfs", "changed":False})
+
+ def test_10_blueprints_delete(self):
+ """Test DELETE /api/v0/blueprints/delete/"""
+ resp = self.server.delete("/api/v0/blueprints/delete/glusterfs")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ # Make sure glusterfs is no longer in the list of blueprints
+ resp = self.server.get("/api/v0/blueprints/list")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual("glusterfs" in blueprints, False)
+
+ def test_11_blueprints_undo(self):
+ """Test POST /api/v0/blueprints/undo//"""
+ resp = self.server.get("/api/v0/blueprints/changes/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+
+ # Revert it to the first commit
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ changes = blueprints[0].get("changes")
+ self.assertEqual(len(changes) > 1, True)
+
+ # Revert it to the first commit
+ commit = changes[-1]["commit"]
+ resp = self.server.post("/api/v0/blueprints/undo/glusterfs/%s" % commit)
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/changes/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ changes = blueprints[0].get("changes")
+ self.assertEqual(len(changes) > 1, True)
+
+ expected_msg = "glusterfs.toml reverted to commit %s" % commit
+ self.assertEqual(changes[0]["message"], expected_msg)
+
+ def test_12_blueprints_tag(self):
+ """Test POST /api/v0/blueprints/tag/"""
+ resp = self.server.post("/api/v0/blueprints/tag/glusterfs")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/changes/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+
+ # Revert it to the first commit
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ changes = blueprints[0].get("changes")
+ self.assertEqual(len(changes) > 1, True)
+ self.assertEqual(changes[0]["revision"], 1)
+
+ def test_13_blueprints_diff(self):
+ """Test /api/v0/blueprints/diff///"""
+ resp = self.server.get("/api/v0/blueprints/changes/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ changes = blueprints[0].get("changes")
+ self.assertEqual(len(changes) >= 2, True)
+
+ from_commit = changes[1].get("commit")
+ self.assertNotEqual(from_commit, None)
+ to_commit = changes[0].get("commit")
+ self.assertNotEqual(to_commit, None)
+
+ # Get the differences between the two commits
+ resp = self.server.get("/api/v0/blueprints/diff/glusterfs/%s/%s" % (from_commit, to_commit))
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data, {"diff": [{"new": {"Version": "0.0.1"}, "old": {"Version": "0.2.1"}}]})
+
+ # Write to the workspace and check the diff
+ test_blueprint = {"description": "An example GlusterFS server with samba, ws version",
+ "name":"glusterfs",
+ "version": "0.3.0",
+ "modules":[{"name":"glusterfs", "version":"3.7.*"},
+ {"name":"glusterfs-cli", "version":"3.7.*"}],
+ "packages":[{"name":"samba", "version":"4.2.*"},
+ {"name":"tmux", "version":"2.2"}]}
+
+ resp = self.server.post("/api/v0/blueprints/workspace",
+ data=json.dumps(test_blueprint),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ # Get the differences between the newest commit and the workspace
+ resp = self.server.get("/api/v0/blueprints/diff/glusterfs/NEWEST/WORKSPACE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ result = {"diff": [{"new": {"Description": "An example GlusterFS server with samba, ws version"},
+ "old": {"Description": "An example GlusterFS server with samba"}},
+ {"new": {"Version": "0.3.0"},
+ "old": {"Version": "0.0.1"}},
+ {"new": {"Package": {"version": "2.2", "name": "tmux"}},
+ "old": None}]}
+ self.assertEqual(data, result)
+
+ def test_14_blueprints_depsolve(self):
+ """Test /api/v0/blueprints/depsolve/"""
+ resp = self.server.get("/api/v0/blueprints/depsolve/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0]["blueprint"]["name"], "glusterfs")
+ self.assertEqual(len(blueprints[0]["dependencies"]) > 10, True)
+ self.assertFalse(data.get("errors"))
+
+ def test_14_blueprints_depsolve_empty(self):
+ """Test /api/v0/blueprints/depsolve/ on empty blueprint"""
+ test_blueprint = {"description": "An empty blueprint",
+ "name":"void",
+ "version": "0.1.0"}
+ resp = self.server.post("/api/v0/blueprints/new",
+ data=json.dumps(test_blueprint),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/depsolve/void")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0]["blueprint"]["name"], "void")
+ self.assertEqual(blueprints[0]["blueprint"]["packages"], [])
+ self.assertEqual(blueprints[0]["blueprint"]["modules"], [])
+ self.assertEqual(blueprints[0]["dependencies"], [])
+ self.assertFalse(data.get("errors"))
+
+ def test_15_blueprints_freeze(self):
+ """Test /api/v0/blueprints/freeze/"""
+ resp = self.server.get("/api/v0/blueprints/freeze/glusterfs")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertNotEqual(blueprints, None)
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0]["blueprint"]["name"], "glusterfs")
+ evra = blueprints[0]["blueprint"]["modules"][0]["version"]
+ self.assertEqual(len(evra) > 10, True)
+
+ def test_projects_list(self):
+ """Test /api/v0/projects/list"""
+ resp = self.server.get("/api/v0/projects/list")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ projects = data.get("projects")
+ self.assertEqual(len(projects) > 10, True)
+
+ def test_projects_info(self):
+ """Test /api/v0/projects/info/"""
+ resp = self.server.get("/api/v0/projects/info/bash")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ projects = data.get("projects")
+ self.assertEqual(len(projects), 1)
+ self.assertEqual(projects[0]["name"], "bash")
+ self.assertEqual(projects[0]["builds"][0]["source"]["license"], "GPLv3+")
+
+ def test_projects_depsolve(self):
+ """Test /api/v0/projects/depsolve/"""
+ resp = self.server.get("/api/v0/projects/depsolve/bash")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ deps = data.get("projects")
+ self.assertEqual(len(deps) > 10, True)
+ self.assertEqual(deps[0]["name"], "basesystem")
+
+ def test_modules_list(self):
+ """Test /api/v0/modules/list"""
+ resp = self.server.get("/api/v0/modules/list")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ modules = data.get("modules")
+ self.assertEqual(len(modules) > 10, True)
+ self.assertEqual(modules[0]["group_type"], "rpm")
+
+ resp = self.server.get("/api/v0/modules/list/d*")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ modules = data.get("modules")
+ self.assertEqual(len(modules) > 0, True)
+ self.assertEqual(modules[0]["name"].startswith("d"), True)
+
+ def test_modules_info(self):
+ """Test /api/v0/modules/info"""
+ resp = self.server.get("/api/v0/modules/info/bash")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ modules = data.get("modules")
+ self.assertEqual(len(modules), 1)
+ self.assertEqual(modules[0]["name"], "bash")
+ self.assertEqual(modules[0]["dependencies"][0]["name"], "basesystem")
+
+ def test_blueprint_new_branch(self):
+ """Test the /api/v0/blueprints/new route with a new branch"""
+ test_blueprint = {"description": "An example GlusterFS server with samba",
+ "name":"glusterfs",
+ "version": "0.2.0",
+ "modules":[{"name":"glusterfs", "version":"3.7.*"},
+ {"name":"glusterfs-cli", "version":"3.7.*"}],
+ "packages":[{"name":"samba", "version":"4.2.*"},
+ {"name":"tmux", "version":"2.2"}]}
+
+ resp = self.server.post("/api/v0/blueprints/new?branch=test",
+ data=json.dumps(test_blueprint),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertEqual(data, {"status":True})
+
+ resp = self.server.get("/api/v0/blueprints/info/glusterfs?branch=test")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ blueprints = data.get("blueprints")
+ self.assertEqual(len(blueprints), 1)
+ self.assertEqual(blueprints[0], test_blueprint)
+
+ def assert_documentation(self, response):
+ """
+ Assert response containing documentation from /api/doc/ is
+ valid *without* comparing to the actual file on disk.
+ """
+ self.assertEqual(200, response.status_code)
+ self.assertTrue(len(response.data) > 1024)
+ # look for some well known strings inside the documentation
+ self.assertRegexpMatches(response.data, r"Lorax [\d.]+ documentation")
+ self.assertRegexpMatches(response.data, r"Copyright \d+, Red Hat, Inc.")
+
+ def test_api_docs(self):
+ """Test the /api/docs/"""
+ resp = self.server.get("/api/docs/")
+ self.assert_documentation(resp)
+
+ def test_api_docs_with_existing_path(self):
+ """Test the /api/docs/modules.html"""
+ resp = self.server.get("/api/docs/modules.html")
+ self.assert_documentation(resp)
+
+ def wait_for_status(self, uuid, wait_status):
+ """Helper function that waits for a status
+
+ :param uuid: UUID of the build to check
+ :type uuid: str
+ :param wait_status: List of statuses to exit on
+ :type wait_status: list of str
+ :returns: True if status was found, False if it timed out
+ :rtype: bool
+
+ This will time out after 60 seconds
+ """
+ start = time.time()
+ while True:
+ resp = self.server.get("/api/v0/compose/info/%s" % uuid)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ queue_status = data.get("queue_status")
+ if queue_status in wait_status:
+ return True
+ if time.time() > start + 60:
+ return False
+ time.sleep(5)
+
+ def test_compose_01_types(self):
+ """Test the /api/v0/compose/types route"""
+ resp = self.server.get("/api/v0/compose/types")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual({"name": "tar", "enabled": True} in data["types"], True)
+
+ def test_compose_02_bad_type(self):
+ """Test that using an unsupported image type failes"""
+ test_compose = {"blueprint_name": "glusterfs",
+ "compose_type": "snakes",
+ "branch": "master"}
+
+ resp = self.server.post("/api/v0/compose?test=1",
+ data=json.dumps(test_compose),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to fail to start test compose: %s" % data)
+ self.assertEqual(data["errors"], ["Invalid compose type (snakes), must be one of ['ext4-filesystem', 'live-iso', 'partitioned-disk', 'qcow2', 'tar']"],
+ "Failed to get errors: %s" % data)
+
+ def test_compose_03_status_fail(self):
+ """Test that requesting a status for a bad uuid is empty"""
+ resp = self.server.get("/api/v0/compose/status/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["uuids"], [], "Failed to get empty result bad uuid: %s" % data)
+
+ def test_compose_04_cancel_fail(self):
+ """Test that requesting a cancel for a bad uuid fails."""
+ resp = self.server.delete("/api/v0/compose/cancel/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to get an error for a bad uuid: %s" % data)
+ self.assertEqual(data["errors"], ["NO-UUID-TO-SEE-HERE is not a valid build uuid"], "Failed to get errors: %s" % data)
+
+ def test_compose_05_delete_fail(self):
+ """Test that requesting a delete for a bad uuid fails."""
+ resp = self.server.delete("/api/v0/compose/delete/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["errors"], ["no-uuid-to-see-here is not a valid build uuid"],
+ "Failed to get an error for a bad uuid: %s" % data)
+
+ def test_compose_06_info_fail(self):
+ """Test that requesting info for a bad uuid fails."""
+ resp = self.server.get("/api/v0/compose/info/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to get an error for a bad uuid: %s" % data)
+ self.assertEqual(data["errors"], ["NO-UUID-TO-SEE-HERE is not a valid build_id"],
+ "Failed to get errors: %s" % data)
+
+ def test_compose_07_metadata_fail(self):
+ """Test that requesting metadata for a bad uuid fails."""
+ resp = self.server.get("/api/v0/compose/metadata/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to get an error for a bad uuid: %s" % data)
+ self.assertEqual(data["errors"], ["NO-UUID-TO-SEE-HERE is not a valid build uuid"],
+ "Failed to get errors: %s" % data)
+
+ def test_compose_08_results_fail(self):
+ """Test that requesting results for a bad uuid fails."""
+ resp = self.server.get("/api/v0/compose/results/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to get an error for a bad uuid: %s" % data)
+ self.assertEqual(data["errors"], ["NO-UUID-TO-SEE-HERE is not a valid build uuid"], "Failed to get errors: %s" % data)
+
+ def test_compose_09_logs_fail(self):
+ """Test that requesting logs for a bad uuid fails."""
+ resp = self.server.get("/api/v0/compose/logs/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to get an error for a bad uuid: %s" % data)
+ self.assertEqual(data["errors"], ["NO-UUID-TO-SEE-HERE is not a valid build uuid"],
+ "Failed to get errors: %s" % data)
+
+ def test_compose_10_log_fail(self):
+ """Test that requesting log for a bad uuid fails."""
+ resp = self.server.get("/api/v0/compose/log/NO-UUID-TO-SEE-HERE")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], False, "Failed to get an error for a bad uuid: %s" % data)
+ self.assertEqual(data["errors"], ["NO-UUID-TO-SEE-HERE is not a valid build uuid"],
+ "Failed to get errors: %s" % data)
+
+ def test_compose_11_create_failed(self):
+ """Test the /api/v0/compose routes with a failed test compose"""
+ test_compose = {"blueprint_name": "glusterfs",
+ "compose_type": "tar",
+ "branch": "master"}
+
+ resp = self.server.post("/api/v0/compose?test=1",
+ data=json.dumps(test_compose),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], True, "Failed to start test compose: %s" % data)
+
+ build_id = data["build_id"]
+
+ # Is it in the queue list (either new or run is fine, based on timing)
+ resp = self.server.get("/api/v0/compose/queue")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [e["id"] for e in data["new"] + data["run"]]
+ self.assertEqual(build_id in ids, True, "Failed to add build to the queue")
+
+ # Wait for it to start
+ self.assertEqual(self.wait_for_status(build_id, ["RUNNING"]), True, "Failed to start test compose")
+
+ # Wait for it to finish
+ self.assertEqual(self.wait_for_status(build_id, ["FAILED"]), True, "Failed to finish test compose")
+
+ resp = self.server.get("/api/v0/compose/info/%s" % build_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["queue_status"], "FAILED", "Build not in FAILED state")
+
+ # Test the /api/v0/compose/failed route
+ resp = self.server.get("/api/v0/compose/failed")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [e["id"] for e in data["failed"]]
+ self.assertEqual(build_id in ids, True, "Failed build not listed by /compose/failed")
+
+ # Test the /api/v0/compose/finished route
+ resp = self.server.get("/api/v0/compose/finished")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["finished"], [], "Finished build not listed by /compose/finished")
+
+ # Test the /api/v0/compose/status/ route
+ resp = self.server.get("/api/v0/compose/status/%s" % build_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [(e["id"], e["queue_status"]) for e in data["uuids"]]
+ self.assertEqual((build_id, "FAILED") in ids, True, "Failed build not listed by /compose/status")
+
+ # Test the /api/v0/compose/cancel/ route
+ resp = self.server.post("/api/v0/compose?test=1",
+ data=json.dumps(test_compose),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], True, "Failed to start test compose: %s" % data)
+
+ cancel_id = data["build_id"]
+
+ # Wait for it to start
+ self.assertEqual(self.wait_for_status(cancel_id, ["RUNNING"]), True, "Failed to start test compose")
+
+ # Cancel the build
+ resp = self.server.delete("/api/v0/compose/cancel/%s" % cancel_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], True, "Failed to cancel test compose: %s" % data)
+
+ # Delete the failed build
+ # Test the /api/v0/compose/delete/ route
+ resp = self.server.delete("/api/v0/compose/delete/%s" % build_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [(e["uuid"], e["status"]) for e in data["uuids"]]
+ self.assertEqual((build_id, True) in ids, True, "Failed to delete test compose: %s" % data)
+
+ # Make sure the failed list is empty
+ resp = self.server.get("/api/v0/compose/failed")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["failed"], [], "Failed to delete the failed build: %s" % data)
+
+ def test_compose_12_create_finished(self):
+ """Test the /api/v0/compose routes with a finished test compose"""
+ test_compose = {"blueprint_name": "glusterfs",
+ "compose_type": "tar",
+ "branch": "master"}
+
+ resp = self.server.post("/api/v0/compose?test=2",
+ data=json.dumps(test_compose),
+ content_type="application/json")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["status"], True, "Failed to start test compose: %s" % data)
+
+ build_id = data["build_id"]
+
+ # Is it in the queue list (either new or run is fine, based on timing)
+ resp = self.server.get("/api/v0/compose/queue")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [e["id"] for e in data["new"] + data["run"]]
+ self.assertEqual(build_id in ids, True, "Failed to add build to the queue")
+
+ # Wait for it to start
+ self.assertEqual(self.wait_for_status(build_id, ["RUNNING"]), True, "Failed to start test compose")
+
+ # Wait for it to finish
+ self.assertEqual(self.wait_for_status(build_id, ["FINISHED"]), True, "Failed to finish test compose")
+
+ resp = self.server.get("/api/v0/compose/info/%s" % build_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["queue_status"], "FINISHED", "Build not in FINISHED state")
+
+ # Test the /api/v0/compose/finished route
+ resp = self.server.get("/api/v0/compose/finished")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [e["id"] for e in data["finished"]]
+ self.assertEqual(build_id in ids, True, "Finished build not listed by /compose/finished")
+
+ # Test the /api/v0/compose/failed route
+ resp = self.server.get("/api/v0/compose/failed")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["failed"], [], "Failed build not listed by /compose/failed")
+
+ # Test the /api/v0/compose/status/ route
+ resp = self.server.get("/api/v0/compose/status/%s" % build_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [(e["id"], e["queue_status"]) for e in data["uuids"]]
+ self.assertEqual((build_id, "FINISHED") in ids, True, "Finished build not listed by /compose/status")
+
+ # Test the /api/v0/compose/metadata/ route
+ resp = self.server.get("/api/v0/compose/metadata/%s" % build_id)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.data) > 1024, True)
+
+ # Test the /api/v0/compose/results/ route
+ resp = self.server.get("/api/v0/compose/results/%s" % build_id)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.data) > 1024, True)
+
+ # Test the /api/v0/compose/image/ route
+ resp = self.server.get("/api/v0/compose/image/%s" % build_id)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.data) > 0, True)
+ self.assertEqual(resp.data, "TEST IMAGE")
+
+ # Delete the finished build
+ # Test the /api/v0/compose/delete/ route
+ resp = self.server.delete("/api/v0/compose/delete/%s" % build_id)
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ ids = [(e["uuid"], e["status"]) for e in data["uuids"]]
+ self.assertEqual((build_id, True) in ids, True, "Failed to delete test compose: %s" % data)
+
+ # Make sure the finished list is empty
+ resp = self.server.get("/api/v0/compose/finished")
+ data = json.loads(resp.data)
+ self.assertNotEqual(data, None)
+ self.assertEqual(data["finished"], [], "Failed to delete the failed build: %s" % data)
diff --git a/tests/pylorax/test_workspace.py b/tests/pylorax/test_workspace.py
new file mode 100644
index 00000000..266b7bc4
--- /dev/null
+++ b/tests/pylorax/test_workspace.py
@@ -0,0 +1,90 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import os
+import mock
+import shutil
+import tempfile
+import unittest
+
+import pylorax.api.recipes as recipes
+from pylorax.api.workspace import workspace_dir, workspace_read, workspace_write, workspace_delete
+from pylorax.sysutils import joinpaths
+
+class WorkspaceTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.repo_dir = tempfile.mkdtemp(prefix="lorax.test.repo.")
+ self.repo = recipes.open_or_create_repo(self.repo_dir)
+
+ self.results_path = "./tests/pylorax/results/"
+ self.examples_path = "./tests/pylorax/blueprints/"
+
+ recipe_path = joinpaths(self.examples_path, "http-server.toml")
+ f = open(recipe_path, 'rb')
+ self.example_recipe = recipes.recipe_from_toml(f.read())
+
+ @classmethod
+ def tearDownClass(self):
+ if self.repo is not None:
+ del self.repo
+ shutil.rmtree(self.repo_dir)
+
+ def test_01_repo_creation(self):
+ """Test that creating the repository succeeded"""
+ self.assertNotEqual(self.repo, None)
+
+ def test_02_workspace_dir(self):
+ """Test the workspace_dir function"""
+ ws_dir = workspace_dir(self.repo, "master")
+ self.assertEqual(ws_dir, joinpaths(self.repo_dir, "git", "workspace", "master"))
+
+ def test_03_workspace_write(self):
+ """Test the workspace_write function"""
+ # Use an example recipe
+ workspace_write(self.repo, "master", self.example_recipe)
+
+ # The file should have ended up here
+ ws_recipe_path = joinpaths(self.repo_dir, "git", "workspace", "master", "http-server.toml")
+ self.assertEqual(os.path.exists(ws_recipe_path), True)
+
+ def test_04_workspace_read(self):
+ """Test the workspace_read function"""
+ # The recipe was written by the workspace_write test. Read it and compare with the source recipe.
+ recipe = workspace_read(self.repo, "master", "http-server")
+ self.assertEqual(self.example_recipe, recipe)
+
+ def test_04_workspace_read_ioerror(self):
+ """Test the workspace_read function dealing with internal IOError"""
+ # The recipe was written by the workspace_write test.
+ with self.assertRaises(recipes.RecipeFileError):
+ with mock.patch('pylorax.api.workspace.recipe_from_toml', side_effect=IOError('TESTING')):
+ workspace_read(self.repo, "master", "http-server")
+
+ def test_05_workspace_delete(self):
+ """Test the workspace_delete function"""
+ ws_recipe_path = joinpaths(self.repo_dir, "git", "workspace", "master", "http-server.toml")
+
+ self.assertEqual(os.path.exists(ws_recipe_path), True)
+ workspace_delete(self.repo, "master", "http-server")
+ self.assertEqual(os.path.exists(ws_recipe_path), False)
+
+ def test_05_workspace_delete_non_existing(self):
+ """Test the workspace_delete function"""
+ ws_recipe_path = joinpaths(self.repo_dir, "git", "workspace", "master", "non-existing.toml")
+
+ workspace_delete(self.repo, "master", "non-existing")
+ self.assertFalse(os.path.exists(ws_recipe_path))
diff --git a/tests/pylorax/test_yumbase.py b/tests/pylorax/test_yumbase.py
new file mode 100644
index 00000000..b0569028
--- /dev/null
+++ b/tests/pylorax/test_yumbase.py
@@ -0,0 +1,88 @@
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import os
+import shutil
+import tempfile
+import unittest
+
+import ConfigParser
+
+from pylorax.api.config import configure, make_yum_dirs
+from pylorax.api.yumbase import get_base_object
+
+
+class YumbaseTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.tmp_dir = tempfile.mkdtemp(prefix="lorax.test.yumbase.")
+ conf_file = os.path.join(self.tmp_dir, 'test.conf')
+ open(conf_file, 'w').write("""[composer]
+# releasever different from the current default
+releasever = 6
+[yum]
+proxy = https://proxy.example.com
+sslverify = False
+[repos]
+use_system_repos = False
+""")
+
+ # will read the above configuration
+ config = configure(conf_file=conf_file, root_dir=self.tmp_dir)
+ make_yum_dirs(config)
+
+ # will read composer config and store a yum config file
+ self.yb = get_base_object(config)
+
+ # will read the stored yum config file
+ self.yumconf = ConfigParser.ConfigParser()
+ self.yumconf.read([config.get("composer", "yum_conf")])
+
+ @classmethod
+ def tearDownClass(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def test_stores_yum_proxy_from_composer_config(self):
+ self.assertEqual('https://proxy.example.com', self.yumconf.get('main', 'proxy'))
+
+ def test_disables_sslverify_if_composer_disables_it(self):
+ self.assertEqual('0', self.yumconf.get('main', 'sslverify'))
+
+ def test_sets_releasever_from_composer(self):
+ self.assertEqual('6', self.yb.conf.yumvar['releasever'])
+
+ def test_doesnt_use_system_repos(self):
+ # no other repos defined for this test
+ self.assertEqual({}, self.yb._repos.repos)
+
+
+class CreateYumDirsTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ self.tmp_dir = tempfile.mkdtemp(prefix="lorax.test.yumbase.")
+
+ @classmethod
+ def tearDownClass(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def test_creates_missing_yum_root_directory(self):
+ config = configure(test_config=True, root_dir=self.tmp_dir)
+
+ # will create the above directory if missing
+ make_yum_dirs(config)
+ _ = get_base_object(config)
+
+ self.assertTrue(os.path.exists(self.tmp_dir + '/var/tmp/composer/yum/root'))