From 077c80ba2df0325bbcf00a157e05dd1aab8d3c86 Mon Sep 17 00:00:00 2001 From: Steve Kowalik Date: Thu, 31 Oct 2019 20:56:46 +1100 Subject: [PATCH] Correct API for NamedUser.get_organization_membership (#1277) NamedUser.get_organization_membership() was calling the API method for fetching the currently authenticated user only, which is incorrect. The method was not tested directly, so add tests. Furthermore, Membership had some errors which had slipped through review, so correct them. Sadly, this is an API break, since passing the organization as an int made no sense in this case. Fixes #1276 --- github/Membership.py | 6 ++--- github/NamedUser.py | 9 +++++--- tests/NamedUser.py | 11 ++++++++++ ...amedUser.testGetOrganizationMembership.txt | 22 +++++++++++++++++++ ...testGetOrganizationMembershipNotMember.txt | 10 +++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/ReplayData/NamedUser.testGetOrganizationMembership.txt create mode 100644 tests/ReplayData/NamedUser.testGetOrganizationMembershipNotMember.txt diff --git a/github/Membership.py b/github/Membership.py index c6cf4b65..f2a6f60d 100644 --- a/github/Membership.py +++ b/github/Membership.py @@ -45,7 +45,7 @@ import github.GithubObject class Membership(github.GithubObject.CompletableGithubObject): """ - This class represents Organizations. The reference can be found here http://developer.github.com/v3/orgs/ + This class represents Membership of an organization. The reference can be found here http://developer.github.com/v3/orgs/ """ def __repr__(self): @@ -94,7 +94,7 @@ class Membership(github.GithubObject.CompletableGithubObject): @property def user(self): """ - :type: :class:`github.AuthenticatedUser.AuthenticatedUser` + :type: :class:`github.NamedUser.NamedUser` """ self._completeIfNotSet(self._user) return self._user.value @@ -119,4 +119,4 @@ class Membership(github.GithubObject.CompletableGithubObject): if "organization" in attributes: # pragma no branch self._organization = self._makeClassAttribute(github.Organization.Organization, attributes["organization"]) if "user" in attributes: # pragma no branch - self._user = self._makeClassAttribute(github.AuthenticatedUser.AuthenticatedUser, attributes["user"]) + self._user = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["user"]) diff --git a/github/NamedUser.py b/github/NamedUser.py index b52bc804..6c096ec4 100644 --- a/github/NamedUser.py +++ b/github/NamedUser.py @@ -626,13 +626,16 @@ class NamedUser(github.GithubObject.CompletableGithubObject): def get_organization_membership(self, org): """ - :calls: `GET /user/memberships/orgs/:org `_ + :calls: `GET /orgs/:org/memberships/:username `_ + :param org: string or :class:`github.Organization.Organization` :rtype: :class:`github.Membership.Membership` """ - assert isinstance(org, int) + assert isinstance(org, (str, six.text_type)) or isinstance(org, github.Organization.Organization), org + if isinstance(org, github.Organization.Organization): + org = org.login headers, data = self._requester.requestJsonAndCheck( "GET", - "/user/memberships/orgs/" + str(org) + "/orgs/" + org + "/memberships/" + self.login ) return github.Membership.Membership(self._requester, headers, data, completed=True) diff --git a/tests/NamedUser.py b/tests/NamedUser.py index 650eb68b..6c53a3cf 100644 --- a/tests/NamedUser.py +++ b/tests/NamedUser.py @@ -127,6 +127,17 @@ class NamedUser(Framework.TestCase): def testGetOrgs(self): self.assertListKeyEqual(self.user.get_orgs(), lambda o: o.login, ["BeaverSoftware"]) + def testGetOrganizationMembership(self): + o = self.user.get_orgs() + membership = self.user.get_organization_membership(o[0]) + self.assertEqual(self.user.login, membership.user.login) + self.assertEqual(membership.state, "active") + self.assertEqual(membership.role, "member") + + def testGetOrganizationMembershipNotMember(self): + from github import UnknownObjectException + self.assertRaises(UnknownObjectException, self.user.get_organization_membership, "BeaverSoftware") + def testGetRepo(self): self.assertEqual(self.user.get_repo("PyGithub").description, "Python library implementing the full Github API v3") diff --git a/tests/ReplayData/NamedUser.testGetOrganizationMembership.txt b/tests/ReplayData/NamedUser.testGetOrganizationMembership.txt new file mode 100644 index 00000000..75d12c51 --- /dev/null +++ b/tests/ReplayData/NamedUser.testGetOrganizationMembership.txt @@ -0,0 +1,22 @@ +https +GET +api.github.com +None +/users/jacquev6/orgs +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4949'), ('content-length', '262'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"82deae94305d8c551e4874e06f6efd48"'), ('date', 'Tue, 29 Oct 2019 06:37:43 GMT'), ('content-type', 'application/json; charset=utf-8')] +[{"avatar_url":"https://secure.gravatar.com/avatar/d563e337cac2fdc644e2aaaad1e23266?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-orgs.png","url":"https://api.github.com/orgs/BeaverSoftware","login":"BeaverSoftware","id":1424031}] + +https +GET +api.github.com +None +/orgs/BeaverSoftware/memberships/jacquev6 +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, read:packages, repo, user, workflow, write:discussion, write:packages'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', 'admin:org, read:org, repo, user, write:org'), ('etag', 'W/"a48fb9108de1ab30ca11a7d7b676b7d0"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('status', '200 OK'), ('x-ratelimit-remaining', '4997'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type'), ('transfer-encoding', 'chunked'), ('x-github-request-id', '965A:5675:180111:1B9D26:5DB7F14D'), ('date', 'Tue, 29 Oct 2019 07:59:15 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1572339549')] +{"url":"https://api.github.com/orgs/BeaverSoftware/memberships/jacquev6","state":"active","role":"member","user":{"following_url":"https://api.github.com/users/jacquev6/following{/other_user}","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","avatar_url":"https://avatars0.githubusercontent.com/u/15225059?v=4","url":"https://api.github.com/users/jacquev6","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","html_url":"https://github.com/jacquev6","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","node_id":"MDQ6VXNlcjE1MjI1MDU5","repos_url":"https://api.github.com/users/jacquev6/repos","received_events_url":"https://api.github.com/users/jacquev6/received_events","gravatar_id":"","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","site_admin":false,"login":"jacquev6","type":"User","id":15225059,"followers_url":"https://api.github.com/users/jacquev6/followers","organizations_url":"https://api.github.com/users/jacquev6/orgs"},"organization":{"issues_url":"https://api.github.com/orgs/BeaverSoftware/issues","members_url":"https://api.github.com/orgs/BeaverSoftware/members{/member}","description":null,"public_members_url":"https://api.github.com/orgs/BeaverSoftware/public_members{/member}","url":"https://api.github.com/orgs/BeaverSoftware","events_url":"https://api.github.com/orgs/BeaverSoftware/events","avatar_url":"https://avatars0.githubusercontent.com/u/1553906?v=4","node_id":"MDEyOk9yZ2FuaXphdGlvbjE1NTM5MDY=","repos_url":"https://api.github.com/orgs/BeaverSoftware/repos","login":"BeaverSoftware","id":1553906,"hooks_url":"https://api.github.com/orgs/BeaverSoftware/hooks"},"organization_url":"https://api.github.com/orgs/BeaverSoftware"} + diff --git a/tests/ReplayData/NamedUser.testGetOrganizationMembershipNotMember.txt b/tests/ReplayData/NamedUser.testGetOrganizationMembershipNotMember.txt new file mode 100644 index 00000000..62b76aa5 --- /dev/null +++ b/tests/ReplayData/NamedUser.testGetOrganizationMembershipNotMember.txt @@ -0,0 +1,10 @@ +https +GET +api.github.com +None +/orgs/BeaverSoftware/memberships/jacquev6 +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +404 +[('status', '404 Not Found'), ('x-ratelimit-remaining', '4994'), ('x-github-media-type', 'github.v3; format=json'), ('content-security-policy', "default-src 'none'"), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type'), ('transfer-encoding', 'chunked'), ('x-github-request-id', 'A8C4:4363:18B898:1CC4F3:5DB80872'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('access-control-allow-origin', '*'), ('x-ratelimit-limit', '5000'), ('x-xss-protection', '1; mode=block'), ('content-encoding', 'gzip'), ('date', 'Tue, 29 Oct 2019 09:38:04 GMT'), ('x-frame-options', 'deny'), ('x-oauth-scopes', 'admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, read:packages, repo, user, workflow, write:discussion, write:packages'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('content-type', 'application/json; charset=utf-8'), ('x-accepted-oauth-scopes', 'admin:org, read:org, repo, user, write:org'), ('x-ratelimit-reset', '1572345425')] +{"documentation_url": "https://developer.github.com/v3/orgs/members/#get-organization-membership", "message": "Not Found"}