diff --git a/src/api/current.rs b/src/api/current.rs index 4ce72af5..5c4ae338 100644 --- a/src/api/current.rs +++ b/src/api/current.rs @@ -1,7 +1,7 @@ //! Get data about the currently authenticated user. use crate::{ - models::{self, gists::Gist, Repository}, + models::{self, gists::Gist, orgs::MembershipInvitation, Repository}, Octocrab, Page, Result, }; use chrono::{DateTime, Utc}; @@ -123,6 +123,26 @@ impl<'octo> CurrentAuthHandler<'octo> { pub fn list_gists_starred_by_authenticated_user(&self) -> ListStarredGistsBuilder<'octo> { ListStarredGistsBuilder::new(self.crab) } + + /// Lists organizations that the current authenticated user is a member of. + /// + /// ```no_run + /// # async fn run() -> octocrab::Result<()> { + /// octocrab::instance() + /// .current() + /// .list_org_memberships_for_authenticated_user() + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// [See the GitHub API documentation](https://docs.github.com/en/rest/orgs/members#list-organization-memberships-for-the-authenticated-user) + pub fn list_org_memberships_for_authenticated_user( + &self, + ) -> ListOrgMembershipsForAuthenticatedUserBuilder<'octo> { + ListOrgMembershipsForAuthenticatedUserBuilder::new(self.crab) + } } /// A builder pattern struct for listing starred repositories. @@ -449,3 +469,53 @@ impl<'octo> ListStarredGistsBuilder<'octo> { self.crab.get("/gists/starred", Some(&self)).await } } + +/// A builder pattern struct for listing organizations the authenticated user is a member of. +/// +/// Created by [`CurrentAuthHandler::list_org_memberships_for_authenticated_user`]. +/// +/// [`CurrentAuthHandler::list_org_memberships_for_authenticated_user`]: ./struct.CurrentAuthHandler.html#method.list_org_memberships_for_authenticated_user +#[derive(serde::Serialize)] +pub struct ListOrgMembershipsForAuthenticatedUserBuilder<'octo> { + #[serde(skip)] + crab: &'octo Octocrab, + + #[serde(skip_serializing_if = "Option::is_none")] + per_page: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + page: Option, +} + +impl<'octo> ListOrgMembershipsForAuthenticatedUserBuilder<'octo> { + fn new(crab: &'octo Octocrab) -> Self { + Self { + crab, + per_page: None, + page: None, + } + } + + /// Results per page (max 100). + /// + /// [See the GitHub API documentation](https://docs.github.com/en/rest/orgs/members#list-organization-memberships-for-the-authenticated-user--parameters) + pub fn per_page(mut self, per_page: impl Into) -> Self { + self.per_page = Some(per_page.into()); + self + } + + /// Page number of the results to fetch. + /// + /// [See the GitHub API documentation](https://docs.github.com/en/rest/orgs/members#list-organization-memberships-for-the-authenticated-user--parameters) + pub fn page(mut self, page: impl Into) -> Self { + self.page = Some(page.into()); + self + } + + /// Sends the actual request. + pub async fn send(self) -> crate::Result> { + self.crab + .get("/user/memberships/orgs", (&self).into()) + .await + } +} diff --git a/tests/current_user_orgs_test.rs b/tests/current_user_orgs_test.rs new file mode 100644 index 00000000..5583c801 --- /dev/null +++ b/tests/current_user_orgs_test.rs @@ -0,0 +1,66 @@ +// Tests for calls to the /user/memberships/orgs API. +mod mock_error; + +use mock_error::setup_error_handler; +use octocrab::{models::orgs::MembershipInvitation, Octocrab, Page}; +use serde::{Deserialize, Serialize}; +use wiremock::{ + matchers::{method, path}, + Mock, MockServer, ResponseTemplate, +}; + +#[derive(Serialize, Deserialize)] +struct FakePage { + items: Vec, +} + +async fn setup_api(template: ResponseTemplate) -> MockServer { + let mock_server = MockServer::start().await; + + Mock::given(method("GET")) + .and(path(format!("/user/memberships/orgs"))) + .respond_with(template) + .mount(&mock_server) + .await; + setup_error_handler( + &mock_server, + &format!("GET on /user/membership/orgs was not received"), + ) + .await; + mock_server +} + +fn setup_octocrab(uri: &str) -> Octocrab { + Octocrab::builder().base_uri(uri).unwrap().build().unwrap() +} + +#[tokio::test] +async fn should_return_page_with_invitations() { + let membership_invitations: Vec = + serde_json::from_str(include_str!("resources/user_membership_orgs_event.json")).unwrap(); + let page_response = FakePage { + items: membership_invitations, + }; + let template = ResponseTemplate::new(200).set_body_json(&page_response); + let mock_server = setup_api(template).await; + let client = setup_octocrab(&mock_server.uri()); + let orgs = client + .current() + .list_org_memberships_for_authenticated_user(); + + let result = orgs.send().await; + assert!( + result.is_ok(), + "expected successful result, got error: {:#?}", + result + ); + + let Page { items, .. } = result.unwrap(); + { + assert_eq!(items.len(), 2); + assert_eq!(items[0].role, "admin"); + assert_eq!(items[0].user.login, "davidmhewitt"); + assert_eq!(items[0].organization.login, "elementary"); + assert_eq!(items[1].organization.login, "EpicGames"); + } +} diff --git a/tests/resources/user_membership_orgs_event.json b/tests/resources/user_membership_orgs_event.json new file mode 100644 index 00000000..1fb938e2 --- /dev/null +++ b/tests/resources/user_membership_orgs_event.json @@ -0,0 +1,82 @@ +[ + { + "url": "https://api.github.com/orgs/elementary/memberships/davidmhewitt", + "state": "active", + "role": "admin", + "organization_url": "https://api.github.com/orgs/elementary", + "user": { + "login": "davidmhewitt", + "id": 3372394, + "node_id": "MDQ6VXNlcjMzNzIzOTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/3372394?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/davidmhewitt", + "html_url": "https://github.com/davidmhewitt", + "followers_url": "https://api.github.com/users/davidmhewitt/followers", + "following_url": "https://api.github.com/users/davidmhewitt/following{/other_user}", + "gists_url": "https://api.github.com/users/davidmhewitt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/davidmhewitt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/davidmhewitt/subscriptions", + "organizations_url": "https://api.github.com/users/davidmhewitt/orgs", + "repos_url": "https://api.github.com/users/davidmhewitt/repos", + "events_url": "https://api.github.com/users/davidmhewitt/events{/privacy}", + "received_events_url": "https://api.github.com/users/davidmhewitt/received_events", + "type": "User", + "site_admin": false + }, + "organization": { + "login": "elementary", + "id": 1978534, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE5Nzg1MzQ=", + "url": "https://api.github.com/orgs/elementary", + "repos_url": "https://api.github.com/orgs/elementary/repos", + "events_url": "https://api.github.com/orgs/elementary/events", + "hooks_url": "https://api.github.com/orgs/elementary/hooks", + "issues_url": "https://api.github.com/orgs/elementary/issues", + "members_url": "https://api.github.com/orgs/elementary/members{/member}", + "public_members_url": "https://api.github.com/orgs/elementary/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/1978534?v=4", + "description": "The thoughtful, capable, and ethical replacement for Windows and macOS—plus AppCenter, the pay-what-you-can app store" + } + }, + { + "url": "https://api.github.com/orgs/EpicGames/memberships/davidmhewitt", + "state": "active", + "role": "member", + "organization_url": "https://api.github.com/orgs/EpicGames", + "user": { + "login": "davidmhewitt", + "id": 3372394, + "node_id": "MDQ6VXNlcjMzNzIzOTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/3372394?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/davidmhewitt", + "html_url": "https://github.com/davidmhewitt", + "followers_url": "https://api.github.com/users/davidmhewitt/followers", + "following_url": "https://api.github.com/users/davidmhewitt/following{/other_user}", + "gists_url": "https://api.github.com/users/davidmhewitt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/davidmhewitt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/davidmhewitt/subscriptions", + "organizations_url": "https://api.github.com/users/davidmhewitt/orgs", + "repos_url": "https://api.github.com/users/davidmhewitt/repos", + "events_url": "https://api.github.com/users/davidmhewitt/events{/privacy}", + "received_events_url": "https://api.github.com/users/davidmhewitt/received_events", + "type": "User", + "site_admin": false + }, + "organization": { + "login": "EpicGames", + "id": 6615685, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjY2MTU2ODU=", + "url": "https://api.github.com/orgs/EpicGames", + "repos_url": "https://api.github.com/orgs/EpicGames/repos", + "events_url": "https://api.github.com/orgs/EpicGames/events", + "hooks_url": "https://api.github.com/orgs/EpicGames/hooks", + "issues_url": "https://api.github.com/orgs/EpicGames/issues", + "members_url": "https://api.github.com/orgs/EpicGames/members{/member}", + "public_members_url": "https://api.github.com/orgs/EpicGames/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/6615685?v=4", + "description": "" + } + } +] \ No newline at end of file