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__).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