Skip to content

Commit

Permalink
added auth.user's git_ssh_keys operations (#719)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmgorsky authored Oct 15, 2024
1 parent 227716f commit d676963
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 4 deletions.
10 changes: 10 additions & 0 deletions src/api/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub use self::follow::{ListUserFollowerBuilder, ListUserFollowingBuilder};
use self::user_repos::ListUserReposBuilder;
use crate::api::users::user_blocks::BlockedUsersBuilder;
use crate::api::users::user_emails::UserEmailsOpsBuilder;
use crate::api::users::user_git_ssh_keys::UserGitSshKeysOpsBuilder;
use crate::api::users::user_gpg_keys::UserGpgKeysOpsBuilder;
use crate::models::UserId;
use crate::params::users::emails::EmailVisibilityState;
Expand All @@ -17,6 +18,7 @@ use crate::{error, GitHubError, Octocrab};
mod follow;
mod user_blocks;
mod user_emails;
mod user_git_ssh_keys;
mod user_gpg_keys;
mod user_repos;

Expand Down Expand Up @@ -192,4 +194,12 @@ impl<'octo> UserHandler<'octo> {
pub fn gpg_keys(&self) -> UserGpgKeysOpsBuilder<'_, '_> {
UserGpgKeysOpsBuilder::new(self)
}

///Git SSH keys operations builder
///* List public SSH keys for the authenticated user
///* Create a public SSH key for the authenticated user
///* Delete a public SSH key for the authenticated user
pub fn git_ssh_keys(&self) -> UserGitSshKeysOpsBuilder<'_, '_> {
UserGitSshKeysOpsBuilder::new(self)
}
}
152 changes: 152 additions & 0 deletions src/api/users/user_git_ssh_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use crate::api::users::UserHandler;
use crate::models::GitSshKey;
use crate::{FromResponse, Page};

#[derive(serde::Serialize)]
pub struct UserGitSshKeysOpsBuilder<'octo, 'b> {
#[serde(skip)]
handler: &'b UserHandler<'octo>,
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl<'octo, 'b> UserGitSshKeysOpsBuilder<'octo, 'b> {
pub(crate) fn new(handler: &'b UserHandler<'octo>) -> Self {
Self {
handler,
per_page: None,
page: None,
}
}

/// Results per page (max 100).
pub fn per_page(mut self, per_page: impl Into<u8>) -> Self {
self.per_page = Some(per_page.into());
self
}

/// Page number of the results to fetch.
pub fn page(mut self, page: impl Into<u32>) -> Self {
self.page = Some(page.into());
self
}

///## List public SSH keys for the authenticated user
///OAuth app tokens and personal access tokens (classic) need the read:public_key scope
///
///works with the following fine-grained token types:
///[GitHub App user access tokens](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app)
///[Fine-grained personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)
///
///The fine-grained token must have the following permission set:
///* "Git SSH keys" user permissions (read)
///
///```no_run
/// use octocrab::models::GitSshKey;
/// use octocrab::{Page, Result};
/// async fn run() -> Result<Page<GitSshKey>> {
/// octocrab::instance()
/// .users("current_user")
/// .git_ssh_keys()
/// .per_page(42).page(3u32)
/// .list()
/// .await
/// }
pub async fn list(&self) -> crate::Result<Page<crate::models::GitSshKey>> {
let route = "/user/keys".to_string();
self.handler.crab.get(route, Some(&self)).await
}

///## Create a public SSH key for the authenticated user
/// OAuth app tokens and personal access tokens (classic) need the `write:gpg_key` scope
///
///works with the following fine-grained token types:
///[GitHub App user access tokens](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app)
///[Fine-grained personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)
///
///The fine-grained token must have the following permission set:
///* "Git SSH keys" user permissions (write)
///
///```no_run
/// use octocrab::models::GitSshKey;
/// use octocrab::Result;
/// async fn run() -> Result<GitSshKey> {
/// octocrab::instance()
/// .users("current_user")
/// .git_ssh_keys()
/// .add("ssh-rsa AAAAB3NzaC1yc2EAAA".to_string(), "2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234".to_string())
/// .await
/// }
pub async fn add(&self, title: String, key: String) -> crate::Result<GitSshKey> {
let route = "/user/keys".to_string();

let params = serde_json::json!({
"title": title,
"key": key,
});
let response = self.handler.crab._post(route, Some(&params)).await?;
if response.status() != http::StatusCode::CREATED {
return Err(crate::map_github_error(response).await.unwrap_err());
}

<GitSshKey>::from_response(crate::map_github_error(response).await?).await
}

///## Delete a public SSH key for the authenticated user
/// OAuth app tokens and personal access tokens (classic) need the `admin:public_key` scope
///
///works with the following fine-grained token types:
///[GitHub App user access tokens](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app)
///[Fine-grained personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)
///
///The fine-grained token must have the following permission set:
///* "Git SSH keys" user permissions (write)
///
///```no_run
/// use octocrab::Result;
/// async fn run() -> Result<()> {
/// octocrab::instance()
/// .users("current_user")
/// .git_ssh_keys()
/// .delete(42)
/// .await
/// }
pub async fn delete(&self, git_ssh_key_id: u64) -> crate::Result<()> {
let route = format!("/user/keys/{git_ssh_key_id}");

let response = self.handler.crab._delete(route, None::<&()>).await?;
if response.status() != http::StatusCode::NO_CONTENT {
return Err(crate::map_github_error(response).await.unwrap_err());
}

Ok(())
}

///## Get a public SSH key for the authenticated user
///
///OAuth app tokens and personal access tokens (classic) need the `read:public_key` scope to use this method.
///
///works with the following token types:
///[GitHub App user access tokens](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app)
///[Fine-grained personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)
///
///The fine-grained token must have the following permission set:
///* "Git SSH keys" user permissions (read)
///
///```no_run
/// use octocrab::models::GitSshKey;
/// use octocrab::Result;
/// async fn run() -> Result<GitSshKey> {
/// octocrab::instance()
/// .users("current_user")
/// .git_ssh_keys()
/// .get(42)
/// .await
/// }
pub async fn get(&self, git_ssh_key_id: u64) -> crate::Result<GitSshKey> {
let route = format!("/user/keys/{git_ssh_key_id}");
self.handler.crab.get(route, None::<&()>).await
}
}
3 changes: 1 addition & 2 deletions src/api/users/user_gpg_keys.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::api::users::UserHandler;
use crate::models::{GpgKey, UserEmailInfo};
use crate::models::GpgKey;
use crate::{FromResponse, Page};
use std::fmt::format;

#[derive(serde::Serialize)]
pub struct UserGpgKeysOpsBuilder<'octo, 'b> {
Expand Down
11 changes: 11 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1159,3 +1159,14 @@ pub struct GpgKey {
#[serde(skip_serializing_if = "Option::is_none")]
pub raw_key: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitSshKey {
pub key: String,
pub id: u64,
pub url: String,
pub title: String,
pub created_at: DateTime<Utc>,
pub verified: bool,
pub read_only: bool,
}
9 changes: 9 additions & 0 deletions tests/resources/user_git_ssh_key_created.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"key": "2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234",
"id": 2,
"url": "https://api.github.com/user/keys/2",
"title": "ssh-rsa AAAAB3NzaC1yc2EAAA",
"created_at": "2020-06-11T21:31:57Z",
"verified": false,
"read_only": false
}
20 changes: 20 additions & 0 deletions tests/resources/user_git_ssh_keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"key": "2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234",
"id": 2,
"url": "https://api.github.com/user/keys/2",
"title": "ssh-rsa AAAAB3NzaC1yc2EAAA",
"created_at": "2020-06-11T21:31:57Z",
"verified": false,
"read_only": false
},
{
"key": "2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJy931234",
"id": 3,
"url": "https://api.github.com/user/keys/3",
"title": "ssh-rsa AAAAB3NzaC1yc2EAAB",
"created_at": "2020-07-11T21:31:57Z",
"verified": false,
"read_only": false
}
]
135 changes: 135 additions & 0 deletions tests/user_git_ssh_keys_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use http::StatusCode;
use wiremock::{
matchers::{method, path},
Mock, MockServer, ResponseTemplate,
};

use mock_error::setup_error_handler;
use octocrab::models::GitSshKey;
use octocrab::Octocrab;

/// Tests API calls related to check runs of a specific commit.
mod mock_error;

const GIT_SSH_KEY_ID: u64 = 42;

async fn setup_git_ssh_keys_mock(
http_method: &str,
mocked_path: &str,
template: ResponseTemplate,
) -> MockServer {
let mock_server = MockServer::start().await;

Mock::given(method(http_method))
.and(path(mocked_path))
.respond_with(template.clone())
.mount(&mock_server)
.await;
setup_error_handler(
&mock_server,
&format!("http method {http_method} on {mocked_path} 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_respond_to_get_git_ssh_key() {
let mocked_response: GitSshKey =
serde_json::from_str(include_str!("resources/user_git_ssh_key_created.json")).unwrap();
let template = ResponseTemplate::new(200).set_body_json(&mocked_response);
let mock_server = setup_git_ssh_keys_mock(
"GET",
format!("/user/keys/{GIT_SSH_KEY_ID}").as_str(),
template,
)
.await;
let client = setup_octocrab(&mock_server.uri());
let result = client
.users("some_other_user")
.git_ssh_keys()
.get(GIT_SSH_KEY_ID)
.await;
assert!(
result.is_ok(),
"expected successful result, got error: {:#?}",
result
);
let response = result.unwrap();
let id = response.id;
assert_eq!(id, 2);
}

#[tokio::test]
async fn should_respond_to_git_ssh_keys_list() {
let mocked_response: Vec<GitSshKey> =
serde_json::from_str(include_str!("resources/user_git_ssh_keys.json")).unwrap();
let template = ResponseTemplate::new(200).set_body_json(&mocked_response);
let mock_server = setup_git_ssh_keys_mock("GET", "/user/keys", template).await;
let client = setup_octocrab(&mock_server.uri());
let result = client
.users("some_other_user")
.git_ssh_keys()
.per_page(42)
.page(3u32)
.list()
.await;
assert!(
result.is_ok(),
"expected successful result, got error: {:#?}",
result
);
let response = result.unwrap();
let id = response.items.first().unwrap().id;
assert_eq!(id, 2);
}

#[tokio::test]
async fn should_respond_to_git_ssh_keys_add() {
let mocked_response: GitSshKey =
serde_json::from_str(include_str!("resources/user_git_ssh_key_created.json")).unwrap();
let template = ResponseTemplate::new(StatusCode::CREATED).set_body_json(&mocked_response);
let mock_server = setup_git_ssh_keys_mock("POST", "/user/keys", template).await;
let client = setup_octocrab(&mock_server.uri());
let result = client
.users("some_user")
.git_ssh_keys()
.add(
"Assh-rsa AAAAB3NzaC1yc2EAA".to_string(),
"A2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv123".to_string(),
)
.await;
assert!(
result.is_ok(),
"expected successful result, got error: {:#?}",
result
);
let result = result.unwrap();
assert_eq!(result.id, 2);
}

#[tokio::test]
async fn should_respond_to_git_ssh_key_delete() {
let template = ResponseTemplate::new(StatusCode::NO_CONTENT);
let mock_server = setup_git_ssh_keys_mock(
"DELETE",
format!("/user/keys/{GIT_SSH_KEY_ID}").as_str(),
template,
)
.await;
let client = setup_octocrab(&mock_server.uri());
let result = client
.users("some_user")
.git_ssh_keys()
.delete(GIT_SSH_KEY_ID)
.await;
assert!(
result.is_ok(),
"expected successful result, got error: {:#?}",
result
);
}
3 changes: 1 addition & 2 deletions tests/user_gpg_keys_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use wiremock::{
};

use mock_error::setup_error_handler;
use octocrab::models::{GpgKey, UserEmailInfo};
use octocrab::params::users::emails::EmailVisibilityState;
use octocrab::models::GpgKey;
use octocrab::Octocrab;

/// Tests API calls related to check runs of a specific commit.
Expand Down

0 comments on commit d676963

Please sign in to comment.