diff options
-rw-r--r-- | docs/api-objects.rst | 1 | ||||
-rw-r--r-- | docs/gl_objects/groups.rst | 56 | ||||
-rw-r--r-- | docs/gl_objects/remote_mirrors.rst | 34 | ||||
-rw-r--r-- | gitlab/exceptions.py | 4 | ||||
-rw-r--r-- | gitlab/mixins.py | 29 | ||||
-rw-r--r-- | gitlab/tests/objects/mocks.py | 35 | ||||
-rw-r--r-- | gitlab/tests/objects/test_commits.py | 3 | ||||
-rw-r--r-- | gitlab/tests/objects/test_groups.py | 101 | ||||
-rw-r--r-- | gitlab/tests/objects/test_projects.py | 123 | ||||
-rw-r--r-- | gitlab/tests/test_gitlab.py | 17 | ||||
-rw-r--r-- | gitlab/tests/test_types.py | 10 | ||||
-rw-r--r-- | gitlab/types.py | 4 | ||||
-rw-r--r-- | gitlab/v4/objects.py | 117 | ||||
-rw-r--r-- | tools/python_test_v4.py | 53 |
14 files changed, 510 insertions, 77 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 32f0d0c..32852f8 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -37,6 +37,7 @@ API examples gl_objects/projects gl_objects/protected_branches gl_objects/runners + gl_objects/remote_mirrors gl_objects/repositories gl_objects/repository_tags gl_objects/search diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index 0bc00d9..d3e4d92 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -67,6 +67,62 @@ Remove a group:: # or group.delete() +Import / Export +=============== + +You can export groups from gitlab, and re-import them to create new groups. + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.GroupExport` + + :class:`gitlab.v4.objects.GroupExportManager` + + :attr:`gitlab.v4.objects.Group.exports` + + :class:`gitlab.v4.objects.GroupImport` + + :class:`gitlab.v4.objects.GroupImportManager` + + :attr:`gitlab.v4.objects.Group.imports` + + :attr:`gitlab.v4.objects.GroupManager.import_group` + +* GitLab API: https://docs.gitlab.com/ce/api/group_import_export.html + +Examples +-------- + +A group export is an asynchronous operation. To retrieve the archive +generated by GitLab you need to: + +#. Create an export using the API +#. Wait for the export to be done +#. Download the result + +.. warning:: + + Unlike the Project Export API, GitLab does not provide an export_status + for Group Exports. It is up to the user to ensure the export is finished. + + However, Group Exports only contain metadata, so they are much faster + than Project Exports. + +:: + + # Create the export + group = gl.groups.get(my_group) + export = group.exports.create() + + # Wait for the export to finish + time.sleep(3) + + # Download the result + with open('/tmp/export.tgz', 'wb') as f: + export.download(streamed=True, action=f.write) + +Import the group:: + + with open('/tmp/export.tgz', 'rb') as f: + gl.groups.import_group(f, path='imported-group', name="Imported Group") + Subgroups ========= diff --git a/docs/gl_objects/remote_mirrors.rst b/docs/gl_objects/remote_mirrors.rst new file mode 100644 index 0000000..ea4f72c --- /dev/null +++ b/docs/gl_objects/remote_mirrors.rst @@ -0,0 +1,34 @@ +########## +Project Remote Mirrors +########## + +Remote Mirrors allow you to set up push mirroring for a project. + +References +========== + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectRemoteMirror` + + :class:`gitlab.v4.objects.ProjectRemoteMirrorManager` + + :attr:`gitlab.v4.objects.Project.remote_mirrors` + +* GitLab API: https://docs.gitlab.com/ce/api/remote_mirrors.html + +Examples +-------- + +Get the list of a project's remote mirrors:: + + mirrors = project.remote_mirrors.list() + +Create (and enable) a remote mirror for a project:: + + mirror = project.wikis.create({'url': 'https://gitlab.com/example.git', + 'enabled': True}) + +Update an existing remote mirror's attributes:: + + mirror.enabled = False + mirror.only_protected_branches = True + mirror.save() diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index d6791f2..099a901 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -209,6 +209,10 @@ class GitlabAttachFileError(GitlabOperationError): pass +class GitlabImportError(GitlabOperationError): + pass + + class GitlabCherryPickError(GitlabOperationError): pass diff --git a/gitlab/mixins.py b/gitlab/mixins.py index dde11d0..9c00c32 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -443,6 +443,35 @@ class AccessRequestMixin(object): self._update_attrs(server_data) +class DownloadMixin(object): + @cli.register_custom_action(("GroupExport", "ProjectExport")) + @exc.on_http_error(exc.GitlabGetError) + def download(self, streamed=False, action=None, chunk_size=1024, **kwargs): + """Download the archive of a resource export. + + Args: + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + reatment + action (callable): Callable responsible of dealing with chunk of + data + chunk_size (int): Size of each chunk + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the server failed to perform the request + + Returns: + str: The blob content if streamed is False, None otherwise + """ + path = "%s/download" % (self.manager.path) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) + return utils.response_content(result, streamed, action, chunk_size) + + class SubscribableMixin(object): @cli.register_custom_action( ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel") diff --git a/gitlab/tests/objects/mocks.py b/gitlab/tests/objects/mocks.py new file mode 100644 index 0000000..e051339 --- /dev/null +++ b/gitlab/tests/objects/mocks.py @@ -0,0 +1,35 @@ +"""Common mocks for resources in gitlab.v4.objects""" + +from httmock import response, urlmatch + + +headers = {"content-type": "application/json"} +binary_content = b"binary content" + + +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/(groups|projects)/1/export", + method="post", +) +def resp_create_export(url, request): + """Common mock for Group/Project Export POST response.""" + content = """{ + "message": "202 Accepted" + }""" + content = content.encode("utf-8") + return response(202, content, headers, None, 25, request) + + +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/(groups|projects)/1/export/download", + method="get", +) +def resp_download_export(url, request): + """Common mock for Group/Project Export Download GET response.""" + headers = {"content-type": "application/octet-stream"} + content = binary_content + return response(200, content, headers, None, 25, request) diff --git a/gitlab/tests/objects/test_commits.py b/gitlab/tests/objects/test_commits.py index 23a4285..7e7c3b4 100644 --- a/gitlab/tests/objects/test_commits.py +++ b/gitlab/tests/objects/test_commits.py @@ -1,6 +1,7 @@ from httmock import urlmatch, response, with_httmock -from .test_projects import headers, TestProject +from .mocks import headers +from .test_projects import TestProject @urlmatch( diff --git a/gitlab/tests/objects/test_groups.py b/gitlab/tests/objects/test_groups.py new file mode 100644 index 0000000..075d915 --- /dev/null +++ b/gitlab/tests/objects/test_groups.py @@ -0,0 +1,101 @@ +import unittest + +from httmock import response, urlmatch, with_httmock + +import gitlab +from .mocks import * # noqa + + +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1", method="get") +def resp_get_group(url, request): + content = '{"name": "name", "id": 1, "path": "path"}' + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups", method="post") +def resp_create_group(url, request): + content = '{"name": "name", "id": 1, "path": "path"}' + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch( + scheme="http", netloc="localhost", path="/api/v4/groups/import", method="post", +) +def resp_create_import(url, request): + """Mock for Group import tests. + + GitLab does not respond with import status for group imports. + """ + + content = """{ + "message": "202 Accepted" + }""" + content = content.encode("utf-8") + return response(202, content, headers, None, 25, request) + + +class TestGroup(unittest.TestCase): + def setUp(self): + self.gl = gitlab.Gitlab( + "http://localhost", + private_token="private_token", + ssl_verify=True, + api_version=4, + ) + + @with_httmock(resp_get_group) + def test_get_group(self): + data = self.gl.groups.get(1) + self.assertIsInstance(data, gitlab.v4.objects.Group) + self.assertEqual(data.name, "name") + self.assertEqual(data.path, "path") + self.assertEqual(data.id, 1) + + @with_httmock(resp_create_group) + def test_create_group(self): + name, path = "name", "path" + data = self.gl.groups.create({"name": name, "path": path}) + self.assertIsInstance(data, gitlab.v4.objects.Group) + self.assertEqual(data.name, name) + self.assertEqual(data.path, path) + + +class TestGroupExport(TestGroup): + def setUp(self): + super(TestGroupExport, self).setUp() + self.group = self.gl.groups.get(1, lazy=True) + + @with_httmock(resp_create_export) + def test_create_group_export(self): + export = self.group.exports.create() + self.assertEqual(export.message, "202 Accepted") + + @unittest.skip("GitLab API endpoint not implemented") + @with_httmock(resp_create_export) + def test_refresh_group_export_status(self): + export = self.group.exports.create() + export.refresh() + self.assertEqual(export.export_status, "finished") + + @with_httmock(resp_create_export, resp_download_export) + def test_download_group_export(self): + export = self.group.exports.create() + download = export.download() + self.assertIsInstance(download, bytes) + self.assertEqual(download, binary_content) + + +class TestGroupImport(TestGroup): + @with_httmock(resp_create_import) + def test_import_group(self): + group_import = self.gl.groups.import_group("file", "api-group", "API Group") + self.assertEqual(group_import["message"], "202 Accepted") + + @unittest.skip("GitLab API endpoint not implemented") + @with_httmock(resp_create_import) + def test_refresh_group_import_status(self): + group_import = self.group.imports.get() + group_import.refresh() + self.assertEqual(group_import.import_status, "finished") diff --git a/gitlab/tests/objects/test_projects.py b/gitlab/tests/objects/test_projects.py index d87f759..48347f9 100644 --- a/gitlab/tests/objects/test_projects.py +++ b/gitlab/tests/objects/test_projects.py @@ -10,21 +10,7 @@ from gitlab import * # noqa from gitlab.v4.objects import * # noqa from httmock import HTTMock, urlmatch, response, with_httmock # noqa - -headers = {"content-type": "application/json"} -binary_content = b"binary content" - - -@urlmatch( - scheme="http", netloc="localhost", path="/api/v4/projects/1/export", method="post", -) -def resp_create_export(url, request): - """Common mock for Project Export tests.""" - content = """{ - "message": "202 Accepted" - }""" - content = content.encode("utf-8") - return response(202, content, headers, None, 25, request) +from .mocks import * # noqa @urlmatch( @@ -52,19 +38,6 @@ def resp_export_status(url, request): @urlmatch( - scheme="http", - netloc="localhost", - path="/api/v4/projects/1/export/download", - method="get", -) -def resp_download_export(url, request): - """Mock for Project Export Download GET response.""" - headers = {"content-type": "application/octet-stream"} - content = binary_content - return response(200, content, headers, None, 25, request) - - -@urlmatch( scheme="http", netloc="localhost", path="/api/v4/projects/import", method="post", ) def resp_import_project(url, request): @@ -117,6 +90,77 @@ def resp_import_github(url, request): return response(200, content, headers, None, 25, request) +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/projects/1/remote_mirrors", + method="get", +) +def resp_get_remote_mirrors(url, request): + """Mock for Project Remote Mirrors GET response.""" + content = """[ + { + "enabled": true, + "id": 101486, + "last_error": null, + "last_successful_update_at": "2020-01-06T17:32:02.823Z", + "last_update_at": "2020-01-06T17:32:02.823Z", + "last_update_started_at": "2020-01-06T17:31:55.864Z", + "only_protected_branches": true, + "update_status": "finished", + "url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git" + } + ]""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/projects/1/remote_mirrors", + method="post", +) +def resp_create_remote_mirror(url, request): + """Mock for Project Remote Mirrors POST response.""" + content = """{ + "enabled": false, + "id": 101486, + "last_error": null, + "last_successful_update_at": null, + "last_update_at": null, + "last_update_started_at": null, + "only_protected_branches": false, + "update_status": "none", + "url": "https://*****:*****@example.com/gitlab/example.git" + }""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/projects/1/remote_mirrors/1", + method="put", +) +def resp_update_remote_mirror(url, request): + """Mock for Project Remote Mirrors PUT response.""" + content = """{ + "enabled": false, + "id": 101486, + "last_error": null, + "last_successful_update_at": "2020-01-06T17:32:02.823Z", + "last_update_at": "2020-01-06T17:32:02.823Z", + "last_update_started_at": "2020-01-06T17:31:55.864Z", + "only_protected_branches": true, + "update_status": "finished", + "url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git" + }""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + class TestProject(unittest.TestCase): """Base class for GitLab Project tests.""" @@ -289,3 +333,26 @@ class TestProjectImport(TestProject): self.assertEqual(ret["name"], name) self.assertEqual(ret["full_path"], "/".join((base_path, name))) self.assertTrue(ret["full_name"].endswith(name)) + + +class TestProjectRemoteMirrors(TestProject): + @with_httmock(resp_get_remote_mirrors) + def test_list_project_remote_mirrors(self): + mirrors = self.project.remote_mirrors.list() + self.assertIsInstance(mirrors, list) + self.assertIsInstance(mirrors[0], ProjectRemoteMirror) + self.assertTrue(mirrors[0].enabled) + + @with_httmock(resp_create_remote_mirror) + def test_create_project_remote_mirror(self): + mirror = self.project.remote_mirrors.create({"url": "https://example.com"}) + self.assertIsInstance(mirror, ProjectRemoteMirror) + self.assertEqual(mirror.update_status, "none") + + @with_httmock(resp_create_remote_mirror, resp_update_remote_mirror) + def test_update_project_remote_mirror(self): + mirror = self.project.remote_mirrors.create({"url": "https://example.com"}) + mirror.only_protected_branches = True + mirror.save() + self.assertEqual(mirror.update_status, "finished") + self.assertTrue(mirror.only_protected_branches) diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index 113093a..6fc551c 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -626,23 +626,6 @@ class TestGitlab(unittest.TestCase): self.assertIsInstance(statistics, ProjectIssuesStatistics) self.assertEqual(statistics.statistics["counts"]["all"], 20) - def test_groups(self): - @urlmatch( - scheme="http", netloc="localhost", path="/api/v4/groups/1", method="get" - ) - def resp_get_group(url, request): - headers = {"content-type": "application/json"} - content = '{"name": "name", "id": 1, "path": "path"}' - content = content.encode("utf-8") - return response(200, content, headers, None, 5, request) - - with HTTMock(resp_get_group): - data = self.gl.groups.get(1) - self.assertIsInstance(data, Group) - self.assertEqual(data.name, "name") - self.assertEqual(data.path, "path") - self.assertEqual(data.id, 1) - def test_issues(self): @urlmatch( scheme="http", netloc="localhost", path="/api/v4/issues", method="get" diff --git a/gitlab/tests/test_types.py b/gitlab/tests/test_types.py index 5b9f2ca..3613383 100644 --- a/gitlab/tests/test_types.py +++ b/gitlab/tests/test_types.py @@ -51,11 +51,19 @@ class TestListAttribute(unittest.TestCase): o.set_from_cli(" ") self.assertEqual([], o.get()) - def test_get_for_api(self): + def test_get_for_api_from_cli(self): o = types.ListAttribute() o.set_from_cli("foo,bar,baz") self.assertEqual("foo,bar,baz", o.get_for_api()) + def test_get_for_api_from_list(self): + o = types.ListAttribute(["foo", "bar", "baz"]) + self.assertEqual("foo,bar,baz", o.get_for_api()) + + def test_get_for_api_does_not_split_string(self): + o = types.ListAttribute("foo") + self.assertEqual("foo", o.get_for_api()) + class TestLowercaseStringAttribute(unittest.TestCase): def test_get_for_api(self): diff --git a/gitlab/types.py b/gitlab/types.py index 525dc30..e07d078 100644 --- a/gitlab/types.py +++ b/gitlab/types.py @@ -38,6 +38,10 @@ class ListAttribute(GitlabAttribute): self._value = [item.strip() for item in cli_value.split(",")] def get_for_api(self): + # Do not comma-split single value passed as string + if isinstance(self._value, str): + return self._value + return ",".join(self._value) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 116c7ec..0d745b4 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1028,6 +1028,26 @@ class GroupEpicManager(CRUDMixin, RESTManager): _types = {"labels": types.ListAttribute} +class GroupExport(DownloadMixin, RESTObject): + _id_attr = None + + +class GroupExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): + _path = "/groups/%(group_id)s/export" + _obj_cls = GroupExport + _from_parent_attrs = {"group_id": "id"} + + +class GroupImport(RESTObject): + _id_attr = None + + +class GroupImportManager(GetWithoutIdMixin, RESTManager): + _path = "/groups/%(group_id)s/import" + _obj_cls = GroupImport + _from_parent_attrs = {"group_id": "id"} + + class GroupIssue(RESTObject): pass @@ -1327,7 +1347,9 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): ("badges", "GroupBadgeManager"), ("boards", "GroupBoardManager"), ("customattributes", "GroupCustomAttributeManager"), + ("exports", "GroupExportManager"), ("epics", "GroupEpicManager"), + ("imports", "GroupImportManager"), ("issues", "GroupIssueManager"), ("labels", "GroupLabelManager"), ("members", "GroupMemberManager"), @@ -1446,15 +1468,27 @@ class GroupManager(CRUDMixin, RESTManager): "statistics", "owned", "with_custom_attributes", + "min_access_level", ) _create_attrs = ( ("name", "path"), ( "description", + "membership_lock", "visibility", - "parent_id", + "share_with_group_lock", + "require_two_factor_authentication", + "two_factor_grace_period", + "project_creation_level", + "auto_devops_enabled", + "subgroup_creation_level", + "emails_disabled", + "avatar", + "mentions_disabled", "lfs_enabled", "request_access_enabled", + "parent_id", + "default_branch_protection", ), ) _update_attrs = ( @@ -1463,12 +1497,51 @@ class GroupManager(CRUDMixin, RESTManager): "name", "path", "description", + "membership_lock", + "share_with_group_lock", "visibility", + "require_two_factor_authentication", + "two_factor_grace_period", + "project_creation_level", + "auto_devops_enabled", + "subgroup_creation_level", + "emails_disabled", + "avatar", + "mentions_disabled", "lfs_enabled", "request_access_enabled", + "default_branch_protection", ), ) + @exc.on_http_error(exc.GitlabImportError) + def import_group(self, file, path, name, parent_id=None, **kwargs): + """Import a group from an archive file. + + Args: + file: Data or file object containing the group + path (str): The path for the new group to be imported. + name (str): The name for the new group. + parent_id (str): ID of a parent group that the group will + be imported into. + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabImportError: If the server failed to perform the request + + Returns: + dict: A representation of the import status. + """ + files = {"file": ("file.tar.gz", file)} + data = {"path": path, "name": name} + if parent_id is not None: + data["parent_id"] = parent_id + + return self.gitlab.http_post( + "/groups/import", post_data=data, files=files, **kwargs + ) + class Hook(ObjectDeleteMixin, RESTObject): _url = "/hooks" @@ -1710,6 +1783,18 @@ class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager): self.gitlab.http_delete(self.path, query_data=data, **kwargs) +class ProjectRemoteMirror(SaveMixin, RESTObject): + pass + + +class ProjectRemoteMirrorManager(ListMixin, CreateMixin, UpdateMixin, RESTManager): + _path = "/projects/%(project_id)s/remote_mirrors" + _obj_cls = ProjectRemoteMirror + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("url",), ("enabled", "only_protected_branches")) + _update_attrs = (tuple(), ("enabled", "only_protected_branches")) + + class ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -4135,36 +4220,9 @@ class ProjectWikiManager(CRUDMixin, RESTManager): _list_filters = ("with_content",) -class ProjectExport(RefreshMixin, RESTObject): +class ProjectExport(DownloadMixin, RefreshMixin, RESTObject): _id_attr = None - @cli.register_custom_action("ProjectExport") - @exc.on_http_error(exc.GitlabGetError) - def download(self, streamed=False, action=None, chunk_size=1024, **kwargs): - """Download the archive of a project export. - - Args: - streamed (bool): If True the data will be processed by chunks of - `chunk_size` and each chunk is passed to `action` for - reatment - action (callable): Callable responsible of dealing with chunk of - data - chunk_size (int): Size of each chunk - **kwargs: Extra options to send to the server (e.g. sudo) - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabGetError: If the server failed to perform the request - - Returns: - str: The blob content if streamed is False, None otherwise - """ - path = "/projects/%s/export/download" % self.project_id - result = self.manager.gitlab.http_get( - path, streamed=streamed, raw=True, **kwargs - ) - return utils.response_content(result, streamed, action, chunk_size) - class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): _path = "/projects/%(project_id)s/export" @@ -4238,6 +4296,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): ("pipelineschedules", "ProjectPipelineScheduleManager"), ("pushrules", "ProjectPushRulesManager"), ("releases", "ProjectReleaseManager"), + ("remote_mirrors", "ProjectRemoteMirrorManager"), ("repositories", "ProjectRegistryRepositoryManager"), ("runners", "ProjectRunnerManager"), ("services", "ProjectServiceManager"), diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 58ef081..7145bc1 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -380,6 +380,33 @@ assert len(group1.variables.list()) == 0 # g_l.delete() # assert len(group1.labels.list()) == 0 + +# group import/export +export = group1.exports.create() +assert export.message == "202 Accepted" + +# We cannot check for export_status with group export API +time.sleep(10) + +import_archive = "/tmp/gitlab-group-export.tgz" +import_path = "imported_group" +import_name = "Imported Group" + +with open(import_archive, "wb") as f: + export.download(streamed=True, action=f.write) + +with open(import_archive, "rb") as f: + output = gl.groups.import_group(f, import_path, import_name) +assert output["message"] == "202 Accepted" + +# We cannot check for returned ID with group import API +time.sleep(10) +group_import = gl.groups.get(import_path) + +assert group_import.path == import_path +assert group_import.name == import_name + + # hooks hook = gl.hooks.create({"url": "http://whatever.com"}) assert len(gl.hooks.list()) == 1 @@ -727,10 +754,17 @@ assert issue1.participants() assert type(issue1.closed_by()) == list assert type(issue1.related_merge_requests()) == list -# issues labels and events +# 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) @@ -1063,6 +1097,23 @@ 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" |