Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions src/api/issue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ pub struct IssueComment {
pub extra: BTreeMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IssueCommentCount {
pub count: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IssueCommentNotification {
pub id: u64,
pub already_read: bool,
pub reason: u64,
pub user: IssueUser,
pub resource_already_read: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IssueAttachment {
Expand Down Expand Up @@ -181,6 +196,41 @@ impl BacklogClient {
let value = self.get(&format!("/issues/{}/attachments", key))?;
deserialize(value)
}

pub fn count_issue_comments(&self, key: &str) -> Result<IssueCommentCount> {
let value = self.get(&format!("/issues/{}/comments/count", key))?;
deserialize(value)
}

pub fn get_issue_comment(&self, key: &str, comment_id: u64) -> Result<IssueComment> {
let value = self.get(&format!("/issues/{}/comments/{}", key, comment_id))?;
deserialize(value)
}

pub fn get_issue_comment_notifications(
&self,
key: &str,
comment_id: u64,
) -> Result<Vec<IssueCommentNotification>> {
let value = self.get(&format!(
"/issues/{}/comments/{}/notifications",
key, comment_id
))?;
deserialize(value)
}

pub fn add_issue_comment_notifications(
&self,
key: &str,
comment_id: u64,
params: &[(String, String)],
) -> Result<Vec<IssueCommentNotification>> {
let value = self.post_form(
&format!("/issues/{}/comments/{}/notifications", key, comment_id),
params,
)?;
deserialize(value)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -249,6 +299,16 @@ mod tests {
})
}

fn notification_json() -> serde_json::Value {
json!({
"id": 1,
"alreadyRead": false,
"reason": 2,
"user": issue_user_json(),
"resourceAlreadyRead": false
})
}

fn attachment_json() -> serde_json::Value {
json!({
"id": 1,
Expand Down Expand Up @@ -358,6 +418,68 @@ mod tests {
assert_eq!(attachments[0].name, "file.txt");
}

#[test]
fn count_issue_comments_returns_count() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/issues/TEST-1/comments/count")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!({"count": 7}));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let count = client.count_issue_comments("TEST-1").unwrap();
assert_eq!(count.count, 7);
}

#[test]
fn get_issue_comment_returns_single() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/issues/TEST-1/comments/1")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(comment_json());
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let comment = client.get_issue_comment("TEST-1", 1).unwrap();
assert_eq!(comment.content.as_deref(), Some("A comment"));
}

#[test]
fn get_issue_comment_notifications_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/issues/TEST-1/comments/1/notifications")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!([notification_json()]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let notifications = client.get_issue_comment_notifications("TEST-1", 1).unwrap();
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].id, 1);
assert!(!notifications[0].already_read);
}

#[test]
fn add_issue_comment_notifications_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(POST)
.path("/issues/TEST-1/comments/1/notifications")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!([notification_json()]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let params = vec![("notifiedUserId[]".to_string(), "1".to_string())];
let notifications = client
.add_issue_comment_notifications("TEST-1", 1, &params)
.unwrap();
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].user.name, "John Doe");
}

#[test]
fn issue_with_null_user_id_deserializes() {
let v = json!({
Expand Down
50 changes: 49 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ pub mod wiki;

use activity::Activity;
use disk_usage::DiskUsage;
use issue::{Issue, IssueAttachment, IssueComment, IssueCount};
use issue::{
Issue, IssueAttachment, IssueComment, IssueCommentCount, IssueCommentNotification, IssueCount,
};
use licence::Licence;
use notification::{Notification, NotificationCount};
use project::{
Expand Down Expand Up @@ -139,6 +141,27 @@ pub trait BacklogApi {
fn get_issue_attachments(&self, _key: &str) -> Result<Vec<IssueAttachment>> {
unimplemented!()
}
fn count_issue_comments(&self, _key: &str) -> Result<IssueCommentCount> {
unimplemented!()
}
fn get_issue_comment(&self, _key: &str, _comment_id: u64) -> Result<IssueComment> {
unimplemented!()
}
fn get_issue_comment_notifications(
&self,
_key: &str,
_comment_id: u64,
) -> Result<Vec<IssueCommentNotification>> {
unimplemented!()
}
fn add_issue_comment_notifications(
&self,
_key: &str,
_comment_id: u64,
_params: &[(String, String)],
) -> Result<Vec<IssueCommentNotification>> {
unimplemented!()
}
fn get_wikis(&self, _params: &[(String, String)]) -> Result<Vec<WikiListItem>> {
unimplemented!()
}
Expand Down Expand Up @@ -317,6 +340,31 @@ impl BacklogApi for BacklogClient {
self.get_issue_attachments(key)
}

fn count_issue_comments(&self, key: &str) -> Result<IssueCommentCount> {
self.count_issue_comments(key)
}

fn get_issue_comment(&self, key: &str, comment_id: u64) -> Result<IssueComment> {
self.get_issue_comment(key, comment_id)
}

fn get_issue_comment_notifications(
&self,
key: &str,
comment_id: u64,
) -> Result<Vec<IssueCommentNotification>> {
self.get_issue_comment_notifications(key, comment_id)
}

fn add_issue_comment_notifications(
&self,
key: &str,
comment_id: u64,
params: &[(String, String)],
) -> Result<Vec<IssueCommentNotification>> {
self.add_issue_comment_notifications(key, comment_id, params)
}

fn get_wikis(&self, params: &[(String, String)]) -> Result<Vec<WikiListItem>> {
self.get_wikis(params)
}
Expand Down
75 changes: 75 additions & 0 deletions src/cmd/issue/comment/count.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use anstream::println;
use anyhow::{Context, Result};

use crate::api::{BacklogApi, BacklogClient};

pub struct IssueCommentCountArgs {
key: String,
json: bool,
}

impl IssueCommentCountArgs {
pub fn new(key: String, json: bool) -> Self {
Self { key, json }
}
}

pub fn count(args: &IssueCommentCountArgs) -> Result<()> {
let client = BacklogClient::from_config()?;
count_with(args, &client)
}

pub fn count_with(args: &IssueCommentCountArgs, api: &dyn BacklogApi) -> Result<()> {
let result = api.count_issue_comments(&args.key)?;
if args.json {
println!(
"{}",
serde_json::to_string_pretty(&result).context("Failed to serialize JSON")?
);
} else {
println!("{}", result.count);
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::api::issue::IssueCommentCount;
use anyhow::anyhow;

struct MockApi {
count: Option<u64>,
}

impl crate::api::BacklogApi for MockApi {
fn count_issue_comments(&self, _key: &str) -> anyhow::Result<IssueCommentCount> {
self.count
.map(|c| IssueCommentCount { count: c })
.ok_or_else(|| anyhow!("no count"))
}
}

fn args(json: bool) -> IssueCommentCountArgs {
IssueCommentCountArgs::new("TEST-1".to_string(), json)
}

#[test]
fn count_with_text_output_succeeds() {
let api = MockApi { count: Some(5) };
assert!(count_with(&args(false), &api).is_ok());
}

#[test]
fn count_with_json_output_succeeds() {
let api = MockApi { count: Some(5) };
assert!(count_with(&args(true), &api).is_ok());
}

#[test]
fn count_with_propagates_api_error() {
let api = MockApi { count: None };
let err = count_with(&args(false), &api).unwrap_err();
assert!(err.to_string().contains("no count"));
}
}
5 changes: 5 additions & 0 deletions src/cmd/issue/comment/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
pub mod add;
pub mod count;
pub mod delete;
pub mod list;
pub mod notification;
pub mod show;
pub mod update;

pub use add::{IssueCommentAddArgs, add};
pub use count::{IssueCommentCountArgs, count};
pub use delete::{IssueCommentDeleteArgs, delete};
pub use list::{IssueCommentListArgs, list};
pub use show::{IssueCommentShowArgs, show};
pub use update::{IssueCommentUpdateArgs, update};
Loading
Loading