diff --git a/.gitignore b/.gitignore index febd0f7f1..b435627ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ *.pyc build/ dist/ +htmlcov/ MANIFEST .*.swp *.egg-info .idea/ docs/_build +.coverage .testrepository/ .tox venv/ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 44644a639..000000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./gitlab/tests $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/.travis.yml b/.travis.yml index 83d2d3391..fc9e93548 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,3 +67,10 @@ jobs: script: - pip3 install tox - tox -e py38 + - stage: test + dist: bionic + name: coverage + python: 3.8 + script: + - pip3 install tox + - tox -e cover diff --git a/MANIFEST.in b/MANIFEST.in index 2d1b15b11..df53d6691 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include COPYING AUTHORS ChangeLog.rst RELEASE_NOTES.rst requirements.txt test-requirements.txt rtd-requirements.txt -include tox.ini .testr.conf .travis.yml +include tox.ini .travis.yml recursive-include tools * recursive-include docs *j2 *.py *.rst api/*.rst Makefile make.bat recursive-include gitlab/tests/data * diff --git a/test-requirements.txt b/test-requirements.txt index 65d09d7d3..a5651cdd1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,10 @@ coverage -discover -testrepository +flaky hacking>=0.9.2,<0.10 httmock jinja2 mock +pytest +pytest-cov sphinx>=1.3 sphinx_rtd_theme diff --git a/tools/avatar.png b/tests/functional/data/avatar.png similarity index 100% rename from tools/avatar.png rename to tests/functional/data/avatar.png diff --git a/tests/functional/test_api_v4.py b/tests/functional/test_api_v4.py new file mode 100644 index 000000000..f5e41576c --- /dev/null +++ b/tests/functional/test_api_v4.py @@ -0,0 +1,1042 @@ +import base64 +import os +import time + +import pytest +import requests + +import gitlab +from tools import reset_gitlab + + +CONFIG_FILE = "/tmp/python-gitlab.cfg" +LOGIN = "root" +PASSWORD = "5iveL!fe" + +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__), "data", "avatar.png") + + +@pytest.fixture() +def gl(): + gl = gitlab.Gitlab.from_config(config_files=[CONFIG_FILE]) + gl.auth() + + yield gl + + # Teardown for flaky re-runs + reset_gitlab.main() + + +def test_token_auth_from_config_file(): + gl = gitlab.Gitlab.from_config(config_files=[CONFIG_FILE]) + gl.auth() + + assert isinstance(gl.user, gitlab.v4.objects.CurrentUser) + + +@pytest.mark.flaky(max_runs=3) +def test_api_v4(gl): + + # markdown + html = gl.markdown("foo") + assert "foo" in html + + success, errors = gl.lint("Invalid") + assert success is False + assert errors + + # sidekiq + out = gl.sidekiq.queue_metrics() + assert isinstance(out, dict) + assert "pages" in out["queues"] + out = gl.sidekiq.process_metrics() + assert isinstance(out, dict) + assert "hostname" in out["processes"][0] + out = gl.sidekiq.job_stats() + assert isinstance(out, dict) + assert "processed" in out["jobs"] + out = gl.sidekiq.compound_metrics() + assert isinstance(out, dict) + assert "jobs" in out + assert "processes" in out + assert "queues" in out + + # settings + settings = gl.settings.get() + settings.default_projects_limit = 42 + settings.save() + settings = gl.settings.get() + assert settings.default_projects_limit == 42 + + # users + new_user = gl.users.create( + { + "email": "foo@bar.com", + "username": "foo", + "name": "foo", + "password": "foo_password", + "avatar": open(AVATAR_PATH, "rb"), + } + ) + avatar_url = new_user.avatar_url.replace("gitlab.test", "localhost:8080") + uploaded_avatar = requests.get(avatar_url).content + assert uploaded_avatar == open(AVATAR_PATH, "rb").read() + users_list = gl.users.list() + for user in users_list: + if user.username == "foo": + break + assert new_user.username == user.username + assert new_user.email == user.email + + new_user.block() + new_user.unblock() + + # user projects list + assert len(new_user.projects.list()) == 0 + + # events list + new_user.events.list() + + foobar_user = gl.users.create( + { + "email": "foobar@example.com", + "username": "foobar", + "name": "Foo Bar", + "password": "foobar_password", + } + ) + + assert gl.users.list(search="foobar")[0].id == foobar_user.id + expected = [new_user, foobar_user] + actual = list(gl.users.list(search="foo")) + assert len(expected) == len(actual) + assert len(gl.users.list(search="asdf")) == 0 + foobar_user.bio = "This is the user bio" + foobar_user.save() + + # GPG keys + gkey = new_user.gpgkeys.create({"key": GPG_KEY}) + assert len(new_user.gpgkeys.list()) == 1 + # Seems broken on the gitlab side + # gkey = new_user.gpgkeys.get(gkey.id) + gkey.delete() + assert len(new_user.gpgkeys.list()) == 0 + + # SSH keys + key = new_user.keys.create({"title": "testkey", "key": SSH_KEY}) + assert len(new_user.keys.list()) == 1 + key.delete() + assert len(new_user.keys.list()) == 0 + + # emails + email = new_user.emails.create({"email": "foo2@bar.com"}) + assert len(new_user.emails.list()) == 1 + email.delete() + assert len(new_user.emails.list()) == 0 + + # custom attributes + attrs = new_user.customattributes.list() + assert len(attrs) == 0 + attr = new_user.customattributes.set("key", "value1") + assert len(gl.users.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(new_user.customattributes.list()) == 1 + attr = new_user.customattributes.set("key", "value2") + attr = new_user.customattributes.get("key") + assert attr.value == "value2" + assert len(new_user.customattributes.list()) == 1 + attr.delete() + assert len(new_user.customattributes.list()) == 0 + + # impersonation tokens + user_token = new_user.impersonationtokens.create( + {"name": "token1", "scopes": ["api", "read_user"]} + ) + l = new_user.impersonationtokens.list(state="active") + assert len(l) == 1 + user_token.delete() + l = new_user.impersonationtokens.list(state="active") + assert len(l) == 0 + l = new_user.impersonationtokens.list(state="inactive") + assert len(l) == 1 + + new_user.delete() + foobar_user.delete() + assert len(gl.users.list()) == 3 + len( + [u for u in gl.users.list() if u.username == "ghost"] + ) + + # current user mail + mail = gl.user.emails.create({"email": "current@user.com"}) + assert len(gl.user.emails.list()) == 1 + mail.delete() + assert len(gl.user.emails.list()) == 0 + + # current user GPG keys + gkey = gl.user.gpgkeys.create({"key": GPG_KEY}) + assert len(gl.user.gpgkeys.list()) == 1 + # Seems broken on the gitlab side + gkey = gl.user.gpgkeys.get(gkey.id) + gkey.delete() + assert len(gl.user.gpgkeys.list()) == 0 + + # current user key + key = gl.user.keys.create({"title": "testkey", "key": SSH_KEY}) + assert len(gl.user.keys.list()) == 1 + key.delete() + assert len(gl.user.keys.list()) == 0 + + # templates + assert gl.dockerfiles.list() + dockerfile = gl.dockerfiles.get("Node") + assert dockerfile.content is not None + + assert gl.gitignores.list() + gitignore = gl.gitignores.get("Node") + assert gitignore.content is not None + + assert gl.gitlabciymls.list() + gitlabciyml = gl.gitlabciymls.get("Nodejs") + assert gitlabciyml.content is not None + + assert gl.licenses.list() + license = gl.licenses.get( + "bsd-2-clause", project="mytestproject", fullname="mytestfullname" + ) + assert "mytestfullname" in license.content + + # groups + user1 = gl.users.create( + { + "email": "user1@test.com", + "username": "user1", + "name": "user1", + "password": "user1_pass", + } + ) + user2 = gl.users.create( + { + "email": "user2@test.com", + "username": "user2", + "name": "user2", + "password": "user2_pass", + } + ) + group1 = gl.groups.create({"name": "group1", "path": "group1"}) + group2 = gl.groups.create({"name": "group2", "path": "group2"}) + + p_id = gl.groups.list(search="group2")[0].id + group3 = gl.groups.create({"name": "group3", "path": "group3", "parent_id": p_id}) + + assert len(gl.groups.list()) == 3 + assert len(gl.groups.list(search="oup1")) == 1 + assert group3.parent_id == p_id + assert group2.subgroups.list()[0].id == group3.id + + group1.members.create( + {"access_level": gitlab.const.OWNER_ACCESS, "user_id": user1.id} + ) + group1.members.create( + {"access_level": gitlab.const.GUEST_ACCESS, "user_id": user2.id} + ) + + group2.members.create( + {"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id} + ) + + # User memberships (admin only) + memberships1 = user1.memberships.list() + assert len(memberships1) == 1 + + memberships2 = user2.memberships.list() + assert len(memberships2) == 2 + + membership = memberships1[0] + assert membership.source_type == "Namespace" + assert membership.access_level == gitlab.const.OWNER_ACCESS + + project_memberships = user1.memberships.list(type="Project") + assert len(project_memberships) == 0 + + group_memberships = user1.memberships.list(type="Namespace") + assert len(group_memberships) == 1 + + try: + membership = user1.memberships.list(type="Invalid") + except gitlab.GitlabListError as e: + error_message = e.error_message + assert error_message == "type does not have a valid value" + + try: + user1.memberships.list(sudo=user1.name) + except gitlab.GitlabListError as e: + error_message = e.error_message + assert error_message == "403 Forbidden" + + # Administrator belongs to the groups + assert len(group1.members.list()) == 3 + assert len(group2.members.list()) == 2 + + group1.members.delete(user1.id) + assert len(group1.members.list()) == 2 + assert len(group1.members.all()) + member = group1.members.get(user2.id) + member.access_level = gitlab.const.OWNER_ACCESS + member.save() + member = group1.members.get(user2.id) + assert member.access_level == gitlab.const.OWNER_ACCESS + + group2.members.delete(gl.user.id) + + # group custom attributes + attrs = group2.customattributes.list() + assert len(attrs) == 0 + attr = group2.customattributes.set("key", "value1") + assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(group2.customattributes.list()) == 1 + attr = group2.customattributes.set("key", "value2") + attr = group2.customattributes.get("key") + assert attr.value == "value2" + assert len(group2.customattributes.list()) == 1 + attr.delete() + assert len(group2.customattributes.list()) == 0 + + # group notification settings + settings = group2.notificationsettings.get() + settings.level = "disabled" + settings.save() + settings = group2.notificationsettings.get() + assert settings.level == "disabled" + + # group badges + badge_image = "http://example.com" + badge_link = "http://example/img.svg" + badge = group2.badges.create({"link_url": badge_link, "image_url": badge_image}) + assert len(group2.badges.list()) == 1 + badge.image_url = "http://another.example.com" + badge.save() + badge = group2.badges.get(badge.id) + assert badge.image_url == "http://another.example.com" + badge.delete() + assert len(group2.badges.list()) == 0 + + # group milestones + gm1 = group1.milestones.create({"title": "groupmilestone1"}) + assert len(group1.milestones.list()) == 1 + gm1.due_date = "2020-01-01T00:00:00Z" + gm1.save() + gm1.state_event = "close" + gm1.save() + gm1 = group1.milestones.get(gm1.id) + assert gm1.state == "closed" + assert len(gm1.issues()) == 0 + assert len(gm1.merge_requests()) == 0 + + # group variables + group1.variables.create({"key": "foo", "value": "bar"}) + g_v = group1.variables.get("foo") + assert g_v.value == "bar" + g_v.value = "baz" + g_v.save() + g_v = group1.variables.get("foo") + assert g_v.value == "baz" + assert len(group1.variables.list()) == 1 + g_v.delete() + assert len(group1.variables.list()) == 0 + + # group labels + # group1.labels.create({"name": "foo", "description": "bar", "color": "#112233"}) + # g_l = group1.labels.get("foo") + # assert g_l.description == "bar" + # g_l.description = "baz" + # g_l.save() + # g_l = group1.labels.get("foo") + # assert g_l.description == "baz" + # assert len(group1.labels.list()) == 1 + # g_l.delete() + # assert len(group1.labels.list()) == 0 + + # hooks + hook = gl.hooks.create({"url": "http://whatever.com"}) + assert len(gl.hooks.list()) == 1 + hook.delete() + assert len(gl.hooks.list()) == 0 + + # projects + admin_project = gl.projects.create({"name": "admin_project"}) + gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) + gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group2.id}) + sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user1.name) + + assert len(gl.projects.list(owned=True)) == 2 + assert len(gl.projects.list(search="admin")) == 1 + + # test pagination + l1 = gl.projects.list(per_page=1, page=1) + l2 = gl.projects.list(per_page=1, page=2) + assert len(l1) == 1 + assert len(l2) == 1 + assert l1[0].id != l2[0].id + + # group custom attributes + attrs = admin_project.customattributes.list() + assert len(attrs) == 0 + attr = admin_project.customattributes.set("key", "value1") + assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(admin_project.customattributes.list()) == 1 + attr = admin_project.customattributes.set("key", "value2") + attr = admin_project.customattributes.get("key") + assert attr.value == "value2" + assert len(admin_project.customattributes.list()) == 1 + attr.delete() + assert len(admin_project.customattributes.list()) == 0 + + # project pages domains + domain = admin_project.pagesdomains.create({"domain": "foo.domain.com"}) + assert len(admin_project.pagesdomains.list()) == 1 + assert len(gl.pagesdomains.list()) == 1 + domain = admin_project.pagesdomains.get("foo.domain.com") + assert domain.domain == "foo.domain.com" + domain.delete() + assert len(admin_project.pagesdomains.list()) == 0 + + # project content (files) + admin_project.files.create( + { + "file_path": "README", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } + ) + readme = admin_project.files.get(file_path="README", ref="master") + readme.content = base64.b64encode(b"Improved README").decode() + time.sleep(2) + readme.save(branch="master", commit_message="new commit") + readme.delete(commit_message="Removing README", branch="master") + + admin_project.files.create( + { + "file_path": "README.rst", + "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 comment + commit.comments.create({"note": "This is a commit comment"}) + # assert len(commit.comments.list()) == 1 + + # commit discussion + count = len(commit.discussions.list()) + discussion = commit.discussions.create({"body": "Discussion body"}) + # assert len(commit.discussions.list()) == (count + 1) + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = commit.discussions.get(discussion.id) + # assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = commit.discussions.get(discussion.id) + # assert len(discussion.attributes["notes"]) == 1 + + # Revert commit + revert_commit = commit.revert(branch="master") + + expected_message = 'Revert "{}"\n\nThis reverts commit {}'.format( + commit.message, commit.id + ) + assert revert_commit["message"] == expected_message + + try: + commit.revert(branch="master") + # Only here to really ensure expected error without a full test framework + raise AssertionError("Two revert attempts should raise GitlabRevertError") + except gitlab.GitlabRevertError: + pass + + # housekeeping + admin_project.housekeeping() + + # repository + tree = admin_project.repository_tree() + assert len(tree) != 0 + assert tree[0]["name"] == "README.rst" + blob_id = tree[0]["id"] + blob = admin_project.repository_raw_blob(blob_id) + assert blob.decode() == "Initial content" + archive1 = admin_project.repository_archive() + archive2 = admin_project.repository_archive("master") + assert archive1 == archive2 + snapshot = admin_project.snapshot() + + # project file uploads + filename = "test.txt" + file_contents = "testing contents" + uploaded_file = admin_project.upload(filename, file_contents) + assert uploaded_file["alt"] == filename + assert uploaded_file["url"].startswith("/uploads/") + assert uploaded_file["url"].endswith("/" + filename) + assert uploaded_file["markdown"] == "[{}]({})".format( + uploaded_file["alt"], uploaded_file["url"] + ) + + # environments + admin_project.environments.create( + {"name": "env1", "external_url": "http://fake.env/whatever"} + ) + envs = admin_project.environments.list() + assert len(envs) == 1 + env = envs[0] + env.external_url = "http://new.env/whatever" + env.save() + env = admin_project.environments.list()[0] + assert env.external_url == "http://new.env/whatever" + env.stop() + env.delete() + assert len(admin_project.environments.list()) == 0 + + # Project clusters + admin_project.clusters.create( + { + "name": "cluster1", + "platform_kubernetes_attributes": { + "api_url": "http://url", + "token": "tokenval", + }, + } + ) + clusters = admin_project.clusters.list() + assert len(clusters) == 1 + cluster = clusters[0] + cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} + cluster.save() + cluster = admin_project.clusters.list()[0] + assert cluster.platform_kubernetes["api_url"] == "http://newurl" + cluster.delete() + assert len(admin_project.clusters.list()) == 0 + + # Group clusters + group1.clusters.create( + { + "name": "cluster1", + "platform_kubernetes_attributes": { + "api_url": "http://url", + "token": "tokenval", + }, + } + ) + clusters = group1.clusters.list() + assert len(clusters) == 1 + cluster = clusters[0] + cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} + cluster.save() + cluster = group1.clusters.list()[0] + assert cluster.platform_kubernetes["api_url"] == "http://newurl" + cluster.delete() + assert len(group1.clusters.list()) == 0 + + # project events + admin_project.events.list() + + # forks + fork = admin_project.forks.create({"namespace": user1.username}) + p = gl.projects.get(fork.id) + assert p.forked_from_project["id"] == admin_project.id + + forks = admin_project.forks.list() + assert fork.id in map(lambda p: p.id, forks) + + # project hooks + hook = admin_project.hooks.create({"url": "http://hook.url"}) + assert len(admin_project.hooks.list()) == 1 + hook.note_events = True + hook.save() + hook = admin_project.hooks.get(hook.id) + assert hook.note_events is True + hook.delete() + + # deploy keys + deploy_key = admin_project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) + project_keys = list(admin_project.keys.list()) + assert len(project_keys) == 1 + + sudo_project.keys.enable(deploy_key.id) + assert len(sudo_project.keys.list()) == 1 + sudo_project.keys.delete(deploy_key.id) + assert len(sudo_project.keys.list()) == 0 + + # labels + # label1 = admin_project.labels.create({"name": "label1", "color": "#778899"}) + # label1 = admin_project.labels.list()[0] + # assert len(admin_project.labels.list()) == 1 + # label1.new_name = "label1updated" + # label1.save() + # assert label1.name == "label1updated" + # label1.subscribe() + # assert label1.subscribed == True + # label1.unsubscribe() + # assert label1.subscribed == False + # label1.delete() + + # milestones + m1 = admin_project.milestones.create({"title": "milestone1"}) + assert len(admin_project.milestones.list()) == 1 + m1.due_date = "2020-01-01T00:00:00Z" + m1.save() + m1.state_event = "close" + m1.save() + m1 = admin_project.milestones.get(m1.id) + assert m1.state == "closed" + assert len(m1.issues()) == 0 + assert len(m1.merge_requests()) == 0 + + # issues + issue1 = admin_project.issues.create({"title": "my issue 1", "milestone_id": m1.id}) + issue2 = admin_project.issues.create({"title": "my issue 2"}) + issue3 = admin_project.issues.create({"title": "my issue 3"}) + assert len(admin_project.issues.list()) == 3 + issue3.state_event = "close" + issue3.save() + assert len(admin_project.issues.list(state="closed")) == 1 + assert len(admin_project.issues.list(state="opened")) == 2 + assert len(admin_project.issues.list(milestone="milestone1")) == 1 + assert m1.issues().next().title == "my issue 1" + size = len(issue1.notes.list()) + note = issue1.notes.create({"body": "This is an issue note"}) + assert len(issue1.notes.list()) == size + 1 + emoji = note.awardemojis.create({"name": "tractor"}) + assert len(note.awardemojis.list()) == 1 + emoji.delete() + assert len(note.awardemojis.list()) == 0 + note.delete() + assert len(issue1.notes.list()) == size + assert isinstance(issue1.user_agent_detail(), dict) + + assert issue1.user_agent_detail()["user_agent"] + assert issue1.participants() + assert type(issue1.closed_by()) == list + assert type(issue1.related_merge_requests()) == list + + # issues labels and events + label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) + issue1.labels = ["label2"] + issue1.save() + events = issue1.resourcelabelevents.list() + assert events + event = issue1.resourcelabelevents.get(events[0].id) + assert event + + size = len(issue1.discussions.list()) + discussion = issue1.discussions.create({"body": "Discussion body"}) + assert len(issue1.discussions.list()) == size + 1 + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = issue1.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = issue1.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + # tags + tag1 = admin_project.tags.create({"tag_name": "v1.0", "ref": "master"}) + assert len(admin_project.tags.list()) == 1 + tag1.set_release_description("Description 1") + tag1.set_release_description("Description 2") + assert tag1.release["description"] == "Description 2" + tag1.delete() + + # project snippet + admin_project.snippets_enabled = True + admin_project.save() + snippet = admin_project.snippets.create( + { + "title": "snip1", + "file_name": "foo.py", + "content": "initial content", + "visibility": gitlab.v4.objects.VISIBILITY_PRIVATE, + } + ) + + assert snippet.user_agent_detail()["user_agent"] + + size = len(snippet.discussions.list()) + discussion = snippet.discussions.create({"body": "Discussion body"}) + assert len(snippet.discussions.list()) == size + 1 + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = snippet.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = snippet.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + snippet.file_name = "bar.py" + snippet.save() + snippet = admin_project.snippets.get(snippet.id) + assert snippet.content().decode() == "initial content" + assert snippet.file_name == "bar.py" + size = len(admin_project.snippets.list()) + snippet.delete() + assert len(admin_project.snippets.list()) == (size - 1) + + # triggers + tr1 = admin_project.triggers.create({"description": "trigger1"}) + assert len(admin_project.triggers.list()) == 1 + tr1.delete() + + # variables + v1 = admin_project.variables.create({"key": "key1", "value": "value1"}) + assert len(admin_project.variables.list()) == 1 + v1.value = "new_value1" + v1.save() + v1 = admin_project.variables.get(v1.key) + assert v1.value == "new_value1" + v1.delete() + + # branches and merges + to_merge = admin_project.branches.create({"branch": "branch1", "ref": "master"}) + admin_project.files.create( + { + "file_path": "README2.rst", + "branch": "branch1", + "content": "Initial content", + "commit_message": "New commit in new branch", + } + ) + mr = admin_project.mergerequests.create( + {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} + ) + + # discussion + size = len(mr.discussions.list()) + discussion = mr.discussions.create({"body": "Discussion body"}) + assert len(mr.discussions.list()) == size + 1 + d_note = discussion.notes.create({"body": "first note"}) + d_note_from_get = discussion.notes.get(d_note.id) + d_note_from_get.body = "updated body" + d_note_from_get.save() + discussion = mr.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + d_note_from_get.delete() + discussion = mr.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + # mr labels and events + mr.labels = ["label2"] + mr.save() + events = mr.resourcelabelevents.list() + assert events + event = mr.resourcelabelevents.get(events[0].id) + assert event + + # rebasing + assert mr.rebase() + + # basic testing: only make sure that the methods exist + mr.commits() + mr.changes() + assert mr.participants() + + 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" + ) + project_import = gl.projects.get(output["id"], lazy=True).imports.get() + count = 0 + while project_import.import_status != "finished": + time.sleep(1) + project_import.refresh() + count += 1 + if count == 10: + raise Exception("Project import taking too much time") + + # project releases + release_test_project = gl.projects.create( + {"name": "release-test-project", "initialize_with_readme": True} + ) + release_name = "Demo Release" + release_tag_name = "v1.2.3" + release_description = "release notes go here" + release_test_project.releases.create( + { + "name": release_name, + "tag_name": release_tag_name, + "description": release_description, + "ref": "master", + } + ) + assert len(release_test_project.releases.list()) == 1 + + # get single release + retrieved_project = release_test_project.releases.get(release_tag_name) + assert retrieved_project.name == release_name + assert retrieved_project.tag_name == release_tag_name + assert retrieved_project.description == release_description + + # delete release + release_test_project.releases.delete(release_tag_name) + assert len(release_test_project.releases.list()) == 0 + release_test_project.delete() + + # status + message = "Test" + emoji = "thumbsup" + status = gl.user.status.get() + status.message = message + status.emoji = emoji + status.save() + new_status = gl.user.status.get() + assert new_status.message == message + assert new_status.emoji == emoji diff --git a/tools/cli_test_v4.sh b/tests/functional/test_cli_v4.sh similarity index 100% rename from tools/cli_test_v4.sh rename to tests/functional/test_cli_v4.sh diff --git a/tests/functional/test_ee.py b/tests/functional/test_ee.py new file mode 100755 index 000000000..e69badc4f --- /dev/null +++ b/tests/functional/test_ee.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python + +import pytest + +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") + + +@pytest.mark.skip(reason="Tests require EE license") +def test_ee_features(): + gl = gitlab.Gitlab.from_config("ee") + project1 = gl.projects.get(P1) + project2 = gl.projects.get(P2) + issue_p1 = project1.issues.get(I_P1) + issue_p2 = project2.issues.get(I_P2) + group1 = gl.groups.get(G1) + mr = project1.mergerequests.get(1) + + start_log("MR approvals") + approval = project1.approvals.get() + v = approval.reset_approvals_on_push + approval.reset_approvals_on_push = not v + approval.save() + approval = project1.approvals.get() + assert v != approval.reset_approvals_on_push + project1.approvals.set_approvers(1, [1], []) + approval = project1.approvals.get() + assert approval.approvers[0]["user"]["id"] == 1 + + approval = mr.approvals.get() + approval.approvals_required = 2 + approval.save() + approval = mr.approvals.get() + assert approval.approvals_required == 2 + approval.approvals_required = 3 + approval.save() + approval = mr.approvals.get() + assert approval.approvals_required == 3 + mr.approvals.set_approvers(1, [1], []) + approval = mr.approvals.get() + assert approval.approvers[0]["user"]["id"] == 1 + + ars = project1.approvalrules.list(all=True) + assert len(ars) == 0 + project.approvalrules.create( + {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} + ) + ars = project1.approvalrules.list(all=True) + assert len(ars) == 1 + ars[0].approvals_required == 2 + ars[0].save() + ars = project1.approvalrules.list(all=True) + assert len(ars) == 1 + assert ars[0].approvals_required == 2 + ars[0].delete() + ars = project1.approvalrules.list(all=True) + assert len(ars) == 0 + end_log() + + start_log("geo nodes") + # very basic tests because we only have 1 node... + nodes = gl.geonodes.list() + status = gl.geonodes.status() + end_log() + + start_log("issue links") + # bit of cleanup just in case + for link in issue_p1.links.list(): + issue_p1.links.delete(link.issue_link_id) + + src, dst = issue_p1.links.create( + {"target_project_id": P2, "target_issue_iid": I_P2} + ) + links = issue_p1.links.list() + link_id = links[0].issue_link_id + issue_p1.links.delete(link_id) + end_log() + + start_log("LDAP links") + # bit of cleanup just in case + if hasattr(group1, "ldap_group_links"): + for link in group1.ldap_group_links: + group1.delete_ldap_group_link(link["cn"], link["provider"]) + assert gl.ldapgroups.list() + group1.add_ldap_group_link(LDAP_CN, 30, LDAP_PROVIDER) + group1.ldap_sync() + group1.delete_ldap_group_link(LDAP_CN) + end_log() + + start_log("boards") + # bit of cleanup just in case + for board in project1.boards.list(): + if board.name == "testboard": + board.delete() + board = project1.boards.create({"name": "testboard"}) + board = project1.boards.get(board.id) + project1.boards.delete(board.id) + + for board in group1.boards.list(): + if board.name == "testboard": + board.delete() + board = group1.boards.create({"name": "testboard"}) + board = group1.boards.get(board.id) + group1.boards.delete(board.id) + end_log() + + start_log("push rules") + pr = project1.pushrules.get() + if pr: + pr.delete() + pr = project1.pushrules.create({"deny_delete_tag": True}) + pr.deny_delete_tag = False + pr.save() + pr = project1.pushrules.get() + assert pr is not None + assert pr.deny_delete_tag == False + pr.delete() + 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/gitlab/tests/__init__.py b/tests/unit/__init__.py similarity index 100% rename from gitlab/tests/__init__.py rename to tests/unit/__init__.py diff --git a/gitlab/tests/data/todo.json b/tests/unit/data/todo.json similarity index 100% rename from gitlab/tests/data/todo.json rename to tests/unit/data/todo.json diff --git a/gitlab/tests/objects/__init__.py b/tests/unit/objects/__init__.py similarity index 100% rename from gitlab/tests/objects/__init__.py rename to tests/unit/objects/__init__.py diff --git a/gitlab/tests/objects/test_application.py b/tests/unit/objects/test_application.py similarity index 100% rename from gitlab/tests/objects/test_application.py rename to tests/unit/objects/test_application.py diff --git a/gitlab/tests/objects/test_projects.py b/tests/unit/objects/test_projects.py similarity index 100% rename from gitlab/tests/objects/test_projects.py rename to tests/unit/objects/test_projects.py diff --git a/gitlab/tests/test_base.py b/tests/unit/test_base.py similarity index 100% rename from gitlab/tests/test_base.py rename to tests/unit/test_base.py diff --git a/gitlab/tests/test_cli.py b/tests/unit/test_cli.py similarity index 100% rename from gitlab/tests/test_cli.py rename to tests/unit/test_cli.py diff --git a/gitlab/tests/test_config.py b/tests/unit/test_config.py similarity index 100% rename from gitlab/tests/test_config.py rename to tests/unit/test_config.py diff --git a/gitlab/tests/test_gitlab.py b/tests/unit/test_gitlab.py similarity index 100% rename from gitlab/tests/test_gitlab.py rename to tests/unit/test_gitlab.py diff --git a/gitlab/tests/test_mixins.py b/tests/unit/test_mixins.py similarity index 100% rename from gitlab/tests/test_mixins.py rename to tests/unit/test_mixins.py diff --git a/gitlab/tests/test_types.py b/tests/unit/test_types.py similarity index 100% rename from gitlab/tests/test_types.py rename to tests/unit/test_types.py diff --git a/gitlab/tests/test_utils.py b/tests/unit/test_utils.py similarity index 100% rename from gitlab/tests/test_utils.py rename to tests/unit/test_utils.py diff --git a/tools/build_test_env.sh b/tools/build_test_env.sh index 7468a9a7d..f6c91ee2d 100755 --- a/tools/build_test_env.sh +++ b/tools/build_test_env.sh @@ -27,15 +27,15 @@ try() { "$@" || fatal "'$@' failed"; } REUSE_CONTAINER= NOVENV= -PY_VER=3 API_VER=4 GITLAB_IMAGE="gitlab/gitlab-ce" GITLAB_TAG="latest" -while getopts :knp:a: opt "$@"; do +VENV_CMD="python3 -m venv" + +while getopts :kn:a: opt "$@"; do case $opt in k) REUSE_CONTAINER=1;; n) NOVENV=1;; - p) PY_VER=$OPTARG;; a) API_VER=$OPTARG;; t) GITLAB_TAG=$OPTARG;; :) fatal "Option -${OPTARG} requires a value";; @@ -44,12 +44,6 @@ while getopts :knp:a: opt "$@"; do esac done -case $PY_VER in - 2) VENV_CMD=virtualenv;; - 3) VENV_CMD="python3 -m venv";; - *) fatal "Wrong python version (2 or 3)";; -esac - case $API_VER in 4) ;; *) fatal "Wrong API version (4 only)";; @@ -130,7 +124,7 @@ if [ -z "$NOVENV" ]; then . "$VENV"/bin/activate || fatal "failed to activate Python virtual environment" log "Installing dependencies into virtualenv..." - try pip install -r requirements.txt + try pip install -r requirements.txt -r test-requirements.txt log "Installing into virtualenv..." try pip install -e . diff --git a/tools/ee-test.py b/tools/ee-test.py deleted file mode 100755 index af1295788..000000000 --- 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 -project.approvalrules.create( - {"name": "approval-rule", "approvals_required": 1, "group_ids": [group1.id]} -) -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -ars[0].approvals_required == 2 -ars[0].save() -ars = project1.approvalrules.list(all=True) -assert len(ars) == 1 -assert ars[0].approvals_required == 2 -ars[0].delete() -ars = project1.approvalrules.list(all=True) -assert len(ars) == 0 -end_log() - -start_log("geo nodes") -# very basic tests because we only have 1 node... -nodes = gl.geonodes.list() -status = gl.geonodes.status() -end_log() - -start_log("issue links") -# bit of cleanup just in case -for link in issue_p1.links.list(): - issue_p1.links.delete(link.issue_link_id) - -src, dst = issue_p1.links.create({"target_project_id": P2, "target_issue_iid": I_P2}) -links = issue_p1.links.list() -link_id = links[0].issue_link_id -issue_p1.links.delete(link_id) -end_log() - -start_log("LDAP links") -# bit of cleanup just in case -if hasattr(group1, "ldap_group_links"): - for link in group1.ldap_group_links: - group1.delete_ldap_group_link(link["cn"], link["provider"]) -assert gl.ldapgroups.list() -group1.add_ldap_group_link(LDAP_CN, 30, LDAP_PROVIDER) -group1.ldap_sync() -group1.delete_ldap_group_link(LDAP_CN) -end_log() - -start_log("boards") -# bit of cleanup just in case -for board in project1.boards.list(): - if board.name == "testboard": - board.delete() -board = project1.boards.create({"name": "testboard"}) -board = project1.boards.get(board.id) -project1.boards.delete(board.id) - -for board in group1.boards.list(): - if board.name == "testboard": - board.delete() -board = group1.boards.create({"name": "testboard"}) -board = group1.boards.get(board.id) -group1.boards.delete(board.id) -end_log() - -start_log("push rules") -pr = project1.pushrules.get() -if pr: - pr.delete() -pr = project1.pushrules.create({"deny_delete_tag": True}) -pr.deny_delete_tag = False -pr.save() -pr = project1.pushrules.get() -assert pr is not None -assert pr.deny_delete_tag == False -pr.delete() -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_tests.sh b/tools/functional_tests.sh index 4123d87fb..9446f9b66 100755 --- a/tools/functional_tests.sh +++ b/tools/functional_tests.sh @@ -18,4 +18,4 @@ setenv_script=$(dirname "$0")/build_test_env.sh || exit 1 BUILD_TEST_ENV_AUTO_CLEANUP=true . "$setenv_script" "$@" || exit 1 -. $(dirname "$0")/cli_test_v${API_VER}.sh +. $(dirname "$0")/../tests/functional/test_cli_v${API_VER}.sh diff --git a/tools/py_functional_tests.sh b/tools/py_functional_tests.sh index 75bb7613d..b4c9ebb6c 100755 --- a/tools/py_functional_tests.sh +++ b/tools/py_functional_tests.sh @@ -18,4 +18,4 @@ setenv_script=$(dirname "$0")/build_test_env.sh || exit 1 BUILD_TEST_ENV_AUTO_CLEANUP=true . "$setenv_script" "$@" || exit 1 -try python "$(dirname "$0")"/python_test_v${API_VER}.py +try pytest -s "$(dirname "$0")"/../tests/functional diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py deleted file mode 100644 index 0703ee340..000000000 --- a/tools/python_test_v4.py +++ /dev/null @@ -1,1013 +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}) - -assert len(gl.groups.list()) == 3 -assert len(gl.groups.list(search="oup1")) == 1 -assert group3.parent_id == p_id -assert group2.subgroups.list()[0].id == group3.id - -group1.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user1.id}) -group1.members.create({"access_level": gitlab.const.GUEST_ACCESS, "user_id": user2.id}) - -group2.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id}) - -# User memberships (admin only) -memberships1 = user1.memberships.list() -assert len(memberships1) == 1 - -memberships2 = user2.memberships.list() -assert len(memberships2) == 2 - -membership = memberships1[0] -assert membership.source_type == "Namespace" -assert membership.access_level == gitlab.const.OWNER_ACCESS - -project_memberships = user1.memberships.list(type="Project") -assert len(project_memberships) == 0 - -group_memberships = user1.memberships.list(type="Namespace") -assert len(group_memberships) == 1 - -try: - membership = user1.memberships.list(type="Invalid") -except gitlab.GitlabListError as e: - error_message = e.error_message -assert error_message == "type does not have a valid value" - -try: - user1.memberships.list(sudo=user1.name) -except gitlab.GitlabListError as e: - error_message = e.error_message -assert error_message == "403 Forbidden" - -# Administrator belongs to the groups -assert len(group1.members.list()) == 3 -assert len(group2.members.list()) == 2 - -group1.members.delete(user1.id) -assert len(group1.members.list()) == 2 -assert len(group1.members.all()) -member = group1.members.get(user2.id) -member.access_level = gitlab.const.OWNER_ACCESS -member.save() -member = group1.members.get(user2.id) -assert member.access_level == gitlab.const.OWNER_ACCESS - -group2.members.delete(gl.user.id) - -# group custom attributes -attrs = group2.customattributes.list() -assert len(attrs) == 0 -attr = group2.customattributes.set("key", "value1") -assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 -assert attr.key == "key" -assert attr.value == "value1" -assert len(group2.customattributes.list()) == 1 -attr = group2.customattributes.set("key", "value2") -attr = group2.customattributes.get("key") -assert attr.value == "value2" -assert len(group2.customattributes.list()) == 1 -attr.delete() -assert len(group2.customattributes.list()) == 0 - -# group notification settings -settings = group2.notificationsettings.get() -settings.level = "disabled" -settings.save() -settings = group2.notificationsettings.get() -assert settings.level == "disabled" - -# group badges -badge_image = "http://example.com" -badge_link = "http://example/img.svg" -badge = group2.badges.create({"link_url": badge_link, "image_url": badge_image}) -assert len(group2.badges.list()) == 1 -badge.image_url = "http://another.example.com" -badge.save() -badge = group2.badges.get(badge.id) -assert badge.image_url == "http://another.example.com" -badge.delete() -assert len(group2.badges.list()) == 0 - -# group milestones -gm1 = group1.milestones.create({"title": "groupmilestone1"}) -assert len(group1.milestones.list()) == 1 -gm1.due_date = "2020-01-01T00:00:00Z" -gm1.save() -gm1.state_event = "close" -gm1.save() -gm1 = group1.milestones.get(gm1.id) -assert gm1.state == "closed" -assert len(gm1.issues()) == 0 -assert len(gm1.merge_requests()) == 0 - -# group variables -group1.variables.create({"key": "foo", "value": "bar"}) -g_v = group1.variables.get("foo") -assert g_v.value == "bar" -g_v.value = "baz" -g_v.save() -g_v = group1.variables.get("foo") -assert g_v.value == "baz" -assert len(group1.variables.list()) == 1 -g_v.delete() -assert len(group1.variables.list()) == 0 - -# group labels -# group1.labels.create({"name": "foo", "description": "bar", "color": "#112233"}) -# g_l = group1.labels.get("foo") -# assert g_l.description == "bar" -# g_l.description = "baz" -# g_l.save() -# g_l = group1.labels.get("foo") -# assert g_l.description == "baz" -# assert len(group1.labels.list()) == 1 -# g_l.delete() -# assert len(group1.labels.list()) == 0 - -# hooks -hook = gl.hooks.create({"url": "http://whatever.com"}) -assert len(gl.hooks.list()) == 1 -hook.delete() -assert len(gl.hooks.list()) == 0 - -# projects -admin_project = gl.projects.create({"name": "admin_project"}) -gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) -gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group2.id}) -sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user1.name) - -assert len(gl.projects.list(owned=True)) == 2 -assert len(gl.projects.list(search="admin")) == 1 - -# test pagination -l1 = gl.projects.list(per_page=1, page=1) -l2 = gl.projects.list(per_page=1, page=2) -assert len(l1) == 1 -assert len(l2) == 1 -assert l1[0].id != l2[0].id - -# group custom attributes -attrs = admin_project.customattributes.list() -assert len(attrs) == 0 -attr = admin_project.customattributes.set("key", "value1") -assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 -assert attr.key == "key" -assert attr.value == "value1" -assert len(admin_project.customattributes.list()) == 1 -attr = admin_project.customattributes.set("key", "value2") -attr = admin_project.customattributes.get("key") -assert attr.value == "value2" -assert len(admin_project.customattributes.list()) == 1 -attr.delete() -assert len(admin_project.customattributes.list()) == 0 - -# project pages domains -domain = admin_project.pagesdomains.create({"domain": "foo.domain.com"}) -assert len(admin_project.pagesdomains.list()) == 1 -assert len(gl.pagesdomains.list()) == 1 -domain = admin_project.pagesdomains.get("foo.domain.com") -assert domain.domain == "foo.domain.com" -domain.delete() -assert len(admin_project.pagesdomains.list()) == 0 - -# project content (files) -admin_project.files.create( - { - "file_path": "README", - "branch": "master", - "content": "Initial content", - "commit_message": "Initial commit", - } -) -readme = admin_project.files.get(file_path="README", ref="master") -readme.content = base64.b64encode(b"Improved README").decode() -time.sleep(2) -readme.save(branch="master", commit_message="new commit") -readme.delete(commit_message="Removing README", branch="master") - -admin_project.files.create( - { - "file_path": "README.rst", - "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 comment -commit.comments.create({"note": "This is a commit comment"}) -# assert len(commit.comments.list()) == 1 - -# commit discussion -count = len(commit.discussions.list()) -discussion = commit.discussions.create({"body": "Discussion body"}) -# assert len(commit.discussions.list()) == (count + 1) -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = commit.discussions.get(discussion.id) -# assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = commit.discussions.get(discussion.id) -# assert len(discussion.attributes["notes"]) == 1 - -# Revert commit -revert_commit = commit.revert(branch="master") - -expected_message = 'Revert "{}"\n\nThis reverts commit {}'.format( - commit.message, commit.id -) -assert revert_commit["message"] == expected_message - -try: - commit.revert(branch="master") - # Only here to really ensure expected error without a full test framework - raise AssertionError("Two revert attempts should raise GitlabRevertError") -except gitlab.GitlabRevertError: - pass - -# housekeeping -admin_project.housekeeping() - -# repository -tree = admin_project.repository_tree() -assert len(tree) != 0 -assert tree[0]["name"] == "README.rst" -blob_id = tree[0]["id"] -blob = admin_project.repository_raw_blob(blob_id) -assert blob.decode() == "Initial content" -archive1 = admin_project.repository_archive() -archive2 = admin_project.repository_archive("master") -assert archive1 == archive2 -snapshot = admin_project.snapshot() - -# project file uploads -filename = "test.txt" -file_contents = "testing contents" -uploaded_file = admin_project.upload(filename, file_contents) -assert uploaded_file["alt"] == filename -assert uploaded_file["url"].startswith("/uploads/") -assert uploaded_file["url"].endswith("/" + filename) -assert uploaded_file["markdown"] == "[{}]({})".format( - uploaded_file["alt"], uploaded_file["url"] -) - -# environments -admin_project.environments.create( - {"name": "env1", "external_url": "http://fake.env/whatever"} -) -envs = admin_project.environments.list() -assert len(envs) == 1 -env = envs[0] -env.external_url = "http://new.env/whatever" -env.save() -env = admin_project.environments.list()[0] -assert env.external_url == "http://new.env/whatever" -env.stop() -env.delete() -assert len(admin_project.environments.list()) == 0 - -# Project clusters -admin_project.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - } -) -clusters = admin_project.clusters.list() -assert len(clusters) == 1 -cluster = clusters[0] -cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} -cluster.save() -cluster = admin_project.clusters.list()[0] -assert cluster.platform_kubernetes["api_url"] == "http://newurl" -cluster.delete() -assert len(admin_project.clusters.list()) == 0 - -# Group clusters -group1.clusters.create( - { - "name": "cluster1", - "platform_kubernetes_attributes": { - "api_url": "http://url", - "token": "tokenval", - }, - } -) -clusters = group1.clusters.list() -assert len(clusters) == 1 -cluster = clusters[0] -cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} -cluster.save() -cluster = group1.clusters.list()[0] -assert cluster.platform_kubernetes["api_url"] == "http://newurl" -cluster.delete() -assert len(group1.clusters.list()) == 0 - -# project events -admin_project.events.list() - -# forks -fork = admin_project.forks.create({"namespace": user1.username}) -p = gl.projects.get(fork.id) -assert p.forked_from_project["id"] == admin_project.id - -forks = admin_project.forks.list() -assert fork.id in map(lambda p: p.id, forks) - -# project hooks -hook = admin_project.hooks.create({"url": "http://hook.url"}) -assert len(admin_project.hooks.list()) == 1 -hook.note_events = True -hook.save() -hook = admin_project.hooks.get(hook.id) -assert hook.note_events is True -hook.delete() - -# deploy keys -deploy_key = admin_project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) -project_keys = list(admin_project.keys.list()) -assert len(project_keys) == 1 - -sudo_project.keys.enable(deploy_key.id) -assert len(sudo_project.keys.list()) == 1 -sudo_project.keys.delete(deploy_key.id) -assert len(sudo_project.keys.list()) == 0 - -# labels -# label1 = admin_project.labels.create({"name": "label1", "color": "#778899"}) -# label1 = admin_project.labels.list()[0] -# assert len(admin_project.labels.list()) == 1 -# label1.new_name = "label1updated" -# label1.save() -# assert label1.name == "label1updated" -# label1.subscribe() -# assert label1.subscribed == True -# label1.unsubscribe() -# assert label1.subscribed == False -# label1.delete() - -# milestones -m1 = admin_project.milestones.create({"title": "milestone1"}) -assert len(admin_project.milestones.list()) == 1 -m1.due_date = "2020-01-01T00:00:00Z" -m1.save() -m1.state_event = "close" -m1.save() -m1 = admin_project.milestones.get(m1.id) -assert m1.state == "closed" -assert len(m1.issues()) == 0 -assert len(m1.merge_requests()) == 0 - -# issues -issue1 = admin_project.issues.create({"title": "my issue 1", "milestone_id": m1.id}) -issue2 = admin_project.issues.create({"title": "my issue 2"}) -issue3 = admin_project.issues.create({"title": "my issue 3"}) -assert len(admin_project.issues.list()) == 3 -issue3.state_event = "close" -issue3.save() -assert len(admin_project.issues.list(state="closed")) == 1 -assert len(admin_project.issues.list(state="opened")) == 2 -assert len(admin_project.issues.list(milestone="milestone1")) == 1 -assert m1.issues().next().title == "my issue 1" -size = len(issue1.notes.list()) -note = issue1.notes.create({"body": "This is an issue note"}) -assert len(issue1.notes.list()) == size + 1 -emoji = note.awardemojis.create({"name": "tractor"}) -assert len(note.awardemojis.list()) == 1 -emoji.delete() -assert len(note.awardemojis.list()) == 0 -note.delete() -assert len(issue1.notes.list()) == size -assert isinstance(issue1.user_agent_detail(), dict) - -assert issue1.user_agent_detail()["user_agent"] -assert issue1.participants() -assert type(issue1.closed_by()) == list -assert type(issue1.related_merge_requests()) == list - -# issues labels and events -label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) -issue1.labels = ["label2"] -issue1.save() -events = issue1.resourcelabelevents.list() -assert events -event = issue1.resourcelabelevents.get(events[0].id) -assert event - - -size = len(issue1.discussions.list()) -discussion = issue1.discussions.create({"body": "Discussion body"}) -assert len(issue1.discussions.list()) == size + 1 -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = issue1.discussions.get(discussion.id) -assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = issue1.discussions.get(discussion.id) -assert len(discussion.attributes["notes"]) == 1 - -# tags -tag1 = admin_project.tags.create({"tag_name": "v1.0", "ref": "master"}) -assert len(admin_project.tags.list()) == 1 -tag1.set_release_description("Description 1") -tag1.set_release_description("Description 2") -assert tag1.release["description"] == "Description 2" -tag1.delete() - -# project snippet -admin_project.snippets_enabled = True -admin_project.save() -snippet = admin_project.snippets.create( - { - "title": "snip1", - "file_name": "foo.py", - "content": "initial content", - "visibility": gitlab.v4.objects.VISIBILITY_PRIVATE, - } -) - -assert snippet.user_agent_detail()["user_agent"] - -size = len(snippet.discussions.list()) -discussion = snippet.discussions.create({"body": "Discussion body"}) -assert len(snippet.discussions.list()) == size + 1 -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = snippet.discussions.get(discussion.id) -assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = snippet.discussions.get(discussion.id) -assert len(discussion.attributes["notes"]) == 1 - -snippet.file_name = "bar.py" -snippet.save() -snippet = admin_project.snippets.get(snippet.id) -assert snippet.content().decode() == "initial content" -assert snippet.file_name == "bar.py" -size = len(admin_project.snippets.list()) -snippet.delete() -assert len(admin_project.snippets.list()) == (size - 1) - -# triggers -tr1 = admin_project.triggers.create({"description": "trigger1"}) -assert len(admin_project.triggers.list()) == 1 -tr1.delete() - -# variables -v1 = admin_project.variables.create({"key": "key1", "value": "value1"}) -assert len(admin_project.variables.list()) == 1 -v1.value = "new_value1" -v1.save() -v1 = admin_project.variables.get(v1.key) -assert v1.value == "new_value1" -v1.delete() - -# branches and merges -to_merge = admin_project.branches.create({"branch": "branch1", "ref": "master"}) -admin_project.files.create( - { - "file_path": "README2.rst", - "branch": "branch1", - "content": "Initial content", - "commit_message": "New commit in new branch", - } -) -mr = admin_project.mergerequests.create( - {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} -) - -# discussion -size = len(mr.discussions.list()) -discussion = mr.discussions.create({"body": "Discussion body"}) -assert len(mr.discussions.list()) == size + 1 -d_note = discussion.notes.create({"body": "first note"}) -d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = "updated body" -d_note_from_get.save() -discussion = mr.discussions.get(discussion.id) -assert discussion.attributes["notes"][-1]["body"] == "updated body" -d_note_from_get.delete() -discussion = mr.discussions.get(discussion.id) -assert len(discussion.attributes["notes"]) == 1 - -# mr labels and events -mr.labels = ["label2"] -mr.save() -events = mr.resourcelabelevents.list() -assert events -event = mr.resourcelabelevents.get(events[0].id) -assert event - -# rebasing -assert mr.rebase() - -# basic testing: only make sure that the methods exist -mr.commits() -mr.changes() -assert mr.participants() - -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" -) -project_import = gl.projects.get(output["id"], lazy=True).imports.get() -count = 0 -while project_import.import_status != "finished": - time.sleep(1) - project_import.refresh() - count += 1 - if count == 10: - raise Exception("Project import taking too much time") - -# project releases -release_test_project = gl.projects.create( - {"name": "release-test-project", "initialize_with_readme": True} -) -release_name = "Demo Release" -release_tag_name = "v1.2.3" -release_description = "release notes go here" -release_test_project.releases.create( - { - "name": release_name, - "tag_name": release_tag_name, - "description": release_description, - "ref": "master", - } -) -assert len(release_test_project.releases.list()) == 1 - -# get single release -retrieved_project = release_test_project.releases.get(release_tag_name) -assert retrieved_project.name == release_name -assert retrieved_project.tag_name == release_tag_name -assert retrieved_project.description == release_description - -# delete release -release_test_project.releases.delete(release_tag_name) -assert len(release_test_project.releases.list()) == 0 -release_test_project.delete() - -# status -message = "Test" -emoji = "thumbsup" -status = gl.user.status.get() -status.message = message -status.emoji = emoji -status.save() -new_status = gl.user.status.get() -assert new_status.message == message -assert new_status.emoji == emoji diff --git a/tools/reset_gitlab.py b/tools/reset_gitlab.py index 64668a974..d67cf2ece 100755 --- a/tools/reset_gitlab.py +++ b/tools/reset_gitlab.py @@ -1,19 +1,25 @@ #!/usr/bin/env python import sys +import time from gitlab import Gitlab def main(): with Gitlab.from_config(config_files=["/tmp/python-gitlab.cfg"]) as gl: - for project in gl.projects.list(): + for project in gl.projects.list(all=True): project.delete() - for group in gl.groups.list(): + for group in gl.groups.list(all=True): group.delete() + + # Gitlab needs time to delete resources; avoid 409 on user deletion + while gl.projects.list(all=True) or gl.groups.list(all=True): + time.sleep(0.1) + for user in gl.users.list(): if user.username != "root": - user.delete() + user.delete(hard_delete=True) if __name__ == "__main__": diff --git a/tox.ini b/tox.ini index 0aa43f09e..987fcd789 100644 --- a/tox.ini +++ b/tox.ini @@ -12,11 +12,11 @@ install_command = pip install {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = - python setup.py testr --testr-args='{posargs}' + pytest tests/unit {posargs} [testenv:pep8] commands = - flake8 {posargs} gitlab/ + flake8 {posargs} gitlab/ tests/ [testenv:black] basepython = python3 @@ -24,7 +24,7 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt black commands = - black {posargs} gitlab + black {posargs} gitlab tests [testenv:venv] commands = {posargs} @@ -39,12 +39,13 @@ commands = python setup.py build_sphinx [testenv:cover] commands = - python setup.py testr --slowest --coverage --testr-args="{posargs}" - coverage report --omit=*tests* - coverage html --omit=*tests* + pytest tests/unit --cov gitlab --cov-report term --cov-report html {posargs} + +[coverage:run] +omit = *tests* [testenv:cli_func_v4] -commands = {toxinidir}/tools/functional_tests.sh -a 4 -p 2 +commands = {toxinidir}/tools/functional_tests.sh -a 4 [testenv:py_func_v4] -commands = {toxinidir}/tools/py_functional_tests.sh -a 4 -p 2 +commands = {toxinidir}/tools/py_functional_tests.sh -a 4