From bd16d24ce7aaae68ce7f7f0cb73e9b6d98c7f378 Mon Sep 17 00:00:00 2001 From: Pierre-Yves FARE Date: Thu, 13 Aug 2020 21:59:23 +0100 Subject: [PATCH] Implement remaining CRUD operation for user API. fix #277 --- .../UserApi/UserApiClientTest.cs | 108 ++++++++++++++++++ .../UserApi/IUserApiClient.cs | 35 ++++++ .../UserApi/Models/GetUserResponse.cs | 9 ++ .../UserApi/Models/PatchUserBody.cs | 27 +++++ .../UserApi/Models/PatchUserResponse.cs | 11 ++ .../UserApi/Models/PostUserResponse.cs | 34 +----- .../UserApi/Models/PutUserBody.cs | 27 +++++ .../UserApi/Models/PutUserResponse.cs | 12 ++ .../UserApi/Models/UserResponseBase.cs | 37 ++++++ .../UserApi/UserApiClient.cs | 69 ++++++++++- 10 files changed, 339 insertions(+), 30 deletions(-) create mode 100644 arangodb-net-standard/UserApi/Models/GetUserResponse.cs create mode 100644 arangodb-net-standard/UserApi/Models/PatchUserBody.cs create mode 100644 arangodb-net-standard/UserApi/Models/PatchUserResponse.cs create mode 100644 arangodb-net-standard/UserApi/Models/PutUserBody.cs create mode 100644 arangodb-net-standard/UserApi/Models/PutUserResponse.cs create mode 100644 arangodb-net-standard/UserApi/Models/UserResponseBase.cs diff --git a/arangodb-net-standard.Test/UserApi/UserApiClientTest.cs b/arangodb-net-standard.Test/UserApi/UserApiClientTest.cs index 5069ff36..069a5c52 100644 --- a/arangodb-net-standard.Test/UserApi/UserApiClientTest.cs +++ b/arangodb-net-standard.Test/UserApi/UserApiClientTest.cs @@ -84,5 +84,113 @@ await _userClient.PostUserAsync(new PostUserBody() Assert.NotNull(ex.ApiError.ErrorMessage); Assert.Equal(1702, ex.ApiError.ErrorNum); // ERROR_USER_DUPLICATE } + + [Fact] + public async Task PutUserAsync_ShouldSucceed() + { + PutUserResponse response = await _userClient.PutUserAsync( + _fixture.UsernameExisting, + new PutUserBody() + { + Extra = new Dictionary() + { + ["somedata"] = nameof(PutUserAsync_ShouldSucceed) + } + }); + + Assert.False(response.Error); + Assert.Equal(HttpStatusCode.OK, response.Code); + Assert.Equal(_fixture.UsernameExisting, response.User); + Assert.True(response.Active); + Assert.True(response.Extra.ContainsKey("somedata")); + Assert.Equal(nameof(PutUserAsync_ShouldSucceed), response.Extra["somedata"].ToString()); + } + + [Fact] + public async Task PutUserAsync_ShouldThrow_WhenUserDoesNotExist() + { + var ex = await Assert.ThrowsAsync(async () => + { + await _userClient.PutUserAsync( + nameof(PutUserAsync_ShouldThrow_WhenUserDoesNotExist), + new PutUserBody() + { + Extra = new Dictionary() + }); + }); + + Assert.True(ex.ApiError.Error); + Assert.Equal(HttpStatusCode.NotFound, ex.ApiError.Code); + Assert.NotNull(ex.ApiError.ErrorMessage); + Assert.Equal(1703, ex.ApiError.ErrorNum); // ERROR_USER_NOT_FOUND + } + + [Fact] + public async Task PatchUserAsync_ShouldSucceed() + { + PatchUserResponse response = await _userClient.PatchUserAsync( + _fixture.UsernameExisting, + new PatchUserBody() + { + Extra = new Dictionary() + { + ["somedata"] = nameof(PatchUserAsync_ShouldSucceed) + } + }); + + Assert.False(response.Error); + Assert.Equal(HttpStatusCode.OK, response.Code); + Assert.Equal(_fixture.UsernameExisting, response.User); + Assert.True(response.Active); + Assert.True(response.Extra.ContainsKey("somedata")); + Assert.Equal(nameof(PatchUserAsync_ShouldSucceed), response.Extra["somedata"].ToString()); + } + + [Fact] + public async Task PatchUserAsync_ShouldThrow_WhenUserDoesNotExist() + { + var ex = await Assert.ThrowsAsync(async () => + { + await _userClient.PatchUserAsync( + nameof(PatchUserAsync_ShouldThrow_WhenUserDoesNotExist), + new PatchUserBody() + { + Extra = new Dictionary() + }); + }); + + Assert.True(ex.ApiError.Error); + Assert.Equal(HttpStatusCode.NotFound, ex.ApiError.Code); + Assert.NotNull(ex.ApiError.ErrorMessage); + Assert.Equal(1703, ex.ApiError.ErrorNum); // ERROR_USER_NOT_FOUND + } + + [Fact] + public async Task GetUserAsync_ShouldSucceed() + { + GetUserResponse response = await _userClient.GetUserAsync( + _fixture.UsernameExisting); + + Assert.False(response.Error); + Assert.Equal(HttpStatusCode.OK, response.Code); + Assert.Equal(_fixture.UsernameExisting, response.User); + Assert.True(response.Active); + Assert.NotNull(response.Extra); + } + + [Fact] + public async Task GetUserAsync_ShouldThrow_WhenUserDoesNotExist() + { + var ex = await Assert.ThrowsAsync(async () => + { + await _userClient.GetUserAsync( + nameof(GetUserAsync_ShouldThrow_WhenUserDoesNotExist)); + }); + + Assert.True(ex.ApiError.Error); + Assert.Equal(HttpStatusCode.NotFound, ex.ApiError.Code); + Assert.NotNull(ex.ApiError.ErrorMessage); + Assert.Equal(1703, ex.ApiError.ErrorNum); // ERROR_USER_NOT_FOUND + } } } diff --git a/arangodb-net-standard/UserApi/IUserApiClient.cs b/arangodb-net-standard/UserApi/IUserApiClient.cs index 749728ba..447453a4 100644 --- a/arangodb-net-standard/UserApi/IUserApiClient.cs +++ b/arangodb-net-standard/UserApi/IUserApiClient.cs @@ -16,6 +16,41 @@ public interface IUserApiClient /// Task PostUserAsync(PostUserBody body); + /// + /// Replace an existing user. + /// You need server access level Administrate in order to execute this REST call. + /// Additionally, a user can change his/her own data. + /// + /// The name of the user. + /// The user information used for to replace operation. + /// + Task PutUserAsync(string username, PutUserBody body); + + /// + /// Partially update an existing user. + /// You need server access level Administrate in order to execute this REST call. + /// Additionally, a user can change his/her own data. + /// + /// The name of the user. + /// The user information used for to replace operation. + /// + Task PatchUserAsync(string username, PatchUserBody body); + + /// + /// Fetches data about the specified user. + /// You can fetch information about yourself or you need the Administrate + /// server access level in order to execute this REST call. + /// + /// The name of the user. + /// + Task GetUserAsync(string username); + + /// + /// Delete a user permanently. + /// You need Administrate for the server access level in order to execute this REST call. + /// + /// The name of the user. + /// Task DeleteUserAsync(string username); } } diff --git a/arangodb-net-standard/UserApi/Models/GetUserResponse.cs b/arangodb-net-standard/UserApi/Models/GetUserResponse.cs new file mode 100644 index 00000000..3df0b0d7 --- /dev/null +++ b/arangodb-net-standard/UserApi/Models/GetUserResponse.cs @@ -0,0 +1,9 @@ +namespace ArangoDBNetStandard.UserApi.Models +{ + /// + /// Represents a response returned after fetching a user. + /// + public class GetUserResponse : UserResponseBase + { + } +} diff --git a/arangodb-net-standard/UserApi/Models/PatchUserBody.cs b/arangodb-net-standard/UserApi/Models/PatchUserBody.cs new file mode 100644 index 00000000..abc80eaa --- /dev/null +++ b/arangodb-net-standard/UserApi/Models/PatchUserBody.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace ArangoDBNetStandard.UserApi.Models +{ + /// + /// Represents a request body to update an existing user. + /// + public class PatchUserBody + { + /// + /// The user password. Specifying a password is mandatory, + /// but the empty string is allowed for passwords. + /// + public string Passwd { get; set; } + + /// + /// An optional flag that specifies whether the user is active. + /// If not specified, this will default to true. + /// + public bool? Active { get; set; } + + /// + /// Optional object with arbitrary extra data about the user. + /// + public Dictionary Extra { get; set; } + } +} diff --git a/arangodb-net-standard/UserApi/Models/PatchUserResponse.cs b/arangodb-net-standard/UserApi/Models/PatchUserResponse.cs new file mode 100644 index 00000000..31277107 --- /dev/null +++ b/arangodb-net-standard/UserApi/Models/PatchUserResponse.cs @@ -0,0 +1,11 @@ +using System.Net; + +namespace ArangoDBNetStandard.UserApi.Models +{ + /// + /// Represents a response returned after updating an existing user. + /// + public class PatchUserResponse : UserResponseBase + { + } +} diff --git a/arangodb-net-standard/UserApi/Models/PostUserResponse.cs b/arangodb-net-standard/UserApi/Models/PostUserResponse.cs index 4f67235c..17008d9a 100644 --- a/arangodb-net-standard/UserApi/Models/PostUserResponse.cs +++ b/arangodb-net-standard/UserApi/Models/PostUserResponse.cs @@ -1,33 +1,9 @@ -using System.Collections.Generic; -using System.Net; - -namespace ArangoDBNetStandard.UserApi.Models +namespace ArangoDBNetStandard.UserApi.Models { - public class PostUserResponse + /// + /// Represents a response returned after creating a user. + /// + public class PostUserResponse : UserResponseBase { - /// - /// The name of the user. - /// - public string User { get; set; } - - /// - /// Whether the user is active. - /// - public bool Active { get; set; } - - /// - /// Object with arbitrary extra data about the user. - /// - public Dictionary Extra { get; set; } - - /// - /// Indicates whether an error occurred (false in this case). - /// - public bool Error { get; set; } - - /// - /// The HTTP status code. - /// - public HttpStatusCode Code { get; set; } } } diff --git a/arangodb-net-standard/UserApi/Models/PutUserBody.cs b/arangodb-net-standard/UserApi/Models/PutUserBody.cs new file mode 100644 index 00000000..93f8fa42 --- /dev/null +++ b/arangodb-net-standard/UserApi/Models/PutUserBody.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace ArangoDBNetStandard.UserApi.Models +{ + /// + /// Represents a request body to replace an existing user. + /// + public class PutUserBody + { + /// + /// The user password. Specifying a password is mandatory, + /// but the empty string is allowed for passwords. + /// + public string Passwd { get; set; } + + /// + /// An optional flag that specifies whether the user is active. + /// If not specified, this will default to true. + /// + public bool? Active { get; set; } + + /// + /// Optional object with arbitrary extra data about the user. + /// + public Dictionary Extra { get; set; } + } +} diff --git a/arangodb-net-standard/UserApi/Models/PutUserResponse.cs b/arangodb-net-standard/UserApi/Models/PutUserResponse.cs new file mode 100644 index 00000000..2de409a0 --- /dev/null +++ b/arangodb-net-standard/UserApi/Models/PutUserResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Net; + +namespace ArangoDBNetStandard.UserApi.Models +{ + /// + /// Represents a response returned after replacing an existing user. + /// + public class PutUserResponse : UserResponseBase + { + } +} diff --git a/arangodb-net-standard/UserApi/Models/UserResponseBase.cs b/arangodb-net-standard/UserApi/Models/UserResponseBase.cs new file mode 100644 index 00000000..3a8edcc8 --- /dev/null +++ b/arangodb-net-standard/UserApi/Models/UserResponseBase.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Net; + +namespace ArangoDBNetStandard.UserApi.Models +{ + /// + /// Represents a common response class with user information + /// returned after performing an operation. + /// + public class UserResponseBase + { + /// + /// The name of the user. + /// + public string User { get; set; } + + /// + /// Whether the user is active. + /// + public bool Active { get; set; } + + /// + /// Object with arbitrary extra data about the user. + /// + public Dictionary Extra { get; set; } + + /// + /// Indicates whether an error occurred (false in this case). + /// + public bool Error { get; set; } + + /// + /// The HTTP status code. + /// + public HttpStatusCode Code { get; set; } + } +} diff --git a/arangodb-net-standard/UserApi/UserApiClient.cs b/arangodb-net-standard/UserApi/UserApiClient.cs index 5007c553..365a1a11 100644 --- a/arangodb-net-standard/UserApi/UserApiClient.cs +++ b/arangodb-net-standard/UserApi/UserApiClient.cs @@ -53,7 +53,7 @@ public UserApiClient(IApiClientTransport client, IApiClientSerialization seriali /// ArangoDB responded with an error. /// The request body containing the user information. /// - public async Task PostUserAsync(PostUserBody body) + public virtual async Task PostUserAsync(PostUserBody body) { var content = GetContent(body, true, true); using (var response = await _client.PostAsync(_userApiPath, content)) @@ -67,6 +67,73 @@ public async Task PostUserAsync(PostUserBody body) } } + /// + /// Replace an existing user. + /// You need server access level Administrate in order to execute this REST call. + /// Additionally, a user can change his/her own data. + /// + /// The name of the user. + /// The user information used for to replace operation. + /// + public virtual async Task PutUserAsync(string username, PutUserBody body) + { + string uri = _userApiPath + '/' + username; + var content = GetContent(body, true, true); + using (var response = await _client.PutAsync(uri, content)) + { + if (response.IsSuccessStatusCode) + { + var stream = await response.Content.ReadAsStreamAsync(); + return DeserializeJsonFromStream(stream); + } + throw await GetApiErrorException(response); + } + } + + /// + /// Partially update an existing user. + /// You need server access level Administrate in order to execute this REST call. + /// Additionally, a user can change his/her own data. + /// + /// The name of the user. + /// The user information used for to replace operation. + /// + public virtual async Task PatchUserAsync(string username, PatchUserBody body) + { + string uri = _userApiPath + '/' + username; + var content = GetContent(body, true, true); + using (var response = await _client.PatchAsync(uri, content)) + { + if (response.IsSuccessStatusCode) + { + var stream = await response.Content.ReadAsStreamAsync(); + return DeserializeJsonFromStream(stream); + } + throw await GetApiErrorException(response); + } + } + + /// + /// Fetches data about the specified user. + /// You can fetch information about yourself or you need the Administrate + /// server access level in order to execute this REST call. + /// + /// The name of the user. + /// + public virtual async Task GetUserAsync(string username) + { + string uri = _userApiPath + '/' + username; + using (var response = await _client.GetAsync(uri)) + { + if (response.IsSuccessStatusCode) + { + var stream = await response.Content.ReadAsStreamAsync(); + return DeserializeJsonFromStream(stream); + } + throw await GetApiErrorException(response); + } + } + /// /// Delete a user permanently. /// You need Administrate for the server access level in order to execute this REST call.