From 62908664ca6d1859a72fbfa55b3a6248b84bcaa6 Mon Sep 17 00:00:00 2001 From: xiao-kong-long <2745240762@qq.com> Date: Wed, 29 Nov 2023 19:15:37 +0800 Subject: [PATCH 1/3] test of application, orgnization and group succ --- .vscode/launch.json | 18 ++++++ .vscode/settings.json | 3 + src/casdoor/application.py | 6 +- src/casdoor/group.py | 56 ++++++++++++++--- src/casdoor/organization.py | 62 ++++++++++++++++--- src/tests/test_application.py | 7 ++- src/tests/test_group.py | 97 ++++++++++++++++++++++++++++++ src/tests/test_organization.py | 107 +++++++++++++++++++++++++++++++++ src/tests/test_util.py | 8 ++- 9 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 src/tests/test_group.py create mode 100644 src/tests/test_organization.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0cb7b28 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: 当前文件", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true, + "cwd": "${workspaceFolder}", + "env": {"PYTHONPATH": "${workspaceFolder}"} + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d950b06 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.extraPaths": ["${workspaceFolder}"] +} diff --git a/src/casdoor/application.py b/src/casdoor/application.py index ea1960f..32a76c1 100644 --- a/src/casdoor/application.py +++ b/src/casdoor/application.py @@ -143,7 +143,7 @@ def get_applications(self) -> List[Application]: r = requests.get(url, params) response = r.json() if response["status"] != "ok": - raise ValueError(response.msg) + raise ValueError(response['msg']) res = [] for element in response["data"]: @@ -166,7 +166,7 @@ def get_application(self, application_id: str) -> Application: r = requests.get(url, params) response = r.json() if response["status"] != "ok": - raise ValueError(response.msg) + raise ValueError(response['msg']) return Application.from_dict(response["data"]) def modify_application(self, method: str, application: Application) -> str: @@ -182,7 +182,7 @@ def modify_application(self, method: str, application: Application) -> str: r = requests.post(url, params=params, data=application_info) response = r.json() if response["status"] != "ok": - raise ValueError(response.msg) + raise ValueError(response['msg']) return str(response["data"]) def add_application(self, application: Application) -> str: diff --git a/src/casdoor/group.py b/src/casdoor/group.py index 8db51ae..0fee9e6 100644 --- a/src/casdoor/group.py +++ b/src/casdoor/group.py @@ -32,12 +32,31 @@ def __init__(self): self.type = "" self.parentId = "" self.isTopGroup = False - self.users = [User] + # self.users = [User] self.title = "" self.key = "" - self.children = [Group] + # self.children = [Group] self.isEnabled = False + @classmethod + def new(cls, owner, name, created_time, display_name): + self = cls() + self.owner = owner + self.name = name + self.createdTime = created_time + self.displayName = display_name + return self + + @classmethod + def from_dict(cls, data: dict): + if not data: + return None + group = cls() + for key, value in data.items(): + if hasattr(group, key): + setattr(group, key, value) + return group + def __str__(self): return str(self.__dict__) @@ -59,8 +78,15 @@ def get_groups(self) -> List[Dict]: "clientSecret": self.client_secret, } r = requests.get(url, params) - groups = r.json() - return groups + response = r.json() + if response["status"] != "ok": + raise ValueError(response['msg']) + + res = [] + for element in response["data"]: + res.append(Group.from_dict(element)) + + return res def get_group(self, group_id: str) -> Dict: """ @@ -76,22 +102,30 @@ def get_group(self, group_id: str) -> Dict: "clientSecret": self.client_secret, } r = requests.get(url, params) - group = r.json() - return group + response = r.json() + if response["status"] != "ok": + raise ValueError(response['msg']) + return Group.from_dict(response["data"]) def modify_group(self, method: str, group: Group) -> Dict: url = self.endpoint + f"/api/{method}" - if group.owner == "": - group.owner = self.org_name + # if group.owner == "": + # group.owner = self.org_name + group.owner = self.org_name params = { "id": f"{group.owner}/{group.name}", "clientId": self.client_id, "clientSecret": self.client_secret, } + group_info = json.dumps(group.to_dict()) + # group_info = json.dumps(group.to_dict(), default=self.custom_encoder) r = requests.post(url, params=params, data=group_info) response = r.json() - return response + if response["status"] != "ok": + raise ValueError(response['msg']) + + return str(response["data"]) def add_group(self, group: Group) -> Dict: response = self.modify_group("add-group", group) @@ -104,3 +138,7 @@ def update_group(self, group: Group) -> Dict: def delete_group(self, group: Group) -> Dict: response = self.modify_group("delete-group", group) return response + + # def custom_encoder(self, o): + # if isinstance(o, (Group, User)): + # return o.__dict__ \ No newline at end of file diff --git a/src/casdoor/organization.py b/src/casdoor/organization.py index 53214d7..28ca61d 100644 --- a/src/casdoor/organization.py +++ b/src/casdoor/organization.py @@ -75,13 +75,43 @@ def __init__(self): self.defaultApplication = "" self.tags = [""] self.languages = [""] - self.themeData = ThemeData + # self.themeData = ThemeData self.masterPassword = "" self.initScore = 0 self.enableSoftDeletion = False self.isProfilePublic = False - self.mfaItems = [MfaItem] - self.accountItems = [AccountItem] + # self.mfaItems = [MfaItem] + # self.accountItems = [AccountItem] + + @classmethod + def new(cls, owner, name, created_time, display_name, website_url, password_type, password_options, country_codes, tags, languages, init_score, enable_soft_deletion, is_profile_public): + self = cls() + self.owner = owner + self.name = name + self.createdTime = created_time + self.displayName = display_name + self.websiteUrl = website_url + self.passwordType = password_type + self.passwordOptions = password_options + self.countryCodes = country_codes + self.tags = tags + self.languages = languages + self.initScore = init_score + self.enableSoftDeletion = enable_soft_deletion + self.isProfilePublic = is_profile_public + + return self + + @classmethod + def from_dict(cls, data: dict): + if data is None: + return None + + org = cls() + for key, value in data.items(): + if hasattr(org, key): + setattr(org, key, value) + return org def __str__(self): return str(self.__dict__) @@ -104,8 +134,14 @@ def get_organizations(self) -> List[Dict]: "clientSecret": self.client_secret, } r = requests.get(url, params) - organizations = r.json() - return organizations + response = r.json() + if response["status"] != "ok": + raise ValueError(response.msg) + + res = [] + for element in response["data"]: + res.append(Organization.from_dict(element)) + return res def get_organization(self, organization_id: str) -> Dict: """ @@ -121,13 +157,17 @@ def get_organization(self, organization_id: str) -> Dict: "clientSecret": self.client_secret, } r = requests.get(url, params) - organization = r.json() - return organization + response = r.json() + if response["status"] != "ok": + raise ValueError(response.msg) + return Organization.from_dict(response["data"]) + def modify_organization(self, method: str, organization: Organization) -> Dict: url = self.endpoint + f"/api/{method}" - if organization.owner == "": - organization.owner = self.org_name + # if organization.owner == "": + # organization.owner = self.org_name + organization.owner = self.org_name params = { "id": f"{organization.owner}/{organization.name}", "clientId": self.client_id, @@ -136,7 +176,9 @@ def modify_organization(self, method: str, organization: Organization) -> Dict: organization_info = json.dumps(organization.to_dict()) r = requests.post(url, params=params, data=organization_info) response = r.json() - return response + if response["status"] != "ok": + raise ValueError(response) + return str(response["data"]) def add_organization(self, organization: Organization) -> Dict: response = self.modify_organization("add-organization", organization) diff --git a/src/tests/test_application.py b/src/tests/test_application.py index 4125967..490e8d4 100644 --- a/src/tests/test_application.py +++ b/src/tests/test_application.py @@ -23,7 +23,7 @@ TestClientSecret, TestEndpoint, TestJwtPublicKey, - TestOrganization, + TestOrga, get_random_name, ) @@ -45,7 +45,7 @@ def test_application(self): ) sdk = CasdoorSDK( - TestEndpoint, TestClientId, TestClientSecret, TestJwtPublicKey, TestOrganization, TestApplication + TestEndpoint, TestClientId, TestClientSecret, TestJwtPublicKey, TestOrg, TestApplication ) try: sdk.add_application(application=application) @@ -94,3 +94,6 @@ def test_application(self): except Exception as e: self.fail(f"Failed to get object: {e}") self.assertIsNone(deleted_application, "Failed to delete object, it's still retrievable") + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_group.py b/src/tests/test_group.py new file mode 100644 index 0000000..5362c4b --- /dev/null +++ b/src/tests/test_group.py @@ -0,0 +1,97 @@ +# Copyright 2023 The Casdoor Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import unittest + +from src.casdoor import CasdoorSDK +from src.casdoor.group import Group +from src.tests.test_util import ( + TestApplication, + TestClientId, + TestClientSecret, + TestEndpoint, + TestJwtPublicKey, + TestOrg, + get_random_name, +) + +class TestGroup(unittest.TestCase): + + def test_group(self): + name = get_random_name("group") + + # Add a new object + group = Group.new( + owner="admin", + name=name, + created_time=datetime.datetime.now().isoformat(), + display_name=name + ) + + sdk = CasdoorSDK( + TestEndpoint, TestClientId, TestClientSecret, TestJwtPublicKey, TestOrg, TestApplication + ) + + try: + sdk.add_group(group) + except Exception as e: + self.fail(f"Failed to add object: {e}") + + + # Get all objects, check if our added object is inside the list + try: + groups = sdk.get_groups() + except Exception as e: + self.fail(f"Failed to get objects: {e}") + names = [item.name for item in groups] + self.assertIn(name, names, "Added object not found in list") + + # Get the object + try: + retrieved_group = sdk.get_group(name) + except Exception as e: + self.fail(f"Failed to get object: {e}") + self.assertEqual(name, retrieved_group.name, "Retrieved object does not match added object") + + # Update the object + updated_display_name = "updated_display_name" + retrieved_group.displayName = updated_display_name + try: + updated_group = sdk.update_group(retrieved_group) + except Exception as e: + self.fail(f"Failed to update object: {e}") + + # Validate the update + try: + updated_group = sdk.get_group(name) + except Exception as e: + self.fail(f"Failed to get object: {e}") + self.assertEqual(updated_display_name, updated_group.displayName, "Failed to update object, display_name mismatch") + + # Delete the object + try: + sdk.delete_group(group) + except Exception as e: + self.fail(f"Failed to delete object: {e}") + + # Validate the deletion + try: + deleted_group = sdk.get_group(name) + except Exception as e: + self.fail(f"Failed to get object: {e}") + self.assertIsNone(deleted_group, "Failed to delete object, it's still retrievable") + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_organization.py b/src/tests/test_organization.py new file mode 100644 index 0000000..8c89f08 --- /dev/null +++ b/src/tests/test_organization.py @@ -0,0 +1,107 @@ +# Copyright 2023 The Casdoor Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import unittest + +from src.casdoor import CasdoorSDK +from src.casdoor.organization import Organization +from src.tests.test_util import ( + TestApplication, + TestClientId, + TestClientSecret, + TestEndpoint, + TestJwtPublicKey, + TestOrg, + get_random_name, +) + +class TestOrganization(unittest.TestCase): + + def test_organization(self): + + + name = get_random_name("Organization") + + # Add a new object + organization = Organization.new( + owner="admin", + name=name, + created_time=datetime.datetime.now().isoformat(), + display_name=name, + website_url="https://example.com", + password_type="plain", + password_options=["AtLeast6"], + country_codes=["US", "ES", "FR", "DE", "GB", "CN", "JP", "KR", "VN", "ID", "SG", "IN"], + tags=[], + languages=["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "pt"], + init_score=2000, + enable_soft_deletion=False, + is_profile_public=False + ) + + sdk = CasdoorSDK( + TestEndpoint, TestClientId, TestClientSecret, TestJwtPublicKey, TestOrg, TestApplication + ) + + + try: + sdk.add_organization(organization) + except Exception as e: + self.fail(f"Failed to add object: {e}") + + # Get all objects, check if our added object is inside the list + try: + organizations = sdk.get_organizations() + except Exception as e: + self.fail(f"Failed to get objects: {e}") + names = [item.name for item in organizations] + self.assertIn(name, names, "Added object not found in list") + + # Get the object + try: + organization = sdk.get_organization(name) + except Exception as e: + self.fail(f"Failed to get object: {e}") + self.assertEqual(organization.name, name) + + # Update the object + updated_display_name = "Updated Casdoor Website" + organization.displayName = updated_display_name + try: + sdk.update_organization(organization) + except Exception as e: + self.fail(f"Failed to update object: {e}") + + + # Validate the update + try: + updated_organization = sdk.get_organization(name) + except Exception as e: + self.fail(f"Failed to get object: {e}") + self.assertEqual(updated_organization.displayName, updated_display_name) + + # Delete the object + sdk.delete_organization(organization) + + # Validate the deletion + try: + deleted_organization = sdk.get_organization(name) + except Exception as e: + self.fail(f"Failed to get object: {e}") + self.assertIsNone(deleted_organization, "Failed to delete object, it's still retrievable") + + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_util.py b/src/tests/test_util.py index d41ac96..405ecaa 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_util.py @@ -17,7 +17,7 @@ TestEndpoint = "https://demo.casdoor.com" TestClientId = "294b09fbc17f95daf2fe" TestClientSecret = "dd8982f7046ccba1bbd7851d5c1ece4e52bf039d" -TestOrganization = "casbin" +TestOrg = "casbin" TestApplication = "app-vue-python-example" TestJwtPublicKey = """-----BEGIN CERTIFICATE----- MIIE+TCCAuGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMDYxHTAbBgNVBAoTFENh @@ -58,3 +58,9 @@ def get_random_code(length): def get_random_name(prefix): return f"{prefix}_{get_random_code(6)}" + + + + + + From 881a965df913c0b4e21e22c13f5b9571a1df3a0b Mon Sep 17 00:00:00 2001 From: xiao-kong-long <2745240762@qq.com> Date: Wed, 29 Nov 2023 19:23:06 +0800 Subject: [PATCH 2/3] design a function that dumps customized object to json data, just change for group class --- src/casdoor/group.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/casdoor/group.py b/src/casdoor/group.py index 0fee9e6..5d12103 100644 --- a/src/casdoor/group.py +++ b/src/casdoor/group.py @@ -32,10 +32,10 @@ def __init__(self): self.type = "" self.parentId = "" self.isTopGroup = False - # self.users = [User] + self.users = [User] self.title = "" self.key = "" - # self.children = [Group] + self.children = [Group] self.isEnabled = False @classmethod @@ -118,8 +118,8 @@ def modify_group(self, method: str, group: Group) -> Dict: "clientSecret": self.client_secret, } - group_info = json.dumps(group.to_dict()) - # group_info = json.dumps(group.to_dict(), default=self.custom_encoder) + # group_info = json.dumps(group.to_dict()) + group_info = json.dumps(group.to_dict(), default=self.custom_encoder) r = requests.post(url, params=params, data=group_info) response = r.json() if response["status"] != "ok": @@ -139,6 +139,6 @@ def delete_group(self, group: Group) -> Dict: response = self.modify_group("delete-group", group) return response - # def custom_encoder(self, o): - # if isinstance(o, (Group, User)): - # return o.__dict__ \ No newline at end of file + def custom_encoder(self, o): + if isinstance(o, (Group, User)): + return o.__dict__ \ No newline at end of file From 8fdb696b63ddb441176e8ca6265bdb49ec09d247 Mon Sep 17 00:00:00 2001 From: xiao-kong-long <2745240762@qq.com> Date: Wed, 29 Nov 2023 21:10:50 +0800 Subject: [PATCH 3/3] ci correction --- src/tests/test_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test_application.py b/src/tests/test_application.py index 490e8d4..fe3d46a 100644 --- a/src/tests/test_application.py +++ b/src/tests/test_application.py @@ -23,7 +23,7 @@ TestClientSecret, TestEndpoint, TestJwtPublicKey, - TestOrga, + TestOrg, get_random_name, )