From 640b80e501ef7f6240afdd968de4b23cd57f2ae1 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sun, 3 Nov 2019 16:51:10 +0800 Subject: WIP: Design the timeline service interface. --- Timeline/Models/Timeline.cs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Timeline/Models/Timeline.cs (limited to 'Timeline/Models/Timeline.cs') diff --git a/Timeline/Models/Timeline.cs b/Timeline/Models/Timeline.cs new file mode 100644 index 00000000..26012878 --- /dev/null +++ b/Timeline/Models/Timeline.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Models +{ + public class TimelinePostInfo + { + public long Id { get; set; } + + public string? Content { get; set; } + + public DateTime Time { get; set; } + + /// + /// The username of the author. + /// + public string Author { get; set; } = default!; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")] + public class BaseTimelineInfo + { + public string? Description { get; set; } + + /// + /// The username of the owner. + /// + public string Owner { get; set; } = default!; + + public TimelineVisibility Visibility { get; set; } + + public List Members { get; set; } = default!; + } + + public class TimelineInfo : BaseTimelineInfo + { + public string Name { get; set; } = default!; + } +} -- cgit v1.2.3 From 468d7f4c416d4469375b7170beb5e388737c0970 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 18 Nov 2019 19:29:37 +0800 Subject: Write tests and fix bugs found via tests. --- .../Controllers/PersonalTimelineControllerTest.cs | 2 +- Timeline.Tests/Helpers/ResponseAssertions.cs | 2 +- .../IntegratedTests/PersonalTimelineTest.cs | 135 +++++++++++++++++++++ Timeline/Entities/TimelineEntity.cs | 17 +-- Timeline/Models/Timeline.cs | 18 ++- Timeline/Resources/Services/Exception.Designer.cs | 2 +- Timeline/Resources/Services/Exception.resx | 2 +- Timeline/Services/TimelineService.cs | 2 +- 8 files changed, 157 insertions(+), 23 deletions(-) (limited to 'Timeline/Models/Timeline.cs') diff --git a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs index a7cbb37e..819017c2 100644 --- a/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs +++ b/Timeline.Tests/Controllers/PersonalTimelineControllerTest.cs @@ -309,7 +309,7 @@ namespace Timeline.Tests.Controllers var req = new TimelinePropertyChangeRequest { Description = "", - Visibility = Entities.TimelineVisibility.Private + Visibility = TimelineVisibility.Private }; _service.Setup(s => s.ChangeProperty(username, req)).Returns(Task.CompletedTask); var result = await _controller.TimelineChangeProperty(username, req); diff --git a/Timeline.Tests/Helpers/ResponseAssertions.cs b/Timeline.Tests/Helpers/ResponseAssertions.cs index 0e6f215b..6d764c68 100644 --- a/Timeline.Tests/Helpers/ResponseAssertions.cs +++ b/Timeline.Tests/Helpers/ResponseAssertions.cs @@ -88,7 +88,7 @@ namespace Timeline.Tests.Helpers return new AndWhichConstraint(this, null); } - var result = JsonConvert.DeserializeObject(body); + var result = JsonConvert.DeserializeObject(body); // TODO! catch and throw on bad format return new AndWhichConstraint(this, result); } } diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs index 9629fc0a..aaa6215c 100644 --- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using Timeline.Models; using Timeline.Models.Http; using Timeline.Tests.Helpers; using Timeline.Tests.Helpers.Authentication; @@ -22,6 +23,64 @@ namespace Timeline.Tests.IntegratedTests } + [Fact] + public async Task Member_Should_Work() + { + const string getUrl = "users/user/timeline"; + const string changeUrl = "users/user/timeline/op/member"; + using var client = await Factory.CreateClientAsUser(); + + async Task AssertMembers(IList members) + { + var res = await client.GetAsync(getUrl); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Members.Should().NotBeNull().And.BeEquivalentTo(members); + } + + async Task AssertEmptyMembers() + { + var res = await client.GetAsync(getUrl); + res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Members.Should().NotBeNull().And.BeEmpty(); + } + + await AssertEmptyMembers(); + { + var res = await client.PostAsJsonAsync(changeUrl, + new TimelineMemberChangeRequest { Add = new List { "admin", "usernotexist" } }); + res.Should().HaveStatusCode(400) + .And.HaveCommonBody() + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.ChangeMemberUserNotExist); + } + { + var res = await client.PostAsJsonAsync(changeUrl, + new TimelineMemberChangeRequest { Remove = new List { "admin", "usernotexist" } }); + res.Should().HaveStatusCode(400) + .And.HaveCommonBody() + .Which.Code.Should().Be(ErrorCodes.Http.Timeline.ChangeMemberUserNotExist); + } + { + var res = await client.PostAsJsonAsync(changeUrl, + new TimelineMemberChangeRequest { Add = new List { "admin" }, Remove = new List { "admin" } }); + res.Should().HaveStatusCode(200); + await AssertEmptyMembers(); + } + { + var res = await client.PostAsJsonAsync(changeUrl, + new TimelineMemberChangeRequest { Add = new List { "admin" } }); + res.Should().HaveStatusCode(200); + await AssertMembers(new List { "admin" }); + } + { + var res = await client.PostAsJsonAsync(changeUrl, + new TimelineMemberChangeRequest { Remove = new List { "admin" } }); + res.Should().HaveStatusCode(200); + await AssertEmptyMembers(); + } + } + [Theory] [InlineData(AuthType.None, 200, 401, 401, 401, 401)] [InlineData(AuthType.User, 200, 200, 403, 200, 403)] @@ -58,5 +117,81 @@ namespace Timeline.Tests.IntegratedTests res.Should().HaveStatusCode(opMemberAdmin); } } + + [Fact] + public async Task Permission_GetPost() + { + const string userUrl = "users/user/timeline/posts"; + const string adminUrl = "users/admin/timeline/posts"; + { // default visibility is registered + { + using var client = Factory.CreateDefaultClient(); + var res = await client.GetAsync(userUrl); + res.Should().HaveStatusCode(403); + } + + { + using var client = await Factory.CreateClientAsUser(); + var res = await client.GetAsync(adminUrl); + res.Should().HaveStatusCode(200); + } + } + + { // change visibility to public + { + using var client = await Factory.CreateClientAsUser(); + var res = await client.PostAsJsonAsync("users/user/timeline/op/property", + new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Public }); + res.Should().HaveStatusCode(200); + } + { + using var client = Factory.CreateDefaultClient(); + var res = await client.GetAsync(userUrl); + res.Should().HaveStatusCode(200); + } + } + + { // change visibility to private + { + using var client = await Factory.CreateClientAsAdmin(); + { + var res = await client.PostAsJsonAsync("users/user/timeline/op/property", + new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Private }); + res.Should().HaveStatusCode(200); + } + { + var res = await client.PostAsJsonAsync("users/admin/timeline/op/property", + new TimelinePropertyChangeRequest { Visibility = TimelineVisibility.Private }); + res.Should().HaveStatusCode(200); + } + } + { + using var client = Factory.CreateDefaultClient(); + var res = await client.GetAsync(userUrl); + res.Should().HaveStatusCode(403); + } + { // user can't read admin's + using var client = await Factory.CreateClientAsUser(); + var res = await client.GetAsync(adminUrl); + res.Should().HaveStatusCode(403); + } + { // admin can read user's + using var client = await Factory.CreateClientAsAdmin(); + var res = await client.GetAsync(userUrl); + res.Should().HaveStatusCode(200); + } + { // add member + using var client = await Factory.CreateClientAsAdmin(); + var res = await client.PostAsJsonAsync("users/admin/timeline/op/member", + new TimelineMemberChangeRequest { Add = new List { "user" } }); + res.Should().HaveStatusCode(200); + } + { // now user can read admin's + using var client = await Factory.CreateClientAsUser(); + var res = await client.GetAsync(adminUrl); + res.Should().HaveStatusCode(200); + } + } + } } } diff --git a/Timeline/Entities/TimelineEntity.cs b/Timeline/Entities/TimelineEntity.cs index f5e22a54..9cacfcae 100644 --- a/Timeline/Entities/TimelineEntity.cs +++ b/Timeline/Entities/TimelineEntity.cs @@ -2,25 +2,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Timeline.Models; namespace Timeline.Entities { - public enum TimelineVisibility - { - /// - /// All people including those without accounts. - /// - Public, - /// - /// Only people signed in. - /// - Register, - /// - /// Only member. - /// - Private - } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is entity object.")] [Table("timelines")] public class TimelineEntity diff --git a/Timeline/Models/Timeline.cs b/Timeline/Models/Timeline.cs index 26012878..85fefff5 100644 --- a/Timeline/Models/Timeline.cs +++ b/Timeline/Models/Timeline.cs @@ -1,11 +1,25 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Timeline.Entities; namespace Timeline.Models { + public enum TimelineVisibility + { + /// + /// All people including those without accounts. + /// + Public, + /// + /// Only people signed in. + /// + Register, + /// + /// Only member. + /// + Private + } + public class TimelinePostInfo { public long Id { get; set; } diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs index 970c306d..1b46f9e9 100644 --- a/Timeline/Resources/Services/Exception.Designer.cs +++ b/Timeline/Resources/Services/Exception.Designer.cs @@ -286,7 +286,7 @@ namespace Timeline.Resources.Services { } /// - /// Looks up a localized string similar to An exception happened when do operation {} on the {} member on timeline.. + /// Looks up a localized string similar to An exception happened when do operation {0} on the {1} member on timeline.. /// internal static string TimelineMemberOperationExceptionDetail { get { diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx index c8f6676a..1d9c0037 100644 --- a/Timeline/Resources/Services/Exception.resx +++ b/Timeline/Resources/Services/Exception.resx @@ -193,7 +193,7 @@ An exception happened when add or remove member on timeline. - An exception happened when do operation {} on the {} member on timeline. + An exception happened when do operation {0} on the {1} 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. diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 494beb11..1d199aae 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -458,7 +458,7 @@ namespace Timeline.Services if (list != null) { Dictionary result = new Dictionary(); - var count = 0; + var count = list.Count; for (var index = 0; index < count; index++) { var username = list[index]; -- cgit v1.2.3 From f927dcf674094857ba9729789ed0a5b272495332 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 18 Nov 2019 19:43:33 +0800 Subject: Continue to write tests. --- .../IntegratedTests/PersonalTimelineTest.cs | 49 ++++++++++++++++++++++ Timeline/Models/Timeline.cs | 1 - Timeline/Services/TimelineService.cs | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) (limited to 'Timeline/Models/Timeline.cs') diff --git a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs index aaa6215c..2e5b86fa 100644 --- a/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs +++ b/Timeline.Tests/IntegratedTests/PersonalTimelineTest.cs @@ -23,6 +23,55 @@ namespace Timeline.Tests.IntegratedTests } + [Fact] + public async Task TimelineGet_Should_Work() + { + using var client = Factory.CreateDefaultClient(); + var res = await client.GetAsync("users/user/timeline"); + var body = res.Should().HaveStatusCode(200) + .And.HaveJsonBody().Which; + body.Owner.Should().Be("user"); + body.Visibility.Should().Be(TimelineVisibility.Register); + body.Description.Should().Be(""); + body.Members.Should().NotBeNull().And.BeEmpty(); + } + + [Fact] + public async Task Description_Should_Work() + { + using var client = await Factory.CreateClientAsUser(); + + async Task AssertDescription(string description) + { + var res = await client.GetAsync("users/user/timeline"); + var body = res.Should().HaveStatusCode(200) + .And.HaveJsonBody() + .Which.Description.Should().Be(description); + } + + const string mockDescription = "haha"; + + await AssertDescription(""); + { + var res = await client.PostAsJsonAsync("users/user/timeline/op/property", + new TimelinePropertyChangeRequest { Description = mockDescription }); + res.Should().HaveStatusCode(200); + await AssertDescription(mockDescription); + } + { + var res = await client.PostAsJsonAsync("users/user/timeline/op/property", + new TimelinePropertyChangeRequest { Description = null }); + res.Should().HaveStatusCode(200); + await AssertDescription(mockDescription); + } + { + var res = await client.PostAsJsonAsync("users/user/timeline/op/property", + new TimelinePropertyChangeRequest { Description = "" }); + res.Should().HaveStatusCode(200); + await AssertDescription(""); + } + } + [Fact] public async Task Member_Should_Work() { diff --git a/Timeline/Models/Timeline.cs b/Timeline/Models/Timeline.cs index 85fefff5..752c698d 100644 --- a/Timeline/Models/Timeline.cs +++ b/Timeline/Models/Timeline.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Timeline.Entities; namespace Timeline.Models { diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 1d199aae..1e64353c 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -717,7 +717,7 @@ namespace Timeline.Services return new BaseTimelineInfo { - Description = timelineEntity.Description, + Description = timelineEntity.Description ?? "", Owner = username, Visibility = timelineEntity.Visibility, Members = memberUsernames.ToList() -- cgit v1.2.3