@@ -5,6 +5,8 @@ use std::collections::BTreeMap;
55use super :: BacklogClient ;
66use crate :: api:: activity:: Activity ;
77use crate :: api:: issue:: Issue ;
8+ use crate :: api:: project:: Project ;
9+ use crate :: api:: wiki:: WikiListItem ;
810
911fn deserialize < T : serde:: de:: DeserializeOwned > ( value : serde_json:: Value , ctx : & str ) -> Result < T > {
1012 serde_json:: from_value ( value. clone ( ) ) . map_err ( |e| {
@@ -42,6 +44,42 @@ pub struct RecentlyViewedIssue {
4244 pub extra : BTreeMap < String , serde_json:: Value > ,
4345}
4446
47+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
48+ #[ serde( rename_all = "camelCase" ) ]
49+ pub struct RecentlyViewedProject {
50+ pub project : Project ,
51+ pub updated : String ,
52+ #[ serde( flatten) ]
53+ pub extra : BTreeMap < String , serde_json:: Value > ,
54+ }
55+
56+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
57+ #[ serde( rename_all = "camelCase" ) ]
58+ pub struct RecentlyViewedWiki {
59+ pub page : WikiListItem ,
60+ pub updated : String ,
61+ #[ serde( flatten) ]
62+ pub extra : BTreeMap < String , serde_json:: Value > ,
63+ }
64+
65+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
66+ #[ serde( rename_all = "camelCase" ) ]
67+ pub struct Star {
68+ pub id : u64 ,
69+ pub comment : Option < String > ,
70+ pub url : String ,
71+ pub title : String ,
72+ pub presenter : User ,
73+ pub created : String ,
74+ #[ serde( flatten) ]
75+ pub extra : BTreeMap < String , serde_json:: Value > ,
76+ }
77+
78+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
79+ pub struct StarCount {
80+ pub count : u64 ,
81+ }
82+
4583impl BacklogClient {
4684 pub fn get_myself ( & self ) -> Result < User > {
4785 let value = self . get ( "/users/myself" ) ?;
@@ -74,6 +112,47 @@ impl BacklogClient {
74112 let value = self . get_with_query ( "/users/myself/recentlyViewedIssues" , params) ?;
75113 deserialize ( value, "recently viewed issues response" )
76114 }
115+
116+ pub fn add_user ( & self , params : & [ ( String , String ) ] ) -> Result < User > {
117+ let value = self . post_form ( "/users" , params) ?;
118+ deserialize ( value, "user response" )
119+ }
120+
121+ pub fn update_user ( & self , user_id : u64 , params : & [ ( String , String ) ] ) -> Result < User > {
122+ let value = self . patch_form ( & format ! ( "/users/{user_id}" ) , params) ?;
123+ deserialize ( value, "user response" )
124+ }
125+
126+ pub fn delete_user ( & self , user_id : u64 ) -> Result < User > {
127+ let value = self . delete_req ( & format ! ( "/users/{user_id}" ) ) ?;
128+ deserialize ( value, "user response" )
129+ }
130+
131+ pub fn get_recently_viewed_projects (
132+ & self ,
133+ params : & [ ( String , String ) ] ,
134+ ) -> Result < Vec < RecentlyViewedProject > > {
135+ let value = self . get_with_query ( "/users/myself/recentlyViewedProjects" , params) ?;
136+ deserialize ( value, "recently viewed projects response" )
137+ }
138+
139+ pub fn get_recently_viewed_wikis (
140+ & self ,
141+ params : & [ ( String , String ) ] ,
142+ ) -> Result < Vec < RecentlyViewedWiki > > {
143+ let value = self . get_with_query ( "/users/myself/recentlyViewedWikis" , params) ?;
144+ deserialize ( value, "recently viewed wikis response" )
145+ }
146+
147+ pub fn get_user_stars ( & self , user_id : u64 , params : & [ ( String , String ) ] ) -> Result < Vec < Star > > {
148+ let value = self . get_with_query ( & format ! ( "/users/{user_id}/stars" ) , params) ?;
149+ deserialize ( value, "user stars response" )
150+ }
151+
152+ pub fn count_user_stars ( & self , user_id : u64 , params : & [ ( String , String ) ] ) -> Result < StarCount > {
153+ let value = self . get_with_query ( & format ! ( "/users/{user_id}/stars/count" ) , params) ?;
154+ deserialize ( value, "star count response" )
155+ }
77156}
78157
79158#[ cfg( test) ]
@@ -82,6 +161,8 @@ mod tests {
82161 use httpmock:: prelude:: * ;
83162 use serde_json:: json;
84163
164+ const TEST_KEY : & str = "test-key" ;
165+
85166 fn user_json ( ) -> serde_json:: Value {
86167 json ! ( {
87168 "id" : 123 ,
@@ -287,4 +368,168 @@ mod tests {
287368 assert_eq ! ( user. user_id, None ) ;
288369 assert_eq ! ( user. mail_address, None ) ;
289370 }
371+
372+ #[ test]
373+ fn add_user_returns_parsed_struct ( ) {
374+ let server = MockServer :: start ( ) ;
375+ server. mock ( |when, then| {
376+ when. method ( POST ) . path ( "/users" ) ;
377+ then. status ( 201 ) . json_body ( user_json ( ) ) ;
378+ } ) ;
379+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
380+ let user = client
381+ . add_user ( & [
382+ ( "userId" . to_string ( ) , "john" . to_string ( ) ) ,
383+ ( "password" . to_string ( ) , "secret" . to_string ( ) ) ,
384+ ( "name" . to_string ( ) , "John Doe" . to_string ( ) ) ,
385+ ( "mailAddress" . to_string ( ) , "john@example.com" . to_string ( ) ) ,
386+ ( "roleType" . to_string ( ) , "1" . to_string ( ) ) ,
387+ ] )
388+ . unwrap ( ) ;
389+ assert_eq ! ( user. id, 123 ) ;
390+ assert_eq ! ( user. name, "John Doe" ) ;
391+ }
392+
393+ #[ test]
394+ fn add_user_returns_error_on_api_failure ( ) {
395+ let server = MockServer :: start ( ) ;
396+ server. mock ( |when, then| {
397+ when. method ( POST ) . path ( "/users" ) ;
398+ then. status ( 403 )
399+ . json_body ( json ! ( { "errors" : [ { "message" : "Forbidden" } ] } ) ) ;
400+ } ) ;
401+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
402+ let err = client. add_user ( & [ ] ) . unwrap_err ( ) ;
403+ assert ! ( err. to_string( ) . contains( "Forbidden" ) ) ;
404+ }
405+
406+ #[ test]
407+ fn update_user_returns_parsed_struct ( ) {
408+ let server = MockServer :: start ( ) ;
409+ server. mock ( |when, then| {
410+ when. method ( httpmock:: Method :: PATCH ) . path ( "/users/123" ) ;
411+ then. status ( 200 ) . json_body ( user_json ( ) ) ;
412+ } ) ;
413+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
414+ let user = client
415+ . update_user ( 123 , & [ ( "name" . to_string ( ) , "New Name" . to_string ( ) ) ] )
416+ . unwrap ( ) ;
417+ assert_eq ! ( user. id, 123 ) ;
418+ }
419+
420+ #[ test]
421+ fn update_user_returns_error_on_not_found ( ) {
422+ let server = MockServer :: start ( ) ;
423+ server. mock ( |when, then| {
424+ when. method ( httpmock:: Method :: PATCH ) . path ( "/users/999" ) ;
425+ then. status ( 404 )
426+ . json_body ( json ! ( { "errors" : [ { "message" : "No user" } ] } ) ) ;
427+ } ) ;
428+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
429+ let err = client. update_user ( 999 , & [ ] ) . unwrap_err ( ) ;
430+ assert ! ( err. to_string( ) . contains( "No user" ) ) ;
431+ }
432+
433+ #[ test]
434+ fn delete_user_returns_parsed_struct ( ) {
435+ let server = MockServer :: start ( ) ;
436+ server. mock ( |when, then| {
437+ when. method ( DELETE ) . path ( "/users/123" ) ;
438+ then. status ( 200 ) . json_body ( user_json ( ) ) ;
439+ } ) ;
440+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
441+ let user = client. delete_user ( 123 ) . unwrap ( ) ;
442+ assert_eq ! ( user. id, 123 ) ;
443+ }
444+
445+ #[ test]
446+ fn delete_user_returns_error_on_not_found ( ) {
447+ let server = MockServer :: start ( ) ;
448+ server. mock ( |when, then| {
449+ when. method ( DELETE ) . path ( "/users/999" ) ;
450+ then. status ( 404 )
451+ . json_body ( json ! ( { "errors" : [ { "message" : "No user" } ] } ) ) ;
452+ } ) ;
453+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
454+ let err = client. delete_user ( 999 ) . unwrap_err ( ) ;
455+ assert ! ( err. to_string( ) . contains( "No user" ) ) ;
456+ }
457+
458+ #[ test]
459+ fn get_recently_viewed_projects_returns_list ( ) {
460+ let server = MockServer :: start ( ) ;
461+ server. mock ( |when, then| {
462+ when. method ( GET )
463+ . path ( "/users/myself/recentlyViewedProjects" ) ;
464+ then. status ( 200 ) . json_body ( json ! ( [ {
465+ "project" : {
466+ "id" : 1 , "projectKey" : "TEST" , "name" : "Test Project" ,
467+ "chartEnabled" : false , "subtaskingEnabled" : false ,
468+ "projectLeaderCanEditProjectLeader" : false ,
469+ "textFormattingRule" : "markdown" , "archived" : false
470+ } ,
471+ "updated" : "2024-06-01T00:00:00Z"
472+ } ] ) ) ;
473+ } ) ;
474+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
475+ let items = client. get_recently_viewed_projects ( & [ ] ) . unwrap ( ) ;
476+ assert_eq ! ( items. len( ) , 1 ) ;
477+ assert_eq ! ( items[ 0 ] . project. project_key, "TEST" ) ;
478+ }
479+
480+ #[ test]
481+ fn get_recently_viewed_wikis_returns_list ( ) {
482+ let server = MockServer :: start ( ) ;
483+ server. mock ( |when, then| {
484+ when. method ( GET ) . path ( "/users/myself/recentlyViewedWikis" ) ;
485+ then. status ( 200 ) . json_body ( json ! ( [ {
486+ "page" : {
487+ "id" : 1 , "projectId" : 1 , "name" : "Home" ,
488+ "tags" : [ ] ,
489+ "createdUser" : { "id" : 1 , "userId" : "admin" , "name" : "Admin" , "roleType" : 1 } ,
490+ "created" : "2024-01-01T00:00:00Z" ,
491+ "updatedUser" : { "id" : 1 , "userId" : "admin" , "name" : "Admin" , "roleType" : 1 } ,
492+ "updated" : "2024-06-01T00:00:00Z"
493+ } ,
494+ "updated" : "2024-06-01T00:00:00Z"
495+ } ] ) ) ;
496+ } ) ;
497+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
498+ let items = client. get_recently_viewed_wikis ( & [ ] ) . unwrap ( ) ;
499+ assert_eq ! ( items. len( ) , 1 ) ;
500+ assert_eq ! ( items[ 0 ] . page. name, "Home" ) ;
501+ }
502+
503+ #[ test]
504+ fn get_user_stars_returns_list ( ) {
505+ let server = MockServer :: start ( ) ;
506+ server. mock ( |when, then| {
507+ when. method ( GET ) . path ( "/users/123/stars" ) ;
508+ then. status ( 200 ) . json_body ( json ! ( [ {
509+ "id" : 1 ,
510+ "comment" : null,
511+ "url" : "https://example.com/issue/1" ,
512+ "title" : "Issue title" ,
513+ "presenter" : { "id" : 2 , "userId" : "alice" , "name" : "Alice" , "roleType" : 1 } ,
514+ "created" : "2024-01-01T00:00:00Z"
515+ } ] ) ) ;
516+ } ) ;
517+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
518+ let stars = client. get_user_stars ( 123 , & [ ] ) . unwrap ( ) ;
519+ assert_eq ! ( stars. len( ) , 1 ) ;
520+ assert_eq ! ( stars[ 0 ] . title, "Issue title" ) ;
521+ assert_eq ! ( stars[ 0 ] . comment, None ) ;
522+ }
523+
524+ #[ test]
525+ fn count_user_stars_returns_count ( ) {
526+ let server = MockServer :: start ( ) ;
527+ server. mock ( |when, then| {
528+ when. method ( GET ) . path ( "/users/123/stars/count" ) ;
529+ then. status ( 200 ) . json_body ( json ! ( { "count" : 42 } ) ) ;
530+ } ) ;
531+ let client = BacklogClient :: new_with ( & server. base_url ( ) , TEST_KEY ) . unwrap ( ) ;
532+ let result = client. count_user_stars ( 123 , & [ ] ) . unwrap ( ) ;
533+ assert_eq ! ( result. count, 42 ) ;
534+ }
290535}
0 commit comments