From 532c6ccd3498a0daabb3d6dbe3f0348f4b2d6a1f Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 7 Jan 2021 21:50:37 +0800 Subject: chore: Split integrated tests to speed up tests. --- .../IntegratedTests/BaseTimelineTest.cs | 32 ++ .../IntegratedTests/TimelinePostTest.cs | 449 ++++++++++++++++++++ .../Timeline.Tests/IntegratedTests/TimelineTest.cs | 461 +-------------------- 3 files changed, 482 insertions(+), 460 deletions(-) create mode 100644 BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs create mode 100644 BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs (limited to 'BackEnd/Timeline.Tests/IntegratedTests') diff --git a/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs new file mode 100644 index 00000000..0bf3b2b2 --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Timeline.Models.Http; + +namespace Timeline.Tests.IntegratedTests +{ + public class BaseTimelineTest : IntegratedTestBase + { + public BaseTimelineTest() : base(3) + { + } + + protected override async Task OnInitializeAsync() + { + for (int i = 0; i <= 3; i++) + { + using var client = await CreateClientAs(i); + await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = $"t{i}" }); + } + } + + public static string CreatePersonalTimelineName(int i) => i == 0 ? "@admin" : $"@user{i}"; + public static string CreateOrdinaryTimelineName(int i) => $"t{i}"; + public delegate string TimelineNameGenerator(int i); + + public static IEnumerable TimelineNameGeneratorTestData() + { + yield return new object[] { new TimelineNameGenerator(CreatePersonalTimelineName) }; + yield return new object[] { new TimelineNameGenerator(CreateOrdinaryTimelineName) }; + } + } +} diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs new file mode 100644 index 00000000..0060ac04 --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -0,0 +1,449 @@ +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Tests.Helpers; +using Xunit; + +namespace Timeline.Tests.IntegratedTests +{ + public static class TimelineHelper + { + public static HttpTimelinePostContent TextPostContent(string text) + { + return new HttpTimelinePostContent("text", text, null, null); + } + + public static HttpTimelinePostCreateRequest TextPostCreateRequest(string text, DateTime? time = null) + { + return new HttpTimelinePostCreateRequest + { + Content = new HttpTimelinePostCreateRequestContent + { + Type = "text", + Text = text + }, + Time = time + }; + } + } + + public class TimelinePostTest : BaseTimelineTest + { + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task GetPostsAndVisibility_Should_Work(TimelineNameGenerator generator) + { + { // default visibility is registered + { + using var client = await CreateDefaultClient(); + await client.TestGetAssertForbiddenAsync($"timelines/{generator(1)}/posts"); + } + + { + using var client = await CreateClientAsUser(); + await client.TestGetAsync($"timelines/{generator(0)}/posts"); + } + } + + { // change visibility to public + { + using var client = await CreateClientAsUser(); + await client.PatchTimelineAsync(generator(1), new() { Visibility = TimelineVisibility.Public }); + } + + { + using var client = await CreateDefaultClient(); + await client.TestGetAsync($"timelines/{generator(1)}/posts"); + } + } + + { // change visibility to private + { + using var client = await CreateClientAsAdministrator(); + await client.PatchTimelineAsync(generator(1), new() { Visibility = TimelineVisibility.Private }); + await client.PatchTimelineAsync(generator(0), new() { Visibility = TimelineVisibility.Private }); + } + { + using var client = await CreateDefaultClient(); + await client.TestGetAssertForbiddenAsync($"timelines/{generator(1)}/posts"); + } + { // user can't read admin's + using var client = await CreateClientAsUser(); + await client.TestGetAssertForbiddenAsync($"timelines/{generator(0)}/posts"); + } + { // admin can read user's + using var client = await CreateClientAsAdministrator(); + await client.TestGetAsync($"timelines/{generator(1)}/posts"); + } + { // add member + using var client = await CreateClientAsAdministrator(); + await client.PutTimelineMemberAsync(generator(0), "user1"); + } + { // now user can read admin's + using var client = await CreateClientAsUser(); + await client.TestGetAsync($"timelines/{generator(0)}/posts"); + } + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_ModifiedSince(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var postContentList = new List { "a", "b", "c", "d" }; + var posts = new List(); + + foreach (var content in postContentList) + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Text = content, Type = TimelinePostContentTypes.Text } }); + posts.Add(post); + await Task.Delay(1000); + } + + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}"); + + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts?modifiedSince={posts[1].LastUpdated.ToString("s", CultureInfo.InvariantCulture) }"); + body.Should().HaveCount(2) + .And.Subject.Select(p => p.Content!.Text).Should().Equal("b", "d"); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task PostList_IncludeDeleted(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var postContentList = new List { "a", "b", "c", "d" }; + var posts = new List(); + + foreach (var content in postContentList) + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Text = content, Type = TimelinePostContentTypes.Text } }); + posts.Add(body); + } + + foreach (var id in new long[] { posts[0].Id, posts[2].Id }) + { + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{id}"); + } + + { + posts = await client.TestGetAsync>($"timelines/{generator(1)}/posts?includeDeleted=true"); + posts.Should().HaveCount(4); + posts.Select(p => p.Deleted).Should().Equal(true, false, true, false); + posts.Select(p => p.Content == null).Should().Equal(true, false, true, false); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task CreatePostPermission_Should_Work(TimelineNameGenerator generator) + { + using (var client = await CreateClientAsUser()) + { + await client.PutTimelineMemberAsync(generator(1), "user2"); + } + + using (var client = await CreateDefaultClient()) + { // no auth should get 401 + await client.TestPostAssertUnauthorizedAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + } + + using (var client = await CreateClientAsUser()) + { + // post self's + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + // post other not as a member should get 403 + await client.TestPostAssertForbiddenAsync($"timelines/{generator(0)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + } + + using (var client = await CreateClientAsAdministrator()) + { // post as admin + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + } + + using (var client = await CreateClientAs(2)) + { // post as member + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task DeletePostPermission_Should_Work(TimelineNameGenerator generator) + { + async Task CreatePost(int userNumber) + { + using var client = await CreateClientAs(userNumber); + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + return body.Id; + } + + using (var client = await CreateClientAsUser()) + { + await client.PutTimelineMemberAsync(generator(1), "user2"); + await client.PutTimelineMemberAsync(generator(1), "user3"); + } + + { // no auth should get 401 + using var client = await CreateDefaultClient(); + await client.TestDeleteAssertUnauthorizedAsync($"timelines/{generator(1)}/posts/12"); + } + + { // self can delete self + var postId = await CreatePost(1); + using var client = await CreateClientAsUser(); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); + } + + { // admin can delete any + var postId = await CreatePost(1); + using var client = await CreateClientAsAdministrator(); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); + } + + { // owner can delete other + var postId = await CreatePost(2); + using var client = await CreateClientAsUser(); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); + } + + { // author can delete self + var postId = await CreatePost(2); + using var client = await CreateClientAs(2); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); + } + + { // otherwise is forbidden + var postId = await CreatePost(2); + using var client = await CreateClientAs(3); + await client.TestDeleteAssertForbiddenAsync($"timelines/{generator(1)}/posts/{postId}"); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task TextPost_Should_Work(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Should().BeEmpty(); + } + + const string mockContent = "aaa"; + HttpTimelinePost createRes; + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest(mockContent)); + body.Content.Should().BeEquivalentTo(TimelineHelper.TextPostContent(mockContent)); + body.Author.Should().BeEquivalentTo(await client.GetUserAsync("user1")); + body.Deleted.Should().BeFalse(); + createRes = body; + } + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Should().BeEquivalentTo(createRes); + } + const string mockContent2 = "bbb"; + var mockTime2 = DateTime.UtcNow.AddDays(-1); + HttpTimelinePost createRes2; + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest(mockContent2, mockTime2)); + body.Should().NotBeNull(); + body.Content.Should().BeEquivalentTo(TimelineHelper.TextPostContent(mockContent2)); + body.Author.Should().BeEquivalentTo(await client.GetUserAsync("user1")); + body.Time.Should().BeCloseTo(mockTime2, 1000); + body.Deleted.Should().BeFalse(); + createRes2 = body; + } + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Should().BeEquivalentTo(createRes, createRes2); + } + { + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{createRes.Id}"); + await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/{createRes.Id}"); + await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/30000"); + } + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Should().BeEquivalentTo(createRes2); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task GetPost_Should_Ordered(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + async Task CreatePost(DateTime time) + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa", time)); + return body.Id; + } + + var now = DateTime.UtcNow; + var id0 = await CreatePost(now.AddDays(1)); + var id1 = await CreatePost(now.AddDays(-1)); + var id2 = await CreatePost(now); + + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Select(p => p.Id).Should().Equal(id1, id2, id0); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task CreatePost_InvalidModel(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + var postUrl = $"timelines/{generator(1)}/posts"; + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = null! }); + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = null! } }); + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "hahaha" } }); + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "text", Text = null } }); + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "image", Data = null } }); + // image not base64 + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "image", Data = "!!!" } }); + // image base64 not image + await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "image", Data = Convert.ToBase64String(new byte[] { 0x01, 0x02, 0x03 }) } }); + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task ImagePost_ShouldWork(TimelineNameGenerator generator) + { + var imageData = ImageHelper.CreatePngWithSize(100, 200); + + long postId; + string postImageUrl; + + void AssertPostContent(HttpTimelinePostContent content) + { + content.Type.Should().Be(TimelinePostContentTypes.Image); + content.Url.Should().EndWith($"timelines/{generator(1)}/posts/{postId}/data"); + content.Text.Should().Be(null); + } + + using var client = await CreateClientAsUser(); + + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + Content = new HttpTimelinePostCreateRequestContent + { + Type = TimelinePostContentTypes.Image, + Data = Convert.ToBase64String(imageData) + } + }); + postId = body.Id; + postImageUrl = body.Content!.Url!; + AssertPostContent(body.Content); + } + + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Should().HaveCount(1); + var post = body[0]; + post.Id.Should().Be(postId); + AssertPostContent(post.Content!); + } + + { + var res = await client.GetAsync($"timelines/{generator(1)}/posts/{postId}/data"); + res.Content.Headers.ContentType!.MediaType.Should().Be("image/png"); + var data = await res.Content.ReadAsByteArrayAsync(); + var image = Image.Load(data, out var format); + image.Width.Should().Be(100); + image.Height.Should().Be(200); + format.Name.Should().Be(PngFormat.Instance.Name); + } + + await CacheTestHelper.TestCache(client, $"timelines/{generator(1)}/posts/{postId}/data"); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); + await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/{postId}"); + + { + var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); + body.Should().BeEmpty(); + } + + { + using var scope = TestApp.Host.Services.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + var count = await database.Data.CountAsync(); + count.Should().Be(0); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task ImagePost_400(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/11234/data", errorCode: ErrorCodes.TimelineController.PostNotExist); + + long postId; + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); + postId = body.Id; + } + + await client.TestGetAssertErrorAsync($"timelines/{generator(1)}/posts/{postId}/data", errorCode: ErrorCodes.TimelineController.PostNoData); + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task PostDataETag(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + long id; + string etag; + + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", new HttpTimelinePostCreateRequest + { + Content = new HttpTimelinePostCreateRequestContent + { + Type = TimelinePostContentTypes.Image, + Data = Convert.ToBase64String(ImageHelper.CreatePngWithSize(100, 50)) + } + }); + body.Content!.ETag.Should().NotBeNullOrEmpty(); + + id = body.Id; + etag = body.Content.ETag!; + } + + { + var res = await client.GetAsync($"timelines/{generator(1)}/posts/{id}/data"); + res.StatusCode.Should().Be(200); + res.Headers.ETag.Should().NotBeNull(); + res.Headers.ETag!.ToString().Should().Be(etag); + } + } + + } +} diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs index f0715082..66261b36 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs @@ -1,63 +1,19 @@ using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Threading.Tasks; -using Timeline.Entities; using Timeline.Models; using Timeline.Models.Http; -using Timeline.Tests.Helpers; using Xunit; namespace Timeline.Tests.IntegratedTests { - public static class TimelineHelper - { - public static HttpTimelinePostContent TextPostContent(string text) - { - return new HttpTimelinePostContent("text", text, null, null); - } - public static HttpTimelinePostCreateRequest TextPostCreateRequest(string text, DateTime? time = null) - { - return new HttpTimelinePostCreateRequest - { - Content = new HttpTimelinePostCreateRequestContent - { - Type = "text", - Text = text - }, - Time = time - }; - } - } - - public class TimelineTest : IntegratedTestBase + public class TimelineTest : BaseTimelineTest { - public TimelineTest() : base(3) - { - } - - protected override async Task OnInitializeAsync() - { - await CreateTestTimelines(); - } - - private async Task CreateTestTimelines() - { - for (int i = 0; i <= 3; i++) - { - using var client = await CreateClientAs(i); - await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = $"t{i}" }); - } - } - [Fact] public async Task TimelineGet_Should_Work() { @@ -270,15 +226,6 @@ namespace Timeline.Tests.IntegratedTests } } - public static string CreatePersonalTimelineName(int i) => i == 0 ? "@admin" : $"@user{i}"; - public static string CreateOrdinaryTimelineName(int i) => $"t{i}"; - public delegate string TimelineNameGenerator(int i); - - public static IEnumerable TimelineNameGeneratorTestData() - { - yield return new object[] { new TimelineNameGenerator(CreatePersonalTimelineName) }; - yield return new object[] { new TimelineNameGenerator(CreateOrdinaryTimelineName) }; - } [Theory] [MemberData(nameof(TimelineNameGeneratorTestData))] @@ -361,325 +308,6 @@ namespace Timeline.Tests.IntegratedTests await AssertEmptyMembers(); } - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task GetPostsAndVisibility_Should_Work(TimelineNameGenerator generator) - { - { // default visibility is registered - { - using var client = await CreateDefaultClient(); - await client.TestGetAssertForbiddenAsync($"timelines/{generator(1)}/posts"); - } - - { - using var client = await CreateClientAsUser(); - await client.TestGetAsync($"timelines/{generator(0)}/posts"); - } - } - - { // change visibility to public - { - using var client = await CreateClientAsUser(); - await client.PatchTimelineAsync(generator(1), new() { Visibility = TimelineVisibility.Public }); - } - - { - using var client = await CreateDefaultClient(); - await client.TestGetAsync($"timelines/{generator(1)}/posts"); - } - } - - { // change visibility to private - { - using var client = await CreateClientAsAdministrator(); - await client.PatchTimelineAsync(generator(1), new() { Visibility = TimelineVisibility.Private }); - await client.PatchTimelineAsync(generator(0), new() { Visibility = TimelineVisibility.Private }); - } - { - using var client = await CreateDefaultClient(); - await client.TestGetAssertForbiddenAsync($"timelines/{generator(1)}/posts"); - } - { // user can't read admin's - using var client = await CreateClientAsUser(); - await client.TestGetAssertForbiddenAsync($"timelines/{generator(0)}/posts"); - } - { // admin can read user's - using var client = await CreateClientAsAdministrator(); - await client.TestGetAsync($"timelines/{generator(1)}/posts"); - } - { // add member - using var client = await CreateClientAsAdministrator(); - await client.PutTimelineMemberAsync(generator(0), "user1"); - } - { // now user can read admin's - using var client = await CreateClientAsUser(); - await client.TestGetAsync($"timelines/{generator(0)}/posts"); - } - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task CreatePostPermission_Should_Work(TimelineNameGenerator generator) - { - using (var client = await CreateClientAsUser()) - { - await client.PutTimelineMemberAsync(generator(1), "user2"); - } - - using (var client = await CreateDefaultClient()) - { // no auth should get 401 - await client.TestPostAssertUnauthorizedAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - } - - using (var client = await CreateClientAsUser()) - { - // post self's - await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - // post other not as a member should get 403 - await client.TestPostAssertForbiddenAsync($"timelines/{generator(0)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - } - - using (var client = await CreateClientAsAdministrator()) - { // post as admin - await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - } - - using (var client = await CreateClientAs(2)) - { // post as member - await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task DeletePostPermission_Should_Work(TimelineNameGenerator generator) - { - async Task CreatePost(int userNumber) - { - using var client = await CreateClientAs(userNumber); - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - return body.Id; - } - - using (var client = await CreateClientAsUser()) - { - await client.PutTimelineMemberAsync(generator(1), "user2"); - await client.PutTimelineMemberAsync(generator(1), "user3"); - } - - { // no auth should get 401 - using var client = await CreateDefaultClient(); - await client.TestDeleteAssertUnauthorizedAsync($"timelines/{generator(1)}/posts/12"); - } - - { // self can delete self - var postId = await CreatePost(1); - using var client = await CreateClientAsUser(); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); - } - - { // admin can delete any - var postId = await CreatePost(1); - using var client = await CreateClientAsAdministrator(); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); - } - - { // owner can delete other - var postId = await CreatePost(2); - using var client = await CreateClientAsUser(); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); - } - - { // author can delete self - var postId = await CreatePost(2); - using var client = await CreateClientAs(2); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); - } - - { // otherwise is forbidden - var postId = await CreatePost(2); - using var client = await CreateClientAs(3); - await client.TestDeleteAssertForbiddenAsync($"timelines/{generator(1)}/posts/{postId}"); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task TextPost_Should_Work(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Should().BeEmpty(); - } - - const string mockContent = "aaa"; - HttpTimelinePost createRes; - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest(mockContent)); - body.Content.Should().BeEquivalentTo(TimelineHelper.TextPostContent(mockContent)); - body.Author.Should().BeEquivalentTo(await client.GetUserAsync("user1")); - body.Deleted.Should().BeFalse(); - createRes = body; - } - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Should().BeEquivalentTo(createRes); - } - const string mockContent2 = "bbb"; - var mockTime2 = DateTime.UtcNow.AddDays(-1); - HttpTimelinePost createRes2; - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest(mockContent2, mockTime2)); - body.Should().NotBeNull(); - body.Content.Should().BeEquivalentTo(TimelineHelper.TextPostContent(mockContent2)); - body.Author.Should().BeEquivalentTo(await client.GetUserAsync("user1")); - body.Time.Should().BeCloseTo(mockTime2, 1000); - body.Deleted.Should().BeFalse(); - createRes2 = body; - } - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Should().BeEquivalentTo(createRes, createRes2); - } - { - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{createRes.Id}"); - await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/{createRes.Id}"); - await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/30000"); - } - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Should().BeEquivalentTo(createRes2); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task GetPost_Should_Ordered(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - async Task CreatePost(DateTime time) - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa", time)); - return body.Id; - } - - var now = DateTime.UtcNow; - var id0 = await CreatePost(now.AddDays(1)); - var id1 = await CreatePost(now.AddDays(-1)); - var id2 = await CreatePost(now); - - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Select(p => p.Id).Should().Equal(id1, id2, id0); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task CreatePost_InvalidModel(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - var postUrl = $"timelines/{generator(1)}/posts"; - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = null! }); - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = null! } }); - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "hahaha" } }); - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "text", Text = null } }); - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "image", Data = null } }); - // image not base64 - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "image", Data = "!!!" } }); - // image base64 not image - await client.TestPostAssertInvalidModelAsync(postUrl, new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Type = "image", Data = Convert.ToBase64String(new byte[] { 0x01, 0x02, 0x03 }) } }); - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task ImagePost_ShouldWork(TimelineNameGenerator generator) - { - var imageData = ImageHelper.CreatePngWithSize(100, 200); - - long postId; - string postImageUrl; - - void AssertPostContent(HttpTimelinePostContent content) - { - content.Type.Should().Be(TimelinePostContentTypes.Image); - content.Url.Should().EndWith($"timelines/{generator(1)}/posts/{postId}/data"); - content.Text.Should().Be(null); - } - - using var client = await CreateClientAsUser(); - - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - Content = new HttpTimelinePostCreateRequestContent - { - Type = TimelinePostContentTypes.Image, - Data = Convert.ToBase64String(imageData) - } - }); - postId = body.Id; - postImageUrl = body.Content!.Url!; - AssertPostContent(body.Content); - } - - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Should().HaveCount(1); - var post = body[0]; - post.Id.Should().Be(postId); - AssertPostContent(post.Content!); - } - - { - var res = await client.GetAsync($"timelines/{generator(1)}/posts/{postId}/data"); - res.Content.Headers.ContentType!.MediaType.Should().Be("image/png"); - var data = await res.Content.ReadAsByteArrayAsync(); - var image = Image.Load(data, out var format); - image.Width.Should().Be(100); - image.Height.Should().Be(200); - format.Name.Should().Be(PngFormat.Instance.Name); - } - - await CacheTestHelper.TestCache(client, $"timelines/{generator(1)}/posts/{postId}/data"); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{postId}"); - await client.TestDeleteAssertErrorAsync($"timelines/{generator(1)}/posts/{postId}"); - - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts"); - body.Should().BeEmpty(); - } - - { - using var scope = TestApp.Host.Services.CreateScope(); - var database = scope.ServiceProvider.GetRequiredService(); - var count = await database.Data.CountAsync(); - count.Should().Be(0); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task ImagePost_400(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/11234/data", errorCode: ErrorCodes.TimelineController.PostNotExist); - - long postId; - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelineHelper.TextPostCreateRequest("aaa")); - postId = body.Id; - } - - await client.TestGetAssertErrorAsync($"timelines/{generator(1)}/posts/{postId}/data", errorCode: ErrorCodes.TimelineController.PostNoData); - } - [Theory] [MemberData(nameof(TimelineNameGeneratorTestData))] public async Task Timeline_LastModified(TimelineNameGenerator generator) @@ -715,61 +343,6 @@ namespace Timeline.Tests.IntegratedTests } } - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_ModifiedSince(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var postContentList = new List { "a", "b", "c", "d" }; - var posts = new List(); - - foreach (var content in postContentList) - { - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Text = content, Type = TimelinePostContentTypes.Text } }); - posts.Add(post); - await Task.Delay(1000); - } - - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}"); - - { - var body = await client.TestGetAsync>($"timelines/{generator(1)}/posts?modifiedSince={posts[1].LastUpdated.ToString("s", CultureInfo.InvariantCulture) }"); - body.Should().HaveCount(2) - .And.Subject.Select(p => p.Content!.Text).Should().Equal("b", "d"); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task PostList_IncludeDeleted(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var postContentList = new List { "a", "b", "c", "d" }; - var posts = new List(); - - foreach (var content in postContentList) - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest { Content = new HttpTimelinePostCreateRequestContent { Text = content, Type = TimelinePostContentTypes.Text } }); - posts.Add(body); - } - - foreach (var id in new long[] { posts[0].Id, posts[2].Id }) - { - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{id}"); - } - - { - posts = await client.TestGetAsync>($"timelines/{generator(1)}/posts?includeDeleted=true"); - posts.Should().HaveCount(4); - posts.Select(p => p.Deleted).Should().Equal(true, false, true, false); - posts.Select(p => p.Content == null).Should().Equal(true, false, true, false); - } - } - [Theory] [MemberData(nameof(TimelineNameGeneratorTestData))] public async Task Post_ModifiedSince_And_IncludeDeleted(TimelineNameGenerator generator) @@ -905,37 +478,5 @@ namespace Timeline.Tests.IntegratedTests } } } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task PostDataETag(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - long id; - string etag; - - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", new HttpTimelinePostCreateRequest - { - Content = new HttpTimelinePostCreateRequestContent - { - Type = TimelinePostContentTypes.Image, - Data = Convert.ToBase64String(ImageHelper.CreatePngWithSize(100, 50)) - } - }); - body.Content!.ETag.Should().NotBeNullOrEmpty(); - - id = body.Id; - etag = body.Content.ETag!; - } - - { - var res = await client.GetAsync($"timelines/{generator(1)}/posts/{id}/data"); - res.StatusCode.Should().Be(HttpStatusCode.OK); - res.Headers.ETag.Should().NotBeNull(); - res.Headers.ETag!.ToString().Should().Be(etag); - } - } } } -- cgit v1.2.3 From 04186d5f1091266b85758d4b4255c6a7c1b498f6 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 7 Jan 2021 22:10:58 +0800 Subject: feat: Timeline info contains bookmark and highlight flag. --- .../IntegratedTests/BookmarkTimelineTest.cs | 38 ++++++++++++++++++++++ .../IntegratedTests/HighlightTimelineTest.cs | 33 +++++++++++++++++++ .../Controllers/BookmarkTimelineController.cs | 2 +- .../Controllers/HighlightTimelineController.cs | 2 +- BackEnd/Timeline/Controllers/TimelineController.cs | 10 +++--- BackEnd/Timeline/Models/Http/Timeline.cs | 8 ++++- BackEnd/Timeline/Models/Mapper/TimelineMapper.cs | 14 +++++--- 7 files changed, 95 insertions(+), 12 deletions(-) (limited to 'BackEnd/Timeline.Tests/IntegratedTests') diff --git a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs index e6ae178f..99cf6d3a 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs @@ -83,5 +83,43 @@ namespace Timeline.Tests.IntegratedTests h.Should().BeEmpty(); } } + + [Fact] + public async Task TimelineGet_IsBookmarkField_ShouldWork() + { + using var client = await CreateClientAsUser(); + await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t" }); + + { + var t = await client.TestGetAsync("timelines/t"); + t.IsBookmark.Should().BeFalse(); + } + + await client.TestPutAsync("bookmarks/t"); + + { + var t = await client.TestGetAsync("timelines/t"); + t.IsBookmark.Should().BeTrue(); + } + + { + var client1 = await CreateDefaultClient(); + var t = await client1.TestGetAsync("timelines/t"); + t.IsBookmark.Should().BeFalse(); + } + + { + var client1 = await CreateClientAsAdministrator(); + var t = await client1.TestGetAsync("timelines/t"); + t.IsBookmark.Should().BeFalse(); + } + + await client.TestDeleteAsync("bookmarks/t"); + + { + var t = await client.TestGetAsync("timelines/t"); + t.IsBookmark.Should().BeFalse(); + } + } } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs index a3f2855e..440759f4 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs @@ -86,5 +86,38 @@ namespace Timeline.Tests.IntegratedTests h.Should().BeEmpty(); } } + + [Fact] + public async Task TimelineGet_IsHighlighField_Should_Work() + { + using var client = await CreateClientAsAdministrator(); + await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t" }); + + { + var t = await client.TestGetAsync("timelines/t"); + t.IsHighlight.Should().BeFalse(); + } + + await client.TestPutAsync("highlights/t"); + + { + var t = await client.TestGetAsync("timelines/t"); + t.IsHighlight.Should().BeTrue(); + } + + { + var client1 = await CreateDefaultClient(); + var t = await client1.TestGetAsync("timelines/t"); + t.IsHighlight.Should().BeTrue(); + } + + await client.TestDeleteAsync("highlights/t"); + + { + var t = await client.TestGetAsync("timelines/t"); + t.IsHighlight.Should().BeFalse(); + } + + } } } diff --git a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs index 4313115e..16793de6 100644 --- a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs +++ b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs @@ -40,7 +40,7 @@ namespace Timeline.Controllers { var ids = await _service.GetBookmarks(this.GetUserId()); var timelines = await _timelineService.GetTimelineList(ids); - return await _timelineMapper.MapToHttp(timelines, Url); + return await _timelineMapper.MapToHttp(timelines, Url, this.GetOptionalUserId()); } /// diff --git a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs index cc19cada..ea012f76 100644 --- a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs +++ b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs @@ -38,7 +38,7 @@ namespace Timeline.Controllers { var ids = await _service.GetHighlightTimelines(); var timelines = await _timelineService.GetTimelineList(ids); - return await _timelineMapper.MapToHttp(timelines, Url); + return await _timelineMapper.MapToHttp(timelines, Url, this.GetOptionalUserId()); } /// diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index efc49952..b2e37b15 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -109,7 +109,7 @@ namespace Timeline.Controllers } var timelines = await _service.GetTimelines(relationship, visibilityFilter); - var result = await _timelineMapper.MapToHttp(timelines, Url); + var result = await _timelineMapper.MapToHttp(timelines, Url, this.GetOptionalUserId()); return result; } @@ -168,7 +168,7 @@ namespace Timeline.Controllers else { var t = await _service.GetTimeline(timelineId); - var result = await _timelineMapper.MapToHttp(t, Url); + var result = await _timelineMapper.MapToHttp(t, Url, this.GetOptionalUserId()); return result; } } @@ -363,7 +363,7 @@ namespace Timeline.Controllers } await _service.ChangeProperty(timelineId, _mapper.Map(body)); var t = await _service.GetTimeline(timelineId); - var result = await _timelineMapper.MapToHttp(t, Url); + var result = await _timelineMapper.MapToHttp(t, Url, this.GetOptionalUserId()); return result; } @@ -448,7 +448,7 @@ namespace Timeline.Controllers try { var timeline = await _service.CreateTimeline(body.Name, userId); - var result = await _timelineMapper.MapToHttp(timeline, Url); + var result = await _timelineMapper.MapToHttp(timeline, Url, this.GetOptionalUserId()); return result; } catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline) @@ -507,7 +507,7 @@ namespace Timeline.Controllers { await _service.ChangeTimelineName(timelineId, body.NewName); var timeline = await _service.GetTimeline(timelineId); - return await _timelineMapper.MapToHttp(timeline, Url); + return await _timelineMapper.MapToHttp(timeline, Url, this.GetOptionalUserId()); } catch (EntityAlreadyExistException) { diff --git a/BackEnd/Timeline/Models/Http/Timeline.cs b/BackEnd/Timeline/Models/Http/Timeline.cs index 06fa4e5a..5e5889f6 100644 --- a/BackEnd/Timeline/Models/Http/Timeline.cs +++ b/BackEnd/Timeline/Models/Http/Timeline.cs @@ -86,7 +86,7 @@ namespace Timeline.Models.Http { public HttpTimeline() { } - public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List members, DateTime createTime, DateTime lastModified, HttpTimelineLinks links) + public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List members, DateTime createTime, DateTime lastModified, bool isHighlight, bool isBookmark, HttpTimelineLinks links) { UniqueId = uniqueId; Title = title; @@ -98,6 +98,8 @@ namespace Timeline.Models.Http Members = members; CreateTime = createTime; LastModified = lastModified; + IsHighlight = isHighlight; + IsBookmark = isBookmark; _links = links; } @@ -144,6 +146,10 @@ namespace Timeline.Models.Http /// public DateTime LastModified { get; set; } = default!; + public bool IsHighlight { get; set; } + + public bool IsBookmark { get; set; } + #pragma warning disable CA1707 // Identifiers should not contain underscores /// /// Related links. diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs index 14ca8fe9..95418573 100644 --- a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs +++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs @@ -15,14 +15,18 @@ namespace Timeline.Models.Mapper { private readonly DatabaseContext _database; private readonly UserMapper _userMapper; + private readonly IHighlightTimelineService _highlightTimelineService; + private readonly IBookmarkTimelineService _bookmarkTimelineService; - public TimelineMapper(DatabaseContext database, UserMapper userMapper) + public TimelineMapper(DatabaseContext database, UserMapper userMapper, IHighlightTimelineService highlightTimelineService, IBookmarkTimelineService bookmarkTimelineService) { _database = database; _userMapper = userMapper; + _highlightTimelineService = highlightTimelineService; + _bookmarkTimelineService = bookmarkTimelineService; } - public async Task MapToHttp(TimelineEntity entity, IUrlHelper urlHelper) + public async Task MapToHttp(TimelineEntity entity, IUrlHelper urlHelper, long? userId) { await _database.Entry(entity).Reference(e => e.Owner).LoadAsync(); await _database.Entry(entity).Collection(e => e.Members).Query().Include(m => m.User).LoadAsync(); @@ -40,6 +44,8 @@ namespace Timeline.Models.Mapper members: await _userMapper.MapToHttp(entity.Members.Select(m => m.User).ToList(), urlHelper), createTime: entity.CreateTime, lastModified: entity.LastModified, + isHighlight: await _highlightTimelineService.IsHighlightTimeline(entity.Id), + isBookmark: userId is not null && await _bookmarkTimelineService.IsBookmark(userId.Value, entity.Id, false, false), links: new HttpTimelineLinks( self: urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName }), posts: urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName }) @@ -47,12 +53,12 @@ namespace Timeline.Models.Mapper ); } - public async Task> MapToHttp(List entities, IUrlHelper urlHelper) + public async Task> MapToHttp(List entities, IUrlHelper urlHelper, long? userId) { var result = new List(); foreach (var entity in entities) { - result.Add(await MapToHttp(entity, urlHelper)); + result.Add(await MapToHttp(entity, urlHelper, userId)); } return result; } -- cgit v1.2.3