From 9df5a86786ac2dcb8bc0f34f69501abfffd0dc9c Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 4 Nov 2019 22:58:24 +0800 Subject: Add controller primarily and of course redesign the service accordingly. --- .../Resources/Controllers/TimelineController.resx | 129 +++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Timeline/Resources/Controllers/TimelineController.resx (limited to 'Timeline/Resources/Controllers/TimelineController.resx') diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx new file mode 100644 index 00000000..420ac419 --- /dev/null +++ b/Timeline/Resources/Controllers/TimelineController.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + An unknown timeline visibility value. Can't convert it. + + + You have no permission to create posts in the timeline. + + + You have no permission to read posts of the timeline. + + \ No newline at end of file -- cgit v1.2.3 From 5f9f9a9e40306f83bf360c3d27e4e33e78565fce Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Thu, 7 Nov 2019 22:06:06 +0800 Subject: Complete PersonalTimelineController and write attribute test. --- .../Controllers/PersonalTimelineControllerTest.cs | 111 +++++++++++++++++++++ Timeline/Controllers/PersonalTimelineController.cs | 86 +++++++++------- Timeline/Filters/Timeline.cs | 47 +++++++++ Timeline/Models/Http/Timeline.cs | 21 ++++ .../Controllers/TimelineController.Designer.cs | 27 +++++ .../Resources/Controllers/TimelineController.resx | 9 ++ .../Controllers/TimelineController.zh.resx | 6 ++ Timeline/Resources/Filters.Designer.cs | 18 ++++ Timeline/Resources/Filters.resx | 6 ++ Timeline/Resources/Filters.zh.resx | 6 ++ Timeline/Resources/Services/Exception.Designer.cs | 15 ++- Timeline/Resources/Services/Exception.resx | 7 +- .../TimelineMemberOperationUserException.cs | 15 ++- Timeline/Services/TimelinePostNotExistException.cs | 23 +++++ Timeline/Services/TimelineService.cs | 62 ++++++++++-- 15 files changed, 406 insertions(+), 53 deletions(-) create mode 100644 Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs create mode 100644 Timeline/Filters/Timeline.cs create mode 100644 Timeline/Services/TimelinePostNotExistException.cs (limited to 'Timeline/Resources/Controllers/TimelineController.resx') diff --git a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs new file mode 100644 index 00000000..d5c470ee --- /dev/null +++ b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Controllers; +using Timeline.Services; +using Moq; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Timeline.Filters; +using Timeline.Tests.Helpers; +using Timeline.Models.Validation; +using System.Reflection; +using Microsoft.AspNetCore.Authorization; +using Timeline.Models.Http; + +namespace Timeline.Tests.Controllers +{ + public class PersonalTimelineControllerTest : IDisposable + { + private readonly Mock _service; + + private readonly PersonalTimelineController _controller; + + public PersonalTimelineControllerTest() + { + _service = new Mock(); + _controller = new PersonalTimelineController(NullLogger.Instance, _service.Object); + } + + public void Dispose() + { + _controller.Dispose(); + } + + [Fact] + public void AttributeTest() + { + static void AssertUsernameParameter(MethodInfo m) + { + m.GetParameter("username") + .Should().BeDecoratedWith() + .And.BeDecoratedWith(); + } + + static void AssertBodyParamter(MethodInfo m) + { + var p = m.GetParameter("body"); + p.Should().BeDecoratedWith(); + p.ParameterType.Should().Be(typeof(TBody)); + } + + var type = typeof(PersonalTimelineController); + type.Should().BeDecoratedWith(); + + { + var m = type.GetMethod(nameof(PersonalTimelineController.TimelineGet)); + m.Should().BeDecoratedWith() + .And.BeDecoratedWith(); + AssertUsernameParameter(m); + } + + { + var m = type.GetMethod(nameof(PersonalTimelineController.PostsGet)); + m.Should().BeDecoratedWith() + .And.BeDecoratedWith(); + AssertUsernameParameter(m); + } + + { + var m = type.GetMethod(nameof(PersonalTimelineController.TimelinePost)); + m.Should().BeDecoratedWith() + .And.BeDecoratedWith() + .And.BeDecoratedWith(); + AssertUsernameParameter(m); + AssertBodyParamter(m); + } + + { + var m = type.GetMethod(nameof(PersonalTimelineController.TimelinePostDelete)); + m.Should().BeDecoratedWith() + .And.BeDecoratedWith() + .And.BeDecoratedWith(); + AssertUsernameParameter(m); + AssertBodyParamter(m); + } + + { + var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeProperty)); + m.Should().BeDecoratedWith() + .And.BeDecoratedWith() + .And.BeDecoratedWith() + .And.BeDecoratedWith(); + AssertUsernameParameter(m); + AssertBodyParamter(m); + } + + { + var m = type.GetMethod(nameof(PersonalTimelineController.TimelineChangeMember)); + m.Should().BeDecoratedWith() + .And.BeDecoratedWith() + .And.BeDecoratedWith() + .And.BeDecoratedWith(); + AssertUsernameParameter(m); + AssertBodyParamter(m); + } + } + } +} diff --git a/Timeline/Controllers/PersonalTimelineController.cs b/Timeline/Controllers/PersonalTimelineController.cs index 1535a0b2..f006ad47 100644 --- a/Timeline/Controllers/PersonalTimelineController.cs +++ b/Timeline/Controllers/PersonalTimelineController.cs @@ -1,13 +1,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System; +using Microsoft.Extensions.Logging; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.Globalization; using System.Threading.Tasks; using Timeline.Auth; -using Timeline.Entities; using Timeline.Filters; using Timeline.Models; using Timeline.Models.Http; @@ -25,6 +23,7 @@ namespace Timeline { public const int PostsGetForbid = 10040101; public const int PostsCreateForbid = 10040102; + public const int MemberAddNotExist = 10040201; } } } @@ -35,6 +34,8 @@ namespace Timeline.Controllers [ApiController] public class PersonalTimelineController : Controller { + private readonly ILogger _logger; + private readonly IPersonalTimelineService _service; private bool IsAdmin() @@ -58,18 +59,21 @@ namespace Timeline.Controllers } } - public PersonalTimelineController(IPersonalTimelineService service) + public PersonalTimelineController(ILogger logger, IPersonalTimelineService service) { + _logger = logger; _service = service; } [HttpGet("users/{username}/timeline")] + [CatchTimelineNotExistException] public async Task> TimelineGet([FromRoute][Username] string username) { return await _service.GetTimeline(username); } [HttpGet("users/{username}/timeline/posts")] + [CatchTimelineNotExistException] public async Task>> PostsGet([FromRoute][Username] string username) { if (!IsAdmin() && !await _service.HasReadPermission(username, GetAuthUsername())) @@ -81,9 +85,10 @@ namespace Timeline.Controllers return await _service.GetPosts(username); } - [HttpPost("user/{username}/timeline/posts/create")] + [HttpPost("user/{username}/timeline/postop/create")] [Authorize] - public async Task PostsCreate([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body) + [CatchTimelineNotExistException] + public async Task> TimelinePost([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body) { if (!IsAdmin() && !await _service.IsMemberOf(username, GetAuthUsername()!)) { @@ -91,51 +96,62 @@ namespace Timeline.Controllers new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid)); } - await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time); - return Ok(); + var res = await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time); + return res; } - [HttpPut("user/{username}/timeline/description")] + [HttpPost("user/{username}/timeline/postop/delete")] [Authorize] - [SelfOrAdmin] - public async Task TimelinePutDescription([FromRoute][Username] string username, [FromBody] string body) - { - await _service.SetDescription(username, body); - return Ok(); - } - - private static TimelineVisibility StringToVisibility(string s) + [CatchTimelineNotExistException] + public async Task TimelinePostDelete([FromRoute][Username] string username, [FromBody] TimelinePostDeleteRequest body) { - if ("public".Equals(s, StringComparison.InvariantCultureIgnoreCase)) + var postId = body.Id!.Value; + if (!IsAdmin() && !await _service.HasPostModifyPermission(username, postId, GetAuthUsername()!)) { - return TimelineVisibility.Public; - } - else if ("register".Equals(s, StringComparison.InvariantCultureIgnoreCase)) - { - return TimelineVisibility.Register; - } - else if ("private".Equals(s, StringComparison.InvariantCultureIgnoreCase)) - { - return TimelineVisibility.Private; + return StatusCode(StatusCodes.Status403Forbidden, + new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid)); } - throw new ArgumentException(ExceptionStringToVisibility); + await _service.DeletePost(username, postId); + return Ok(); } - [HttpPut("user/{username}/timeline/visibility")] + [HttpPost("user/{username}/timeline/op/property")] [Authorize] [SelfOrAdmin] - public async Task TimelinePutVisibility([FromRoute][Username] string username, [FromBody][RegularExpression("public|register|private")] string body) + [CatchTimelineNotExistException] + public async Task TimelineChangeProperty([FromRoute][Username] string username, [FromBody] TimelinePropertyChangeRequest body) { - await _service.SetVisibility(username, StringToVisibility(body)); + await _service.ChangeProperty(username, body); return Ok(); } - [HttpPost("user/{username}/timeline/members/change")] + [HttpPost("user/{username}/timeline/op/member")] [Authorize] [SelfOrAdmin] - public async Task TimelineMembersChange([FromRoute][Username] string username, [FromBody] TimelineMemberChangeRequest body) + [CatchTimelineNotExistException] + public async Task TimelineChangeMember([FromRoute][Username] string username, [FromBody] TimelineMemberChangeRequest body) { - //TODO! + try + { + await _service.ChangeMember(username, body.Add, body.Remove); + return Ok(); + } + catch (TimelineMemberOperationUserException e) + { + if (e.InnerException is UsernameBadFormatException) + { + return BadRequest(CommonResponse.InvalidModel( + string.Format(CultureInfo.CurrentCulture, MessageMemberUsernameBadFormat, e.Index, e.Operation))); + } + else if (e.InnerException is UserNotExistException) + { + return BadRequest(new CommonResponse(ErrorCodes.Http.Timeline.MemberAddNotExist, + string.Format(CultureInfo.CurrentCulture, MessageMemberUserNotExist, e.Index, e.Operation))); + } + + _logger.LogError(e, LogUnknownTimelineMemberOperationUserException); + throw; + } } } } diff --git a/Timeline/Filters/Timeline.cs b/Timeline/Filters/Timeline.cs new file mode 100644 index 00000000..7859d409 --- /dev/null +++ b/Timeline/Filters/Timeline.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Timeline.Models.Http; +using Timeline.Services; +using static Timeline.Resources.Filters; + +namespace Timeline +{ + public static partial class ErrorCodes + { + public static partial class Http + { + public static partial class Filter // bxx = 1xx + { + public static class Timeline // bbb = 102 + { + public const int UserNotExist = 11020101; + public const int NameNotExist = 11020102; + } + } + } + } +} + +namespace Timeline.Filters +{ + public class CatchTimelineNotExistExceptionAttribute : ExceptionFilterAttribute + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")] + public override void OnException(ExceptionContext context) + { + if (context.Exception is TimelineNotExistException e) + { + if (e.InnerException is UserNotExistException) + { + context.Result = new BadRequestObjectResult( + new CommonResponse(ErrorCodes.Http.Filter.Timeline.UserNotExist, MessageTimelineNotExistUser)); + } + else + { + context.Result = new BadRequestObjectResult( + new CommonResponse(ErrorCodes.Http.Filter.Timeline.NameNotExist, MessageTimelineNotExist)); + } + } + } + } +} diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs index 37de9e58..f676afa0 100644 --- a/Timeline/Models/Http/Timeline.cs +++ b/Timeline/Models/Http/Timeline.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; +using Timeline.Entities; namespace Timeline.Models.Http { @@ -14,6 +15,26 @@ namespace Timeline.Models.Http public DateTime? Time { get; set; } } + public class TimelinePostCreateResponse + { + public long Id { get; set; } + + public DateTime Time { get; set; } + } + + public class TimelinePostDeleteRequest + { + [Required] + public long? Id { get; set; } + } + + public class TimelinePropertyChangeRequest + { + public string? Description { get; set; } + + public TimelineVisibility? Visibility { get; set; } + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")] public class TimelineMemberChangeRequest { diff --git a/Timeline/Resources/Controllers/TimelineController.Designer.cs b/Timeline/Resources/Controllers/TimelineController.Designer.cs index 1e56f651..5a4209c3 100644 --- a/Timeline/Resources/Controllers/TimelineController.Designer.cs +++ b/Timeline/Resources/Controllers/TimelineController.Designer.cs @@ -69,6 +69,33 @@ namespace Timeline.Resources.Controllers { } } + /// + /// Looks up a localized string similar to An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown.. + /// + internal static string LogUnknownTimelineMemberOperationUserException { + get { + return ResourceManager.GetString("LogUnknownTimelineMemberOperationUserException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0}-st username to do operation {1} on is of bad format.. + /// + internal static string MessageMemberUsernameBadFormat { + get { + return ResourceManager.GetString("MessageMemberUsernameBadFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0}-st user to do operation {1} on does not exist.. + /// + internal static string MessageMemberUserNotExist { + get { + return ResourceManager.GetString("MessageMemberUserNotExist", resourceCulture); + } + } + /// /// Looks up a localized string similar to You have no permission to create posts in the timeline.. /// diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx index 420ac419..7e323164 100644 --- a/Timeline/Resources/Controllers/TimelineController.resx +++ b/Timeline/Resources/Controllers/TimelineController.resx @@ -120,6 +120,15 @@ An unknown timeline visibility value. Can't convert it. + + An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown. + + + The {0}-st username to do operation {1} on is of bad format. + + + The {0}-st user to do operation {1} on does not exist. + You have no permission to create posts in the timeline. diff --git a/Timeline/Resources/Controllers/TimelineController.zh.resx b/Timeline/Resources/Controllers/TimelineController.zh.resx index e22f44fa..cacce5fa 100644 --- a/Timeline/Resources/Controllers/TimelineController.zh.resx +++ b/Timeline/Resources/Controllers/TimelineController.zh.resx @@ -117,6 +117,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 第{0}个做{1}操作的用户名格式错误。 + + + 第{0}个做{1}操作的用户不存在。 + 你没有权限在这个时间线中创建消息。 diff --git a/Timeline/Resources/Filters.Designer.cs b/Timeline/Resources/Filters.Designer.cs index 3481e4ae..5576190d 100644 --- a/Timeline/Resources/Filters.Designer.cs +++ b/Timeline/Resources/Filters.Designer.cs @@ -123,6 +123,24 @@ namespace Timeline.Resources { } } + /// + /// Looks up a localized string similar to The requested timeline does not exist.. + /// + internal static string MessageTimelineNotExist { + get { + return ResourceManager.GetString("MessageTimelineNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The requested personal timeline does not exist because the user does not exist.. + /// + internal static string MessageTimelineNotExistUser { + get { + return ResourceManager.GetString("MessageTimelineNotExistUser", resourceCulture); + } + } + /// /// Looks up a localized string similar to The user does not exist.. /// diff --git a/Timeline/Resources/Filters.resx b/Timeline/Resources/Filters.resx index b91d4612..7bfbc703 100644 --- a/Timeline/Resources/Filters.resx +++ b/Timeline/Resources/Filters.resx @@ -138,6 +138,12 @@ You can't access the resource unless you are the owner or administrator. + + The requested timeline does not exist. + + + The requested personal timeline does not exist because the user does not exist. + The user does not exist. diff --git a/Timeline/Resources/Filters.zh.resx b/Timeline/Resources/Filters.zh.resx index 159ac04a..36aac788 100644 --- a/Timeline/Resources/Filters.zh.resx +++ b/Timeline/Resources/Filters.zh.resx @@ -129,6 +129,12 @@ 你无权访问该资源除非你是资源的拥有者或者管理员。 + + 请求的时间线不存在。 + + + 请求的个人时间线不存在因为该用户不存在。 + 用户不存在。 diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs index 1f6493cb..970c306d 100644 --- a/Timeline/Resources/Services/Exception.Designer.cs +++ b/Timeline/Resources/Services/Exception.Designer.cs @@ -286,11 +286,11 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to An exception happened when operating on the {} member on timeline.. + /// Looks up a localized string similar to An exception happened when do operation {} on the {} member on timeline.. /// - internal static string TimelineMemberOperationExceptionIndex { + internal static string TimelineMemberOperationExceptionDetail { get { - return ResourceManager.GetString("TimelineMemberOperationExceptionIndex", resourceCulture); + return ResourceManager.GetString("TimelineMemberOperationExceptionDetail", resourceCulture); } } @@ -312,6 +312,15 @@ namespace Timeline.Resources.Services { } } + /// + /// Looks up a localized string similar to The timeline post does not exist. You can't do operation on it.. + /// + internal static string TimelinePostNotExistException { + get { + return ResourceManager.GetString("TimelinePostNotExistException", resourceCulture); + } + } + /// /// Looks up a localized string similar to The use is not a member of the timeline.. /// diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx index 3e9d3747..c8f6676a 100644 --- a/Timeline/Resources/Services/Exception.resx +++ b/Timeline/Resources/Services/Exception.resx @@ -192,8 +192,8 @@ An exception happened when add or remove member on timeline. - - An exception happened when operating on the {} member on timeline. + + An exception happened when do operation {} on the {} member on timeline. Timeline name is of bad format. If this is a personal timeline, it means the username is of bad format and inner exception should be a UsernameBadFormatException. @@ -201,6 +201,9 @@ Timeline does not exist. If this is a personal timeline, it means the user does not exist and inner exception should be a UserNotExistException. + + The timeline post does not exist. You can't do operation on it. + The use is not a member of the timeline. diff --git a/Timeline/Services/TimelineMemberOperationUserException.cs b/Timeline/Services/TimelineMemberOperationUserException.cs index 998f1a6e..543ee160 100644 --- a/Timeline/Services/TimelineMemberOperationUserException.cs +++ b/Timeline/Services/TimelineMemberOperationUserException.cs @@ -6,6 +6,12 @@ namespace Timeline.Services [Serializable] public class TimelineMemberOperationUserException : Exception { + public enum MemberOperation + { + Add, + Remove + } + public TimelineMemberOperationUserException() : base(Resources.Services.Exception.TimelineMemberOperationException) { } public TimelineMemberOperationUserException(string message) : base(message) { } public TimelineMemberOperationUserException(string message, Exception inner) : base(message, inner) { } @@ -13,10 +19,13 @@ namespace Timeline.Services System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - public TimelineMemberOperationUserException(int index, string username, Exception inner) : base(MakeIndexMessage(index), inner) { Index = index; Username = username; } + public TimelineMemberOperationUserException(int index, MemberOperation operation, string username, Exception inner) + : base(MakeMessage(operation, index), inner) { Operation = operation; Index = index; Username = username; } + + private static string MakeMessage(MemberOperation operation, int index) => string.Format(CultureInfo.CurrentCulture, + Resources.Services.Exception.TimelineMemberOperationExceptionDetail, operation, index); - private static string MakeIndexMessage(int index) => string.Format(CultureInfo.CurrentCulture, - Resources.Services.Exception.TimelineMemberOperationExceptionIndex, index); + public MemberOperation? Operation { get; set; } /// /// The index of the member on which the operation failed. diff --git a/Timeline/Services/TimelinePostNotExistException.cs b/Timeline/Services/TimelinePostNotExistException.cs new file mode 100644 index 00000000..97e5d550 --- /dev/null +++ b/Timeline/Services/TimelinePostNotExistException.cs @@ -0,0 +1,23 @@ +using System; + +namespace Timeline.Services +{ + [Serializable] + public class TimelinePostNotExistException : Exception + { + public TimelinePostNotExistException() { } + public TimelinePostNotExistException(string message) : base(message) { } + public TimelinePostNotExistException(string message, Exception inner) : base(message, inner) { } + protected TimelinePostNotExistException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + public TimelinePostNotExistException(long id) : base(Resources.Services.Exception.TimelinePostNotExistException) { Id = id; } + + public TimelinePostNotExistException(long id, string message) : base(message) { Id = id; } + + public TimelinePostNotExistException(long id, string message, Exception inner) : base(message, inner) { Id = id; } + + public long Id { get; set; } + } +} diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 7fe32cac..28b1f91d 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Models; +using Timeline.Models.Http; namespace Timeline.Services { @@ -45,7 +46,7 @@ namespace Timeline.Services /// The author's username. /// The content. /// The time of the post. If null, then use current time. - /// + /// The info of the created post. /// Thrown when or or is null. /// /// Thrown when timeline name is of bad format. @@ -61,14 +62,14 @@ namespace Timeline.Services /// /// Thrown if is of bad format. /// Thrown if does not exist. - Task CreatePost(string name, string author, string content, DateTime? time); + Task CreatePost(string name, string author, string content, DateTime? time); /// - /// Set the visibility permission of a timeline. + /// Delete a post /// /// Username or the timeline name. See remarks of . - /// The new visibility. - /// Thrown when is null. + /// The id of the post to delete. + /// Thrown when or is null. /// /// Thrown when timeline name is of bad format. /// For normal timeline, it means name is an empty string. @@ -81,14 +82,21 @@ namespace Timeline.Services /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - Task SetVisibility(string name, TimelineVisibility visibility); + /// + /// Thrown when the post with given id does not exist or is deleted already. + /// + /// + /// First use + /// to check the permission. + /// + Task DeletePost(string name, long id); /// - /// Set the description of a timeline. + /// Set the properties of a timeline. /// /// Username or the timeline name. See remarks of . - /// The new description. - /// Thrown when or is null. + /// The new properties. Null member means not to change. + /// Thrown when or is null. /// /// Thrown when timeline name is of bad format. /// For normal timeline, it means name is an empty string. @@ -101,7 +109,7 @@ namespace Timeline.Services /// For personal timeline, it means the user of that username does not exist /// and the inner exception should be a . /// - Task SetDescription(string name, string description); + Task ChangeProperty(string name, TimelinePropertyChangeRequest newProperties); /// /// Remove members to a timeline. @@ -159,6 +167,40 @@ namespace Timeline.Services /// True if can read, false if can't read. Task HasReadPermission(string name, string? username); + /// + /// Verify whether a user has the permission to modify a post. + /// + /// Username or the timeline name. See remarks of . + /// The user to check on. + /// True if can modify, false if can't modify. + /// Thrown when or is null. + /// + /// Thrown when timeline name is of bad format. + /// For normal timeline, it means name is an empty string. + /// For personal timeline, it means the username is of bad format, + /// the inner exception should be a . + /// + /// + /// Thrown when timeline does not exist. + /// For normal timeline, it means the name does not exist. + /// For personal timeline, it means the user of that username does not exist + /// and the inner exception should be a . + /// + /// + /// Thrown when the post with given id does not exist or is deleted already. + /// + /// + /// Thrown when is of bad format. + /// + /// + /// Thrown when does not exist. + /// + /// + /// This method does not check whether the user is administrator. + /// It only checks whether he is the author of the post or the owner of the timeline. + /// + Task HasPostModifyPermission(string name, long id, string username); + /// /// Verify whether a user is member of a timeline. /// -- cgit v1.2.3 From c348cd3972aa30eb3f09ae61ba99374527a01af8 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Tue, 12 Nov 2019 20:09:41 +0800 Subject: Complete personal timeline controller unit tests. --- .../Controllers/PersonalTimelineControllerTest.cs | 153 ++++++++++++++++++++- Timeline/Controllers/PersonalTimelineController.cs | 25 +++- .../Controllers/TimelineController.Designer.cs | 28 +++- .../Resources/Controllers/TimelineController.resx | 12 +- .../Controllers/TimelineController.zh.resx | 12 +- 5 files changed, 209 insertions(+), 21 deletions(-) (limited to 'Timeline/Resources/Controllers/TimelineController.resx') diff --git a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs index 27b37f94..aecd10af 100644 --- a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs +++ b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs @@ -143,7 +143,7 @@ namespace Timeline.Tests.Controllers .Which; result.StatusCode.Should().Be(StatusCodes.Status403Forbidden); result.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostsGetForbid); + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostListGetForbid); _service.VerifyAll(); } @@ -186,7 +186,7 @@ namespace Timeline.Tests.Controllers })).Result.Should().NotBeNull().And.BeAssignableTo().Which; result.StatusCode.Should().Be(StatusCodes.Status403Forbidden); result.Value.Should().BeAssignableTo() - .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostsCreateForbid); + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationCreateForbid); _service.VerifyAll(); } @@ -237,6 +237,153 @@ namespace Timeline.Tests.Controllers _service.VerifyAll(); } - //TODO! Write all the other tests. + [Fact] + public async Task PostOperationDelete_Forbid() + { + const string username = "username"; + const long postId = 2; + SetUser(false); + _service.Setup(s => s.HasPostModifyPermission(username, postId, authUsername)).ReturnsAsync(false); + var result = (await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest + { + Id = postId + })).Should().NotBeNull().And.BeAssignableTo().Which; + result.StatusCode.Should().Be(StatusCodes.Status403Forbidden); + result.Value.Should().BeAssignableTo() + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationDeleteForbid); + _service.VerifyAll(); + } + + [Fact] + public async Task PostOperationDelete_NotExist() + { + const string username = "username"; + const long postId = 2; + SetUser(true); + _service.Setup(s => s.DeletePost(username, postId)).ThrowsAsync(new TimelinePostNotExistException()); + var result = (await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest + { + Id = postId + })).Should().NotBeNull().And.BeAssignableTo().Which; + result.StatusCode.Should().Be(StatusCodes.Status400BadRequest); + result.Value.Should().BeAssignableTo() + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.PostOperationDeleteNotExist); + _service.VerifyAll(); + } + + [Fact] + public async Task PostOperationDelete_Admin_Success() + { + const string username = "username"; + const long postId = 2; + SetUser(true); + _service.Setup(s => s.DeletePost(username, postId)).Returns(Task.CompletedTask); + var result = await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest + { + Id = postId + }); + result.Should().NotBeNull().And.BeAssignableTo(); + _service.VerifyAll(); + } + + [Fact] + public async Task PostOperationDelete_User_Success() + { + const string username = "username"; + const long postId = 2; + SetUser(false); + _service.Setup(s => s.DeletePost(username, postId)).Returns(Task.CompletedTask); + _service.Setup(s => s.HasPostModifyPermission(username, postId, authUsername)).ReturnsAsync(true); + var result = await _controller.PostOperationDelete(username, new TimelinePostDeleteRequest + { + Id = postId + }); + result.Should().NotBeNull().And.BeAssignableTo(); + _service.VerifyAll(); + } + + [Fact] + public async Task TimelineChangeProperty_Success() + { + const string username = "username"; + var req = new TimelinePropertyChangeRequest + { + Description = "", + Visibility = Entities.TimelineVisibility.Private + }; + _service.Setup(s => s.ChangeProperty(username, req)).Returns(Task.CompletedTask); + var result = await _controller.TimelineChangeProperty(username, req); + result.Should().NotBeNull().And.BeAssignableTo(); + _service.VerifyAll(); + } + + [Fact] + public async Task TimelineChangeMember_Success() + { + const string username = "username"; + var add = new List { "aaa" }; + var remove = new List { "rrr" }; + _service.Setup(s => s.ChangeMember(username, add, remove)).Returns(Task.CompletedTask); + var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest + { + Add = add, + Remove = remove + }); + result.Should().NotBeNull().And.BeAssignableTo(); + _service.VerifyAll(); + } + + [Fact] + public async Task TimelineChangeMember_UsernameBadFormat() + { + const string username = "username"; + var add = new List { "aaa" }; + var remove = new List { "rrr" }; + _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync( + new TimelineMemberOperationUserException("test", new UsernameBadFormatException())); + var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest + { + Add = add, + Remove = remove + }); + result.Should().NotBeNull().And.BeAssignableTo() + .Which.Value.Should().BeAssignableTo() + .Which.Code.Should().Be(ErrorCodes.Http.Common.InvalidModel); + _service.VerifyAll(); + } + + [Fact] + public async Task TimelineChangeMember_AddNotExist() + { + const string username = "username"; + var add = new List { "aaa" }; + var remove = new List { "rrr" }; + _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync( + new TimelineMemberOperationUserException("test", new UserNotExistException())); + var result = await _controller.TimelineChangeMember(username, new TimelineMemberChangeRequest + { + Add = add, + Remove = remove + }); + result.Should().NotBeNull().And.BeAssignableTo() + .Which.Value.Should().BeAssignableTo() + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.MemberAddNotExist); + _service.VerifyAll(); + } + + [Fact] + public async Task TimelineChangeMember_UnknownTimelineMemberOperationUserException() + { + const string username = "username"; + var add = new List { "aaa" }; + var remove = new List { "rrr" }; + _service.Setup(s => s.ChangeMember(username, add, remove)).ThrowsAsync( + new TimelineMemberOperationUserException("test", null)); + await _controller.Awaiting(c => c.TimelineChangeMember(username, new TimelineMemberChangeRequest + { + Add = add, + Remove = remove + })).Should().ThrowAsync(); // Should rethrow. + } } } diff --git a/Timeline/Controllers/PersonalTimelineController.cs b/Timeline/Controllers/PersonalTimelineController.cs index f41e354b..f0f4e4c2 100644 --- a/Timeline/Controllers/PersonalTimelineController.cs +++ b/Timeline/Controllers/PersonalTimelineController.cs @@ -21,9 +21,11 @@ namespace Timeline { public static class Timeline // ccc = 004 { - public const int PostsGetForbid = 10040101; - public const int PostsCreateForbid = 10040102; - public const int MemberAddNotExist = 10040201; + public const int PostListGetForbid = 10040101; + public const int PostOperationCreateForbid = 10040102; + public const int PostOperationDeleteForbid = 10040103; + public const int PostOperationDeleteNotExist = 10040201; + public const int MemberAddNotExist = 10040301; } } } @@ -79,7 +81,7 @@ namespace Timeline.Controllers if (!IsAdmin() && !await _service.HasReadPermission(username, GetAuthUsername())) { return StatusCode(StatusCodes.Status403Forbidden, - new CommonResponse(ErrorCodes.Http.Timeline.PostsGetForbid, MessagePostsGetForbid)); + new CommonResponse(ErrorCodes.Http.Timeline.PostListGetForbid, MessagePostListGetForbid)); } return await _service.GetPosts(username); @@ -93,7 +95,7 @@ namespace Timeline.Controllers if (!IsAdmin() && !await _service.IsMemberOf(username, GetAuthUsername()!)) { return StatusCode(StatusCodes.Status403Forbidden, - new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid)); + new CommonResponse(ErrorCodes.Http.Timeline.PostOperationCreateForbid, MessagePostOperationCreateForbid)); } var res = await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time); @@ -109,9 +111,18 @@ namespace Timeline.Controllers if (!IsAdmin() && !await _service.HasPostModifyPermission(username, postId, GetAuthUsername()!)) { return StatusCode(StatusCodes.Status403Forbidden, - new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid)); + new CommonResponse(ErrorCodes.Http.Timeline.PostOperationDeleteForbid, MessagePostOperationCreateForbid)); + } + try + { + await _service.DeletePost(username, postId); + } + catch (TimelinePostNotExistException) + { + return BadRequest(new CommonResponse( + ErrorCodes.Http.Timeline.PostOperationDeleteNotExist, + MessagePostOperationDeleteNotExist)); } - await _service.DeletePost(username, postId); return Ok(); } diff --git a/Timeline/Resources/Controllers/TimelineController.Designer.cs b/Timeline/Resources/Controllers/TimelineController.Designer.cs index 5a4209c3..47c43fa2 100644 --- a/Timeline/Resources/Controllers/TimelineController.Designer.cs +++ b/Timeline/Resources/Controllers/TimelineController.Designer.cs @@ -96,21 +96,39 @@ namespace Timeline.Resources.Controllers { } } + /// + /// Looks up a localized string similar to You have no permission to read posts of the timeline.. + /// + internal static string MessagePostListGetForbid { + get { + return ResourceManager.GetString("MessagePostListGetForbid", resourceCulture); + } + } + /// /// Looks up a localized string similar to You have no permission to create posts in the timeline.. /// - internal static string MessagePostsCreateForbid { + internal static string MessagePostOperationCreateForbid { get { - return ResourceManager.GetString("MessagePostsCreateForbid", resourceCulture); + return ResourceManager.GetString("MessagePostOperationCreateForbid", resourceCulture); } } /// - /// Looks up a localized string similar to You have no permission to read posts of the timeline.. + /// Looks up a localized string similar to You have no permission to delete posts in the timeline.. + /// + internal static string MessagePostOperationDeleteForbid { + get { + return ResourceManager.GetString("MessagePostOperationDeleteForbid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The post to delete does not exist.. /// - internal static string MessagePostsGetForbid { + internal static string MessagePostOperationDeleteNotExist { get { - return ResourceManager.GetString("MessagePostsGetForbid", resourceCulture); + return ResourceManager.GetString("MessagePostOperationDeleteNotExist", resourceCulture); } } } diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx index 7e323164..0cf7e881 100644 --- a/Timeline/Resources/Controllers/TimelineController.resx +++ b/Timeline/Resources/Controllers/TimelineController.resx @@ -129,10 +129,16 @@ The {0}-st user to do operation {1} on does not exist. - + + You have no permission to read posts of the timeline. + + You have no permission to create posts in the timeline. - - You have no permission to read posts of the timeline. + + You have no permission to delete posts in the timeline. + + + The post to delete does not exist. \ No newline at end of file diff --git a/Timeline/Resources/Controllers/TimelineController.zh.resx b/Timeline/Resources/Controllers/TimelineController.zh.resx index cacce5fa..170ab4cd 100644 --- a/Timeline/Resources/Controllers/TimelineController.zh.resx +++ b/Timeline/Resources/Controllers/TimelineController.zh.resx @@ -123,10 +123,16 @@ 第{0}个做{1}操作的用户不存在。 - + + 你没有权限读取这个时间线消息。 + + 你没有权限在这个时间线中创建消息。 - - 你没有权限读取这个时间线消息。 + + 你没有权限在这个时间线中删除消息。 + + + 要删除的消息不存在。 \ No newline at end of file -- cgit v1.2.3