Add smoke tests for gerrit-master chart

This change adds some tests that can be executed to test, whether
a Gerrit instance (e.g. as installed by the gerrit-master chart)
is running.

The tests test the following functions:

- UI-endpoint is available
- Creating a project via REST
- Cloning a project
- Pushing a commit
- Deleting a project via REST

Change-Id: I12a753309d9f551a736464d8238ccaa2f53560ec
diff --git a/README.md b/README.md
index 0d57798..361d6ea 100644
--- a/README.md
+++ b/README.md
@@ -134,9 +134,14 @@
 To run all tests, execute:
 
 ```sh
-pipenv run pytest
+pipenv run pytest -m "not smoke"
 ```
 
+***note
+The `-m "not smoke"`-option excludes the smoke tests, which will fail, since
+no Gerrit-instance will be running, when they are executed.
+***
+
 Some tests will need to create files in a temporary directory. Some of these
 files will be mounted into docker containers by tests. For this to work make
 either sure that the system temporary directory is accessible by the Docker
@@ -144,7 +149,7 @@
 by executing:
 
 ```sh
-pipenv run pytest --basetemp=/tmp/k8sgerrit
+pipenv run pytest --basetemp=/tmp/k8sgerrit -m "not smoke"
 ```
 
 By default the tests will build all images from scratch. This will greatly
@@ -152,7 +157,7 @@
 a tag can be provided as follows:
 
 ```sh
-pipenv run pytest --tag=v0.1
+pipenv run pytest --tag=v0.1 -m "not smoke"
 ```
 
 The tests will then use the existing images with the provided tag. If an image
@@ -162,14 +167,14 @@
 created by docker. To enable the cache, execute:
 
 ```sh
-pipenv run pytest --build-cache
+pipenv run pytest --build-cache -m "not smoke"
 ```
 
 Slow tests may be marked with the decorator `@pytest.mark.slow`. These tests
 may then be skipped as follows:
 
 ```sh
-pipenv run pytest --skip-slow
+pipenv run pytest --skip-slow -m "not smoke"
 ```
 
 There are also other marks, allowing to select tests (refer to
@@ -232,3 +237,21 @@
 Marks structure tests. These tests are meant to test, whether certain components
 exist in a container. These tests ensure that components expected by the users
 of the container, e.g. the helm charts, are present in the containers.
+
+## Running smoke tests
+
+To run smoke tests, use the following command:
+
+```sh
+pipenv run pytest \
+  -m "smoke" \
+  --basetemp="<tmp-dir for tests>" \
+  --ingress-url="<Gerrit URL>" \
+  --gerrit-user="<Gerrit user>" \
+  --gerrit-pwd
+```
+
+The smoke tests require a Gerrit user that is allowed to create and delete
+projects. The username has to be given by `--gerit-user`. Setting the
+`--gerrit-pwd`-flag will cause a password prompt to enter the password of the
+Gerrit-user.
diff --git a/tests/conftest.py b/tests/conftest.py
index f49b90d..de47dff 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -14,6 +14,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import argparse
+import getpass
 import os
 import sys
 
@@ -26,6 +28,35 @@
 # Base images that are not published and thus only tagged with "latest"
 BASE_IMGS = ["base", "gerrit-base"]
 
+# pylint: disable=W0622
+class PasswordPromptAction(argparse.Action):
+    def __init__(
+        self,
+        option_strings,
+        dest=None,
+        nargs=0,
+        default=None,
+        required=False,
+        type=None,
+        metavar=None,
+        help=None,
+    ):
+
+        super(PasswordPromptAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=nargs,
+            default=default,
+            required=required,
+            metavar=metavar,
+            type=type,
+            help=help,
+        )
+
+    def __call__(self, parser, args, values, option_string=None):
+        password = getpass.getpass()
+        setattr(args, self.dest, password)
+
 
 def pytest_addoption(parser):
     parser.addoption(
@@ -107,6 +138,18 @@
         help="URL of the ingress domain used by the cluster.",
     )
     parser.addoption(
+        "--gerrit-user",
+        action="store",
+        default="admin",
+        help="Gerrit admin username to be used for smoke tests. (default: admin)",
+    )
+    parser.addoption(
+        "--gerrit-pwd",
+        action=PasswordPromptAction,
+        default="secret",
+        help="Gerrit admin password to be used for smoke tests. (default: secret)",
+    )
+    parser.addoption(
         "--skip-slow", action="store_true", help="If set, skip slow tests."
     )
 
diff --git a/tests/helm-charts/gerrit-master/test_chart_gerrit_master_smoke_test.py b/tests/helm-charts/gerrit-master/test_chart_gerrit_master_smoke_test.py
new file mode 100644
index 0000000..09e7b74
--- /dev/null
+++ b/tests/helm-charts/gerrit-master/test_chart_gerrit_master_smoke_test.py
@@ -0,0 +1,121 @@
+# pylint: disable=W0613
+
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os.path
+import re
+import shutil
+
+from pathlib import Path
+
+import git
+import pytest
+import requests
+
+import utils
+
+
+@pytest.fixture(scope="module")
+def admin_creds(request):
+    user = request.config.getoption("--gerrit-user")
+    pwd = request.config.getoption("--gerrit-pwd")
+    return user, pwd
+
+
+@pytest.fixture(scope="class")
+def tmp_test_repo(request, tmp_path_factory):
+    tmp_dir = tmp_path_factory.mktemp("gerrit_master_chart_clone_test")
+    yield tmp_dir
+    shutil.rmtree(tmp_dir)
+
+
+@pytest.fixture(scope="class")
+def random_repo_name():
+    return utils.create_random_string(16)
+
+
+@pytest.mark.smoke
+def test_ui_connection(request):
+    response = requests.get(request.config.getoption("--ingress-url"))
+    assert response.status_code == requests.codes["OK"]
+    assert re.search(r'content="Gerrit Code Review"', response.text)
+
+
+@pytest.mark.smoke
+@pytest.mark.incremental
+class TestGerritMasterRestGitCalls:
+    def _is_delete_project_plugin_enabled(self, gerrit_url, user, pwd):
+        url = "%s/a/plugins/delete-project/gerrit~status" % gerrit_url
+        response = requests.get(url, auth=(user, pwd))
+        return response.status_code == requests.codes["OK"]
+
+    def test_create_project_rest(self, request, random_repo_name, admin_creds):
+        create_project_url = "%s/a/projects/%s" % (
+            request.config.getoption("--ingress-url"),
+            random_repo_name,
+        )
+        response = requests.put(create_project_url, auth=admin_creds)
+        assert response.status_code == requests.codes["CREATED"]
+
+    def test_cloning_project(
+        self, request, tmp_test_repo, random_repo_name, admin_creds
+    ):
+        repo_url = "%s/%s.git" % (
+            request.config.getoption("--ingress-url"),
+            random_repo_name,
+        )
+        repo_url = repo_url.replace("//", "//%s:%s@" % admin_creds)
+        repo = git.Repo.clone_from(repo_url, tmp_test_repo)
+        assert repo.git_dir == os.path.join(tmp_test_repo, ".git")
+
+    def test_push_commit(self, request, tmp_test_repo, random_repo_name):
+        repo = git.Repo.init(tmp_test_repo)
+        file_name = os.path.join(tmp_test_repo, "test.txt")
+        Path(file_name).touch()
+        repo.index.add([file_name])
+        repo.index.commit("initial commit")
+
+        origin = repo.remote(name="origin")
+        with repo.git.custom_environment(GIT_SSL_NO_VERIFY="true"):
+            result = origin.push(refspec="master:master")
+            assert result
+
+        git_cmd = git.cmd.Git()
+        url = "%s/%s.git" % (
+            request.config.getoption("--ingress-url"),
+            random_repo_name,
+        )
+        with git_cmd.custom_environment(GIT_SSL_NO_VERIFY="true"):
+            for ref in git_cmd.ls_remote(url).split("\n"):
+                hash_ref_list = ref.split("\t")
+                if hash_ref_list[1] == "HEAD":
+                    assert repo.head.object.hexsha == hash_ref_list[0]
+                    return
+        pytest.fail(msg="SHA of remote HEAD was not equal to SHA of local HEAD.")
+
+    def test_delete_project_rest(self, request, random_repo_name, admin_creds):
+        if not self._is_delete_project_plugin_enabled(
+            request.config.getoption("--ingress-url"), admin_creds[0], admin_creds[1]
+        ):
+            pytest.skip(
+                "Delete-project plugin not installed."
+                + "The test project (%s) has to be deleted manually." % random_repo_name
+            )
+        project_url = "%s/a/projects/%s/delete-project~delete" % (
+            request.config.getoption("--ingress-url"),
+            random_repo_name,
+        )
+        response = requests.post(project_url, auth=admin_creds)
+        assert response.status_code == requests.codes["NO_CONTENT"]