diff options
author | Nejc Habjan <nejc.habjan@siemens.com> | 2020-09-05 00:15:04 +0200 |
---|---|---|
committer | Nejc Habjan <nejc.habjan@siemens.com> | 2020-09-15 22:05:48 +0200 |
commit | 79489c775141c4ddd1f7aecae90dae8061d541fe (patch) | |
tree | 7c8f45e232049e0cad13af57e05b7ccbd8c6bb82 /tools/functional | |
parent | 0d89a6e61bd4ae244c1545463272ef830d72dda9 (diff) | |
download | gitlab-79489c775141c4ddd1f7aecae90dae8061d541fe.tar.gz |
test(env): replace custom scripts with pytest and docker-compose
Diffstat (limited to 'tools/functional')
-rw-r--r-- | tools/functional/api/test_gitlab.py | 8 | ||||
-rw-r--r-- | tools/functional/cli/conftest.py | 4 | ||||
-rw-r--r-- | tools/functional/conftest.py | 99 | ||||
-rwxr-xr-x | tools/functional/ee-test.py | 159 | ||||
-rw-r--r-- | tools/functional/fixtures/avatar.png | bin | 0 -> 592 bytes | |||
-rw-r--r-- | tools/functional/fixtures/docker-compose.yml | 31 | ||||
-rw-r--r-- | tools/functional/fixtures/set_token.rb | 9 | ||||
-rw-r--r-- | tools/functional/python_test_v4.py | 1141 |
8 files changed, 1445 insertions, 6 deletions
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 Binary files differnew file mode 100644 index 0000000..a3a767c --- /dev/null +++ b/tools/functional/fixtures/avatar.png 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 |