diff options
| -rw-r--r-- | .travis.yml | 18 | ||||
| -rw-r--r-- | README.rst | 24 | ||||
| -rw-r--r-- | docs/cli.rst | 12 | ||||
| -rw-r--r-- | docs/gl_objects/commits.rst | 4 | ||||
| -rw-r--r-- | gitlab/__init__.py | 4 | ||||
| -rw-r--r-- | gitlab/base.py | 4 | ||||
| -rw-r--r-- | gitlab/tests/objects/test_commits.py | 106 | ||||
| -rw-r--r-- | gitlab/tests/test_gitlab.py | 44 | ||||
| -rw-r--r-- | gitlab/v4/objects.py | 18 | ||||
| -rwxr-xr-x | tools/build_test_env.sh | 16 | ||||
| -rwxr-xr-x | tools/cli_test_v4.sh | 7 | ||||
| -rw-r--r-- | tools/python_test_v4.py | 8 | ||||
| -rw-r--r-- | tox.ini | 5 |
13 files changed, 207 insertions, 63 deletions
diff --git a/.travis.yml b/.travis.yml index 83d2d33..a86780e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,22 @@ jobs: - pip3 install tox - tox -e py_func_v4 - stage: test + name: cli_func_nightly + dist: bionic + python: 3.8 + env: GITLAB_TAG=nightly + script: + - pip3 install tox + - tox -e cli_func_v4 + - stage: test + name: py_func_nightly + dist: bionic + python: 3.8 + env: GITLAB_TAG=nightly + script: + - pip3 install tox + - tox -e py_func_v4 + - stage: test name: docs dist: bionic python: 3.8 @@ -67,3 +83,5 @@ jobs: script: - pip3 install tox - tox -e py38 + allow_failures: + - env: GITLAB_TAG=nightly @@ -129,11 +129,11 @@ You need to install ``tox`` to run unit tests and documentation builds locally: .. code-block:: bash - # run the unit tests for python 2/3, and the pep8 tests: + # run the unit tests for all supported python3 versions, and the pep8 tests: tox # run tests in one environment only: - tox -epy35 + tox -epy36 # build the documentation, the result will be generated in # build/sphinx/html/ @@ -156,6 +156,26 @@ To run these tests: # run the python API tests: ./tools/py_functional_tests.sh +By default, the tests run against the ``gitlab/gitlab-ce:latest`` image. You can +override both the image and tag with the ``-i`` and ``-t`` options, or by providing +either the ``GITLAB_IMAGE`` or ``GITLAB_TAG`` environment variables. + +This way you can run tests against different versions, such as ``nightly`` for +features in an upcoming release, or an older release (e.g. ``12.8.0-ce.0``). +The tag must match an exact tag on Docker Hub: + +.. code-block:: bash + + # run tests against `nightly` or specific tag + ./tools/py_functional_tests.sh -t nightly + ./tools/py_functional_tests.sh -t 12.8.0-ce.0 + + # run tests against the latest gitlab EE image + ./tools/py_functional_tests.sh -i gitlab/gitlab-ee + + # override tags with environment variables + GITLAB_TAG=nightly ./tools/py_functional_tests.sh + You can also build a test environment using the following command: .. code-block:: bash diff --git a/docs/cli.rst b/docs/cli.rst index 3207902..b4a6c5e 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -236,6 +236,18 @@ Create a snippet: $ gitlab project-snippet create --project-id 2 --title "the title" \ --file-name "the name" --code "the code" +Get a specific project commit by its SHA id: + +.. code-block:: console + + $ gitlab project-commit get --project-id 2 --id a43290c + +Get the GPG signature of a signed commit: + +.. code-block:: console + + $ gitlab project-commit signature --project-id 2 --id a43290c + Define the status of a commit (as would be done from a CI tool for example): .. code-block:: console diff --git a/docs/gl_objects/commits.rst b/docs/gl_objects/commits.rst index abfedc8..e6bdfd8 100644 --- a/docs/gl_objects/commits.rst +++ b/docs/gl_objects/commits.rst @@ -82,6 +82,10 @@ Get the references the commit has been pushed to (branches and tags):: commit.refs('tag') # only tags commit.refs('branch') # only branches +Get the GPG signature of the commit (if the commit was signed):: + + commit.signature() + List the merge requests related to a commit:: commit.merge_requests() diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 44a249d..a12ffb9 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -800,9 +800,9 @@ class GitlabList(object): @property def prev_page(self): - """The next page number. + """The previous page number. - If None, the current page is the last. + If None, the current page is the first. """ return int(self._prev_page) if self._prev_page else None diff --git a/gitlab/base.py b/gitlab/base.py index a791db2..bc27237 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -194,9 +194,9 @@ class RESTObjectList(object): @property def prev_page(self): - """The next page number. + """The previous page number. - If None, the current page is the last. + If None, the current page is the first. """ return self._list.prev_page diff --git a/gitlab/tests/objects/test_commits.py b/gitlab/tests/objects/test_commits.py new file mode 100644 index 0000000..23a4285 --- /dev/null +++ b/gitlab/tests/objects/test_commits.py @@ -0,0 +1,106 @@ +from httmock import urlmatch, response, with_httmock + +from .test_projects import headers, TestProject + + +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/projects/1/repository/commits/6b2257ea", + method="get", +) +def resp_get_commit(url, request): + """Mock for commit GET response.""" + content = """{ + "id": "6b2257eabcec3db1f59dafbd84935e3caea04235", + "short_id": "6b2257ea", + "title": "Initial commit" + }""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch( + scheme="http", path="/api/v4/projects/1/repository/commits", method="post", +) +def resp_create_commit(url, request): + """Mock for commit create POST response.""" + content = """{ + "id": "ed899a2f4b50b4370feeea94676502b42383c746", + "short_id": "ed899a2f", + "title": "Commit message" + }""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch( + scheme="http", path="/api/v4/projects/1/repository/commits/6b2257ea", method="post", +) +def resp_revert_commit(url, request): + """Mock for commit revert POST response.""" + content = """{ + "id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad", + "short_id": "8b090c1b", + "title":"Revert \\"Initial commit\\"" + }""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/projects/1/repository/commits/6b2257ea/signature", + method="get", +) +def resp_get_commit_gpg_signature(url, request): + """Mock for commit GPG signature GET response.""" + content = """{ + "gpg_key_id": 1, + "gpg_key_primary_keyid": "8254AAB3FBD54AC9", + "gpg_key_user_name": "John Doe", + "gpg_key_user_email": "johndoe@example.com", + "verification_status": "verified", + "gpg_key_subkey_id": null + }""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +class TestCommit(TestProject): + """ + Base class for commit tests. Inherits from TestProject, + since currently all commit methods are under projects. + """ + + @with_httmock(resp_get_commit) + def test_get_commit(self): + commit = self.project.commits.get("6b2257ea") + self.assertEqual(commit.short_id, "6b2257ea") + self.assertEqual(commit.title, "Initial commit") + + @with_httmock(resp_create_commit) + def test_create_commit(self): + data = { + "branch": "master", + "commit_message": "Commit message", + "actions": [{"action": "create", "file_path": "README", "content": "",}], + } + commit = self.project.commits.create(data) + self.assertEqual(commit.short_id, "ed899a2f") + self.assertEqual(commit.title, data["commit_message"]) + + @with_httmock(resp_revert_commit) + def test_revert_commit(self): + commit = self.project.commits.get("6b2257ea", lazy=True) + revert_commit = commit.revert(branch="master") + self.assertEqual(revert_commit["short_id"], "8b090c1b") + self.assertEqual(revert_commit["title"], 'Revert "Initial commit"') + + @with_httmock(resp_get_commit_gpg_signature) + def test_get_commit_gpg_signature(self): + commit = self.project.commits.get("6b2257ea", lazy=True) + signature = commit.signature() + self.assertEqual(signature["gpg_key_primary_keyid"], "8254AAB3FBD54AC9") + self.assertEqual(signature["verification_status"], "verified") diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index 591f166..d104c7d 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -843,50 +843,6 @@ class TestGitlab(unittest.TestCase): self.gl.users.get(1, lazy=True).activate() self.gl.users.get(1, lazy=True).deactivate() - def test_commit_revert(self): - @urlmatch( - scheme="http", - netloc="localhost", - path="/api/v4/projects/1/repository/commits/6b2257ea", - method="get", - ) - def resp_get_commit(url, request): - headers = {"content-type": "application/json"} - content = """{ - "id": "6b2257eabcec3db1f59dafbd84935e3caea04235", - "short_id": "6b2257ea", - "title": "Initial commit" - }""" - content = content.encode("utf-8") - return response(200, content, headers, None, 5, request) - - @urlmatch( - scheme="http", - netloc="localhost", - path="/api/v4/projects/1/repository/commits/6b2257ea", - method="post", - ) - def resp_revert_commit(url, request): - headers = {"content-type": "application/json"} - content = """{ - "id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad", - "short_id": "8b090c1b", - "title":"Revert \\"Initial commit\\"" - }""" - content = content.encode("utf-8") - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_get_commit): - project = self.gl.projects.get(1, lazy=True) - commit = project.commits.get("6b2257ea") - self.assertEqual(commit.short_id, "6b2257ea") - self.assertEqual(commit.title, "Initial commit") - - with HTTMock(resp_revert_commit): - revert_commit = commit.revert(branch="master") - self.assertEqual(revert_commit["short_id"], "8b090c1b") - self.assertEqual(revert_commit["title"], 'Revert "Initial commit"') - def test_update_submodule(self): @urlmatch( scheme="http", netloc="localhost", path="/api/v4/projects/1$", method="get" diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 6b5b703..8852a1e 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2172,6 +2172,24 @@ class ProjectCommit(RESTObject): post_data = {"branch": branch} return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) + @cli.register_custom_action("ProjectCommit") + @exc.on_http_error(exc.GitlabGetError) + def signature(self, **kwargs): + """Get the GPG signature of the commit. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the signature could not be retrieved + + Returns: + dict: The commit's GPG signature data + """ + path = "%s/%s/signature" % (self.manager.path, self.get_id()) + return self.manager.gitlab.http_get(path, **kwargs) + class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager): _path = "/projects/%(project_id)s/repository/commits" diff --git a/tools/build_test_env.sh b/tools/build_test_env.sh index 7468a9a..91c2896 100755 --- a/tools/build_test_env.sh +++ b/tools/build_test_env.sh @@ -27,16 +27,16 @@ try() { "$@" || fatal "'$@' failed"; } REUSE_CONTAINER= NOVENV= -PY_VER=3 API_VER=4 -GITLAB_IMAGE="gitlab/gitlab-ce" -GITLAB_TAG="latest" -while getopts :knp:a: opt "$@"; do +GITLAB_IMAGE="${GITLAB_IMAGE:-gitlab/gitlab-ce}" +GITLAB_TAG="${GITLAB_TAG:-latest}" +VENV_CMD="python3 -m venv" +while getopts :knp:a:i:t: opt "$@"; do case $opt in k) REUSE_CONTAINER=1;; n) NOVENV=1;; - p) PY_VER=$OPTARG;; a) API_VER=$OPTARG;; + i) GITLAB_IMAGE=$OPTARG;; t) GITLAB_TAG=$OPTARG;; :) fatal "Option -${OPTARG} requires a value";; '?') fatal "Unknown option: -${OPTARG}";; @@ -44,12 +44,6 @@ while getopts :knp:a: opt "$@"; do esac done -case $PY_VER in - 2) VENV_CMD=virtualenv;; - 3) VENV_CMD="python3 -m venv";; - *) fatal "Wrong python version (2 or 3)";; -esac - case $API_VER in 4) ;; *) fatal "Wrong API version (4 only)";; diff --git a/tools/cli_test_v4.sh b/tools/cli_test_v4.sh index cf157f4..b916705 100755 --- a/tools/cli_test_v4.sh +++ b/tools/cli_test_v4.sh @@ -113,6 +113,13 @@ testcase "revert commit" ' --id "$COMMIT_ID" --branch master ' +# Test commit GPG signature +testcase "attempt to get GPG signature of unsigned commit" ' + OUTPUT=$(GITLAB project-commit signature --project-id "$PROJECT_ID" \ + --id "$COMMIT_ID" 2>&1 || exit 0) + echo "$OUTPUT" | grep -q "404 GPG Signature Not Found" +' + # Test project labels testcase "create project label" ' OUTPUT=$(GITLAB -v project-label create --project-id $PROJECT_ID \ diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index fad8c69..69b0d31 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -473,6 +473,14 @@ commit = admin_project.commits.list()[0] # assert commit.refs() # assert commit.merge_requests() +# commit GPG signature (for unsigned commits) +# TODO: reasonable tests for signed commits? +try: + signature = commit.signature() +except gitlab.GitlabGetError as e: + error_message = e.error_message +assert error_message == "404 GPG Signature Not Found" + # commit comment commit.comments.create({"note": "This is a commit comment"}) # assert len(commit.comments.list()) == 1 @@ -4,6 +4,7 @@ skipsdist = True envlist = py38,py37,py36,pep8,black [testenv] +passenv = GITLAB_IMAGE GITLAB_TAG setenv = VIRTUAL_ENV={envdir} whitelist_externals = true usedevelop = True @@ -44,7 +45,7 @@ commands = coverage html --omit=*tests* [testenv:cli_func_v4] -commands = {toxinidir}/tools/functional_tests.sh -a 4 -p 2 +commands = {toxinidir}/tools/functional_tests.sh -a 4 [testenv:py_func_v4] -commands = {toxinidir}/tools/py_functional_tests.sh -a 4 -p 2 +commands = {toxinidir}/tools/py_functional_tests.sh -a 4 |
