From 79489c775141c4ddd1f7aecae90dae8061d541fe Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sat, 5 Sep 2020 00:15:04 +0200 Subject: test(env): replace custom scripts with pytest and docker-compose --- docker-requirements.txt | 4 + tools/avatar.png | Bin 592 -> 0 bytes tools/build_test_env.sh | 158 ---- tools/ee-test.py | 159 ---- tools/functional/api/test_gitlab.py | 8 + tools/functional/cli/conftest.py | 4 +- tools/functional/conftest.py | 99 ++- tools/functional/ee-test.py | 159 ++++ tools/functional/fixtures/avatar.png | Bin 0 -> 592 bytes tools/functional/fixtures/docker-compose.yml | 31 + tools/functional/fixtures/set_token.rb | 9 + tools/functional/python_test_v4.py | 1141 ++++++++++++++++++++++++++ tools/functional_tests.sh | 21 - tools/generate_token.py | 51 -- tools/py_functional_tests.sh | 22 - tools/python_test_v4.py | 1138 ------------------------- tools/reset_gitlab.py | 20 - tox.ini | 6 +- 18 files changed, 1453 insertions(+), 1577 deletions(-) create mode 100644 docker-requirements.txt delete mode 100644 tools/avatar.png delete mode 100755 tools/build_test_env.sh delete mode 100755 tools/ee-test.py create mode 100644 tools/functional/api/test_gitlab.py create mode 100755 tools/functional/ee-test.py create mode 100644 tools/functional/fixtures/avatar.png create mode 100644 tools/functional/fixtures/docker-compose.yml create mode 100644 tools/functional/fixtures/set_token.rb create mode 100644 tools/functional/python_test_v4.py delete mode 100755 tools/functional_tests.sh delete mode 100755 tools/generate_token.py delete mode 100755 tools/py_functional_tests.sh delete mode 100644 tools/python_test_v4.py delete mode 100755 tools/reset_gitlab.py diff --git a/docker-requirements.txt b/docker-requirements.txt new file mode 100644 index 0000000..1bcd74b --- /dev/null +++ b/docker-requirements.txt @@ -0,0 +1,4 @@ +-r requirements.txt +-r test-requirements.txt +pytest-console-scripts +pytest-docker diff --git a/tools/avatar.png b/tools/avatar.png deleted file mode 100644 index a3a767c..0000000 Binary files a/tools/avatar.png and /dev/null differ diff --git a/tools/build_test_env.sh b/tools/build_test_env.sh deleted file mode 100755 index a7b64b6..0000000 --- a/tools/build_test_env.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/bin/sh -# Copyright (C) 2016 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -pecho() { printf %s\\n "$*"; } -log() { - [ "$#" -eq 0 ] || { pecho "$@"; return 0; } - while IFS= read -r log_line || [ -n "${log_line}" ]; do - log "${log_line}" - done -} -error() { log "ERROR: $@" >&2; } -fatal() { error "$@"; exit 1; } -try() { "$@" || fatal "'$@' failed"; } - -REUSE_CONTAINER= -NOVENV= -API_VER=4 -DEFAULT_GITLAB_IMAGE=gitlab/gitlab-ce -DEFAULT_GITLAB_TAG=13.3.6-ce.0 -GITLAB_IMAGE="${GITLAB_IMAGE:-$DEFAULT_GITLAB_IMAGE}" -GITLAB_TAG="${GITLAB_TAG:-$DEFAULT_GITLAB_TAG}" -VENV_CMD="python3 -m venv" -while getopts :knp:a:i:t: opt "$@"; do - case $opt in - k) REUSE_CONTAINER=1;; - n) NOVENV=1;; - a) API_VER=$OPTARG;; - i) GITLAB_IMAGE=$OPTARG;; - t) GITLAB_TAG=$OPTARG;; - :) fatal "Option -${OPTARG} requires a value";; - '?') fatal "Unknown option: -${OPTARG}";; - *) fatal "Internal error: opt=${opt}";; - esac -done - -case $API_VER in - 4) ;; - *) fatal "Wrong API version (4 only)";; -esac - -for req in \ - curl \ - docker \ - ; -do - command -v "${req}" >/dev/null 2>&1 || fatal "${req} is required" -done - -VENV=$(pwd)/.venv || exit 1 -CONFIG=/tmp/python-gitlab.cfg - -cleanup() { - rm -f "${CONFIG}" - log "Deactivating Python virtualenv..." - command -v deactivate >/dev/null 2>&1 && deactivate || true - log "Deleting python virtualenv..." - rm -rf "$VENV" - if [ -z "$REUSE_CONTAINER" ]; then - log "Stopping gitlab-test docker container..." - docker rm -f gitlab-test >/dev/null - fi - log "Done." -} -[ -z "${BUILD_TEST_ENV_AUTO_CLEANUP+set}" ] || { - trap cleanup EXIT - trap 'exit 1' HUP INT TERM -} - -if [ -z "$REUSE_CONTAINER" ] || ! docker top gitlab-test >/dev/null 2>&1; then - try docker pull "$GITLAB_IMAGE:$GITLAB_TAG" - GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.test' -gitlab_rails['initial_root_password'] = '5iveL!fe' -gitlab_rails['initial_shared_runners_registration_token'] = 'sTPNtWLEuSrHzoHP8oCU' -registry['enable'] = false -nginx['redirect_http_to_https'] = false -nginx['listen_port'] = 80 -nginx['listen_https'] = false -pages_external_url 'http://pages.gitlab.lxd' -gitlab_pages['enable'] = true -gitlab_pages['inplace_chroot'] = true -prometheus['enable'] = false -alertmanager['enable'] = false -node_exporter['enable'] = false -redis_exporter['enable'] = false -postgres_exporter['enable'] = false -pgbouncer_exporter['enable'] = false -gitlab_exporter['enable'] = false -grafana['enable'] = false -letsencrypt['enable'] = false -" - try docker run --name gitlab-test --detach --publish 8080:80 \ - --publish 2222:22 --env "GITLAB_OMNIBUS_CONFIG=$GITLAB_OMNIBUS_CONFIG" \ - "$GITLAB_IMAGE:$GITLAB_TAG" >/dev/null -fi - -if [ -z "$NOVENV" ]; then - log "Creating Python virtualenv..." - try $VENV_CMD "$VENV" - . "$VENV"/bin/activate || fatal "failed to activate Python virtual environment" - - log "Installing dependencies into virtualenv..." - try pip install -r requirements.txt - - log "Installing into virtualenv..." - try pip install -e . - - # to run generate_token.py - pip install requests-html pytest-console-scripts -fi - -log "Waiting for gitlab to come online... " -I=0 -while :; do - sleep 1 - docker top gitlab-test >/dev/null 2>&1 || fatal "docker failed to start" - sleep 4 - docker logs gitlab-test 2>&1 | grep "gitlab Reconfigured!" \ - && break - I=$((I+5)) - log "Waiting for GitLab to reconfigure.. (${I}s)" - [ "$I" -lt 180 ] || fatal "timed out" -done - -# Get the token -TOKEN=$($(dirname $0)/generate_token.py) - -cat > $CONFIG << EOF -[global] -default = local -timeout = 30 - -[local] -url = http://localhost:8080 -private_token = $TOKEN -api_version = $API_VER -EOF - -log "Config file content ($CONFIG):" -log <$CONFIG - -if [ ! -z "$REUSE_CONTAINER" ]; then - echo reset gitlab - $(dirname $0)/reset_gitlab.py -fi -log "Test environment initialized." diff --git a/tools/ee-test.py b/tools/ee-test.py deleted file mode 100755 index 3f75655..0000000 --- a/tools/ee-test.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python - -import gitlab - - -P1 = "root/project1" -P2 = "root/project2" -MR_P1 = 1 -I_P1 = 1 -I_P2 = 1 -EPIC_ISSUES = [4, 5] -G1 = "group1" -LDAP_CN = "app1" -LDAP_PROVIDER = "ldapmain" - - -def start_log(message): - print("Testing %s... " % message, end="") - - -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 -project1.approvalrules.create( - {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} -) -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -assert 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() -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() diff --git a/tools/functional/api/test_gitlab.py b/tools/functional/api/test_gitlab.py new file mode 100644 index 0000000..5cf3418 --- /dev/null +++ b/tools/functional/api/test_gitlab.py @@ -0,0 +1,8 @@ +""" +Temporary module to run legacy tests as a single pytest test case +as they're all plain asserts at module level. +""" + + +def test_api_v4(gl): + from tools.functional import python_test_v4 diff --git a/tools/functional/cli/conftest.py b/tools/functional/cli/conftest.py index 13c3096..ba94dcb 100644 --- a/tools/functional/cli/conftest.py +++ b/tools/functional/cli/conftest.py @@ -2,7 +2,7 @@ import pytest @pytest.fixture -def gitlab_cli(script_runner, CONFIG): +def gitlab_cli(script_runner, gitlab_config): """Wrapper fixture to help make test cases less verbose.""" def _gitlab_cli(subcommands): @@ -10,7 +10,7 @@ def gitlab_cli(script_runner, CONFIG): Return a script_runner.run method that takes a default gitlab command, and subcommands passed as arguments inside test cases. """ - command = ["gitlab", "--config-file", CONFIG] + command = ["gitlab", "--config-file", gitlab_config] for subcommand in subcommands: # ensure we get strings (e.g from IDs) diff --git a/tools/functional/conftest.py b/tools/functional/conftest.py index e60fa39..ec0d08b 100644 --- a/tools/functional/conftest.py +++ b/tools/functional/conftest.py @@ -1,6 +1,8 @@ +import time import tempfile from pathlib import Path from random import randint +from subprocess import check_output import pytest @@ -8,6 +10,7 @@ import gitlab TEMP_DIR = tempfile.gettempdir() +TEST_DIR = Path(__file__).resolve().parent def random_id(): @@ -20,15 +23,103 @@ def random_id(): return randint(9, 9999) +def reset_gitlab(gl): + # previously tools/reset_gitlab.py + for project in gl.projects.list(): + project.delete() + for group in gl.groups.list(): + group.delete() + for variable in gl.variables.list(): + variable.delete() + for user in gl.users.list(): + if user.username != "root": + user.delete() + + +def set_token(container): + set_token_rb = TEST_DIR / "fixtures" / "set_token.rb" + + with open(set_token_rb, "r") as f: + set_token_command = f.read().strip() + + rails_command = [ + "docker", + "exec", + container, + "gitlab-rails", + "runner", + set_token_command, + ] + output = check_output(rails_command).decode().strip() + + return output + + @pytest.fixture(scope="session") -def CONFIG(): - return Path(TEMP_DIR) / "python-gitlab.cfg" +def docker_compose_file(): + return TEST_DIR / "fixtures" / "docker-compose.yml" @pytest.fixture(scope="session") -def gl(CONFIG): +def check_is_alive(request): + """ + Return a healthcheck function fixture for the GitLab container spinup. + """ + start = time.time() + + # Temporary manager to disable capsys in a session-scoped fixture + # so people know it takes a while for GitLab to spin up + # https://github.com/pytest-dev/pytest/issues/2704 + capmanager = request.config.pluginmanager.getplugin("capturemanager") + + def _check(container): + delay = int(time.time() - start) + + with capmanager.global_and_fixture_disabled(): + print(f"Waiting for GitLab to reconfigure.. (~{delay}s)") + + logs = ["docker", "logs", container] + output = check_output(logs).decode() + + return "gitlab Reconfigured!" in output + + return _check + + +@pytest.fixture(scope="session") +def gitlab_config(check_is_alive, docker_ip, docker_services): + config_file = Path(TEMP_DIR) / "python-gitlab.cfg" + port = docker_services.port_for("gitlab", 80) + + docker_services.wait_until_responsive( + timeout=180, pause=5, check=lambda: check_is_alive("gitlab-test") + ) + + token = set_token("gitlab-test") + + config = f"""[global] +default = local +timeout = 60 + +[local] +url = http://{docker_ip}:{port} +private_token = {token} +api_version = 4""" + + with open(config_file, "w") as f: + f.write(config) + + return config_file + + +@pytest.fixture(scope="session") +def gl(gitlab_config): """Helper instance to make fixtures and asserts directly via the API.""" - return gitlab.Gitlab.from_config("local", [CONFIG]) + + instance = gitlab.Gitlab.from_config("local", [gitlab_config]) + reset_gitlab(instance) + + return instance @pytest.fixture(scope="module") diff --git a/tools/functional/ee-test.py b/tools/functional/ee-test.py new file mode 100755 index 0000000..3f75655 --- /dev/null +++ b/tools/functional/ee-test.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python + +import gitlab + + +P1 = "root/project1" +P2 = "root/project2" +MR_P1 = 1 +I_P1 = 1 +I_P2 = 1 +EPIC_ISSUES = [4, 5] +G1 = "group1" +LDAP_CN = "app1" +LDAP_PROVIDER = "ldapmain" + + +def start_log(message): + print("Testing %s... " % message, end="") + + +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 +project1.approvalrules.create( + {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} +) +ars = project1.approvalrules.list(all=True) +assert len(ars) == 1 +assert 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() +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() diff --git a/tools/functional/fixtures/avatar.png b/tools/functional/fixtures/avatar.png new file mode 100644 index 0000000..a3a767c Binary files /dev/null and b/tools/functional/fixtures/avatar.png differ diff --git a/tools/functional/fixtures/docker-compose.yml b/tools/functional/fixtures/docker-compose.yml new file mode 100644 index 0000000..5dd9d90 --- /dev/null +++ b/tools/functional/fixtures/docker-compose.yml @@ -0,0 +1,31 @@ +version: '3' +services: + gitlab: + image: 'gitlab/gitlab-ce:latest' + container_name: 'gitlab-test' + hostname: 'gitlab.test' + privileged: true # Just in case https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/1350 + environment: + GITLAB_OMNIBUS_CONFIG: | + external_url 'http://gitlab.test' + gitlab_rails['initial_root_password'] = '5iveL!fe' + gitlab_rails['initial_shared_runners_registration_token'] = 'sTPNtWLEuSrHzoHP8oCU' + registry['enable'] = false + nginx['redirect_http_to_https'] = false + nginx['listen_port'] = 80 + nginx['listen_https'] = false + pages_external_url 'http://pages.gitlab.lxd' + gitlab_pages['enable'] = true + gitlab_pages['inplace_chroot'] = true + prometheus['enable'] = false + alertmanager['enable'] = false + node_exporter['enable'] = false + redis_exporter['enable'] = false + postgres_exporter['enable'] = false + pgbouncer_exporter['enable'] = false + gitlab_exporter['enable'] = false + grafana['enable'] = false + letsencrypt['enable'] = false + ports: + - '8080:80' + - '2222:22' diff --git a/tools/functional/fixtures/set_token.rb b/tools/functional/fixtures/set_token.rb new file mode 100644 index 0000000..735dcd5 --- /dev/null +++ b/tools/functional/fixtures/set_token.rb @@ -0,0 +1,9 @@ +# https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#programmatically-creating-a-personal-access-token + +user = User.find_by_username('root') + +token = user.personal_access_tokens.create(scopes: [:api, :sudo], name: 'default'); +token.set_token('python-gitlab-token'); +token.save! + +puts token.token diff --git a/tools/functional/python_test_v4.py b/tools/functional/python_test_v4.py new file mode 100644 index 0000000..6c90fe6 --- /dev/null +++ b/tools/functional/python_test_v4.py @@ -0,0 +1,1141 @@ +import base64 +import tempfile +import time +from pathlib import Path + +import requests + +import gitlab + +LOGIN = "root" +PASSWORD = "5iveL!fe" + +SSH_KEY = ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZAjAX8vTiHD7Yi3/EzuVaDChtih" + "79HyJZ6H9dEqxFfmGA1YnncE0xujQ64TCebhkYJKzmTJCImSVkOu9C4hZgsw6eE76n" + "+Cg3VwEeDUFy+GXlEJWlHaEyc3HWioxgOALbUp3rOezNh+d8BDwwqvENGoePEBsz5l" + "a6WP5lTi/HJIjAl6Hu+zHgdj1XVExeH+S52EwpZf/ylTJub0Bl5gHwf/siVE48mLMI" + "sqrukXTZ6Zg+8EHAIvIQwJ1dKcXe8P5IoLT7VKrbkgAnolS0I8J+uH7KtErZJb5oZh" + "S4OEwsNpaXMAr+6/wWSpircV2/e7sFLlhlKBC4Iq1MpqlZ7G3p foo@bar" +) +DEPLOY_KEY = ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFdRyjJQh+1niBpXqE2I8dzjG" + "MXFHlRjX9yk/UfOn075IdaockdU58sw2Ai1XIWFpZpfJkW7z+P47ZNSqm1gzeXI" + "rtKa9ZUp8A7SZe8vH4XVn7kh7bwWCUirqtn8El9XdqfkzOs/+FuViriUWoJVpA6" + "WZsDNaqINFKIA5fj/q8XQw+BcS92L09QJg9oVUuH0VVwNYbU2M2IRmSpybgC/gu" + "uWTrnCDMmLItksATifLvRZwgdI8dr+q6tbxbZknNcgEPrI2jT0hYN9ZcjNeWuyv" + "rke9IepE7SPBT41C+YtUX4dfDZDmczM1cE0YL/krdUCfuZHMa4ZS2YyNd6slufc" + "vn bar@foo" +) + +GPG_KEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFn5mzYBCADH6SDVPAp1zh/hxmTi0QplkOfExBACpuY6OhzNdIg+8/528b3g +Y5YFR6T/HLv/PmeHskUj21end1C0PNG2T9dTx+2Vlh9ISsSG1kyF9T5fvMR3bE0x +Dl6S489CXZrjPTS9SHk1kF+7dwjUxLJyxF9hPiSihFefDFu3NeOtG/u8vbC1mewQ +ZyAYue+mqtqcCIFFoBz7wHKMWjIVSJSyTkXExu4OzpVvy3l2EikbvavI3qNz84b+ +Mgkv/kiBlNoCy3CVuPk99RYKZ3lX1vVtqQ0OgNGQvb4DjcpyjmbKyibuZwhDjIOh +au6d1OyEbayTntd+dQ4j9EMSnEvm/0MJ4eXPABEBAAG0G0dpdGxhYlRlc3QxIDxm +YWtlQGZha2UudGxkPokBNwQTAQgAIQUCWfmbNgIbAwULCQgHAgYVCAkKCwIEFgID +AQIeAQIXgAAKCRBgxELHf8f3hF3yB/wNJlWPKY65UsB4Lo0hs1OxdxCDqXogSi0u +6crDEIiyOte62pNZKzWy8TJcGZvznRTZ7t8hXgKFLz3PRMcl+vAiRC6quIDUj+2V +eYfwaItd1lUfzvdCaC7Venf4TQ74f5vvNg/zoGwE6eRoSbjlLv9nqsxeA0rUBUQL +LYikWhVMP3TrlfgfduYvh6mfgh57BDLJ9kJVpyfxxx9YLKZbaas9sPa6LgBtR555 +JziUxHmbEv8XCsUU8uoFeP1pImbNBplqE3wzJwzOMSmmch7iZzrAwfN7N2j3Wj0H +B5kQddJ9dmB4BbU0IXGhWczvdpxboI2wdY8a1JypxOdePoph/43iuQENBFn5mzYB +CADnTPY0Zf3d9zLjBNgIb3yDl94uOcKCq0twNmyjMhHzGqw+UMe9BScy34GL94Al +xFRQoaL+7P8hGsnsNku29A/VDZivcI+uxTx4WQ7OLcn7V0bnHV4d76iky2ufbUt/ +GofthjDs1SonePO2N09sS4V4uK0d5N4BfCzzXgvg8etCLxNmC9BGt7AaKUUzKBO4 +2QvNNaC2C/8XEnOgNWYvR36ylAXAmo0sGFXUsBCTiq1fugS9pwtaS2JmaVpZZ3YT +pMZlS0+SjC5BZYFqSmKCsA58oBRzCxQz57nR4h5VEflgD+Hy0HdW0UHETwz83E6/ +U0LL6YyvhwFr6KPq5GxinSvfABEBAAGJAR8EGAEIAAkFAln5mzYCGwwACgkQYMRC +x3/H94SJgwgAlKQb10/xcL/epdDkR7vbiei7huGLBpRDb/L5fM8B5W77Qi8Xmuqj +cCu1j99ZCA5hs/vwVn8j8iLSBGMC5gxcuaar/wtmiaEvT9fO/h6q4opG7NcuiJ8H +wRj8ccJmRssNqDD913PLz7T40Ts62blhrEAlJozGVG/q7T3RAZcskOUHKeHfc2RI +YzGsC/I9d7k6uxAv1L9Nm5F2HaAQDzhkdd16nKkGaPGR35cT1JLInkfl5cdm7ldN +nxs4TLO3kZjUTgWKdhpgRNF5hwaz51ZjpebaRf/ZqRuNyX4lIRolDxzOn/+O1o8L +qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ== +=5OGa +-----END PGP PUBLIC KEY BLOCK-----""" +AVATAR_PATH = Path(__file__).resolve().parent / "fixtures" / "avatar.png" +TEMP_DIR = Path(tempfile.gettempdir()) + +# token authentication from config file +gl = gitlab.Gitlab.from_config(config_files=[TEMP_DIR / "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", + } +) + +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}) +group4 = gl.groups.create({"name": "group4", "path": "group4"}) + +assert len(gl.groups.list()) == 4 +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}) + +group4.share(group1.id, gitlab.const.DEVELOPER_ACCESS) +group4.share(group2.id, gitlab.const.MAINTAINER_ACCESS) +# Reload group4 to have updated shared_with_groups +group4 = gl.groups.get(group4.id) +assert len(group4.shared_with_groups) == 2 +group4.unshare(group1.id) +# Reload group4 to have updated shared_with_groups +group4 = gl.groups.get(group4.id) +assert len(group4.shared_with_groups) == 1 + +# 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 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 + + +# group import/export +export = group1.exports.create() +assert export.message == "202 Accepted" + +# We cannot check for export_status with group export API +time.sleep(10) + +import_archive = TEMP_DIR / "gitlab-group-export.tgz" +import_path = "imported_group" +import_name = "Imported Group" + +with open(import_archive, "wb") as f: + export.download(streamed=True, action=f.write) + +with open(import_archive, "rb") as f: + output = gl.groups.import_group(f, import_path, import_name) +assert output["message"] == "202 Accepted" + +# We cannot check for returned ID with group import API +time.sleep(10) +group_import = gl.groups.get(import_path) + +assert group_import.path == import_path +assert group_import.name == import_name + + +# 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)) == 3 +assert len(gl.projects.list(search="admin")) == 1 +assert len(gl.projects.list(as_list=False)) == 4 + +# 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", + "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 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 Signature Not Found" + +# 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 + +# deploy tokens +deploy_token = admin_project.deploytokens.create( + { + "name": "foo", + "username": "bar", + "expires_at": "2022-01-01", + "scopes": ["read_registry"], + } +) +assert len(admin_project.deploytokens.list()) == 1 +assert gl.deploytokens.list() == admin_project.deploytokens.list() + +assert admin_project.deploytokens.list()[0].name == "foo" +assert admin_project.deploytokens.list()[0].expires_at == "2022-01-01T00:00:00.000Z" +assert admin_project.deploytokens.list()[0].scopes == ["read_registry"] +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211963 is fixed +# assert admin_project.deploytokens.list()[0].username == "bar" +deploy_token.delete() +assert len(admin_project.deploytokens.list()) == 0 +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +# assert len(gl.deploytokens.list()) == 0 + + +deploy_token_group = gl.groups.create( + {"name": "deploy_token_group", "path": "deploy_token_group"} +) + +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211878 is fixed +# deploy_token = group_deploy_token.deploytokens.create( +# { +# "name": "foo", +# "scopes": ["read_registry"], +# } +# ) + +# Remove once https://gitlab.com/gitlab-org/gitlab/-/issues/211878 is fixed +deploy_token = deploy_token_group.deploytokens.create( + { + "name": "foo", + "username": "", + "expires_at": "", + "scopes": ["read_repository"], + } +) + +assert len(deploy_token_group.deploytokens.list()) == 1 +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +# assert gl.deploytokens.list() == deploy_token_group.deploytokens.list() +deploy_token.delete() +assert len(deploy_token_group.deploytokens.list()) == 0 +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +# assert len(gl.deploytokens.list()) == 0 + +deploy_token_group.delete() + +# 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 + +# issue labels +label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) +issue1.labels = ["label2"] +issue1.save() + +assert issue1 in admin_project.issues.list(labels=["label2"]) +assert issue1 in admin_project.issues.list(labels="label2") +assert issue1 in admin_project.issues.list(labels="Any") +assert issue1 not in admin_project.issues.list(labels="None") + +# issue events +events = issue1.resourcelabelevents.list() +assert events +event = issue1.resourcelabelevents.get(events[0].id) +assert event + +# issue milestones +milestones = issue1.resourcemilestoneevents.list() +assert milestones +milestone = issue1.resourcemilestoneevents.get(milestones[0].id) +assert milestone + +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) +# TO BE RE-ENABLED AFTER 13.1 +# 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() + + +# 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 + +# mr milestone events +mr.milestone_id = m1.id +mr.save() +milestones = mr.resourcemilestoneevents.list() +assert milestones +milestone = mr.resourcemilestoneevents.get(milestones[0].id) +assert milestone + +# 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): + 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) + ex.refresh() + count += 1 + if count == 10: + raise Exception("Project export taking too much time") +with open(TEMP_DIR / "gitlab-export.tgz", "wb") as f: + ex.download(streamed=True, action=f.write) + +output = gl.projects.import_project( + open(TEMP_DIR / "gitlab-export.tgz", "rb"), + "imported_project", + name="Imported Project", +) +project_import = gl.projects.get(output["id"], lazy=True).imports.get() + +assert project_import.path == "imported_project" +assert project_import.name == "Imported Project" + +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() + +# project remote mirrors +mirror_url = "http://gitlab.test/root/mirror.git" + +# create remote mirror +mirror = admin_project.remote_mirrors.create({"url": mirror_url}) +assert mirror.url == mirror_url + +# update remote mirror +mirror.enabled = True +mirror.save() + +# list remote mirrors +mirror = admin_project.remote_mirrors.list()[0] +assert isinstance(mirror, gitlab.v4.objects.ProjectRemoteMirror) +assert mirror.url == mirror_url +assert mirror.enabled is True + +# 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/functional_tests.sh b/tools/functional_tests.sh deleted file mode 100755 index 9b91f0f..0000000 --- a/tools/functional_tests.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# Copyright (C) 2015 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -setenv_script=$(dirname "$0")/build_test_env.sh || exit 1 -BUILD_TEST_ENV_AUTO_CLEANUP=true -. "$setenv_script" "$@" || exit 1 - -pytest --script-launch-mode=subprocess "$(dirname "$0")/functional/cli" diff --git a/tools/generate_token.py b/tools/generate_token.py deleted file mode 100755 index 89909bd..0000000 --- a/tools/generate_token.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python - -from urllib.parse import urljoin -from requests_html import HTMLSession - -ENDPOINT = "http://localhost:8080" -LOGIN = "root" -PASSWORD = "5iveL!fe" - - -class GitlabSession(HTMLSession): - def __init__(self, endpoint, *args, **kwargs): - super().__init__(*args, **kwargs) - self.endpoint = endpoint - self.csrf = None - - def find_csrf_token(self, html): - param = html.find("meta[name=csrf-param]")[0].attrs["content"] - token = html.find("meta[name=csrf-token]")[0].attrs["content"] - self.csrf = {param: token} - - def obtain_csrf_token(self): - r = self.get(urljoin(self.endpoint, "/")) - self.find_csrf_token(r.html) - - def sign_in(self, login, password): - data = {"user[login]": login, "user[password]": password, **self.csrf} - r = self.post(urljoin(self.endpoint, "/users/sign_in"), data=data) - self.find_csrf_token(r.html) - - def obtain_personal_access_token(self, name): - data = { - "personal_access_token[name]": name, - "personal_access_token[scopes][]": ["api", "sudo"], - **self.csrf, - } - r = self.post( - urljoin(self.endpoint, "/profile/personal_access_tokens"), data=data - ) - return r.html.find("#created-personal-access-token")[0].attrs["value"] - - -def main(): - with GitlabSession(ENDPOINT) as s: - s.obtain_csrf_token() - s.sign_in(LOGIN, PASSWORD) - print(s.obtain_personal_access_token("default")) - - -if __name__ == "__main__": - main() diff --git a/tools/py_functional_tests.sh b/tools/py_functional_tests.sh deleted file mode 100755 index 1009cb9..0000000 --- a/tools/py_functional_tests.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# Copyright (C) 2015 Gauvain Pocentek -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -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 -pytest "$(dirname "$0")/functional/api" diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py deleted file mode 100644 index 7ff97b6..0000000 --- a/tools/python_test_v4.py +++ /dev/null @@ -1,1138 +0,0 @@ -import base64 -import os -import time - -import requests - -import gitlab - -LOGIN = "root" -PASSWORD = "5iveL!fe" - -SSH_KEY = ( - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZAjAX8vTiHD7Yi3/EzuVaDChtih" - "79HyJZ6H9dEqxFfmGA1YnncE0xujQ64TCebhkYJKzmTJCImSVkOu9C4hZgsw6eE76n" - "+Cg3VwEeDUFy+GXlEJWlHaEyc3HWioxgOALbUp3rOezNh+d8BDwwqvENGoePEBsz5l" - "a6WP5lTi/HJIjAl6Hu+zHgdj1XVExeH+S52EwpZf/ylTJub0Bl5gHwf/siVE48mLMI" - "sqrukXTZ6Zg+8EHAIvIQwJ1dKcXe8P5IoLT7VKrbkgAnolS0I8J+uH7KtErZJb5oZh" - "S4OEwsNpaXMAr+6/wWSpircV2/e7sFLlhlKBC4Iq1MpqlZ7G3p foo@bar" -) -DEPLOY_KEY = ( - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFdRyjJQh+1niBpXqE2I8dzjG" - "MXFHlRjX9yk/UfOn075IdaockdU58sw2Ai1XIWFpZpfJkW7z+P47ZNSqm1gzeXI" - "rtKa9ZUp8A7SZe8vH4XVn7kh7bwWCUirqtn8El9XdqfkzOs/+FuViriUWoJVpA6" - "WZsDNaqINFKIA5fj/q8XQw+BcS92L09QJg9oVUuH0VVwNYbU2M2IRmSpybgC/gu" - "uWTrnCDMmLItksATifLvRZwgdI8dr+q6tbxbZknNcgEPrI2jT0hYN9ZcjNeWuyv" - "rke9IepE7SPBT41C+YtUX4dfDZDmczM1cE0YL/krdUCfuZHMa4ZS2YyNd6slufc" - "vn bar@foo" -) - -GPG_KEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBFn5mzYBCADH6SDVPAp1zh/hxmTi0QplkOfExBACpuY6OhzNdIg+8/528b3g -Y5YFR6T/HLv/PmeHskUj21end1C0PNG2T9dTx+2Vlh9ISsSG1kyF9T5fvMR3bE0x -Dl6S489CXZrjPTS9SHk1kF+7dwjUxLJyxF9hPiSihFefDFu3NeOtG/u8vbC1mewQ -ZyAYue+mqtqcCIFFoBz7wHKMWjIVSJSyTkXExu4OzpVvy3l2EikbvavI3qNz84b+ -Mgkv/kiBlNoCy3CVuPk99RYKZ3lX1vVtqQ0OgNGQvb4DjcpyjmbKyibuZwhDjIOh -au6d1OyEbayTntd+dQ4j9EMSnEvm/0MJ4eXPABEBAAG0G0dpdGxhYlRlc3QxIDxm -YWtlQGZha2UudGxkPokBNwQTAQgAIQUCWfmbNgIbAwULCQgHAgYVCAkKCwIEFgID -AQIeAQIXgAAKCRBgxELHf8f3hF3yB/wNJlWPKY65UsB4Lo0hs1OxdxCDqXogSi0u -6crDEIiyOte62pNZKzWy8TJcGZvznRTZ7t8hXgKFLz3PRMcl+vAiRC6quIDUj+2V -eYfwaItd1lUfzvdCaC7Venf4TQ74f5vvNg/zoGwE6eRoSbjlLv9nqsxeA0rUBUQL -LYikWhVMP3TrlfgfduYvh6mfgh57BDLJ9kJVpyfxxx9YLKZbaas9sPa6LgBtR555 -JziUxHmbEv8XCsUU8uoFeP1pImbNBplqE3wzJwzOMSmmch7iZzrAwfN7N2j3Wj0H -B5kQddJ9dmB4BbU0IXGhWczvdpxboI2wdY8a1JypxOdePoph/43iuQENBFn5mzYB -CADnTPY0Zf3d9zLjBNgIb3yDl94uOcKCq0twNmyjMhHzGqw+UMe9BScy34GL94Al -xFRQoaL+7P8hGsnsNku29A/VDZivcI+uxTx4WQ7OLcn7V0bnHV4d76iky2ufbUt/ -GofthjDs1SonePO2N09sS4V4uK0d5N4BfCzzXgvg8etCLxNmC9BGt7AaKUUzKBO4 -2QvNNaC2C/8XEnOgNWYvR36ylAXAmo0sGFXUsBCTiq1fugS9pwtaS2JmaVpZZ3YT -pMZlS0+SjC5BZYFqSmKCsA58oBRzCxQz57nR4h5VEflgD+Hy0HdW0UHETwz83E6/ -U0LL6YyvhwFr6KPq5GxinSvfABEBAAGJAR8EGAEIAAkFAln5mzYCGwwACgkQYMRC -x3/H94SJgwgAlKQb10/xcL/epdDkR7vbiei7huGLBpRDb/L5fM8B5W77Qi8Xmuqj -cCu1j99ZCA5hs/vwVn8j8iLSBGMC5gxcuaar/wtmiaEvT9fO/h6q4opG7NcuiJ8H -wRj8ccJmRssNqDD913PLz7T40Ts62blhrEAlJozGVG/q7T3RAZcskOUHKeHfc2RI -YzGsC/I9d7k6uxAv1L9Nm5F2HaAQDzhkdd16nKkGaPGR35cT1JLInkfl5cdm7ldN -nxs4TLO3kZjUTgWKdhpgRNF5hwaz51ZjpebaRf/ZqRuNyX4lIRolDxzOn/+O1o8L -qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ== -=5OGa ------END PGP PUBLIC KEY BLOCK-----""" -AVATAR_PATH = os.path.join(os.path.dirname(__file__), "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", - } -) - -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}) -group4 = gl.groups.create({"name": "group4", "path": "group4"}) - -assert len(gl.groups.list()) == 4 -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}) - -group4.share(group1.id, gitlab.const.DEVELOPER_ACCESS) -group4.share(group2.id, gitlab.const.MAINTAINER_ACCESS) -# Reload group4 to have updated shared_with_groups -group4 = gl.groups.get(group4.id) -assert len(group4.shared_with_groups) == 2 -group4.unshare(group1.id) -# Reload group4 to have updated shared_with_groups -group4 = gl.groups.get(group4.id) -assert len(group4.shared_with_groups) == 1 - -# 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 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 - - -# group import/export -export = group1.exports.create() -assert export.message == "202 Accepted" - -# We cannot check for export_status with group export API -time.sleep(10) - -import_archive = "/tmp/gitlab-group-export.tgz" -import_path = "imported_group" -import_name = "Imported Group" - -with open(import_archive, "wb") as f: - export.download(streamed=True, action=f.write) - -with open(import_archive, "rb") as f: - output = gl.groups.import_group(f, import_path, import_name) -assert output["message"] == "202 Accepted" - -# We cannot check for returned ID with group import API -time.sleep(10) -group_import = gl.groups.get(import_path) - -assert group_import.path == import_path -assert group_import.name == import_name - - -# 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)) == 3 -assert len(gl.projects.list(search="admin")) == 1 -assert len(gl.projects.list(as_list=False)) == 4 - -# 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", - "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 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 Signature Not Found" - -# 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 - -# deploy tokens -deploy_token = admin_project.deploytokens.create( - { - "name": "foo", - "username": "bar", - "expires_at": "2022-01-01", - "scopes": ["read_registry"], - } -) -assert len(admin_project.deploytokens.list()) == 1 -assert gl.deploytokens.list() == admin_project.deploytokens.list() - -assert admin_project.deploytokens.list()[0].name == "foo" -assert admin_project.deploytokens.list()[0].expires_at == "2022-01-01T00:00:00.000Z" -assert admin_project.deploytokens.list()[0].scopes == ["read_registry"] -# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211963 is fixed -# assert admin_project.deploytokens.list()[0].username == "bar" -deploy_token.delete() -assert len(admin_project.deploytokens.list()) == 0 -# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed -# assert len(gl.deploytokens.list()) == 0 - - -deploy_token_group = gl.groups.create( - {"name": "deploy_token_group", "path": "deploy_token_group"} -) - -# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211878 is fixed -# deploy_token = group_deploy_token.deploytokens.create( -# { -# "name": "foo", -# "scopes": ["read_registry"], -# } -# ) - -# Remove once https://gitlab.com/gitlab-org/gitlab/-/issues/211878 is fixed -deploy_token = deploy_token_group.deploytokens.create( - { - "name": "foo", - "username": "", - "expires_at": "", - "scopes": ["read_repository"], - } -) - -assert len(deploy_token_group.deploytokens.list()) == 1 -# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed -# assert gl.deploytokens.list() == deploy_token_group.deploytokens.list() -deploy_token.delete() -assert len(deploy_token_group.deploytokens.list()) == 0 -# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed -# assert len(gl.deploytokens.list()) == 0 - -deploy_token_group.delete() - -# 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 - -# issue labels -label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) -issue1.labels = ["label2"] -issue1.save() - -assert issue1 in admin_project.issues.list(labels=["label2"]) -assert issue1 in admin_project.issues.list(labels="label2") -assert issue1 in admin_project.issues.list(labels="Any") -assert issue1 not in admin_project.issues.list(labels="None") - -# issue events -events = issue1.resourcelabelevents.list() -assert events -event = issue1.resourcelabelevents.get(events[0].id) -assert event - -# issue milestones -milestones = issue1.resourcemilestoneevents.list() -assert milestones -milestone = issue1.resourcemilestoneevents.get(milestones[0].id) -assert milestone - -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) -# TO BE RE-ENABLED AFTER 13.1 -# 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() - - -# 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 - -# mr milestone events -mr.milestone_id = m1.id -mr.save() -milestones = mr.resourcemilestoneevents.list() -assert milestones -milestone = mr.resourcemilestoneevents.get(milestones[0].id) -assert milestone - -# 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): - 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) - 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", name="Imported Project" -) -project_import = gl.projects.get(output["id"], lazy=True).imports.get() - -assert project_import.path == "imported_project" -assert project_import.name == "Imported Project" - -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() - -# project remote mirrors -mirror_url = "http://gitlab.test/root/mirror.git" - -# create remote mirror -mirror = admin_project.remote_mirrors.create({"url": mirror_url}) -assert mirror.url == mirror_url - -# update remote mirror -mirror.enabled = True -mirror.save() - -# list remote mirrors -mirror = admin_project.remote_mirrors.list()[0] -assert isinstance(mirror, gitlab.v4.objects.ProjectRemoteMirror) -assert mirror.url == mirror_url -assert mirror.enabled is True - -# 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/reset_gitlab.py b/tools/reset_gitlab.py deleted file mode 100755 index 64668a9..0000000 --- a/tools/reset_gitlab.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python - -import sys - -from gitlab import Gitlab - - -def main(): - with Gitlab.from_config(config_files=["/tmp/python-gitlab.cfg"]) as gl: - for project in gl.projects.list(): - project.delete() - for group in gl.groups.list(): - group.delete() - for user in gl.users.list(): - if user.username != "root": - user.delete() - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tox.ini b/tox.ini index df7ca09..92196e5 100644 --- a/tox.ini +++ b/tox.ini @@ -55,7 +55,9 @@ commands = omit = *tests* [testenv:cli_func_v4] -commands = {toxinidir}/tools/functional_tests.sh -a 4 +deps = -r{toxinidir}/docker-requirements.txt +commands = pytest --script-launch-mode=subprocess tools/functional/cli {posargs} [testenv:py_func_v4] -commands = {toxinidir}/tools/py_functional_tests.sh -a 4 +deps = -r{toxinidir}/docker-requirements.txt +commands = pytest tools/functional/api {posargs} -- cgit v1.2.1