From a4e555282531be964160e247fda8aaa080969b59 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sun, 23 Feb 2020 20:49:33 +0100 Subject: [PATCH 01/11] chore: use pytest for unit tests and coverage --- .gitignore | 2 ++ .testr.conf | 4 ---- MANIFEST.in | 2 +- test-requirements.txt | 4 ++-- tox.ini | 10 ++++++---- 5 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 .testr.conf diff --git a/.gitignore b/.gitignore index febd0f7f1..b435627ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ *.pyc build/ dist/ +htmlcov/ MANIFEST .*.swp *.egg-info .idea/ docs/_build +.coverage .testrepository/ .tox venv/ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 44644a639..000000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./gitlab/tests $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/MANIFEST.in b/MANIFEST.in index 2d1b15b11..df53d6691 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include COPYING AUTHORS ChangeLog.rst RELEASE_NOTES.rst requirements.txt test-requirements.txt rtd-requirements.txt -include tox.ini .testr.conf .travis.yml +include tox.ini .travis.yml recursive-include tools * recursive-include docs *j2 *.py *.rst api/*.rst Makefile make.bat recursive-include gitlab/tests/data * diff --git a/test-requirements.txt b/test-requirements.txt index 65d09d7d3..c78843606 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,9 @@ coverage -discover -testrepository hacking>=0.9.2,<0.10 httmock jinja2 mock +pytest +pytest-cov sphinx>=1.3 sphinx_rtd_theme diff --git a/tox.ini b/tox.ini index 0aa43f09e..35ad0a21c 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ install_command = pip install {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = - python setup.py testr --testr-args='{posargs}' + pytest {posargs} gitlab/tests [testenv:pep8] commands = @@ -39,9 +39,11 @@ commands = python setup.py build_sphinx [testenv:cover] commands = - python setup.py testr --slowest --coverage --testr-args="{posargs}" - coverage report --omit=*tests* - coverage html --omit=*tests* + pytest --cov gitlab --cov-report term --cov-report html \ + {posargs} gitlab/tests + +[coverage:run] +omit = *tests* [testenv:cli_func_v4] commands = {toxinidir}/tools/functional_tests.sh -a 4 -p 2 From a65db0a6c88ce15809ac199f99c435a6ce34561a Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sun, 23 Feb 2020 20:54:32 +0100 Subject: [PATCH 02/11] ci: add coverage job to Travis --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 83d2d3391..fc9e93548 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,3 +67,10 @@ jobs: script: - pip3 install tox - tox -e py38 + - stage: test + dist: bionic + name: coverage + python: 3.8 + script: + - pip3 install tox + - tox -e cover From eaa1bd086b2d930ea7d461a7caf524dbed6e7bb2 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Wed, 19 Feb 2020 21:33:23 +0100 Subject: [PATCH 03/11] refactor: move unit tests into tests/unit --- {gitlab/tests => tests/unit}/__init__.py | 0 {gitlab/tests => tests/unit}/data/todo.json | 0 {gitlab/tests => tests/unit}/objects/__init__.py | 0 {gitlab/tests => tests/unit}/objects/test_application.py | 0 {gitlab/tests => tests/unit}/objects/test_projects.py | 0 {gitlab/tests => tests/unit}/test_base.py | 0 {gitlab/tests => tests/unit}/test_cli.py | 0 {gitlab/tests => tests/unit}/test_config.py | 0 {gitlab/tests => tests/unit}/test_gitlab.py | 0 {gitlab/tests => tests/unit}/test_mixins.py | 0 {gitlab/tests => tests/unit}/test_types.py | 0 {gitlab/tests => tests/unit}/test_utils.py | 0 tox.ini | 4 ++-- 13 files changed, 2 insertions(+), 2 deletions(-) rename {gitlab/tests => tests/unit}/__init__.py (100%) rename {gitlab/tests => tests/unit}/data/todo.json (100%) rename {gitlab/tests => tests/unit}/objects/__init__.py (100%) rename {gitlab/tests => tests/unit}/objects/test_application.py (100%) rename {gitlab/tests => tests/unit}/objects/test_projects.py (100%) rename {gitlab/tests => tests/unit}/test_base.py (100%) rename {gitlab/tests => tests/unit}/test_cli.py (100%) rename {gitlab/tests => tests/unit}/test_config.py (100%) rename {gitlab/tests => tests/unit}/test_gitlab.py (100%) rename {gitlab/tests => tests/unit}/test_mixins.py (100%) rename {gitlab/tests => tests/unit}/test_types.py (100%) rename {gitlab/tests => tests/unit}/test_utils.py (100%) diff --git a/gitlab/tests/__init__.py b/tests/unit/__init__.py similarity index 100% rename from gitlab/tests/__init__.py rename to tests/unit/__init__.py diff --git a/gitlab/tests/data/todo.json b/tests/unit/data/todo.json similarity index 100% rename from gitlab/tests/data/todo.json rename to tests/unit/data/todo.json diff --git a/gitlab/tests/objects/__init__.py b/tests/unit/objects/__init__.py similarity index 100% rename from gitlab/tests/objects/__init__.py rename to tests/unit/objects/__init__.py diff --git a/gitlab/tests/objects/test_application.py b/tests/unit/objects/test_application.py similarity index 100% rename from gitlab/tests/objects/test_application.py rename to tests/unit/objects/test_application.py diff --git a/gitlab/tests/objects/test_projects.py b/tests/unit/objects/test_projects.py similarity index 100% rename from gitlab/tests/objects/test_projects.py rename to tests/unit/objects/test_projects.py diff --git a/gitlab/tests/test_base.py b/tests/unit/test_base.py similarity index 100% rename from gitlab/tests/test_base.py rename to tests/unit/test_base.py diff --git a/gitlab/tests/test_cli.py b/tests/unit/test_cli.py similarity index 100% rename from gitlab/tests/test_cli.py rename to tests/unit/test_cli.py diff --git a/gitlab/tests/test_config.py b/tests/unit/test_config.py similarity index 100% rename from gitlab/tests/test_config.py rename to tests/unit/test_config.py diff --git a/gitlab/tests/test_gitlab.py b/tests/unit/test_gitlab.py similarity index 100% rename from gitlab/tests/test_gitlab.py rename to tests/unit/test_gitlab.py diff --git a/gitlab/tests/test_mixins.py b/tests/unit/test_mixins.py similarity index 100% rename from gitlab/tests/test_mixins.py rename to tests/unit/test_mixins.py diff --git a/gitlab/tests/test_types.py b/tests/unit/test_types.py similarity index 100% rename from gitlab/tests/test_types.py rename to tests/unit/test_types.py diff --git a/gitlab/tests/test_utils.py b/tests/unit/test_utils.py similarity index 100% rename from gitlab/tests/test_utils.py rename to tests/unit/test_utils.py diff --git a/tox.ini b/tox.ini index 35ad0a21c..1ffc8a059 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ commands = [testenv:pep8] commands = - flake8 {posargs} gitlab/ + flake8 {posargs} gitlab/ tests/ [testenv:black] basepython = python3 @@ -24,7 +24,7 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt black commands = - black {posargs} gitlab + black {posargs} gitlab tests [testenv:venv] commands = {posargs} From 77f18da14af0aeff763a9c33bfb6d701a05e1285 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Wed, 19 Feb 2020 21:50:30 +0100 Subject: [PATCH 04/11] refactor: move functional tests into tests/functional --- {tools => tests/functional/data}/avatar.png | Bin .../functional/test_api_v4.py | 2 +- .../functional/test_cli_v4.sh | 0 tools/ee-test.py => tests/functional/test_ee.py | 0 tools/functional_tests.sh | 2 +- tools/py_functional_tests.sh | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename {tools => tests/functional/data}/avatar.png (100%) rename tools/python_test_v4.py => tests/functional/test_api_v4.py (99%) rename tools/cli_test_v4.sh => tests/functional/test_cli_v4.sh (100%) rename tools/ee-test.py => tests/functional/test_ee.py (100%) diff --git a/tools/avatar.png b/tests/functional/data/avatar.png similarity index 100% rename from tools/avatar.png rename to tests/functional/data/avatar.png diff --git a/tools/python_test_v4.py b/tests/functional/test_api_v4.py similarity index 99% rename from tools/python_test_v4.py rename to tests/functional/test_api_v4.py index 0703ee340..fdbb518ca 100644 --- a/tools/python_test_v4.py +++ b/tests/functional/test_api_v4.py @@ -56,7 +56,7 @@ qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ== =5OGa -----END PGP PUBLIC KEY BLOCK-----""" -AVATAR_PATH = os.path.join(os.path.dirname(__file__), "avatar.png") +AVATAR_PATH = os.path.join(os.path.dirname(__file__), "data", "avatar.png") # token authentication from config file diff --git a/tools/cli_test_v4.sh b/tests/functional/test_cli_v4.sh similarity index 100% rename from tools/cli_test_v4.sh rename to tests/functional/test_cli_v4.sh diff --git a/tools/ee-test.py b/tests/functional/test_ee.py similarity index 100% rename from tools/ee-test.py rename to tests/functional/test_ee.py diff --git a/tools/functional_tests.sh b/tools/functional_tests.sh index 4123d87fb..9446f9b66 100755 --- a/tools/functional_tests.sh +++ b/tools/functional_tests.sh @@ -18,4 +18,4 @@ setenv_script=$(dirname "$0")/build_test_env.sh || exit 1 BUILD_TEST_ENV_AUTO_CLEANUP=true . "$setenv_script" "$@" || exit 1 -. $(dirname "$0")/cli_test_v${API_VER}.sh +. $(dirname "$0")/../tests/functional/test_cli_v${API_VER}.sh diff --git a/tools/py_functional_tests.sh b/tools/py_functional_tests.sh index 75bb7613d..f88b76a4a 100755 --- a/tools/py_functional_tests.sh +++ b/tools/py_functional_tests.sh @@ -18,4 +18,4 @@ setenv_script=$(dirname "$0")/build_test_env.sh || exit 1 BUILD_TEST_ENV_AUTO_CLEANUP=true . "$setenv_script" "$@" || exit 1 -try python "$(dirname "$0")"/python_test_v${API_VER}.py +try python "$(dirname "$0")"/../tests/functional/test_api_v${API_VER}.py From 54f29071f45167becc7939f5d091583b310d6ca2 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Fri, 28 Feb 2020 21:48:49 +0100 Subject: [PATCH 05/11] chore: clean up paths and posargs order --- tox.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 1ffc8a059..96910108e 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ install_command = pip install {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = - pytest {posargs} gitlab/tests + pytest tests/unit {posargs} [testenv:pep8] commands = @@ -39,8 +39,7 @@ commands = python setup.py build_sphinx [testenv:cover] commands = - pytest --cov gitlab --cov-report term --cov-report html \ - {posargs} gitlab/tests + pytest tests/unit --cov gitlab --cov-report term --cov-report html {posargs} [coverage:run] omit = *tests* From 7fe0aeb6adb34262463126f39911c3e6abeeee0a Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Fri, 28 Feb 2020 21:57:10 +0100 Subject: [PATCH 06/11] chore: drop python2 references in testenv --- tools/build_test_env.sh | 12 +++--------- tox.ini | 4 ++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tools/build_test_env.sh b/tools/build_test_env.sh index 7468a9a7d..6ebdfd3d1 100755 --- a/tools/build_test_env.sh +++ b/tools/build_test_env.sh @@ -27,15 +27,15 @@ 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 +VENV_CMD="python3 -m venv" + +while getopts :kn:a: opt "$@"; do case $opt in k) REUSE_CONTAINER=1;; n) NOVENV=1;; - p) PY_VER=$OPTARG;; a) API_VER=$OPTARG;; t) GITLAB_TAG=$OPTARG;; :) fatal "Option -${OPTARG} requires a value";; @@ -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/tox.ini b/tox.ini index 96910108e..987fcd789 100644 --- a/tox.ini +++ b/tox.ini @@ -45,7 +45,7 @@ commands = 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 From 8d4d08da3c180a792fce14a5cca9227b94f4edc0 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Fri, 28 Feb 2020 23:01:35 +0100 Subject: [PATCH 07/11] test: ensure pytest is in functional test env --- tools/build_test_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build_test_env.sh b/tools/build_test_env.sh index 6ebdfd3d1..f6c91ee2d 100755 --- a/tools/build_test_env.sh +++ b/tools/build_test_env.sh @@ -124,7 +124,7 @@ if [ -z "$NOVENV" ]; then . "$VENV"/bin/activate || fatal "failed to activate Python virtual environment" log "Installing dependencies into virtualenv..." - try pip install -r requirements.txt + try pip install -r requirements.txt -r test-requirements.txt log "Installing into virtualenv..." try pip install -e . From 9870c969309cd7623fb4c3601df84a9cb2ca4fcf Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sat, 29 Feb 2020 10:40:53 +0100 Subject: [PATCH 08/11] test: wrap EE tests in a skipped pytest testcase This makes one huge testcase but it can be split up later maybe so that features can start to be picked up by pytest as they are moved from EE tiers into core etc. --- tests/functional/test_ee.py | 272 ++++++++++++++++++------------------ 1 file changed, 138 insertions(+), 134 deletions(-) diff --git a/tests/functional/test_ee.py b/tests/functional/test_ee.py index af1295788..4e0bd691e 100755 --- a/tests/functional/test_ee.py +++ b/tests/functional/test_ee.py @@ -22,138 +22,142 @@ def end_log(): print("OK") -gl = gitlab.Gitlab.from_config("ee") -project1 = gl.projects.get(P1) -project2 = gl.projects.get(P2) -issue_p1 = project1.issues.get(I_P1) -issue_p2 = project2.issues.get(I_P2) -group1 = gl.groups.get(G1) -mr = project1.mergerequests.get(1) - -start_log("MR approvals") -approval = project1.approvals.get() -v = approval.reset_approvals_on_push -approval.reset_approvals_on_push = not v -approval.save() -approval = project1.approvals.get() -assert v != approval.reset_approvals_on_push -project1.approvals.set_approvers(1, [1], []) -approval = project1.approvals.get() -assert approval.approvers[0]["user"]["id"] == 1 - -approval = mr.approvals.get() -approval.approvals_required = 2 -approval.save() -approval = mr.approvals.get() -assert approval.approvals_required == 2 -approval.approvals_required = 3 -approval.save() -approval = mr.approvals.get() -assert approval.approvals_required == 3 -mr.approvals.set_approvers(1, [1], []) -approval = mr.approvals.get() -assert approval.approvers[0]["user"]["id"] == 1 - -ars = project1.approvalrules.list(all=True) -assert len(ars) == 0 -project.approvalrules.create( - {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} -) -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -ars[0].approvals_required == 2 -ars[0].save() -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -assert ars[0].approvals_required == 2 -ars[0].delete() -ars = project1.approvalrules.list(all=True) -assert len(ars) == 0 -end_log() - -start_log("geo nodes") -# very basic tests because we only have 1 node... -nodes = gl.geonodes.list() -status = gl.geonodes.status() -end_log() - -start_log("issue links") -# bit of cleanup just in case -for link in issue_p1.links.list(): - issue_p1.links.delete(link.issue_link_id) - -src, dst = issue_p1.links.create({"target_project_id": P2, "target_issue_iid": I_P2}) -links = issue_p1.links.list() -link_id = links[0].issue_link_id -issue_p1.links.delete(link_id) -end_log() - -start_log("LDAP links") -# bit of cleanup just in case -if hasattr(group1, "ldap_group_links"): - for link in group1.ldap_group_links: - group1.delete_ldap_group_link(link["cn"], link["provider"]) -assert gl.ldapgroups.list() -group1.add_ldap_group_link(LDAP_CN, 30, LDAP_PROVIDER) -group1.ldap_sync() -group1.delete_ldap_group_link(LDAP_CN) -end_log() - -start_log("boards") -# bit of cleanup just in case -for board in project1.boards.list(): - if board.name == "testboard": - board.delete() -board = project1.boards.create({"name": "testboard"}) -board = project1.boards.get(board.id) -project1.boards.delete(board.id) - -for board in group1.boards.list(): - if board.name == "testboard": - board.delete() -board = group1.boards.create({"name": "testboard"}) -board = group1.boards.get(board.id) -group1.boards.delete(board.id) -end_log() - -start_log("push rules") -pr = project1.pushrules.get() -if pr: +@pytest.mark.skip(reason="Tests require EE license") +def test_ee_features(): + gl = gitlab.Gitlab.from_config("ee") + project1 = gl.projects.get(P1) + project2 = gl.projects.get(P2) + issue_p1 = project1.issues.get(I_P1) + issue_p2 = project2.issues.get(I_P2) + group1 = gl.groups.get(G1) + mr = project1.mergerequests.get(1) + + start_log("MR approvals") + approval = project1.approvals.get() + v = approval.reset_approvals_on_push + approval.reset_approvals_on_push = not v + approval.save() + approval = project1.approvals.get() + assert v != approval.reset_approvals_on_push + project1.approvals.set_approvers(1, [1], []) + approval = project1.approvals.get() + assert approval.approvers[0]["user"]["id"] == 1 + + approval = mr.approvals.get() + approval.approvals_required = 2 + approval.save() + approval = mr.approvals.get() + assert approval.approvals_required == 2 + approval.approvals_required = 3 + approval.save() + approval = mr.approvals.get() + assert approval.approvals_required == 3 + mr.approvals.set_approvers(1, [1], []) + approval = mr.approvals.get() + assert approval.approvers[0]["user"]["id"] == 1 + + ars = project1.approvalrules.list(all=True) + assert len(ars) == 0 + project.approvalrules.create( + {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} + ) + ars = project1.approvalrules.list(all=True) + assert len(ars) == 1 + ars[0].approvals_required == 2 + ars[0].save() + ars = project1.approvalrules.list(all=True) + assert len(ars) == 1 + assert ars[0].approvals_required == 2 + ars[0].delete() + ars = project1.approvalrules.list(all=True) + assert len(ars) == 0 + end_log() + + start_log("geo nodes") + # very basic tests because we only have 1 node... + nodes = gl.geonodes.list() + status = gl.geonodes.status() + end_log() + + start_log("issue links") + # bit of cleanup just in case + for link in issue_p1.links.list(): + issue_p1.links.delete(link.issue_link_id) + + src, dst = issue_p1.links.create( + {"target_project_id": P2, "target_issue_iid": I_P2} + ) + links = issue_p1.links.list() + link_id = links[0].issue_link_id + issue_p1.links.delete(link_id) + end_log() + + start_log("LDAP links") + # bit of cleanup just in case + if hasattr(group1, "ldap_group_links"): + for link in group1.ldap_group_links: + group1.delete_ldap_group_link(link["cn"], link["provider"]) + assert gl.ldapgroups.list() + group1.add_ldap_group_link(LDAP_CN, 30, LDAP_PROVIDER) + group1.ldap_sync() + group1.delete_ldap_group_link(LDAP_CN) + end_log() + + start_log("boards") + # bit of cleanup just in case + for board in project1.boards.list(): + if board.name == "testboard": + board.delete() + board = project1.boards.create({"name": "testboard"}) + board = project1.boards.get(board.id) + project1.boards.delete(board.id) + + for board in group1.boards.list(): + if board.name == "testboard": + board.delete() + board = group1.boards.create({"name": "testboard"}) + board = group1.boards.get(board.id) + group1.boards.delete(board.id) + end_log() + + start_log("push rules") + pr = project1.pushrules.get() + if pr: + pr.delete() + pr = project1.pushrules.create({"deny_delete_tag": True}) + pr.deny_delete_tag = False + pr.save() + pr = project1.pushrules.get() + assert pr is not None + assert pr.deny_delete_tag == False pr.delete() -pr = project1.pushrules.create({"deny_delete_tag": True}) -pr.deny_delete_tag = False -pr.save() -pr = project1.pushrules.get() -assert pr is not None -assert pr.deny_delete_tag == False -pr.delete() -end_log() - -start_log("license") -l = gl.get_license() -assert "user_limit" in l -try: - gl.set_license("dummykey") -except Exception as e: - assert "The license key is invalid." in e.error_message -end_log() - -start_log("epics") -epic = group1.epics.create({"title": "Test epic"}) -epic.title = "Fixed title" -epic.labels = ["label1", "label2"] -epic.save() -epic = group1.epics.get(epic.iid) -assert epic.title == "Fixed title" -assert len(group1.epics.list()) - -# issues -assert not epic.issues.list() -for i in EPIC_ISSUES: - epic.issues.create({"issue_id": i}) -assert len(EPIC_ISSUES) == len(epic.issues.list()) -for ei in epic.issues.list(): - ei.delete() - -epic.delete() -end_log() + end_log() + + start_log("license") + l = gl.get_license() + assert "user_limit" in l + try: + gl.set_license("dummykey") + except Exception as e: + assert "The license key is invalid." in e.error_message + end_log() + + start_log("epics") + epic = group1.epics.create({"title": "Test epic"}) + epic.title = "Fixed title" + epic.labels = ["label1", "label2"] + epic.save() + epic = group1.epics.get(epic.iid) + assert epic.title == "Fixed title" + assert len(group1.epics.list()) + + # issues + assert not epic.issues.list() + for i in EPIC_ISSUES: + epic.issues.create({"issue_id": i}) + assert len(EPIC_ISSUES) == len(epic.issues.list()) + for ei in epic.issues.list(): + ei.delete() + + epic.delete() + end_log() From 3d227eec8ec53ba2fa2be89486ca5847680c1810 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sat, 29 Feb 2020 11:38:56 +0100 Subject: [PATCH 09/11] fix: add missing import --- tests/functional/test_ee.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional/test_ee.py b/tests/functional/test_ee.py index 4e0bd691e..e69badc4f 100755 --- a/tests/functional/test_ee.py +++ b/tests/functional/test_ee.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +import pytest + import gitlab From 216ba735e8cfa08695c04491d00c9dd556f1ac5d Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sat, 29 Feb 2020 13:43:39 +0100 Subject: [PATCH 10/11] fix: ensure reset_gitlab works for teardown --- tools/reset_gitlab.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/reset_gitlab.py b/tools/reset_gitlab.py index 64668a974..d67cf2ece 100755 --- a/tools/reset_gitlab.py +++ b/tools/reset_gitlab.py @@ -1,19 +1,25 @@ #!/usr/bin/env python import sys +import time from gitlab import Gitlab def main(): with Gitlab.from_config(config_files=["/tmp/python-gitlab.cfg"]) as gl: - for project in gl.projects.list(): + for project in gl.projects.list(all=True): project.delete() - for group in gl.groups.list(): + for group in gl.groups.list(all=True): group.delete() + + # Gitlab needs time to delete resources; avoid 409 on user deletion + while gl.projects.list(all=True) or gl.groups.list(all=True): + time.sleep(0.1) + for user in gl.users.list(): if user.username != "root": - user.delete() + user.delete(hard_delete=True) if __name__ == "__main__": From d501e5f63aa6b03bb83cacf46a5211de476a1271 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sat, 29 Feb 2020 14:11:38 +0100 Subject: [PATCH 11/11] test(func_v4): convert plain asserts to one large pytest testcase This is just a first step before breaking them down. --- test-requirements.txt | 1 + tests/functional/test_api_v4.py | 1915 ++++++++++++++++--------------- tools/py_functional_tests.sh | 2 +- 3 files changed, 974 insertions(+), 944 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index c78843606..a5651cdd1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,5 @@ coverage +flaky hacking>=0.9.2,<0.10 httmock jinja2 diff --git a/tests/functional/test_api_v4.py b/tests/functional/test_api_v4.py index fdbb518ca..f5e41576c 100644 --- a/tests/functional/test_api_v4.py +++ b/tests/functional/test_api_v4.py @@ -2,10 +2,14 @@ import os import time +import pytest import requests import gitlab +from tools import reset_gitlab + +CONFIG_FILE = "/tmp/python-gitlab.cfg" LOGIN = "root" PASSWORD = "5iveL!fe" @@ -59,955 +63,980 @@ AVATAR_PATH = os.path.join(os.path.dirname(__file__), "data", "avatar.png") -# token authentication from config file -gl = gitlab.Gitlab.from_config(config_files=["/tmp/python-gitlab.cfg"]) -gl.auth() -assert isinstance(gl.user, gitlab.v4.objects.CurrentUser) - -# markdown -html = gl.markdown("foo") -assert "foo" in html - -success, errors = gl.lint("Invalid") -assert success is False -assert errors - -# sidekiq -out = gl.sidekiq.queue_metrics() -assert isinstance(out, dict) -assert "pages" in out["queues"] -out = gl.sidekiq.process_metrics() -assert isinstance(out, dict) -assert "hostname" in out["processes"][0] -out = gl.sidekiq.job_stats() -assert isinstance(out, dict) -assert "processed" in out["jobs"] -out = gl.sidekiq.compound_metrics() -assert isinstance(out, dict) -assert "jobs" in out -assert "processes" in out -assert "queues" in out - -# settings -settings = gl.settings.get() -settings.default_projects_limit = 42 -settings.save() -settings = gl.settings.get() -assert settings.default_projects_limit == 42 - -# users -new_user = gl.users.create( - { - "email": "foo@bar.com", - "username": "foo", - "name": "foo", - "password": "foo_password", - "avatar": open(AVATAR_PATH, "rb"), - } -) -avatar_url = new_user.avatar_url.replace("gitlab.test", "localhost:8080") -uploaded_avatar = requests.get(avatar_url).content -assert uploaded_avatar == open(AVATAR_PATH, "rb").read() -users_list = gl.users.list() -for user in users_list: - if user.username == "foo": - break -assert new_user.username == user.username -assert new_user.email == user.email - -new_user.block() -new_user.unblock() - -# user projects list -assert len(new_user.projects.list()) == 0 - -# events list -new_user.events.list() - -foobar_user = gl.users.create( - { - "email": "foobar@example.com", - "username": "foobar", - "name": "Foo Bar", - "password": "foobar_password", - } -) +@pytest.fixture() +def gl(): + gl = gitlab.Gitlab.from_config(config_files=[CONFIG_FILE]) + gl.auth() + + yield gl + + # Teardown for flaky re-runs + reset_gitlab.main() + + +def test_token_auth_from_config_file(): + gl = gitlab.Gitlab.from_config(config_files=[CONFIG_FILE]) + gl.auth() + + assert isinstance(gl.user, gitlab.v4.objects.CurrentUser) + + +@pytest.mark.flaky(max_runs=3) +def test_api_v4(gl): + + # markdown + html = gl.markdown("foo") + assert "foo" in html + + success, errors = gl.lint("Invalid") + assert success is False + assert errors + + # sidekiq + out = gl.sidekiq.queue_metrics() + assert isinstance(out, dict) + assert "pages" in out["queues"] + out = gl.sidekiq.process_metrics() + assert isinstance(out, dict) + assert "hostname" in out["processes"][0] + out = gl.sidekiq.job_stats() + assert isinstance(out, dict) + assert "processed" in out["jobs"] + out = gl.sidekiq.compound_metrics() + assert isinstance(out, dict) + assert "jobs" in out + assert "processes" in out + assert "queues" in out + + # settings + settings = gl.settings.get() + settings.default_projects_limit = 42 + settings.save() + settings = gl.settings.get() + assert settings.default_projects_limit == 42 + + # users + new_user = gl.users.create( + { + "email": "foo@bar.com", + "username": "foo", + "name": "foo", + "password": "foo_password", + "avatar": open(AVATAR_PATH, "rb"), + } + ) + avatar_url = new_user.avatar_url.replace("gitlab.test", "localhost:8080") + uploaded_avatar = requests.get(avatar_url).content + assert uploaded_avatar == open(AVATAR_PATH, "rb").read() + users_list = gl.users.list() + for user in users_list: + if user.username == "foo": + break + assert new_user.username == user.username + assert new_user.email == user.email + + new_user.block() + new_user.unblock() + + # user projects list + assert len(new_user.projects.list()) == 0 + + # events list + new_user.events.list() + + foobar_user = gl.users.create( + { + "email": "foobar@example.com", + "username": "foobar", + "name": "Foo Bar", + "password": "foobar_password", + } + ) + + assert gl.users.list(search="foobar")[0].id == foobar_user.id + expected = [new_user, foobar_user] + actual = list(gl.users.list(search="foo")) + assert len(expected) == len(actual) + assert len(gl.users.list(search="asdf")) == 0 + foobar_user.bio = "This is the user bio" + foobar_user.save() + + # GPG keys + gkey = new_user.gpgkeys.create({"key": GPG_KEY}) + assert len(new_user.gpgkeys.list()) == 1 + # Seems broken on the gitlab side + # gkey = new_user.gpgkeys.get(gkey.id) + gkey.delete() + assert len(new_user.gpgkeys.list()) == 0 + + # SSH keys + key = new_user.keys.create({"title": "testkey", "key": SSH_KEY}) + assert len(new_user.keys.list()) == 1 + key.delete() + assert len(new_user.keys.list()) == 0 + + # emails + email = new_user.emails.create({"email": "foo2@bar.com"}) + assert len(new_user.emails.list()) == 1 + email.delete() + assert len(new_user.emails.list()) == 0 + + # custom attributes + attrs = new_user.customattributes.list() + assert len(attrs) == 0 + attr = new_user.customattributes.set("key", "value1") + assert len(gl.users.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(new_user.customattributes.list()) == 1 + attr = new_user.customattributes.set("key", "value2") + attr = new_user.customattributes.get("key") + assert attr.value == "value2" + assert len(new_user.customattributes.list()) == 1 + attr.delete() + assert len(new_user.customattributes.list()) == 0 + + # impersonation tokens + user_token = new_user.impersonationtokens.create( + {"name": "token1", "scopes": ["api", "read_user"]} + ) + l = new_user.impersonationtokens.list(state="active") + assert len(l) == 1 + user_token.delete() + l = new_user.impersonationtokens.list(state="active") + assert len(l) == 0 + l = new_user.impersonationtokens.list(state="inactive") + assert len(l) == 1 + + new_user.delete() + foobar_user.delete() + assert len(gl.users.list()) == 3 + len( + [u for u in gl.users.list() if u.username == "ghost"] + ) + + # current user mail + mail = gl.user.emails.create({"email": "current@user.com"}) + assert len(gl.user.emails.list()) == 1 + mail.delete() + assert len(gl.user.emails.list()) == 0 + + # current user GPG keys + gkey = gl.user.gpgkeys.create({"key": GPG_KEY}) + assert len(gl.user.gpgkeys.list()) == 1 + # Seems broken on the gitlab side + gkey = gl.user.gpgkeys.get(gkey.id) + gkey.delete() + assert len(gl.user.gpgkeys.list()) == 0 + + # current user key + key = gl.user.keys.create({"title": "testkey", "key": SSH_KEY}) + assert len(gl.user.keys.list()) == 1 + key.delete() + assert len(gl.user.keys.list()) == 0 + + # templates + assert gl.dockerfiles.list() + dockerfile = gl.dockerfiles.get("Node") + assert dockerfile.content is not None + + assert gl.gitignores.list() + gitignore = gl.gitignores.get("Node") + assert gitignore.content is not None + + assert gl.gitlabciymls.list() + gitlabciyml = gl.gitlabciymls.get("Nodejs") + assert gitlabciyml.content is not None + + assert gl.licenses.list() + license = gl.licenses.get( + "bsd-2-clause", project="mytestproject", fullname="mytestfullname" + ) + assert "mytestfullname" in license.content + + # groups + user1 = gl.users.create( + { + "email": "user1@test.com", + "username": "user1", + "name": "user1", + "password": "user1_pass", + } + ) + user2 = gl.users.create( + { + "email": "user2@test.com", + "username": "user2", + "name": "user2", + "password": "user2_pass", + } + ) + group1 = gl.groups.create({"name": "group1", "path": "group1"}) + group2 = gl.groups.create({"name": "group2", "path": "group2"}) + + p_id = gl.groups.list(search="group2")[0].id + group3 = gl.groups.create({"name": "group3", "path": "group3", "parent_id": p_id}) + + assert len(gl.groups.list()) == 3 + assert len(gl.groups.list(search="oup1")) == 1 + assert group3.parent_id == p_id + assert group2.subgroups.list()[0].id == group3.id + + group1.members.create( + {"access_level": gitlab.const.OWNER_ACCESS, "user_id": user1.id} + ) + group1.members.create( + {"access_level": gitlab.const.GUEST_ACCESS, "user_id": user2.id} + ) + + group2.members.create( + {"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id} + ) + + # User memberships (admin only) + memberships1 = user1.memberships.list() + assert len(memberships1) == 1 + + memberships2 = user2.memberships.list() + assert len(memberships2) == 2 + + membership = memberships1[0] + assert membership.source_type == "Namespace" + assert membership.access_level == gitlab.const.OWNER_ACCESS + + project_memberships = user1.memberships.list(type="Project") + assert len(project_memberships) == 0 + + group_memberships = user1.memberships.list(type="Namespace") + assert len(group_memberships) == 1 -assert gl.users.list(search="foobar")[0].id == foobar_user.id -expected = [new_user, foobar_user] -actual = list(gl.users.list(search="foo")) -assert len(expected) == len(actual) -assert len(gl.users.list(search="asdf")) == 0 -foobar_user.bio = "This is the user bio" -foobar_user.save() - -# GPG keys -gkey = new_user.gpgkeys.create({"key": GPG_KEY}) -assert len(new_user.gpgkeys.list()) == 1 -# Seems broken on the gitlab side -# gkey = new_user.gpgkeys.get(gkey.id) -gkey.delete() -assert len(new_user.gpgkeys.list()) == 0 - -# SSH keys -key = new_user.keys.create({"title": "testkey", "key": SSH_KEY}) -assert len(new_user.keys.list()) == 1 -key.delete() -assert len(new_user.keys.list()) == 0 - -# emails -email = new_user.emails.create({"email": "foo2@bar.com"}) -assert len(new_user.emails.list()) == 1 -email.delete() -assert len(new_user.emails.list()) == 0 - -# custom attributes -attrs = new_user.customattributes.list() -assert len(attrs) == 0 -attr = new_user.customattributes.set("key", "value1") -assert len(gl.users.list(custom_attributes={"key": "value1"})) == 1 -assert attr.key == "key" -assert attr.value == "value1" -assert len(new_user.customattributes.list()) == 1 -attr = new_user.customattributes.set("key", "value2") -attr = new_user.customattributes.get("key") -assert attr.value == "value2" -assert len(new_user.customattributes.list()) == 1 -attr.delete() -assert len(new_user.customattributes.list()) == 0 - -# impersonation tokens -user_token = new_user.impersonationtokens.create( - {"name": "token1", "scopes": ["api", "read_user"]} -) -l = new_user.impersonationtokens.list(state="active") -assert len(l) == 1 -user_token.delete() -l = new_user.impersonationtokens.list(state="active") -assert len(l) == 0 -l = new_user.impersonationtokens.list(state="inactive") -assert len(l) == 1 - -new_user.delete() -foobar_user.delete() -assert len(gl.users.list()) == 3 + len( - [u for u in gl.users.list() if u.username == "ghost"] -) + try: + membership = user1.memberships.list(type="Invalid") + except gitlab.GitlabListError as e: + error_message = e.error_message + assert error_message == "type does not have a valid value" -# current user mail -mail = gl.user.emails.create({"email": "current@user.com"}) -assert len(gl.user.emails.list()) == 1 -mail.delete() -assert len(gl.user.emails.list()) == 0 - -# current user GPG keys -gkey = gl.user.gpgkeys.create({"key": GPG_KEY}) -assert len(gl.user.gpgkeys.list()) == 1 -# Seems broken on the gitlab side -gkey = gl.user.gpgkeys.get(gkey.id) -gkey.delete() -assert len(gl.user.gpgkeys.list()) == 0 - -# current user key -key = gl.user.keys.create({"title": "testkey", "key": SSH_KEY}) -assert len(gl.user.keys.list()) == 1 -key.delete() -assert len(gl.user.keys.list()) == 0 - -# templates -assert gl.dockerfiles.list() -dockerfile = gl.dockerfiles.get("Node") -assert dockerfile.content is not None - -assert gl.gitignores.list() -gitignore = gl.gitignores.get("Node") -assert gitignore.content is not None - -assert gl.gitlabciymls.list() -gitlabciyml = gl.gitlabciymls.get("Nodejs") -assert gitlabciyml.content is not None - -assert gl.licenses.list() -license = gl.licenses.get( - "bsd-2-clause", project="mytestproject", fullname="mytestfullname" -) -assert "mytestfullname" in license.content - -# groups -user1 = gl.users.create( - { - "email": "user1@test.com", - "username": "user1", - "name": "user1", - "password": "user1_pass", - } -) -user2 = gl.users.create( - { - "email": "user2@test.com", - "username": "user2", - "name": "user2", - "password": "user2_pass", - } -) -group1 = gl.groups.create({"name": "group1", "path": "group1"}) -group2 = gl.groups.create({"name": "group2", "path": "group2"}) - -p_id = gl.groups.list(search="group2")[0].id -group3 = gl.groups.create({"name": "group3", "path": "group3", "parent_id": p_id}) - -assert len(gl.groups.list()) == 3 -assert len(gl.groups.list(search="oup1")) == 1 -assert group3.parent_id == p_id -assert group2.subgroups.list()[0].id == group3.id - -group1.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user1.id}) -group1.members.create({"access_level": gitlab.const.GUEST_ACCESS, "user_id": user2.id}) - -group2.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id}) - -# User memberships (admin only) -memberships1 = user1.memberships.list() -assert len(memberships1) == 1 - -memberships2 = user2.memberships.list() -assert len(memberships2) == 2 - -membership = memberships1[0] -assert membership.source_type == "Namespace" -assert membership.access_level == gitlab.const.OWNER_ACCESS - -project_memberships = user1.memberships.list(type="Project") -assert len(project_memberships) == 0 - -group_memberships = user1.memberships.list(type="Namespace") -assert len(group_memberships) == 1 - -try: - membership = user1.memberships.list(type="Invalid") -except gitlab.GitlabListError as e: - error_message = e.error_message -assert error_message == "type does not have a valid value" - -try: - user1.memberships.list(sudo=user1.name) -except gitlab.GitlabListError as e: - error_message = e.error_message -assert error_message == "403 Forbidden" - -# Administrator belongs to the groups -assert len(group1.members.list()) == 3 -assert len(group2.members.list()) == 2 - -group1.members.delete(user1.id) -assert len(group1.members.list()) == 2 -assert len(group1.members.all()) -member = group1.members.get(user2.id) -member.access_level = gitlab.const.OWNER_ACCESS -member.save() -member = group1.members.get(user2.id) -assert member.access_level == gitlab.const.OWNER_ACCESS - -group2.members.delete(gl.user.id) - -# group custom attributes -attrs = group2.customattributes.list() -assert len(attrs) == 0 -attr = group2.customattributes.set("key", "value1") -assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 -assert attr.key == "key" -assert attr.value == "value1" -assert len(group2.customattributes.list()) == 1 -attr = group2.customattributes.set("key", "value2") -attr = group2.customattributes.get("key") -assert attr.value == "value2" -assert len(group2.customattributes.list()) == 1 -attr.delete() -assert len(group2.customattributes.list()) == 0 - -# group notification settings -settings = group2.notificationsettings.get() -settings.level = "disabled" -settings.save() -settings = group2.notificationsettings.get() -assert settings.level == "disabled" - -# group badges -badge_image = "http://example.com" -badge_link = "http://example/img.svg" -badge = group2.badges.create({"link_url": badge_link, "image_url": badge_image}) -assert len(group2.badges.list()) == 1 -badge.image_url = "http://another.example.com" -badge.save() -badge = group2.badges.get(badge.id) -assert badge.image_url == "http://another.example.com" -badge.delete() -assert len(group2.badges.list()) == 0 - -# group milestones -gm1 = group1.milestones.create({"title": "groupmilestone1"}) -assert len(group1.milestones.list()) == 1 -gm1.due_date = "2020-01-01T00:00:00Z" -gm1.save() -gm1.state_event = "close" -gm1.save() -gm1 = group1.milestones.get(gm1.id) -assert gm1.state == "closed" -assert len(gm1.issues()) == 0 -assert len(gm1.merge_requests()) == 0 - -# group variables -group1.variables.create({"key": "foo", "value": "bar"}) -g_v = group1.variables.get("foo") -assert g_v.value == "bar" -g_v.value = "baz" -g_v.save() -g_v = group1.variables.get("foo") -assert g_v.value == "baz" -assert len(group1.variables.list()) == 1 -g_v.delete() -assert len(group1.variables.list()) == 0 - -# group labels -# group1.labels.create({"name": "foo", "description": "bar", "color": "#112233"}) -# g_l = group1.labels.get("foo") -# assert g_l.description == "bar" -# g_l.description = "baz" -# g_l.save() -# g_l = group1.labels.get("foo") -# assert g_l.description == "baz" -# assert len(group1.labels.list()) == 1 -# g_l.delete() -# assert len(group1.labels.list()) == 0 - -# hooks -hook = gl.hooks.create({"url": "http://whatever.com"}) -assert len(gl.hooks.list()) == 1 -hook.delete() -assert len(gl.hooks.list()) == 0 - -# projects -admin_project = gl.projects.create({"name": "admin_project"}) -gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) -gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group2.id}) -sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user1.name) - -assert len(gl.projects.list(owned=True)) == 2 -assert len(gl.projects.list(search="admin")) == 1 - -# test pagination -l1 = gl.projects.list(per_page=1, page=1) -l2 = gl.projects.list(per_page=1, page=2) -assert len(l1) == 1 -assert len(l2) == 1 -assert l1[0].id != l2[0].id - -# group custom attributes -attrs = admin_project.customattributes.list() -assert len(attrs) == 0 -attr = admin_project.customattributes.set("key", "value1") -assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 -assert attr.key == "key" -assert attr.value == "value1" -assert len(admin_project.customattributes.list()) == 1 -attr = admin_project.customattributes.set("key", "value2") -attr = admin_project.customattributes.get("key") -assert attr.value == "value2" -assert len(admin_project.customattributes.list()) == 1 -attr.delete() -assert len(admin_project.customattributes.list()) == 0 - -# project pages domains -domain = admin_project.pagesdomains.create({"domain": "foo.domain.com"}) -assert len(admin_project.pagesdomains.list()) == 1 -assert len(gl.pagesdomains.list()) == 1 -domain = admin_project.pagesdomains.get("foo.domain.com") -assert domain.domain == "foo.domain.com" -domain.delete() -assert len(admin_project.pagesdomains.list()) == 0 - -# project content (files) -admin_project.files.create( - { - "file_path": "README", - "branch": "master", - "content": "Initial content", - "commit_message": "Initial commit", - } -) -readme = admin_project.files.get(file_path="README", ref="master") -readme.content = base64.b64encode(b"Improved README").decode() -time.sleep(2) -readme.save(branch="master", commit_message="new commit") -readme.delete(commit_message="Removing README", branch="master") - -admin_project.files.create( - { - "file_path": "README.rst", + try: + user1.memberships.list(sudo=user1.name) + except gitlab.GitlabListError as e: + error_message = e.error_message + assert error_message == "403 Forbidden" + + # Administrator belongs to the groups + assert len(group1.members.list()) == 3 + assert len(group2.members.list()) == 2 + + group1.members.delete(user1.id) + assert len(group1.members.list()) == 2 + assert len(group1.members.all()) + member = group1.members.get(user2.id) + member.access_level = gitlab.const.OWNER_ACCESS + member.save() + member = group1.members.get(user2.id) + assert member.access_level == gitlab.const.OWNER_ACCESS + + group2.members.delete(gl.user.id) + + # group custom attributes + attrs = group2.customattributes.list() + assert len(attrs) == 0 + attr = group2.customattributes.set("key", "value1") + assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(group2.customattributes.list()) == 1 + attr = group2.customattributes.set("key", "value2") + attr = group2.customattributes.get("key") + assert attr.value == "value2" + assert len(group2.customattributes.list()) == 1 + attr.delete() + assert len(group2.customattributes.list()) == 0 + + # group notification settings + settings = group2.notificationsettings.get() + settings.level = "disabled" + settings.save() + settings = group2.notificationsettings.get() + assert settings.level == "disabled" + + # group badges + badge_image = "http://example.com" + badge_link = "http://example/img.svg" + badge = group2.badges.create({"link_url": badge_link, "image_url": badge_image}) + assert len(group2.badges.list()) == 1 + badge.image_url = "http://another.example.com" + badge.save() + badge = group2.badges.get(badge.id) + assert badge.image_url == "http://another.example.com" + badge.delete() + assert len(group2.badges.list()) == 0 + + # group milestones + gm1 = group1.milestones.create({"title": "groupmilestone1"}) + assert len(group1.milestones.list()) == 1 + gm1.due_date = "2020-01-01T00:00:00Z" + gm1.save() + gm1.state_event = "close" + gm1.save() + gm1 = group1.milestones.get(gm1.id) + assert gm1.state == "closed" + assert len(gm1.issues()) == 0 + assert len(gm1.merge_requests()) == 0 + + # group variables + group1.variables.create({"key": "foo", "value": "bar"}) + g_v = group1.variables.get("foo") + assert g_v.value == "bar" + g_v.value = "baz" + g_v.save() + g_v = group1.variables.get("foo") + assert g_v.value == "baz" + assert len(group1.variables.list()) == 1 + g_v.delete() + assert len(group1.variables.list()) == 0 + + # group labels + # group1.labels.create({"name": "foo", "description": "bar", "color": "#112233"}) + # g_l = group1.labels.get("foo") + # assert g_l.description == "bar" + # g_l.description = "baz" + # g_l.save() + # g_l = group1.labels.get("foo") + # assert g_l.description == "baz" + # assert len(group1.labels.list()) == 1 + # g_l.delete() + # assert len(group1.labels.list()) == 0 + + # hooks + hook = gl.hooks.create({"url": "http://whatever.com"}) + assert len(gl.hooks.list()) == 1 + hook.delete() + assert len(gl.hooks.list()) == 0 + + # projects + admin_project = gl.projects.create({"name": "admin_project"}) + gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) + gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group2.id}) + sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user1.name) + + assert len(gl.projects.list(owned=True)) == 2 + assert len(gl.projects.list(search="admin")) == 1 + + # test pagination + l1 = gl.projects.list(per_page=1, page=1) + l2 = gl.projects.list(per_page=1, page=2) + assert len(l1) == 1 + assert len(l2) == 1 + assert l1[0].id != l2[0].id + + # group custom attributes + attrs = admin_project.customattributes.list() + assert len(attrs) == 0 + attr = admin_project.customattributes.set("key", "value1") + assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(admin_project.customattributes.list()) == 1 + attr = admin_project.customattributes.set("key", "value2") + attr = admin_project.customattributes.get("key") + assert attr.value == "value2" + assert len(admin_project.customattributes.list()) == 1 + attr.delete() + assert len(admin_project.customattributes.list()) == 0 + + # project pages domains + domain = admin_project.pagesdomains.create({"domain": "foo.domain.com"}) + assert len(admin_project.pagesdomains.list()) == 1 + assert len(gl.pagesdomains.list()) == 1 + domain = admin_project.pagesdomains.get("foo.domain.com") + assert domain.domain == "foo.domain.com" + domain.delete() + assert len(admin_project.pagesdomains.list()) == 0 + + # project content (files) + admin_project.files.create( + { + "file_path": "README", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } + ) + readme = admin_project.files.get(file_path="README", ref="master") + readme.content = base64.b64encode(b"Improved README").decode() + time.sleep(2) + readme.save(branch="master", commit_message="new commit") + readme.delete(commit_message="Removing README", branch="master") + + admin_project.files.create( + { + "file_path": "README.rst", + "branch": "master", + "content": "Initial content", + "commit_message": "New commit", + } + ) + readme = admin_project.files.get(file_path="README.rst", ref="master") + # The first decode() is the ProjectFile method, the second one is the bytes + # object method + assert readme.decode().decode() == "Initial content" + + blame = admin_project.files.blame(file_path="README.rst", ref="master") + + data = { "branch": "master", - "content": "Initial content", - "commit_message": "New commit", + "commit_message": "blah blah blah", + "actions": [{"action": "create", "file_path": "blah", "content": "blah"}], } -) -readme = admin_project.files.get(file_path="README.rst", ref="master") -# The first decode() is the ProjectFile method, the second one is the bytes -# object method -assert readme.decode().decode() == "Initial content" - -blame = admin_project.files.blame(file_path="README.rst", ref="master") - -data = { - "branch": "master", - "commit_message": "blah blah blah", - "actions": [{"action": "create", "file_path": "blah", "content": "blah"}], -} -admin_project.commits.create(data) -assert "@@" in admin_project.commits.list()[0].diff()[0]["diff"] - -# commit status -commit = admin_project.commits.list()[0] -# size = len(commit.statuses.list()) -# status = commit.statuses.create({"state": "success", "sha": commit.id}) -# assert len(commit.statuses.list()) == size + 1 - -# assert commit.refs() -# assert commit.merge_requests() - -# commit comment -commit.comments.create({"note": "This is a commit comment"}) -# assert len(commit.comments.list()) == 1 - -# commit discussion -count = len(commit.discussions.list()) -discussion = commit.discussions.create({"body": "Discussion body"}) -# assert len(commit.discussions.list()) == (count + 1) -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = commit.discussions.get(discussion.id) -# assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = commit.discussions.get(discussion.id) -# assert len(discussion.attributes["notes"]) == 1 - -# Revert commit -revert_commit = commit.revert(branch="master") - -expected_message = 'Revert "{}"\n\nThis reverts commit {}'.format( - commit.message, commit.id -) -assert revert_commit["message"] == expected_message - -try: - commit.revert(branch="master") - # Only here to really ensure expected error without a full test framework - raise AssertionError("Two revert attempts should raise GitlabRevertError") -except gitlab.GitlabRevertError: - pass - -# housekeeping -admin_project.housekeeping() - -# repository -tree = admin_project.repository_tree() -assert len(tree) != 0 -assert tree[0]["name"] == "README.rst" -blob_id = tree[0]["id"] -blob = admin_project.repository_raw_blob(blob_id) -assert blob.decode() == "Initial content" -archive1 = admin_project.repository_archive() -archive2 = admin_project.repository_archive("master") -assert archive1 == archive2 -snapshot = admin_project.snapshot() - -# project file uploads -filename = "test.txt" -file_contents = "testing contents" -uploaded_file = admin_project.upload(filename, file_contents) -assert uploaded_file["alt"] == filename -assert uploaded_file["url"].startswith("/uploads/") -assert uploaded_file["url"].endswith("/" + filename) -assert uploaded_file["markdown"] == "[{}]({})".format( - uploaded_file["alt"], uploaded_file["url"] -) - -# environments -admin_project.environments.create( - {"name": "env1", "external_url": "http://fake.env/whatever"} -) -envs = admin_project.environments.list() -assert len(envs) == 1 -env = envs[0] -env.external_url = "http://new.env/whatever" -env.save() -env = admin_project.environments.list()[0] -assert env.external_url == "http://new.env/whatever" -env.stop() -env.delete() -assert len(admin_project.environments.list()) == 0 - -# Project clusters -admin_project.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - } -) -clusters = admin_project.clusters.list() -assert len(clusters) == 1 -cluster = clusters[0] -cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} -cluster.save() -cluster = admin_project.clusters.list()[0] -assert cluster.platform_kubernetes["api_url"] == "http://newurl" -cluster.delete() -assert len(admin_project.clusters.list()) == 0 - -# Group clusters -group1.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - } -) -clusters = group1.clusters.list() -assert len(clusters) == 1 -cluster = clusters[0] -cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} -cluster.save() -cluster = group1.clusters.list()[0] -assert cluster.platform_kubernetes["api_url"] == "http://newurl" -cluster.delete() -assert len(group1.clusters.list()) == 0 - -# project events -admin_project.events.list() - -# forks -fork = admin_project.forks.create({"namespace": user1.username}) -p = gl.projects.get(fork.id) -assert p.forked_from_project["id"] == admin_project.id - -forks = admin_project.forks.list() -assert fork.id in map(lambda p: p.id, forks) - -# project hooks -hook = admin_project.hooks.create({"url": "http://hook.url"}) -assert len(admin_project.hooks.list()) == 1 -hook.note_events = True -hook.save() -hook = admin_project.hooks.get(hook.id) -assert hook.note_events is True -hook.delete() - -# deploy keys -deploy_key = admin_project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) -project_keys = list(admin_project.keys.list()) -assert len(project_keys) == 1 - -sudo_project.keys.enable(deploy_key.id) -assert len(sudo_project.keys.list()) == 1 -sudo_project.keys.delete(deploy_key.id) -assert len(sudo_project.keys.list()) == 0 - -# labels -# label1 = admin_project.labels.create({"name": "label1", "color": "#778899"}) -# label1 = admin_project.labels.list()[0] -# assert len(admin_project.labels.list()) == 1 -# label1.new_name = "label1updated" -# label1.save() -# assert label1.name == "label1updated" -# label1.subscribe() -# assert label1.subscribed == True -# label1.unsubscribe() -# assert label1.subscribed == False -# label1.delete() - -# milestones -m1 = admin_project.milestones.create({"title": "milestone1"}) -assert len(admin_project.milestones.list()) == 1 -m1.due_date = "2020-01-01T00:00:00Z" -m1.save() -m1.state_event = "close" -m1.save() -m1 = admin_project.milestones.get(m1.id) -assert m1.state == "closed" -assert len(m1.issues()) == 0 -assert len(m1.merge_requests()) == 0 - -# issues -issue1 = admin_project.issues.create({"title": "my issue 1", "milestone_id": m1.id}) -issue2 = admin_project.issues.create({"title": "my issue 2"}) -issue3 = admin_project.issues.create({"title": "my issue 3"}) -assert len(admin_project.issues.list()) == 3 -issue3.state_event = "close" -issue3.save() -assert len(admin_project.issues.list(state="closed")) == 1 -assert len(admin_project.issues.list(state="opened")) == 2 -assert len(admin_project.issues.list(milestone="milestone1")) == 1 -assert m1.issues().next().title == "my issue 1" -size = len(issue1.notes.list()) -note = issue1.notes.create({"body": "This is an issue note"}) -assert len(issue1.notes.list()) == size + 1 -emoji = note.awardemojis.create({"name": "tractor"}) -assert len(note.awardemojis.list()) == 1 -emoji.delete() -assert len(note.awardemojis.list()) == 0 -note.delete() -assert len(issue1.notes.list()) == size -assert isinstance(issue1.user_agent_detail(), dict) - -assert issue1.user_agent_detail()["user_agent"] -assert issue1.participants() -assert type(issue1.closed_by()) == list -assert type(issue1.related_merge_requests()) == list - -# issues labels and events -label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) -issue1.labels = ["label2"] -issue1.save() -events = issue1.resourcelabelevents.list() -assert events -event = issue1.resourcelabelevents.get(events[0].id) -assert event - - -size = len(issue1.discussions.list()) -discussion = issue1.discussions.create({"body": "Discussion body"}) -assert len(issue1.discussions.list()) == size + 1 -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = issue1.discussions.get(discussion.id) -assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = issue1.discussions.get(discussion.id) -assert len(discussion.attributes["notes"]) == 1 - -# tags -tag1 = admin_project.tags.create({"tag_name": "v1.0", "ref": "master"}) -assert len(admin_project.tags.list()) == 1 -tag1.set_release_description("Description 1") -tag1.set_release_description("Description 2") -assert tag1.release["description"] == "Description 2" -tag1.delete() - -# project snippet -admin_project.snippets_enabled = True -admin_project.save() -snippet = admin_project.snippets.create( - { - "title": "snip1", - "file_name": "foo.py", - "content": "initial content", - "visibility": gitlab.v4.objects.VISIBILITY_PRIVATE, - } -) + admin_project.commits.create(data) + assert "@@" in admin_project.commits.list()[0].diff()[0]["diff"] + + # commit status + commit = admin_project.commits.list()[0] + # size = len(commit.statuses.list()) + # status = commit.statuses.create({"state": "success", "sha": commit.id}) + # assert len(commit.statuses.list()) == size + 1 + + # assert commit.refs() + # assert commit.merge_requests() + + # commit comment + commit.comments.create({"note": "This is a commit comment"}) + # assert len(commit.comments.list()) == 1 + + # commit discussion + count = len(commit.discussions.list()) + discussion = commit.discussions.create({"body": "Discussion body"}) + # assert len(commit.discussions.list()) == (count + 1) + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = commit.discussions.get(discussion.id) + # assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = commit.discussions.get(discussion.id) + # assert len(discussion.attributes["notes"]) == 1 + + # Revert commit + revert_commit = commit.revert(branch="master") + + expected_message = 'Revert "{}"\n\nThis reverts commit {}'.format( + commit.message, commit.id + ) + assert revert_commit["message"] == expected_message -assert snippet.user_agent_detail()["user_agent"] - -size = len(snippet.discussions.list()) -discussion = snippet.discussions.create({"body": "Discussion body"}) -assert len(snippet.discussions.list()) == size + 1 -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = snippet.discussions.get(discussion.id) -assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = snippet.discussions.get(discussion.id) -assert len(discussion.attributes["notes"]) == 1 - -snippet.file_name = "bar.py" -snippet.save() -snippet = admin_project.snippets.get(snippet.id) -assert snippet.content().decode() == "initial content" -assert snippet.file_name == "bar.py" -size = len(admin_project.snippets.list()) -snippet.delete() -assert len(admin_project.snippets.list()) == (size - 1) - -# triggers -tr1 = admin_project.triggers.create({"description": "trigger1"}) -assert len(admin_project.triggers.list()) == 1 -tr1.delete() - -# variables -v1 = admin_project.variables.create({"key": "key1", "value": "value1"}) -assert len(admin_project.variables.list()) == 1 -v1.value = "new_value1" -v1.save() -v1 = admin_project.variables.get(v1.key) -assert v1.value == "new_value1" -v1.delete() - -# branches and merges -to_merge = admin_project.branches.create({"branch": "branch1", "ref": "master"}) -admin_project.files.create( - { - "file_path": "README2.rst", - "branch": "branch1", - "content": "Initial content", - "commit_message": "New commit in new branch", - } -) -mr = admin_project.mergerequests.create( - {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} -) + try: + commit.revert(branch="master") + # Only here to really ensure expected error without a full test framework + raise AssertionError("Two revert attempts should raise GitlabRevertError") + except gitlab.GitlabRevertError: + pass + + # housekeeping + admin_project.housekeeping() + + # repository + tree = admin_project.repository_tree() + assert len(tree) != 0 + assert tree[0]["name"] == "README.rst" + blob_id = tree[0]["id"] + blob = admin_project.repository_raw_blob(blob_id) + assert blob.decode() == "Initial content" + archive1 = admin_project.repository_archive() + archive2 = admin_project.repository_archive("master") + assert archive1 == archive2 + snapshot = admin_project.snapshot() + + # project file uploads + filename = "test.txt" + file_contents = "testing contents" + uploaded_file = admin_project.upload(filename, file_contents) + assert uploaded_file["alt"] == filename + assert uploaded_file["url"].startswith("/uploads/") + assert uploaded_file["url"].endswith("/" + filename) + assert uploaded_file["markdown"] == "[{}]({})".format( + uploaded_file["alt"], uploaded_file["url"] + ) + + # environments + admin_project.environments.create( + {"name": "env1", "external_url": "http://fake.env/whatever"} + ) + envs = admin_project.environments.list() + assert len(envs) == 1 + env = envs[0] + env.external_url = "http://new.env/whatever" + env.save() + env = admin_project.environments.list()[0] + assert env.external_url == "http://new.env/whatever" + env.stop() + env.delete() + assert len(admin_project.environments.list()) == 0 + + # Project clusters + admin_project.clusters.create( + { + "name": "cluster1", + "platform_kubernetes_attributes": { + "api_url": "http://url", + "token": "tokenval", + }, + } + ) + clusters = admin_project.clusters.list() + assert len(clusters) == 1 + cluster = clusters[0] + cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} + cluster.save() + cluster = admin_project.clusters.list()[0] + assert cluster.platform_kubernetes["api_url"] == "http://newurl" + cluster.delete() + assert len(admin_project.clusters.list()) == 0 + + # Group clusters + group1.clusters.create( + { + "name": "cluster1", + "platform_kubernetes_attributes": { + "api_url": "http://url", + "token": "tokenval", + }, + } + ) + clusters = group1.clusters.list() + assert len(clusters) == 1 + cluster = clusters[0] + cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} + cluster.save() + cluster = group1.clusters.list()[0] + assert cluster.platform_kubernetes["api_url"] == "http://newurl" + cluster.delete() + assert len(group1.clusters.list()) == 0 + + # project events + admin_project.events.list() + + # forks + fork = admin_project.forks.create({"namespace": user1.username}) + p = gl.projects.get(fork.id) + assert p.forked_from_project["id"] == admin_project.id + + forks = admin_project.forks.list() + assert fork.id in map(lambda p: p.id, forks) + + # project hooks + hook = admin_project.hooks.create({"url": "http://hook.url"}) + assert len(admin_project.hooks.list()) == 1 + hook.note_events = True + hook.save() + hook = admin_project.hooks.get(hook.id) + assert hook.note_events is True + hook.delete() + + # deploy keys + deploy_key = admin_project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) + project_keys = list(admin_project.keys.list()) + assert len(project_keys) == 1 + + sudo_project.keys.enable(deploy_key.id) + assert len(sudo_project.keys.list()) == 1 + sudo_project.keys.delete(deploy_key.id) + assert len(sudo_project.keys.list()) == 0 + + # labels + # label1 = admin_project.labels.create({"name": "label1", "color": "#778899"}) + # label1 = admin_project.labels.list()[0] + # assert len(admin_project.labels.list()) == 1 + # label1.new_name = "label1updated" + # label1.save() + # assert label1.name == "label1updated" + # label1.subscribe() + # assert label1.subscribed == True + # label1.unsubscribe() + # assert label1.subscribed == False + # label1.delete() + + # milestones + m1 = admin_project.milestones.create({"title": "milestone1"}) + assert len(admin_project.milestones.list()) == 1 + m1.due_date = "2020-01-01T00:00:00Z" + m1.save() + m1.state_event = "close" + m1.save() + m1 = admin_project.milestones.get(m1.id) + assert m1.state == "closed" + assert len(m1.issues()) == 0 + assert len(m1.merge_requests()) == 0 + + # issues + issue1 = admin_project.issues.create({"title": "my issue 1", "milestone_id": m1.id}) + issue2 = admin_project.issues.create({"title": "my issue 2"}) + issue3 = admin_project.issues.create({"title": "my issue 3"}) + assert len(admin_project.issues.list()) == 3 + issue3.state_event = "close" + issue3.save() + assert len(admin_project.issues.list(state="closed")) == 1 + assert len(admin_project.issues.list(state="opened")) == 2 + assert len(admin_project.issues.list(milestone="milestone1")) == 1 + assert m1.issues().next().title == "my issue 1" + size = len(issue1.notes.list()) + note = issue1.notes.create({"body": "This is an issue note"}) + assert len(issue1.notes.list()) == size + 1 + emoji = note.awardemojis.create({"name": "tractor"}) + assert len(note.awardemojis.list()) == 1 + emoji.delete() + assert len(note.awardemojis.list()) == 0 + note.delete() + assert len(issue1.notes.list()) == size + assert isinstance(issue1.user_agent_detail(), dict) + + assert issue1.user_agent_detail()["user_agent"] + assert issue1.participants() + assert type(issue1.closed_by()) == list + assert type(issue1.related_merge_requests()) == list + + # issues labels and events + label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) + issue1.labels = ["label2"] + issue1.save() + events = issue1.resourcelabelevents.list() + assert events + event = issue1.resourcelabelevents.get(events[0].id) + assert event + + size = len(issue1.discussions.list()) + discussion = issue1.discussions.create({"body": "Discussion body"}) + assert len(issue1.discussions.list()) == size + 1 + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = issue1.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = issue1.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + # tags + tag1 = admin_project.tags.create({"tag_name": "v1.0", "ref": "master"}) + assert len(admin_project.tags.list()) == 1 + tag1.set_release_description("Description 1") + tag1.set_release_description("Description 2") + assert tag1.release["description"] == "Description 2" + tag1.delete() + + # project snippet + admin_project.snippets_enabled = True + admin_project.save() + snippet = admin_project.snippets.create( + { + "title": "snip1", + "file_name": "foo.py", + "content": "initial content", + "visibility": gitlab.v4.objects.VISIBILITY_PRIVATE, + } + ) + + assert snippet.user_agent_detail()["user_agent"] + + size = len(snippet.discussions.list()) + discussion = snippet.discussions.create({"body": "Discussion body"}) + assert len(snippet.discussions.list()) == size + 1 + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = snippet.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = snippet.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + snippet.file_name = "bar.py" + snippet.save() + snippet = admin_project.snippets.get(snippet.id) + assert snippet.content().decode() == "initial content" + assert snippet.file_name == "bar.py" + size = len(admin_project.snippets.list()) + snippet.delete() + assert len(admin_project.snippets.list()) == (size - 1) + + # triggers + tr1 = admin_project.triggers.create({"description": "trigger1"}) + assert len(admin_project.triggers.list()) == 1 + tr1.delete() + + # variables + v1 = admin_project.variables.create({"key": "key1", "value": "value1"}) + assert len(admin_project.variables.list()) == 1 + v1.value = "new_value1" + v1.save() + v1 = admin_project.variables.get(v1.key) + assert v1.value == "new_value1" + v1.delete() + + # branches and merges + to_merge = admin_project.branches.create({"branch": "branch1", "ref": "master"}) + admin_project.files.create( + { + "file_path": "README2.rst", + "branch": "branch1", + "content": "Initial content", + "commit_message": "New commit in new branch", + } + ) + mr = admin_project.mergerequests.create( + {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} + ) + + # discussion + size = len(mr.discussions.list()) + discussion = mr.discussions.create({"body": "Discussion body"}) + assert len(mr.discussions.list()) == size + 1 + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = mr.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = mr.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + # mr labels and events + mr.labels = ["label2"] + mr.save() + events = mr.resourcelabelevents.list() + assert events + event = mr.resourcelabelevents.get(events[0].id) + assert event + + # rebasing + assert mr.rebase() + + # basic testing: only make sure that the methods exist + mr.commits() + mr.changes() + assert mr.participants() -# discussion -size = len(mr.discussions.list()) -discussion = mr.discussions.create({"body": "Discussion body"}) -assert len(mr.discussions.list()) == size + 1 -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = mr.discussions.get(discussion.id) -assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = mr.discussions.get(discussion.id) -assert len(discussion.attributes["notes"]) == 1 - -# mr labels and events -mr.labels = ["label2"] -mr.save() -events = mr.resourcelabelevents.list() -assert events -event = mr.resourcelabelevents.get(events[0].id) -assert event - -# rebasing -assert mr.rebase() - -# basic testing: only make sure that the methods exist -mr.commits() -mr.changes() -assert mr.participants() - -mr.merge() -admin_project.branches.delete("branch1") - -try: mr.merge() -except gitlab.GitlabMRClosedError: - pass - -# protected branches -p_b = admin_project.protectedbranches.create({"name": "*-stable"}) -assert p_b.name == "*-stable" -p_b = admin_project.protectedbranches.get("*-stable") -# master is protected by default when a branch has been created -assert len(admin_project.protectedbranches.list()) == 2 -admin_project.protectedbranches.delete("master") -p_b.delete() -assert len(admin_project.protectedbranches.list()) == 0 - -# stars -admin_project.star() -assert admin_project.star_count == 1 -admin_project.unstar() -assert admin_project.star_count == 0 - -# project boards -# boards = admin_project.boards.list() -# assert(len(boards)) -# board = boards[0] -# lists = board.lists.list() -# begin_size = len(lists) -# last_list = lists[-1] -# last_list.position = 0 -# last_list.save() -# last_list.delete() -# lists = board.lists.list() -# assert(len(lists) == begin_size - 1) - -# project badges -badge_image = "http://example.com" -badge_link = "http://example/img.svg" -badge = admin_project.badges.create({"link_url": badge_link, "image_url": badge_image}) -assert len(admin_project.badges.list()) == 1 -badge.image_url = "http://another.example.com" -badge.save() -badge = admin_project.badges.get(badge.id) -assert badge.image_url == "http://another.example.com" -badge.delete() -assert len(admin_project.badges.list()) == 0 - -# project wiki -wiki_content = "Wiki page content" -wp = admin_project.wikis.create({"title": "wikipage", "content": wiki_content}) -assert len(admin_project.wikis.list()) == 1 -wp = admin_project.wikis.get(wp.slug) -assert wp.content == wiki_content -# update and delete seem broken -# wp.content = 'new content' -# wp.save() -# wp.delete() -# assert(len(admin_project.wikis.list()) == 0) - -# namespaces -ns = gl.namespaces.list(all=True) -assert len(ns) != 0 -ns = gl.namespaces.list(search="root", all=True)[0] -assert ns.kind == "user" - -# features -# Disabled as this fails with GitLab 11.11 -# feat = gl.features.set("foo", 30) -# assert feat.name == "foo" -# assert len(gl.features.list()) == 1 -# feat.delete() -# assert len(gl.features.list()) == 0 - -# broadcast messages -msg = gl.broadcastmessages.create({"message": "this is the message"}) -msg.color = "#444444" -msg.save() -msg_id = msg.id -msg = gl.broadcastmessages.list(all=True)[0] -assert msg.color == "#444444" -msg = gl.broadcastmessages.get(msg_id) -assert msg.color == "#444444" -msg.delete() -assert len(gl.broadcastmessages.list()) == 0 - -# notification settings -settings = gl.notificationsettings.get() -settings.level = gitlab.NOTIFICATION_LEVEL_WATCH -settings.save() -settings = gl.notificationsettings.get() -assert settings.level == gitlab.NOTIFICATION_LEVEL_WATCH - -# services -service = admin_project.services.get("asana") -service.api_key = "whatever" -service.save() -service = admin_project.services.get("asana") -assert service.active == True -service.delete() -service = admin_project.services.get("asana") -assert service.active == False - -# snippets -snippets = gl.snippets.list(all=True) -assert len(snippets) == 0 -snippet = gl.snippets.create( - {"title": "snippet1", "file_name": "snippet1.py", "content": "import gitlab"} -) -snippet = gl.snippets.get(snippet.id) -snippet.title = "updated_title" -snippet.save() -snippet = gl.snippets.get(snippet.id) -assert snippet.title == "updated_title" -content = snippet.content() -assert content.decode() == "import gitlab" - -assert snippet.user_agent_detail()["user_agent"] - -snippet.delete() -snippets = gl.snippets.list(all=True) -assert len(snippets) == 0 - -# user activities -gl.user_activities.list(query_parameters={"from": "2019-01-01"}) - -# events -gl.events.list() - -# rate limit -settings = gl.settings.get() -settings.throttle_authenticated_api_enabled = True -settings.throttle_authenticated_api_requests_per_period = 1 -settings.throttle_authenticated_api_period_in_seconds = 3 -settings.save() -projects = list() -for i in range(0, 20): - projects.append(gl.projects.create({"name": str(i) + "ok"})) - -error_message = None -for i in range(20, 40): + admin_project.branches.delete("branch1") + try: - projects.append( - gl.projects.create({"name": str(i) + "shouldfail"}, obey_rate_limit=False) - ) - except gitlab.GitlabCreateError as e: - error_message = e.error_message - break -assert "Retry later" in error_message -settings.throttle_authenticated_api_enabled = False -settings.save() -[current_project.delete() for current_project in projects] - -# project import/export -ex = admin_project.exports.create({}) -ex.refresh() -count = 0 -while ex.export_status != "finished": - time.sleep(1) + mr.merge() + except gitlab.GitlabMRClosedError: + pass + + # protected branches + p_b = admin_project.protectedbranches.create({"name": "*-stable"}) + assert p_b.name == "*-stable" + p_b = admin_project.protectedbranches.get("*-stable") + # master is protected by default when a branch has been created + assert len(admin_project.protectedbranches.list()) == 2 + admin_project.protectedbranches.delete("master") + p_b.delete() + assert len(admin_project.protectedbranches.list()) == 0 + + # stars + admin_project.star() + assert admin_project.star_count == 1 + admin_project.unstar() + assert admin_project.star_count == 0 + + # project boards + # boards = admin_project.boards.list() + # assert(len(boards)) + # board = boards[0] + # lists = board.lists.list() + # begin_size = len(lists) + # last_list = lists[-1] + # last_list.position = 0 + # last_list.save() + # last_list.delete() + # lists = board.lists.list() + # assert(len(lists) == begin_size - 1) + + # project badges + badge_image = "http://example.com" + badge_link = "http://example/img.svg" + badge = admin_project.badges.create( + {"link_url": badge_link, "image_url": badge_image} + ) + assert len(admin_project.badges.list()) == 1 + badge.image_url = "http://another.example.com" + badge.save() + badge = admin_project.badges.get(badge.id) + assert badge.image_url == "http://another.example.com" + badge.delete() + assert len(admin_project.badges.list()) == 0 + + # project wiki + wiki_content = "Wiki page content" + wp = admin_project.wikis.create({"title": "wikipage", "content": wiki_content}) + assert len(admin_project.wikis.list()) == 1 + wp = admin_project.wikis.get(wp.slug) + assert wp.content == wiki_content + # update and delete seem broken + # wp.content = 'new content' + # wp.save() + # wp.delete() + # assert(len(admin_project.wikis.list()) == 0) + + # namespaces + ns = gl.namespaces.list(all=True) + assert len(ns) != 0 + ns = gl.namespaces.list(search="root", all=True)[0] + assert ns.kind == "user" + + # features + # Disabled as this fails with GitLab 11.11 + # feat = gl.features.set("foo", 30) + # assert feat.name == "foo" + # assert len(gl.features.list()) == 1 + # feat.delete() + # assert len(gl.features.list()) == 0 + + # broadcast messages + msg = gl.broadcastmessages.create({"message": "this is the message"}) + msg.color = "#444444" + msg.save() + msg_id = msg.id + msg = gl.broadcastmessages.list(all=True)[0] + assert msg.color == "#444444" + msg = gl.broadcastmessages.get(msg_id) + assert msg.color == "#444444" + msg.delete() + assert len(gl.broadcastmessages.list()) == 0 + + # notification settings + settings = gl.notificationsettings.get() + settings.level = gitlab.NOTIFICATION_LEVEL_WATCH + settings.save() + settings = gl.notificationsettings.get() + assert settings.level == gitlab.NOTIFICATION_LEVEL_WATCH + + # services + service = admin_project.services.get("asana") + service.api_key = "whatever" + service.save() + service = admin_project.services.get("asana") + assert service.active == True + service.delete() + service = admin_project.services.get("asana") + assert service.active == False + + # snippets + snippets = gl.snippets.list(all=True) + assert len(snippets) == 0 + snippet = gl.snippets.create( + {"title": "snippet1", "file_name": "snippet1.py", "content": "import gitlab"} + ) + snippet = gl.snippets.get(snippet.id) + snippet.title = "updated_title" + snippet.save() + snippet = gl.snippets.get(snippet.id) + assert snippet.title == "updated_title" + content = snippet.content() + assert content.decode() == "import gitlab" + + assert snippet.user_agent_detail()["user_agent"] + + snippet.delete() + snippets = gl.snippets.list(all=True) + assert len(snippets) == 0 + + # user activities + gl.user_activities.list(query_parameters={"from": "2019-01-01"}) + + # events + gl.events.list() + + # rate limit + settings = gl.settings.get() + settings.throttle_authenticated_api_enabled = True + settings.throttle_authenticated_api_requests_per_period = 1 + settings.throttle_authenticated_api_period_in_seconds = 3 + settings.save() + projects = list() + for i in range(0, 20): + projects.append(gl.projects.create({"name": str(i) + "ok"})) + + error_message = None + for i in range(20, 40): + try: + projects.append( + gl.projects.create( + {"name": str(i) + "shouldfail"}, obey_rate_limit=False + ) + ) + except gitlab.GitlabCreateError as e: + error_message = e.error_message + break + assert "Retry later" in error_message + settings.throttle_authenticated_api_enabled = False + settings.save() + [current_project.delete() for current_project in projects] + + # project import/export + ex = admin_project.exports.create({}) ex.refresh() - count += 1 - if count == 10: - raise Exception("Project export taking too much time") -with open("/tmp/gitlab-export.tgz", "wb") as f: - ex.download(streamed=True, action=f.write) - -output = gl.projects.import_project( - open("/tmp/gitlab-export.tgz", "rb"), "imported_project" -) -project_import = gl.projects.get(output["id"], lazy=True).imports.get() -count = 0 -while project_import.import_status != "finished": - time.sleep(1) - project_import.refresh() - count += 1 - if count == 10: - raise Exception("Project import taking too much time") - -# project releases -release_test_project = gl.projects.create( - {"name": "release-test-project", "initialize_with_readme": True} -) -release_name = "Demo Release" -release_tag_name = "v1.2.3" -release_description = "release notes go here" -release_test_project.releases.create( - { - "name": release_name, - "tag_name": release_tag_name, - "description": release_description, - "ref": "master", - } -) -assert len(release_test_project.releases.list()) == 1 - -# get single release -retrieved_project = release_test_project.releases.get(release_tag_name) -assert retrieved_project.name == release_name -assert retrieved_project.tag_name == release_tag_name -assert retrieved_project.description == release_description - -# delete release -release_test_project.releases.delete(release_tag_name) -assert len(release_test_project.releases.list()) == 0 -release_test_project.delete() - -# status -message = "Test" -emoji = "thumbsup" -status = gl.user.status.get() -status.message = message -status.emoji = emoji -status.save() -new_status = gl.user.status.get() -assert new_status.message == message -assert new_status.emoji == emoji + count = 0 + while ex.export_status != "finished": + time.sleep(1) + ex.refresh() + count += 1 + if count == 10: + raise Exception("Project export taking too much time") + with open("/tmp/gitlab-export.tgz", "wb") as f: + ex.download(streamed=True, action=f.write) + + output = gl.projects.import_project( + open("/tmp/gitlab-export.tgz", "rb"), "imported_project" + ) + project_import = gl.projects.get(output["id"], lazy=True).imports.get() + count = 0 + while project_import.import_status != "finished": + time.sleep(1) + project_import.refresh() + count += 1 + if count == 10: + raise Exception("Project import taking too much time") + + # project releases + release_test_project = gl.projects.create( + {"name": "release-test-project", "initialize_with_readme": True} + ) + release_name = "Demo Release" + release_tag_name = "v1.2.3" + release_description = "release notes go here" + release_test_project.releases.create( + { + "name": release_name, + "tag_name": release_tag_name, + "description": release_description, + "ref": "master", + } + ) + assert len(release_test_project.releases.list()) == 1 + + # get single release + retrieved_project = release_test_project.releases.get(release_tag_name) + assert retrieved_project.name == release_name + assert retrieved_project.tag_name == release_tag_name + assert retrieved_project.description == release_description + + # delete release + release_test_project.releases.delete(release_tag_name) + assert len(release_test_project.releases.list()) == 0 + release_test_project.delete() + + # status + message = "Test" + emoji = "thumbsup" + status = gl.user.status.get() + status.message = message + status.emoji = emoji + status.save() + new_status = gl.user.status.get() + assert new_status.message == message + assert new_status.emoji == emoji diff --git a/tools/py_functional_tests.sh b/tools/py_functional_tests.sh index f88b76a4a..b4c9ebb6c 100755 --- a/tools/py_functional_tests.sh +++ b/tools/py_functional_tests.sh @@ -18,4 +18,4 @@ setenv_script=$(dirname "$0")/build_test_env.sh || exit 1 BUILD_TEST_ENV_AUTO_CLEANUP=true . "$setenv_script" "$@" || exit 1 -try python "$(dirname "$0")"/../tests/functional/test_api_v${API_VER}.py +try pytest -s "$(dirname "$0")"/../tests/functional