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
93 changes: 92 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use space_notification::SpaceNotification;
use team::Team;
use user::{RecentlyViewedIssue, RecentlyViewedProject, RecentlyViewedWiki, Star, StarCount, User};
use watch::{Watching, WatchingCount};
use wiki::{Wiki, WikiAttachment, WikiHistory, WikiListItem};
use wiki::{Wiki, WikiAttachment, WikiCount, WikiHistory, WikiListItem, WikiSharedFile, WikiTag};

/// Abstraction over the Backlog HTTP API.
///
Expand Down Expand Up @@ -459,6 +459,49 @@ pub trait BacklogApi {
fn get_wiki_attachments(&self, _wiki_id: u64) -> Result<Vec<WikiAttachment>> {
unimplemented!()
}
fn get_wiki_count(&self, _params: &[(String, String)]) -> Result<WikiCount> {
unimplemented!()
}
fn get_wiki_tags(&self, _params: &[(String, String)]) -> Result<Vec<WikiTag>> {
unimplemented!()
}
fn get_wiki_stars(&self, _wiki_id: u64) -> Result<Vec<Star>> {
unimplemented!()
}
fn add_wiki_attachments(
&self,
_wiki_id: u64,
_attachment_ids: &[u64],
) -> Result<Vec<WikiAttachment>> {
unimplemented!()
}
fn download_wiki_attachment(
&self,
_wiki_id: u64,
_attachment_id: u64,
) -> Result<(Vec<u8>, String)> {
unimplemented!()
}
fn delete_wiki_attachment(&self, _wiki_id: u64, _attachment_id: u64) -> Result<WikiAttachment> {
unimplemented!()
}
fn get_wiki_shared_files(&self, _wiki_id: u64) -> Result<Vec<WikiSharedFile>> {
unimplemented!()
}
fn link_wiki_shared_files(
&self,
_wiki_id: u64,
_shared_file_ids: &[u64],
) -> Result<Vec<WikiSharedFile>> {
unimplemented!()
}
fn unlink_wiki_shared_file(
&self,
_wiki_id: u64,
_shared_file_id: u64,
) -> Result<WikiSharedFile> {
unimplemented!()
}
fn get_teams(&self, _params: &[(String, String)]) -> Result<Vec<Team>> {
unimplemented!()
}
Expand Down Expand Up @@ -1055,6 +1098,54 @@ impl BacklogApi for BacklogClient {
self.get_wiki_attachments(wiki_id)
}

fn get_wiki_count(&self, params: &[(String, String)]) -> Result<WikiCount> {
self.get_wiki_count(params)
}

fn get_wiki_tags(&self, params: &[(String, String)]) -> Result<Vec<WikiTag>> {
self.get_wiki_tags(params)
}

fn get_wiki_stars(&self, wiki_id: u64) -> Result<Vec<Star>> {
self.get_wiki_stars(wiki_id)
}

fn add_wiki_attachments(
&self,
wiki_id: u64,
attachment_ids: &[u64],
) -> Result<Vec<WikiAttachment>> {
self.add_wiki_attachments(wiki_id, attachment_ids)
}

fn download_wiki_attachment(
&self,
wiki_id: u64,
attachment_id: u64,
) -> Result<(Vec<u8>, String)> {
self.download_wiki_attachment(wiki_id, attachment_id)
}

fn delete_wiki_attachment(&self, wiki_id: u64, attachment_id: u64) -> Result<WikiAttachment> {
self.delete_wiki_attachment(wiki_id, attachment_id)
}

fn get_wiki_shared_files(&self, wiki_id: u64) -> Result<Vec<WikiSharedFile>> {
self.get_wiki_shared_files(wiki_id)
}

fn link_wiki_shared_files(
&self,
wiki_id: u64,
shared_file_ids: &[u64],
) -> Result<Vec<WikiSharedFile>> {
self.link_wiki_shared_files(wiki_id, shared_file_ids)
}

fn unlink_wiki_shared_file(&self, wiki_id: u64, shared_file_id: u64) -> Result<WikiSharedFile> {
self.unlink_wiki_shared_file(wiki_id, shared_file_id)
}

fn get_teams(&self, params: &[(String, String)]) -> Result<Vec<Team>> {
self.get_teams(params)
}
Expand Down
252 changes: 252 additions & 0 deletions src/api/wiki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ use anyhow::Result;
use serde::{Deserialize, Serialize};

use super::BacklogClient;
use crate::api::user::Star;

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

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WikiSharedFile {
pub id: u64,
pub dir: String,
pub name: String,
pub size: u64,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -116,6 +133,82 @@ impl BacklogClient {
let value = self.get(&format!("/wikis/{}/attachments", wiki_id))?;
deserialize(value)
}

pub fn get_wiki_count(&self, params: &[(String, String)]) -> Result<WikiCount> {
let value = self.get_with_query("/wikis/count", params)?;
deserialize(value)
}

pub fn get_wiki_tags(&self, params: &[(String, String)]) -> Result<Vec<WikiTag>> {
let value = self.get_with_query("/wikis/tags", params)?;
deserialize(value)
}

pub fn get_wiki_stars(&self, wiki_id: u64) -> Result<Vec<Star>> {
let value = self.get(&format!("/wikis/{}/stars", wiki_id))?;
deserialize(value)
}

pub fn add_wiki_attachments(
&self,
wiki_id: u64,
attachment_ids: &[u64],
) -> Result<Vec<WikiAttachment>> {
let params: Vec<(String, String)> = attachment_ids
.iter()
.map(|id| ("attachmentId[]".to_string(), id.to_string()))
.collect();
let value = self.post_form(&format!("/wikis/{}/attachments", wiki_id), &params)?;
deserialize(value)
}

pub fn download_wiki_attachment(
&self,
wiki_id: u64,
attachment_id: u64,
) -> Result<(Vec<u8>, String)> {
self.download(&format!("/wikis/{}/attachments/{}", wiki_id, attachment_id))
}

pub fn delete_wiki_attachment(
&self,
wiki_id: u64,
attachment_id: u64,
) -> Result<WikiAttachment> {
let value =
self.delete_req(&format!("/wikis/{}/attachments/{}", wiki_id, attachment_id))?;
deserialize(value)
}

pub fn get_wiki_shared_files(&self, wiki_id: u64) -> Result<Vec<WikiSharedFile>> {
let value = self.get(&format!("/wikis/{}/sharedFiles", wiki_id))?;
deserialize(value)
}

pub fn link_wiki_shared_files(
&self,
wiki_id: u64,
shared_file_ids: &[u64],
) -> Result<Vec<WikiSharedFile>> {
let params: Vec<(String, String)> = shared_file_ids
.iter()
.map(|id| ("fileId[]".to_string(), id.to_string()))
.collect();
let value = self.post_form(&format!("/wikis/{}/sharedFiles", wiki_id), &params)?;
deserialize(value)
}

pub fn unlink_wiki_shared_file(
&self,
wiki_id: u64,
shared_file_id: u64,
) -> Result<WikiSharedFile> {
let value = self.delete_req(&format!(
"/wikis/{}/sharedFiles/{}",
wiki_id, shared_file_id
))?;
deserialize(value)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -286,4 +379,163 @@ mod tests {
let wiki: Wiki = serde_json::from_value(v).unwrap();
assert!(wiki.created_user.user_id.is_none());
}

fn shared_file_json() -> serde_json::Value {
json!({
"id": 1,
"dir": "/docs",
"name": "spec.pdf",
"size": 2048_u64
})
}

fn star_json() -> serde_json::Value {
json!({
"id": 1,
"comment": null,
"url": "https://example.backlog.com/wiki/TEST/Home",
"title": "Home",
"presenter": {
"id": 1, "userId": "john", "name": "John Doe",
"roleType": 1, "mailAddress": "john@example.com"
},
"created": "2024-01-01T00:00:00Z"
})
}

#[test]
fn get_wiki_count_returns_count() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/wikis/count")
.query_param("apiKey", TEST_KEY)
.query_param("projectIdOrKey", "TEST");
then.status(200).json_body(json!({"count": 42}));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let result = client
.get_wiki_count(&[("projectIdOrKey".to_string(), "TEST".to_string())])
.unwrap();
assert_eq!(result.count, 42);
}

#[test]
fn get_wiki_tags_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/wikis/tags")
.query_param("apiKey", TEST_KEY);
then.status(200)
.json_body(json!([{"id": 1, "name": "guide"}]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let tags = client.get_wiki_tags(&[]).unwrap();
assert_eq!(tags.len(), 1);
assert_eq!(tags[0].name, "guide");
}

#[test]
fn get_wiki_stars_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/wikis/1/stars")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!([star_json()]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let stars = client.get_wiki_stars(1).unwrap();
assert_eq!(stars.len(), 1);
assert_eq!(stars[0].title, "Home");
}

#[test]
fn add_wiki_attachments_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(POST)
.path("/wikis/1/attachments")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!([attachment_json()]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let attachments = client.add_wiki_attachments(1, &[1]).unwrap();
assert_eq!(attachments.len(), 1);
assert_eq!(attachments[0].name, "image.png");
}

#[test]
fn download_wiki_attachment_returns_bytes() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET).path("/wikis/1/attachments/1");
then.status(200)
.header("Content-Disposition", "attachment; filename=\"image.png\"")
.body(b"hello");
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let (bytes, filename) = client.download_wiki_attachment(1, 1).unwrap();
assert_eq!(bytes, b"hello");
assert_eq!(filename, "image.png");
}

#[test]
fn delete_wiki_attachment_returns_attachment() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(DELETE)
.path("/wikis/1/attachments/1")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(attachment_json());
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let attachment = client.delete_wiki_attachment(1, 1).unwrap();
assert_eq!(attachment.name, "image.png");
}

#[test]
fn get_wiki_shared_files_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(GET)
.path("/wikis/1/sharedFiles")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!([shared_file_json()]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let files = client.get_wiki_shared_files(1).unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0].name, "spec.pdf");
}

#[test]
fn link_wiki_shared_files_returns_list() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(POST)
.path("/wikis/1/sharedFiles")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(json!([shared_file_json()]));
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let files = client.link_wiki_shared_files(1, &[1]).unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0].name, "spec.pdf");
}

#[test]
fn unlink_wiki_shared_file_returns_file() {
let server = MockServer::start();
server.mock(|when, then| {
when.method(DELETE)
.path("/wikis/1/sharedFiles/1")
.query_param("apiKey", TEST_KEY);
then.status(200).json_body(shared_file_json());
});
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
let file = client.unlink_wiki_shared_file(1, 1).unwrap();
assert_eq!(file.name, "spec.pdf");
}
}
Loading