diff --git a/tools/functional/api/test_clusters.py b/tools/functional/api/test_clusters.py new file mode 100644 index 000000000..8930aad84 --- /dev/null +++ b/tools/functional/api/test_clusters.py @@ -0,0 +1,46 @@ +def test_project_clusters(project): + project.clusters.create( + { + "name": "cluster1", + "platform_kubernetes_attributes": { + "api_url": "http://url", + "token": "tokenval", + }, + } + ) + clusters = project.clusters.list() + assert len(clusters) == 1 + + cluster = clusters[0] + cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} + cluster.save() + + cluster = project.clusters.list()[0] + assert cluster.platform_kubernetes["api_url"] == "http://newurl" + + cluster.delete() + assert len(project.clusters.list()) == 0 + + +def test_group_clusters(group): + group.clusters.create( + { + "name": "cluster1", + "platform_kubernetes_attributes": { + "api_url": "http://url", + "token": "tokenval", + }, + } + ) + clusters = group.clusters.list() + assert len(clusters) == 1 + + cluster = clusters[0] + cluster.platform_kubernetes_attributes = {"api_url": "http://newurl"} + cluster.save() + + cluster = group.clusters.list()[0] + assert cluster.platform_kubernetes["api_url"] == "http://newurl" + + cluster.delete() + assert len(group.clusters.list()) == 0 diff --git a/tools/functional/api/test_current_user.py b/tools/functional/api/test_current_user.py new file mode 100644 index 000000000..580245757 --- /dev/null +++ b/tools/functional/api/test_current_user.py @@ -0,0 +1,42 @@ +def test_current_user_email(gl): + gl.auth() + 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 + + +def test_current_user_gpg_keys(gl, GPG_KEY): + gl.auth() + 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 + + +def test_current_user_ssh_keys(gl, SSH_KEY): + gl.auth() + 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 + + +def test_current_user_status(gl): + gl.auth() + message = "Test" + emoji = "thumbsup" + status = gl.user.status.get() + + status.message = message + status.emoji = emoji + status.save() + + new_status = gl.user.status.get() + assert new_status.message == message + assert new_status.emoji == emoji diff --git a/tools/functional/api/test_deploy_keys.py b/tools/functional/api/test_deploy_keys.py new file mode 100644 index 000000000..18828a252 --- /dev/null +++ b/tools/functional/api/test_deploy_keys.py @@ -0,0 +1,12 @@ +def test_project_deploy_keys(gl, project, DEPLOY_KEY): + deploy_key = project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) + project_keys = list(project.keys.list()) + assert len(project_keys) == 1 + + project2 = gl.projects.create({"name": "deploy-key-project"}) + project2.keys.enable(deploy_key.id) + assert len(project2.keys.list()) == 1 + + project2.keys.delete(deploy_key.id) + assert len(project2.keys.list()) == 0 + project2.delete() diff --git a/tools/functional/api/test_deploy_tokens.py b/tools/functional/api/test_deploy_tokens.py new file mode 100644 index 000000000..efcf8b1b3 --- /dev/null +++ b/tools/functional/api/test_deploy_tokens.py @@ -0,0 +1,36 @@ +def test_project_deploy_tokens(gl, project): + deploy_token = project.deploytokens.create( + { + "name": "foo", + "username": "bar", + "expires_at": "2022-01-01", + "scopes": ["read_registry"], + } + ) + assert len(project.deploytokens.list()) == 1 + assert gl.deploytokens.list() == project.deploytokens.list() + + assert project.deploytokens.list()[0].name == "foo" + assert project.deploytokens.list()[0].expires_at == "2022-01-01T00:00:00.000Z" + assert project.deploytokens.list()[0].scopes == ["read_registry"] + assert project.deploytokens.list()[0].username == "bar" + + deploy_token.delete() + assert len(project.deploytokens.list()) == 0 + assert len(gl.deploytokens.list()) == 0 + + +def test_group_deploy_tokens(gl, group): + deploy_token = group.deploytokens.create( + { + "name": "foo", + "scopes": ["read_registry"], + } + ) + + assert len(group.deploytokens.list()) == 1 + assert gl.deploytokens.list() == group.deploytokens.list() + + deploy_token.delete() + assert len(group.deploytokens.list()) == 0 + assert len(gl.deploytokens.list()) == 0 diff --git a/tools/functional/api/test_gitlab.py b/tools/functional/api/test_gitlab.py index 5cf3418d6..347213c21 100644 --- a/tools/functional/api/test_gitlab.py +++ b/tools/functional/api/test_gitlab.py @@ -1,8 +1,183 @@ -""" -Temporary module to run legacy tests as a single pytest test case -as they're all plain asserts at module level. -""" +import pytest +import gitlab -def test_api_v4(gl): - from tools.functional import python_test_v4 + +def test_auth_from_config(gl, temp_dir): + """Test token authentication from config file""" + test_gitlab = gitlab.Gitlab.from_config( + config_files=[temp_dir / "python-gitlab.cfg"] + ) + test_gitlab.auth() + assert isinstance(test_gitlab.user, gitlab.v4.objects.CurrentUser) + + +def test_broadcast_messages(gl): + 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 + + +def test_markdown(gl): + html = gl.markdown("foo") + assert "foo" in html + + +def test_lint(gl): + success, errors = gl.lint("Invalid") + assert success is False + assert errors + + +def test_sidekiq_queue_metrics(gl): + out = gl.sidekiq.queue_metrics() + assert isinstance(out, dict) + assert "pages" in out["queues"] + + +def test_sidekiq_process_metrics(gl): + out = gl.sidekiq.process_metrics() + assert isinstance(out, dict) + assert "hostname" in out["processes"][0] + + +def test_sidekiq_job_stats(gl): + out = gl.sidekiq.job_stats() + assert isinstance(out, dict) + assert "processed" in out["jobs"] + + +def test_sidekiq_compound_metrics(gl): + out = gl.sidekiq.compound_metrics() + assert isinstance(out, dict) + assert "jobs" in out + assert "processes" in out + assert "queues" in out + + +def test_gitlab_settings(gl): + settings = gl.settings.get() + settings.default_projects_limit = 42 + settings.save() + settings = gl.settings.get() + assert settings.default_projects_limit == 42 + + +def test_template_dockerfile(gl): + assert gl.dockerfiles.list() + + dockerfile = gl.dockerfiles.get("Node") + assert dockerfile.content is not None + + +def test_template_gitignore(gl): + assert gl.gitignores.list() + gitignore = gl.gitignores.get("Node") + assert gitignore.content is not None + + +def test_template_gitlabciyml(gl): + assert gl.gitlabciymls.list() + gitlabciyml = gl.gitlabciymls.get("Nodejs") + assert gitlabciyml.content is not None + + +def test_template_license(gl): + assert gl.licenses.list() + license = gl.licenses.get( + "bsd-2-clause", project="mytestproject", fullname="mytestfullname" + ) + assert "mytestfullname" in license.content + + +def test_hooks(gl): + hook = gl.hooks.create({"url": "http://whatever.com"}) + assert len(gl.hooks.list()) == 1 + + hook.delete() + assert len(gl.hooks.list()) == 0 + + +def test_namespaces(gl): + namespace = gl.namespaces.list(all=True) + assert len(namespace) != 0 + + namespace = gl.namespaces.list(search="root", all=True)[0] + assert namespace.kind == "user" + + +def test_notification_settings(gl): + settings = gl.notificationsettings.get() + settings.level = gitlab.NOTIFICATION_LEVEL_WATCH + settings.save() + + settings = gl.notificationsettings.get() + assert settings.level == gitlab.NOTIFICATION_LEVEL_WATCH + + +def test_user_activities(gl): + activities = gl.user_activities.list(query_parameters={"from": "2019-01-01"}) + assert isinstance(activities, list) + + +def test_events(gl): + events = gl.events.list() + assert isinstance(events, list) + + +@pytest.mark.skip +def test_features(gl): + feat = gl.features.set("foo", 30) + assert feat.name == "foo" + assert len(gl.features.list()) == 1 + + feat.delete() + assert len(gl.features.list()) == 0 + + +def test_pagination(gl, project): + project2 = gl.projects.create({"name": "project-page-2"}) + + list1 = gl.projects.list(per_page=1, page=1) + list2 = gl.projects.list(per_page=1, page=2) + assert len(list1) == 1 + assert len(list2) == 1 + assert list1[0].id != list2[0].id + + project2.delete() + + +def test_rate_limits(gl): + 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"})) + + with pytest.raises(gitlab.GitlabCreateError) as e: + for i in range(20, 40): + projects.append( + gl.projects.create( + {"name": str(i) + "shouldfail"}, obey_rate_limit=False + ) + ) + + assert "Retry later" in str(e.value) + + settings.throttle_authenticated_api_enabled = False + settings.save() + [project.delete() for project in projects] diff --git a/tools/functional/api/test_groups.py b/tools/functional/api/test_groups.py new file mode 100644 index 000000000..5a7065051 --- /dev/null +++ b/tools/functional/api/test_groups.py @@ -0,0 +1,190 @@ +import pytest + +import gitlab + + +def test_groups(gl): + # TODO: This one still needs lots of work + user = gl.users.create( + { + "email": "user@test.com", + "username": "user", + "name": "user", + "password": "user_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": user.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 = user.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 = user.memberships.list(type="Project") + assert len(project_memberships) == 0 + + group_memberships = user.memberships.list(type="Namespace") + assert len(group_memberships) == 1 + + with pytest.raises(gitlab.GitlabListError) as e: + membership = user.memberships.list(type="Invalid") + assert "type does not have a valid value" in str(e.value) + + with pytest.raises(gitlab.GitlabListError) as e: + user.memberships.list(sudo=user.name) + assert "403 Forbidden" in str(e.value) + + # Administrator belongs to the groups + assert len(group1.members.list()) == 3 + assert len(group2.members.list()) == 2 + + group1.members.delete(user.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) + + +@pytest.mark.skip(reason="Commented out in legacy test") +def test_group_labels(group): + group.labels.create({"name": "foo", "description": "bar", "color": "#112233"}) + label = group.labels.get("foo") + assert label.description == "bar" + + label.description = "baz" + label.save() + label = group.labels.get("foo") + assert label.description == "baz" + assert len(group.labels.list()) == 1 + + label.delete() + assert len(group.labels.list()) == 0 + + +def test_group_notification_settings(group): + settings = group.notificationsettings.get() + settings.level = "disabled" + settings.save() + + settings = group.notificationsettings.get() + assert settings.level == "disabled" + + +def test_group_badges(group): + badge_image = "http://example.com" + badge_link = "http://example/img.svg" + badge = group.badges.create({"link_url": badge_link, "image_url": badge_image}) + assert len(group.badges.list()) == 1 + + badge.image_url = "http://another.example.com" + badge.save() + + badge = group.badges.get(badge.id) + assert badge.image_url == "http://another.example.com" + + badge.delete() + assert len(group.badges.list()) == 0 + + +def test_group_milestones(group): + milestone = group.milestones.create({"title": "groupmilestone1"}) + assert len(group.milestones.list()) == 1 + + milestone.due_date = "2020-01-01T00:00:00Z" + milestone.save() + milestone.state_event = "close" + milestone.save() + + milestone = group.milestones.get(milestone.id) + assert milestone.state == "closed" + assert len(milestone.issues()) == 0 + assert len(milestone.merge_requests()) == 0 + + +def test_group_custom_attributes(gl, group): + attrs = group.customattributes.list() + assert len(attrs) == 0 + + attr = group.customattributes.set("key", "value1") + assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 + assert attr.key == "key" + assert attr.value == "value1" + assert len(group.customattributes.list()) == 1 + + attr = group.customattributes.set("key", "value2") + attr = group.customattributes.get("key") + assert attr.value == "value2" + assert len(group.customattributes.list()) == 1 + + attr.delete() + assert len(group.customattributes.list()) == 0 + + +def test_group_subgroups_projects(gl, user): + # TODO: fixture factories + group1 = gl.groups.list(search="group1")[0] + group2 = gl.groups.list(search="group2")[0] + + group3 = gl.groups.create( + {"name": "subgroup1", "path": "subgroup1", "parent_id": group1.id} + ) + group4 = gl.groups.create( + {"name": "subgroup2", "path": "subgroup2", "parent_id": group2.id} + ) + + gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) + gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group3.id}) + + assert group3.parent_id == group1.id + assert group4.parent_id == group2.id + assert gr1_project.namespace["id"] == group1.id + assert gr2_project.namespace["parent_id"] == group1.id diff --git a/tools/functional/api/test_import_export.py b/tools/functional/api/test_import_export.py new file mode 100644 index 000000000..207d2e9c4 --- /dev/null +++ b/tools/functional/api/test_import_export.py @@ -0,0 +1,61 @@ +import time + + +def test_group_import_export(gl, group, temp_dir): + export = group.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 + + +def test_project_import_export(gl, project, temp_dir): + export = project.exports.create() + export.refresh() + + count = 0 + while export.export_status != "finished": + time.sleep(1) + export.refresh() + count += 1 + if count == 15: + raise Exception("Project export taking too much time") + + with open(temp_dir / "gitlab-export.tgz", "wb") as f: + export.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 == 15: + raise Exception("Project import taking too much time") diff --git a/tools/functional/api/test_issues.py b/tools/functional/api/test_issues.py new file mode 100644 index 000000000..ebff72b0f --- /dev/null +++ b/tools/functional/api/test_issues.py @@ -0,0 +1,89 @@ +import gitlab + + +def test_create_issue(project): + issue = project.issues.create({"title": "my issue 1"}) + issue2 = project.issues.create({"title": "my issue 2"}) + assert len(project.issues.list()) == 2 + + issue2.state_event = "close" + issue2.save() + assert len(project.issues.list(state="closed")) == 1 + assert len(project.issues.list(state="opened")) == 1 + + assert isinstance(issue.user_agent_detail(), dict) + assert issue.user_agent_detail()["user_agent"] + assert issue.participants() + assert type(issue.closed_by()) == list + assert type(issue.related_merge_requests()) == list + + +def test_issue_notes(issue): + size = len(issue.notes.list()) + + note = issue.notes.create({"body": "This is an issue note"}) + assert len(issue.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(issue.notes.list()) == size + + +def test_issue_labels(project, issue): + label = project.labels.create({"name": "label2", "color": "#aabbcc"}) + issue.labels = ["label2"] + issue.save() + + assert issue in project.issues.list(labels=["label2"]) + assert issue in project.issues.list(labels="label2") + assert issue in project.issues.list(labels="Any") + assert issue not in project.issues.list(labels="None") + + +def test_issue_events(issue): + events = issue.resourcelabelevents.list() + assert isinstance(events, list) + + event = issue.resourcelabelevents.get(events[0].id) + assert isinstance(event, gitlab.v4.objects.ProjectIssueResourceLabelEvent) + + +def test_issue_milestones(project, milestone): + data = {"title": "my issue 1", "milestone_id": milestone.id} + issue = project.issues.create(data) + assert milestone.issues().next().title == "my issue 1" + + milestone_events = issue.resourcemilestoneevents.list() + assert isinstance(milestone_events, list) + + milestone_event = issue.resourcemilestoneevents.get(milestone_events[0].id) + assert isinstance( + milestone_event, gitlab.v4.objects.ProjectIssueResourceMilestoneEvent + ) + + milestone_issues = project.issues.list(milestone=milestone.title) + assert len(milestone_issues) == 1 + + +def test_issue_discussions(issue): + size = len(issue.discussions.list()) + + discussion = issue.discussions.create({"body": "Discussion body"}) + assert len(issue.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 = issue.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + + d_note_from_get.delete() + discussion = issue.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 diff --git a/tools/functional/api/test_merge_requests.py b/tools/functional/api/test_merge_requests.py new file mode 100644 index 000000000..8c2ad54c5 --- /dev/null +++ b/tools/functional/api/test_merge_requests.py @@ -0,0 +1,96 @@ +import pytest + +import gitlab + + +def test_merge_requests(project): + project.files.create( + { + "file_path": "README.rst", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } + ) + + source_branch = "branch1" + branch = project.branches.create({"branch": source_branch, "ref": "master"}) + + project.files.create( + { + "file_path": "README2.rst", + "branch": source_branch, + "content": "Initial content", + "commit_message": "New commit in new branch", + } + ) + mr = project.mergerequests.create( + {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} + ) + + +def test_merge_request_discussion(project): + mr = project.mergerequests.list()[0] + size = len(mr.discussions.list()) + + discussion = mr.discussions.create({"body": "Discussion body"}) + assert len(mr.discussions.list()) == size + 1 + + note = discussion.notes.create({"body": "first note"}) + note_from_get = discussion.notes.get(note.id) + note_from_get.body = "updated body" + note_from_get.save() + + discussion = mr.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + + note_from_get.delete() + discussion = mr.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + +def test_merge_request_labels(project): + mr = project.mergerequests.list()[0] + mr.labels = ["label2"] + mr.save() + + events = mr.resourcelabelevents.list() + assert events + + event = mr.resourcelabelevents.get(events[0].id) + assert event + + +def test_merge_request_milestone_events(project, milestone): + mr = project.mergerequests.list()[0] + mr.milestone_id = milestone.id + mr.save() + + milestones = mr.resourcemilestoneevents.list() + assert milestones + + milestone = mr.resourcemilestoneevents.get(milestones[0].id) + assert milestone + + +def test_merge_request_basic(project): + mr = project.mergerequests.list()[0] + # basic testing: only make sure that the methods exist + mr.commits() + mr.changes() + assert mr.participants() + + +def test_merge_request_rebase(project): + mr = project.mergerequests.list()[0] + assert mr.rebase() + + +def test_merge_request_merge(project): + mr = project.mergerequests.list()[0] + mr.merge() + project.branches.delete(mr.source_branch) + + with pytest.raises(gitlab.GitlabMRClosedError): + # Two merge attempts should raise GitlabMRClosedError + mr.merge() diff --git a/tools/functional/api/test_projects.py b/tools/functional/api/test_projects.py new file mode 100644 index 000000000..3e88c0c10 --- /dev/null +++ b/tools/functional/api/test_projects.py @@ -0,0 +1,298 @@ +import pytest + +import gitlab + + +def test_create_project(gl, user): + # Moved from group tests chunk in legacy tests, TODO cleanup + admin_project = gl.projects.create({"name": "admin_project"}) + assert isinstance(admin_project, gitlab.v4.objects.Project) + assert len(gl.projects.list(search="admin")) == 1 + + sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user.id) + + created = gl.projects.list() + created_gen = gl.projects.list(as_list=False) + owned = gl.projects.list(owned=True) + + assert admin_project in created and sudo_project in created + assert admin_project in owned and sudo_project not in owned + assert len(created) == len(list(created_gen)) + + admin_project.delete() + sudo_project.delete() + + +def test_project_badges(project): + badge_image = "http://example.com" + badge_link = "http://example/img.svg" + + badge = project.badges.create({"link_url": badge_link, "image_url": badge_image}) + assert len(project.badges.list()) == 1 + + badge.image_url = "http://another.example.com" + badge.save() + + badge = project.badges.get(badge.id) + assert badge.image_url == "http://another.example.com" + + badge.delete() + assert len(project.badges.list()) == 0 + + +@pytest.mark.skip(reason="Commented out in legacy test") +def test_project_boards(project): + boards = 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 + + +def test_project_custom_attributes(gl, project): + attrs = project.customattributes.list() + assert len(attrs) == 0 + + attr = project.customattributes.set("key", "value1") + assert attr.key == "key" + assert attr.value == "value1" + assert len(project.customattributes.list()) == 1 + assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 + + attr = project.customattributes.set("key", "value2") + attr = project.customattributes.get("key") + assert attr.value == "value2" + assert len(project.customattributes.list()) == 1 + + attr.delete() + assert len(project.customattributes.list()) == 0 + + +def test_project_environments(project): + project.environments.create( + {"name": "env1", "external_url": "http://fake.env/whatever"} + ) + environments = project.environments.list() + assert len(environments) == 1 + + environment = environments[0] + environment.external_url = "http://new.env/whatever" + environment.save() + + environment = project.environments.list()[0] + assert environment.external_url == "http://new.env/whatever" + + environment.stop() + environment.delete() + assert len(project.environments.list()) == 0 + + +def test_project_events(project): + events = project.events.list() + assert isinstance(events, list) + + +def test_project_file_uploads(project): + filename = "test.txt" + file_contents = "testing contents" + + uploaded_file = 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"] + ) + + +def test_project_forks(gl, project, user): + fork = project.forks.create({"namespace": user.username}) + fork_project = gl.projects.get(fork.id) + assert fork_project.forked_from_project["id"] == project.id + + forks = project.forks.list() + assert fork.id in map(lambda fork_project: fork_project.id, forks) + + +def test_project_hooks(project): + hook = project.hooks.create({"url": "http://hook.url"}) + assert len(project.hooks.list()) == 1 + + hook.note_events = True + hook.save() + + hook = project.hooks.get(hook.id) + assert hook.note_events is True + hook.delete() + + +def test_project_housekeeping(project): + project.housekeeping() + + +def test_project_labels(project): + label = project.labels.create({"name": "label", "color": "#778899"}) + label = project.labels.list()[0] + assert len(project.labels.list()) == 1 + + label.new_name = "labelupdated" + label.save() + assert label.name == "labelupdated" + + label.subscribe() + assert label.subscribed == True + + label.unsubscribe() + assert label.subscribed == False + + label.delete() + assert len(project.labels.list()) == 0 + + +def test_project_milestones(project): + milestone = project.milestones.create({"title": "milestone1"}) + assert len(project.milestones.list()) == 1 + + milestone.due_date = "2020-01-01T00:00:00Z" + milestone.save() + + milestone.state_event = "close" + milestone.save() + + milestone = project.milestones.get(milestone.id) + assert milestone.state == "closed" + assert len(milestone.issues()) == 0 + assert len(milestone.merge_requests()) == 0 + + +def test_project_pages_domains(gl, project): + domain = project.pagesdomains.create({"domain": "foo.domain.com"}) + assert len(project.pagesdomains.list()) == 1 + assert len(gl.pagesdomains.list()) == 1 + + domain = project.pagesdomains.get("foo.domain.com") + assert domain.domain == "foo.domain.com" + + domain.delete() + assert len(project.pagesdomains.list()) == 0 + + +def test_project_protected_branches(project): + p_b = project.protectedbranches.create({"name": "*-stable"}) + assert p_b.name == "*-stable" + assert len(project.protectedbranches.list()) == 1 + + p_b = project.protectedbranches.get("*-stable") + p_b.delete() + assert len(project.protectedbranches.list()) == 0 + + +def test_project_releases(gl): + 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 = project.releases.create( + { + "name": release_name, + "tag_name": release_tag_name, + "description": release_description, + "ref": "master", + } + ) + assert len(project.releases.list()) == 1 + assert project.releases.get(release_tag_name) + assert release.name == release_name + assert release.tag_name == release_tag_name + assert release.description == release_description + + project.releases.delete(release_tag_name) + assert len(project.releases.list()) == 0 + project.delete() + + +def test_project_remote_mirrors(project): + mirror_url = "http://gitlab.test/root/mirror.git" + + mirror = project.remote_mirrors.create({"url": mirror_url}) + assert mirror.url == mirror_url + + mirror.enabled = True + mirror.save() + + mirror = project.remote_mirrors.list()[0] + assert isinstance(mirror, gitlab.v4.objects.ProjectRemoteMirror) + assert mirror.url == mirror_url + assert mirror.enabled is True + + +def test_project_services(project): + service = project.services.get("asana") + service.api_key = "whatever" + service.save() + + service = project.services.get("asana") + assert service.active is True + + service.delete() + + service = project.services.get("asana") + assert service.active is False + + +def test_project_stars(project): + project.star() + assert project.star_count == 1 + + project.unstar() + assert project.star_count == 0 + + +def test_project_tags(project): + project.files.create( + { + "file_path": "README", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } + ) + tag = project.tags.create({"tag_name": "v1.0", "ref": "master"}) + assert len(project.tags.list()) == 1 + + tag.set_release_description("Description 1") + tag.set_release_description("Description 2") + assert tag.release["description"] == "Description 2" + + tag.delete() + assert len(project.tags.list()) == 0 + + +def test_project_triggers(project): + trigger = project.triggers.create({"description": "trigger1"}) + assert len(project.triggers.list()) == 1 + trigger.delete() + + +def test_project_wiki(project): + content = "Wiki page content" + wiki = project.wikis.create({"title": "wikipage", "content": content}) + assert len(project.wikis.list()) == 1 + + wiki = project.wikis.get(wiki.slug) + assert wiki.content == content + + # update and delete seem broken + wiki.content = "new content" + wiki.save() + wiki.delete() + assert len(project.wikis.list()) == 0 diff --git a/tools/functional/api/test_repository.py b/tools/functional/api/test_repository.py new file mode 100644 index 000000000..a2bfd23f2 --- /dev/null +++ b/tools/functional/api/test_repository.py @@ -0,0 +1,126 @@ +import base64 +import time + +import pytest + +import gitlab + + +def test_repository_files(project): + project.files.create( + { + "file_path": "README", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } + ) + readme = 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") + + project.files.create( + { + "file_path": "README.rst", + "branch": "master", + "content": "Initial content", + "commit_message": "New commit", + } + ) + readme = 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 = project.files.blame(file_path="README.rst", ref="master") + assert blame + + +def test_repository_tree(project): + tree = project.repository_tree() + assert len(tree) != 0 + assert tree[0]["name"] == "README.rst" + + blob_id = tree[0]["id"] + blob = project.repository_raw_blob(blob_id) + assert blob.decode() == "Initial content" + + archive = project.repository_archive() + assert isinstance(archive, bytes) + + archive2 = project.repository_archive("master") + assert archive == archive2 + + snapshot = project.snapshot() + assert isinstance(snapshot, bytes) + + +def test_create_commit(project): + data = { + "branch": "master", + "commit_message": "blah blah blah", + "actions": [{"action": "create", "file_path": "blah", "content": "blah"}], + } + commit = project.commits.create(data) + + assert "@@" in project.commits.list()[0].diff()[0]["diff"] + assert isinstance(commit.refs(), list) + assert isinstance(commit.merge_requests(), list) + + +def test_create_commit_status(project): + commit = 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 + + +def test_commit_signature(project): + commit = project.commits.list()[0] + + with pytest.raises(gitlab.GitlabGetError) as e: + signature = commit.signature() + + assert "404 Signature Not Found" in str(e.value) + + +def test_commit_comment(project): + commit = project.commits.list()[0] + + commit.comments.create({"note": "This is a commit comment"}) + assert len(commit.comments.list()) == 1 + + +def test_commit_discussion(project): + commit = project.commits.list()[0] + count = len(commit.discussions.list()) + + discussion = commit.discussions.create({"body": "Discussion body"}) + assert len(commit.discussions.list()) == (count + 1) + + note = discussion.notes.create({"body": "first note"}) + note_from_get = discussion.notes.get(note.id) + note_from_get.body = "updated body" + note_from_get.save() + discussion = commit.discussions.get(discussion.id) + # assert discussion.attributes["notes"][-1]["body"] == "updated body" + note_from_get.delete() + discussion = commit.discussions.get(discussion.id) + # assert len(discussion.attributes["notes"]) == 1 + + +def test_revert_commit(project): + commit = project.commits.list()[0] + revert_commit = commit.revert(branch="master") + + expected_message = 'Revert "{}"\n\nThis reverts commit {}'.format( + commit.message, commit.id + ) + assert revert_commit["message"] == expected_message + + with pytest.raises(gitlab.GitlabRevertError): + # Two revert attempts should raise GitlabRevertError + commit.revert(branch="master") diff --git a/tools/functional/api/test_snippets.py b/tools/functional/api/test_snippets.py new file mode 100644 index 000000000..936fbfb32 --- /dev/null +++ b/tools/functional/api/test_snippets.py @@ -0,0 +1,74 @@ +import gitlab + + +def test_snippets(gl): + 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 + + +def test_project_snippets(project): + project.snippets_enabled = True + project.save() + + snippet = 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"] + + +def test_project_snippet_discussion(project): + snippet = project.snippets.list()[0] + size = len(snippet.discussions.list()) + + discussion = snippet.discussions.create({"body": "Discussion body"}) + assert len(snippet.discussions.list()) == size + 1 + + note = discussion.notes.create({"body": "first note"}) + note_from_get = discussion.notes.get(note.id) + note_from_get.body = "updated body" + note_from_get.save() + + discussion = snippet.discussions.get(discussion.id) + assert discussion.attributes["notes"][-1]["body"] == "updated body" + + note_from_get.delete() + discussion = snippet.discussions.get(discussion.id) + assert len(discussion.attributes["notes"]) == 1 + + +def test_project_snippet_file(project): + snippet = project.snippets.list()[0] + snippet.file_name = "bar.py" + snippet.save() + + snippet = project.snippets.get(snippet.id) + assert snippet.content().decode() == "initial content" + assert snippet.file_name == "bar.py" + + size = len(project.snippets.list()) + snippet.delete() + assert len(project.snippets.list()) == (size - 1) diff --git a/tools/functional/api/test_users.py b/tools/functional/api/test_users.py index f70da4a0f..e92c0fd95 100644 --- a/tools/functional/api/test_users.py +++ b/tools/functional/api/test_users.py @@ -3,6 +3,158 @@ https://docs.gitlab.com/ee/api/users.html https://docs.gitlab.com/ee/api/users.html#delete-authentication-identity-from-user """ +import time +from pathlib import Path + +import pytest +import requests + + +@pytest.fixture(scope="session") +def avatar_path(test_dir): + return test_dir / "fixtures" / "avatar.png" + + +def test_create_user(gl, avatar_path): + user = gl.users.create( + { + "email": "foo@bar.com", + "username": "foo", + "name": "foo", + "password": "foo_password", + "avatar": open(avatar_path, "rb"), + } + ) + + created_user = gl.users.list(username="foo")[0] + assert created_user.username == user.username + assert created_user.email == user.email + + avatar_url = user.avatar_url.replace("gitlab.test", "localhost:8080") + uploaded_avatar = requests.get(avatar_url).content + assert uploaded_avatar == open(avatar_path, "rb").read() + + +def test_block_user(gl, user): + user.block() + users = gl.users.list(blocked=True) + assert user in users + + user.unblock() + users = gl.users.list(blocked=False) + assert user in users + + +def test_delete_user(gl, wait_for_sidekiq): + new_user = gl.users.create( + { + "email": "delete-user@test.com", + "username": "delete-user", + "name": "delete-user", + "password": "delete-user-pass", + } + ) + + new_user.delete() + wait_for_sidekiq() + + assert new_user.id not in [user.id for user in gl.users.list()] + + +def test_user_projects_list(gl, user): + projects = user.projects.list() + assert len(projects) == 0 + + +def test_user_events_list(gl, user): + events = user.events.list() + assert len(events) == 0 + + +def test_user_bio(gl, user): + user.bio = "This is the user bio" + user.save() + + +def test_list_multiple_users(gl, user): + second_email = f"{user.email}.2" + second_username = f"{user.username}_2" + second_user = gl.users.create( + { + "email": second_email, + "username": second_username, + "name": "Foo Bar", + "password": "foobar_password", + } + ) + assert gl.users.list(search=second_user.username)[0].id == second_user.id + + expected = [user, second_user] + actual = list(gl.users.list(search=user.username)) + + assert len(expected) == len(actual) + assert len(gl.users.list(search="asdf")) == 0 + + +def test_user_gpg_keys(gl, user, GPG_KEY): + gkey = user.gpgkeys.create({"key": GPG_KEY}) + assert len(user.gpgkeys.list()) == 1 + + # Seems broken on the gitlab side + # gkey = user.gpgkeys.get(gkey.id) + + gkey.delete() + assert len(user.gpgkeys.list()) == 0 + + +def test_user_ssh_keys(gl, user, SSH_KEY): + key = user.keys.create({"title": "testkey", "key": SSH_KEY}) + assert len(user.keys.list()) == 1 + + key.delete() + assert len(user.keys.list()) == 0 + + +def test_user_email(gl, user): + email = user.emails.create({"email": "foo2@bar.com"}) + assert len(user.emails.list()) == 1 + + email.delete() + assert len(user.emails.list()) == 0 + + +def test_user_custom_attributes(gl, user): + attrs = user.customattributes.list() + assert len(attrs) == 0 + + attr = 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(user.customattributes.list()) == 1 + + attr = user.customattributes.set("key", "value2") + attr = user.customattributes.get("key") + assert attr.value == "value2" + assert len(user.customattributes.list()) == 1 + + attr.delete() + assert len(user.customattributes.list()) == 0 + + +def test_user_impersonation_tokens(gl, user): + token = user.impersonationtokens.create( + {"name": "token1", "scopes": ["api", "read_user"]} + ) + + tokens = user.impersonationtokens.list(state="active") + assert len(tokens) == 1 + + token.delete() + tokens = user.impersonationtokens.list(state="active") + assert len(tokens) == 0 + tokens = user.impersonationtokens.list(state="inactive") + assert len(tokens) == 1 def test_user_identities(gl, user): @@ -11,10 +163,8 @@ def test_user_identities(gl, user): user.provider = provider user.extern_uid = "1" user.save() - assert provider in [item["provider"] for item in user.identities] user.identityproviders.delete(provider) user = gl.users.get(user.id) - assert provider not in [item["provider"] for item in user.identities] diff --git a/tools/functional/conftest.py b/tools/functional/conftest.py index 9b80d2047..3bb196078 100644 --- a/tools/functional/conftest.py +++ b/tools/functional/conftest.py @@ -1,6 +1,7 @@ import tempfile import time import uuid +from contextlib import contextmanager from pathlib import Path from random import randint from subprocess import check_output @@ -42,6 +43,15 @@ def set_token(container, rootdir): return output +def pytest_report_collectionfinish(config, startdir, items): + return [ + "", + "Starting GitLab container.", + "Waiting for GitLab to reconfigure.", + "This may take a few minutes.", + ] + + @pytest.fixture(scope="session") def temp_dir(): return Path(tempfile.gettempdir()) @@ -58,29 +68,33 @@ def docker_compose_file(test_dir): @pytest.fixture(scope="session") -def check_is_alive(request): +def check_is_alive(): """ Return a healthcheck function fixture for the GitLab container spinup. """ - start = time.time() - - # Temporary manager to disable capsys in a session-scoped fixture - # so people know it takes a while for GitLab to spin up - # https://github.com/pytest-dev/pytest/issues/2704 - capmanager = request.config.pluginmanager.getplugin("capturemanager") def _check(container): - delay = int(time.time() - start) + logs = ["docker", "logs", container] + return "gitlab Reconfigured!" in check_output(logs).decode() - with capmanager.global_and_fixture_disabled(): - print(f"Waiting for GitLab to reconfigure.. (~{delay}s)") + return _check - logs = ["docker", "logs", container] - output = check_output(logs).decode() - return "gitlab Reconfigured!" in output +@pytest.fixture +def wait_for_sidekiq(gl): + """ + Return a helper function to wait until there are no busy sidekiq processes. - return _check + Use this with asserts for slow tasks (group/project/user creation/deletion). + """ + + def _wait(timeout=30, step=0.5): + for _ in range(timeout): + if not gl.sidekiq.process_metrics()["processes"][0]["busy"]: + return + time.sleep(step) + + return _wait @pytest.fixture(scope="session") @@ -89,7 +103,7 @@ def gitlab_config(check_is_alive, docker_ip, docker_services, temp_dir, test_dir port = docker_services.port_for("gitlab", 80) docker_services.wait_until_responsive( - timeout=180, pause=5, check=lambda: check_is_alive("gitlab-test") + timeout=200, pause=5, check=lambda: check_is_alive("gitlab-test") ) token = set_token("gitlab-test", rootdir=test_dir) @@ -181,6 +195,14 @@ def issue(project): return project.issues.create(data) +@pytest.fixture(scope="module") +def milestone(project): + _id = uuid.uuid4().hex + data = {"title": f"milestone{_id}"} + + return project.milestones.create(data) + + @pytest.fixture(scope="module") def label(project): """Label fixture for project label API resource tests.""" @@ -242,3 +264,61 @@ def group_deploy_token(group): } return group.deploytokens.create(data) + + +@pytest.fixture(scope="session") +def GPG_KEY(): + return """-----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-----""" + + +@pytest.fixture(scope="session") +def SSH_KEY(): + return ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZAjAX8vTiHD7Yi3/EzuVaDChtih" + "79HyJZ6H9dEqxFfmGA1YnncE0xujQ64TCebhkYJKzmTJCImSVkOu9C4hZgsw6eE76n" + "+Cg3VwEeDUFy+GXlEJWlHaEyc3HWioxgOALbUp3rOezNh+d8BDwwqvENGoePEBsz5l" + "a6WP5lTi/HJIjAl6Hu+zHgdj1XVExeH+S52EwpZf/ylTJub0Bl5gHwf/siVE48mLMI" + "sqrukXTZ6Zg+8EHAIvIQwJ1dKcXe8P5IoLT7VKrbkgAnolS0I8J+uH7KtErZJb5oZh" + "S4OEwsNpaXMAr+6/wWSpircV2/e7sFLlhlKBC4Iq1MpqlZ7G3p foo@bar" + ) + + +@pytest.fixture(scope="session") +def DEPLOY_KEY(): + return ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFdRyjJQh+1niBpXqE2I8dzjG" + "MXFHlRjX9yk/UfOn075IdaockdU58sw2Ai1XIWFpZpfJkW7z+P47ZNSqm1gzeXI" + "rtKa9ZUp8A7SZe8vH4XVn7kh7bwWCUirqtn8El9XdqfkzOs/+FuViriUWoJVpA6" + "WZsDNaqINFKIA5fj/q8XQw+BcS92L09QJg9oVUuH0VVwNYbU2M2IRmSpybgC/gu" + "uWTrnCDMmLItksATifLvRZwgdI8dr+q6tbxbZknNcgEPrI2jT0hYN9ZcjNeWuyv" + "rke9IepE7SPBT41C+YtUX4dfDZDmczM1cE0YL/krdUCfuZHMa4ZS2YyNd6slufc" + "vn bar@foo" + ) diff --git a/tools/functional/python_test_v4.py b/tools/functional/python_test_v4.py deleted file mode 100644 index b29f9f3b5..000000000 --- a/tools/functional/python_test_v4.py +++ /dev/null @@ -1,1141 +0,0 @@ -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