using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; 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 TimelinePostContentInfo TextPostContent(string text) { return new TimelinePostContentInfo { Type = "text", Text = text }; } public static TimelinePostCreateRequest TextPostCreateRequest(string text, DateTime? time = null) { return new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = "text", Text = text }, Time = time }; } } public class TimelineTest : IntegratedTestBase { public TimelineTest(WebApplicationFactory factory) : base(factory, 3) { } protected override async Task OnInitializeAsync() { await CreateTestTimelines(); } private List _testTimelines; private async Task CreateTestTimelines() { _testTimelines = new List(); for (int i = 0; i <= 3; i++) { var client = await CreateClientAs(i); var res = await client.PostAsJsonAsync("timelines", new TimelineCreateRequest { Name = $"t{i}" }); var timelineInfo = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; _testTimelines.Add(timelineInfo); } } private static string GeneratePersonalTimelineUrl(int id, string subpath = null) { return $"timelines/@{(id == 0 ? "admin" : ("user" + id))}/{(subpath ?? "")}"; } private static string GenerateOrdinaryTimelineUrl(int id, string subpath = null) { return $"timelines/t{id}/{(subpath ?? "")}"; } public static IEnumerable TimelineUrlGeneratorData() { yield return new[] { new Func(GeneratePersonalTimelineUrl) }; yield return new[] { new Func(GenerateOrdinaryTimelineUrl) }; } private static string GeneratePersonalTimelineUrlByName(string name, string subpath = null) { return $"timelines/@{name}/{(subpath ?? "")}"; } private static string GenerateOrdinaryTimelineUrlByName(string name, string subpath = null) { return $"timelines/{name}/{(subpath ?? "")}"; } public static IEnumerable TimelineUrlByNameGeneratorData() { yield return new[] { new Func(GeneratePersonalTimelineUrlByName) }; yield return new[] { new Func(GenerateOrdinaryTimelineUrlByName) }; } [Fact] public async Task TimelineGet_Should_Work() { using var client = await CreateDefaultClient(); { var res = await client.GetAsync("timelines/@user1"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Owner.Should().BeEquivalentTo(UserInfos[1]); body.Visibility.Should().Be(TimelineVisibility.Register); body.Description.Should().Be(""); body.Members.Should().NotBeNull().And.BeEmpty(); var links = body._links; links.Should().NotBeNull(); links.Self.Should().EndWith("timelines/@user1"); links.Posts.Should().EndWith("timelines/@user1/posts"); } { var res = await client.GetAsync("timelines/t1"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Owner.Should().BeEquivalentTo(UserInfos[1]); body.Visibility.Should().Be(TimelineVisibility.Register); body.Description.Should().Be(""); body.Members.Should().NotBeNull().And.BeEmpty(); var links = body._links; links.Should().NotBeNull(); links.Self.Should().EndWith("timelines/t1"); links.Posts.Should().EndWith("timelines/t1/posts"); } } [Fact] public async Task TimelineList() { TimelineInfo user1Timeline; var client = await CreateDefaultClient(); { var res = await client.GetAsync("timelines/@user1"); user1Timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; } { var testResult = new List(); testResult.Add(user1Timeline); testResult.AddRange(_testTimelines); var res = await client.GetAsync("timelines"); res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which.Should().BeEquivalentTo(testResult); } } [Fact] public async Task TimelineList_WithQuery() { var testResultRelate = new List(); var testResultOwn = new List(); var testResultJoin = new List(); var testResultOwnPrivate = new List(); var testResultRelatePublic = new List(); var testResultRelateRegister = new List(); var testResultJoinPrivate = new List(); var testResultPublic = new List(); { var client = await CreateClientAsUser(); { var res = await client.PutAsync("timelines/@user1/members/user3", null); res.Should().HaveStatusCode(200); } { var res = await client.PutAsync("timelines/t1/members/user3", null); res.Should().HaveStatusCode(200); } { var res = await client.PatchAsJsonAsync("timelines/@user1", new TimelinePatchRequest { Visibility = TimelineVisibility.Public }); res.Should().HaveStatusCode(200); } { var res = await client.PatchAsJsonAsync("timelines/t1", new TimelinePatchRequest { Visibility = TimelineVisibility.Register }); res.Should().HaveStatusCode(200); } { var res = await client.GetAsync("timelines/@user1"); var timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; testResultRelate.Add(timeline); testResultJoin.Add(timeline); testResultRelatePublic.Add(timeline); testResultPublic.Add(timeline); } { var res = await client.GetAsync("timelines/t1"); var timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; testResultRelate.Add(timeline); testResultJoin.Add(timeline); testResultRelateRegister.Add(timeline); } } { var client = await CreateClientAs(2); { var res = await client.PutAsync("timelines/@user2/members/user3", null); res.Should().HaveStatusCode(200); } { var res = await client.PutAsync("timelines/t2/members/user3", null); res.Should().HaveStatusCode(200); } { var res = await client.PatchAsJsonAsync("timelines/@user2", new TimelinePatchRequest { Visibility = TimelineVisibility.Register }); res.Should().HaveStatusCode(200); } { var res = await client.PatchAsJsonAsync("timelines/t2", new TimelinePatchRequest { Visibility = TimelineVisibility.Private }); res.Should().HaveStatusCode(200); } { var res = await client.GetAsync("timelines/@user2"); var timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; testResultRelate.Add(timeline); testResultJoin.Add(timeline); testResultRelateRegister.Add(timeline); } { var res = await client.GetAsync("timelines/t2"); var timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; testResultRelate.Add(timeline); testResultJoin.Add(timeline); testResultJoinPrivate.Add(timeline); } } { var client = await CreateClientAs(3); { var res = await client.PatchAsJsonAsync("timelines/@user3", new TimelinePatchRequest { Visibility = TimelineVisibility.Private }); res.Should().HaveStatusCode(200); } { var res = await client.PatchAsJsonAsync("timelines/t3", new TimelinePatchRequest { Visibility = TimelineVisibility.Register }); res.Should().HaveStatusCode(200); } { var res = await client.GetAsync("timelines/@user3"); var timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; testResultRelate.Add(timeline); testResultOwn.Add(timeline); testResultOwnPrivate.Add(timeline); } { var res = await client.GetAsync("timelines/t3"); var timeline = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; testResultRelate.Add(timeline); testResultOwn.Add(timeline); testResultRelateRegister.Add(timeline); } } { var client = await CreateClientAs(3); { var res = await client.GetAsync("timelines?relate=user3"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultRelate); } { var res = await client.GetAsync("timelines?relate=user3&relateType=own"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultOwn); } { var res = await client.GetAsync("timelines?relate=user3&visibility=public"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultRelatePublic); } { var res = await client.GetAsync("timelines?relate=user3&visibility=register"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultRelateRegister); } { var res = await client.GetAsync("timelines?relate=user3&relateType=join&visibility=private"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultJoinPrivate); } { var res = await client.GetAsync("timelines?relate=user3&relateType=own&visibility=private"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultOwnPrivate); } } { var client = await CreateDefaultClient(); { var res = await client.GetAsync("timelines?visibility=public"); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody>() .Which; body.Should().BeEquivalentTo(testResultPublic); } } } [Fact] public async Task TimelineList_InvalidModel() { var client = await CreateClientAsUser(); { var res = await client.GetAsync("timelines?relate=us!!"); res.Should().BeInvalidModel(); } { var res = await client.GetAsync("timelines?relateType=aaa"); res.Should().BeInvalidModel(); } { var res = await client.GetAsync("timelines?visibility=aaa"); res.Should().BeInvalidModel(); } } [Fact] public async Task TimelineCreate_Should_Work() { { using var client = await CreateDefaultClient(); var res = await client.PostAsJsonAsync("timelines", new TimelineCreateRequest { Name = "aaa" }); res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } using (var client = await CreateClientAsUser()) { { var res = await client.PostAsJsonAsync("timelines", new TimelineCreateRequest { Name = "!!!" }); res.Should().BeInvalidModel(); } TimelineInfo timelineInfo; { var res = await client.PostAsJsonAsync("timelines", new TimelineCreateRequest { Name = "aaa" }); timelineInfo = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; } { var res = await client.GetAsync("timelines/aaa"); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Should().BeEquivalentTo(timelineInfo); } { var res = await client.PostAsJsonAsync("timelines", new TimelineCreateRequest { Name = "aaa" }); res.Should().HaveStatusCode(400) .And.HaveCommonBody(ErrorCodes.TimelineController.NameConflict); } } } [Fact] public async Task TimelineDelete_Should_Work() { { using var client = await CreateDefaultClient(); var res = await client.DeleteAsync("timelines/t1"); res.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } { using var client = await CreateClientAs(2); var res = await client.DeleteAsync("timelines/t1"); res.Should().HaveStatusCode(HttpStatusCode.Forbidden); } { using var client = await CreateClientAsAdministrator(); { var res = await client.DeleteAsync("timelines/!!!"); res.Should().BeInvalidModel(); } { var res = await client.DeleteAsync("timelines/t2"); res.Should().BeDelete(true); } { var res = await client.DeleteAsync("timelines/t2"); res.Should().BeDelete(false); } } { using var client = await CreateClientAs(1); { var res = await client.DeleteAsync("timelines/!!!"); res.Should().BeInvalidModel(); } { var res = await client.DeleteAsync("timelines/t1"); res.Should().BeDelete(true); } { var res = await client.DeleteAsync("timelines/t1"); res.Should().HaveStatusCode(HttpStatusCode.NotFound); } } } [Theory] [MemberData(nameof(TimelineUrlByNameGeneratorData))] public async Task InvalidModel_BadName(Func generator) { using var client = await CreateClientAsAdministrator(); { var res = await client.GetAsync(generator("aaa!!!", null)); res.Should().BeInvalidModel(); } { var res = await client.PatchAsJsonAsync(generator("aaa!!!", null), new TimelinePatchRequest { }); res.Should().BeInvalidModel(); } { var res = await client.PutAsync(generator("aaa!!!", "members/user1"), null); res.Should().BeInvalidModel(); } { var res = await client.DeleteAsync(generator("aaa!!!", "members/user1")); res.Should().BeInvalidModel(); } { var res = await client.GetAsync(generator("aaa!!!", "posts")); res.Should().BeInvalidModel(); } { var res = await client.PostAsJsonAsync(generator("aaa!!!", "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().BeInvalidModel(); } { var res = await client.DeleteAsync(generator("aaa!!!", "posts/123")); res.Should().BeInvalidModel(); } { var res = await client.GetAsync(generator("aaa!!!", "posts/123/data")); res.Should().BeInvalidModel(); } } [Theory] [MemberData(nameof(TimelineUrlByNameGeneratorData))] public async Task Ordinary_NotFound(Func generator) { var errorCode = generator == GenerateOrdinaryTimelineUrlByName ? ErrorCodes.TimelineController.NotExist : ErrorCodes.UserCommon.NotExist; using var client = await CreateClientAsAdministrator(); { var res = await client.GetAsync(generator("notexist", null)); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.PatchAsJsonAsync(generator("notexist", null), new TimelinePatchRequest { }); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.PutAsync(generator("notexist", "members/user1"), null); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.DeleteAsync(generator("notexist", "members/user1")); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.GetAsync(generator("notexist", "posts")); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.PostAsJsonAsync(generator("notexist", "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.DeleteAsync(generator("notexist", "posts/123")); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } { var res = await client.GetAsync(generator("notexist", "posts/123/data")); res.Should().HaveStatusCode(404).And.HaveCommonBody(errorCode); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task Description_Should_Work(Func generator) { using var client = await CreateClientAsUser(); async Task AssertDescription(string description) { var res = await client.GetAsync(generator(1, null)); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Description.Should().Be(description); } const string mockDescription = "haha"; await AssertDescription(""); { var res = await client.PatchAsJsonAsync(generator(1, null), new TimelinePatchRequest { Description = mockDescription }); res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which.Description.Should().Be(mockDescription); await AssertDescription(mockDescription); } { var res = await client.PatchAsJsonAsync(generator(1, null), new TimelinePatchRequest { Description = null }); res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which.Description.Should().Be(mockDescription); await AssertDescription(mockDescription); } { var res = await client.PatchAsJsonAsync(generator(1, null), new TimelinePatchRequest { Description = "" }); res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which.Description.Should().Be(""); await AssertDescription(""); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task Member_Should_Work(Func generator) { var getUrl = generator(1, null); using var client = await 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.PutAsync(generator(1, "members/usernotexist"), null); res.Should().HaveStatusCode(400) .And.HaveCommonBody(ErrorCodes.TimelineController.MemberPut_NotExist); } await AssertEmptyMembers(); { var res = await client.PutAsync(generator(1, "members/user2"), null); res.Should().HaveStatusCode(200); } await AssertMembers(new List { UserInfos[2] }); { var res = await client.DeleteAsync(generator(1, "members/user2")); res.Should().BeDelete(true); } await AssertEmptyMembers(); { var res = await client.DeleteAsync(generator(1, "members/aaa")); res.Should().BeDelete(false); } await AssertEmptyMembers(); } [Theory] [InlineData(nameof(GenerateOrdinaryTimelineUrl), -1, 200, 401, 401, 401, 401)] [InlineData(nameof(GenerateOrdinaryTimelineUrl), 1, 200, 200, 403, 200, 403)] [InlineData(nameof(GenerateOrdinaryTimelineUrl), 0, 200, 200, 200, 200, 200)] [InlineData(nameof(GeneratePersonalTimelineUrl), -1, 200, 401, 401, 401, 401)] [InlineData(nameof(GeneratePersonalTimelineUrl), 1, 200, 200, 403, 200, 403)] [InlineData(nameof(GeneratePersonalTimelineUrl), 0, 200, 200, 200, 200, 200)] public async Task Permission_Timeline(string generatorName, int userNumber, int get, int opPatchUser, int opPatchAdmin, int opMemberUser, int opMemberAdmin) { var method = GetType().GetMethod(generatorName, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); Func generator = (int id, string subpath) => (string)method.Invoke(null, new object[] { id, subpath }); using var client = await CreateClientAs(userNumber); { var res = await client.GetAsync("timelines/t1"); res.Should().HaveStatusCode(get); } { var res = await client.PatchAsJsonAsync(generator(1, null), new TimelinePatchRequest { Description = "hahaha" }); res.Should().HaveStatusCode(opPatchUser); } { var res = await client.PatchAsJsonAsync(generator(0, null), new TimelinePatchRequest { Description = "hahaha" }); res.Should().HaveStatusCode(opPatchAdmin); } { var res = await client.PutAsync(generator(1, "members/user2"), null); res.Should().HaveStatusCode(opMemberUser); } { var res = await client.DeleteAsync(generator(1, "members/user2")); res.Should().HaveStatusCode(opMemberUser); } { var res = await client.PutAsync(generator(0, "members/user2"), null); res.Should().HaveStatusCode(opMemberAdmin); } { var res = await client.DeleteAsync(generator(0, "members/user2")); res.Should().HaveStatusCode(opMemberAdmin); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task Visibility_Test(Func generator) { var userUrl = generator(1, "posts"); var adminUrl = generator(0, "posts"); { using var client = await CreateClientAsUser(); using var content = new StringContent(@"{""visibility"":""abcdefg""}", System.Text.Encoding.UTF8, System.Net.Mime.MediaTypeNames.Application.Json); var res = await client.PatchAsync(generator(1, null), content); res.Should().BeInvalidModel(); } { // default visibility is registered { using var client = await CreateDefaultClient(); var res = await client.GetAsync(userUrl); res.Should().HaveStatusCode(403); } { using var client = await CreateClientAsUser(); var res = await client.GetAsync(adminUrl); res.Should().HaveStatusCode(200); } } { // change visibility to public { using var client = await CreateClientAsUser(); var res = await client.PatchAsJsonAsync(generator(1, null), new TimelinePatchRequest { Visibility = TimelineVisibility.Public }); res.Should().HaveStatusCode(200); } { using var client = await CreateDefaultClient(); var res = await client.GetAsync(userUrl); res.Should().HaveStatusCode(200); } } { // change visibility to private { using var client = await CreateClientAsAdministrator(); { var res = await client.PatchAsJsonAsync(generator(1, null), new TimelinePatchRequest { Visibility = TimelineVisibility.Private }); res.Should().HaveStatusCode(200); } { var res = await client.PatchAsJsonAsync(generator(0, null), new TimelinePatchRequest { Visibility = TimelineVisibility.Private }); res.Should().HaveStatusCode(200); } } { using var client = await CreateDefaultClient(); var res = await client.GetAsync(userUrl); res.Should().HaveStatusCode(403); } { // user can't read admin's using var client = await CreateClientAsUser(); var res = await client.GetAsync(adminUrl); res.Should().HaveStatusCode(403); } { // admin can read user's using var client = await CreateClientAsAdministrator(); var res = await client.GetAsync(userUrl); res.Should().HaveStatusCode(200); } { // add member using var client = await CreateClientAsAdministrator(); var res = await client.PutAsync(generator(0, "members/user1"), null); res.Should().HaveStatusCode(200); } { // now user can read admin's using var client = await CreateClientAsUser(); var res = await client.GetAsync(adminUrl); res.Should().HaveStatusCode(200); } } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task Permission_Post_Create(Func generator) { using (var client = await CreateClientAsUser()) { var res = await client.PutAsync(generator(1, "members/user2"), null); res.Should().HaveStatusCode(200); } using (var client = await CreateDefaultClient()) { { // no auth should get 401 var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().HaveStatusCode(401); } } using (var client = await CreateClientAsUser()) { { // post self's var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().HaveStatusCode(200); } { // post other not as a member should get 403 var res = await client.PostAsJsonAsync(generator(0, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().HaveStatusCode(403); } } using (var client = await CreateClientAsAdministrator()) { { // post as admin var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().HaveStatusCode(200); } } using (var client = await CreateClientAs(2)) { { // post as member var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); res.Should().HaveStatusCode(200); } } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task Permission_Post_Delete(Func generator) { async Task CreatePost(int userNumber) { using var client = await CreateClientAs(userNumber); var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); return res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Id; } using (var client = await CreateClientAsUser()) { { var res = await client.PutAsync(generator(1, "members/user2"), null); res.Should().HaveStatusCode(200); } { var res = await client.PutAsync(generator(1, "members/user3"), null); res.Should().HaveStatusCode(200); } } { // no auth should get 401 using var client = await CreateDefaultClient(); var res = await client.DeleteAsync(generator(1, "posts/12")); res.Should().HaveStatusCode(401); } { // self can delete self var postId = await CreatePost(1); using var client = await CreateClientAsUser(); var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().HaveStatusCode(200); } { // admin can delete any var postId = await CreatePost(1); using var client = await CreateClientAsAdministrator(); var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().HaveStatusCode(200); } { // owner can delete other var postId = await CreatePost(2); using var client = await CreateClientAsUser(); var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().HaveStatusCode(200); } { // author can delete self var postId = await CreatePost(2); using var client = await CreateClientAs(2); var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().HaveStatusCode(200); } { // otherwise is forbidden var postId = await CreatePost(2); using var client = await CreateClientAs(3); var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().HaveStatusCode(403); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task TextPost_ShouldWork(Func generator) { { using var client = await CreateClientAsUser(); { var res = await client.GetAsync(generator(1, "posts")); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Should().NotBeNull().And.BeEmpty(); } { var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest(null)); res.Should().BeInvalidModel(); } const string mockContent = "aaa"; TimelinePostInfo createRes; { var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest(mockContent)); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which; body.Should().NotBeNull(); body.Content.Should().BeEquivalentTo(TimelineHelper.TextPostContent(mockContent)); body.Author.Should().BeEquivalentTo(UserInfos[1]); createRes = body; } { var res = await client.GetAsync(generator(1, "posts")); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Should().NotBeNull().And.BeEquivalentTo(createRes); } const string mockContent2 = "bbb"; var mockTime2 = DateTime.Now.AddDays(-1); TimelinePostInfo createRes2; { var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest(mockContent2, mockTime2)); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which; body.Should().NotBeNull(); body.Content.Should().BeEquivalentTo(TimelineHelper.TextPostContent(mockContent2)); body.Author.Should().BeEquivalentTo(UserInfos[1]); body.Time.Should().BeCloseTo(mockTime2, 1000); createRes2 = body; } { var res = await client.GetAsync(generator(1, "posts")); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Should().NotBeNull().And.BeEquivalentTo(createRes, createRes2); } { var res = await client.DeleteAsync(generator(1, $"posts/{createRes.Id}")); res.Should().BeDelete(true); } { var res = await client.DeleteAsync(generator(1, $"posts/{createRes.Id}")); res.Should().BeDelete(false); } { var res = await client.DeleteAsync(generator(1, "posts/30000")); res.Should().BeDelete(false); } { var res = await client.GetAsync(generator(1, "posts")); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Should().NotBeNull().And.BeEquivalentTo(createRes2); } } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task GetPost_Should_Ordered(Func generator) { using var client = await CreateClientAsUser(); async Task CreatePost(DateTime time) { var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa", time)); return res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Id; } var now = DateTime.Now; var id0 = await CreatePost(now.AddDays(1)); var id1 = await CreatePost(now.AddDays(-1)); var id2 = await CreatePost(now); { var res = await client.GetAsync(generator(1, "posts")); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Select(p => p.Id).Should().Equal(id1, id2, id0); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task CreatePost_InvalidModel(Func generator) { using var client = await CreateClientAsUser(); { var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = null }); res.Should().BeInvalidModel(); } { var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = null } }); res.Should().BeInvalidModel(); } { var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = "hahaha" } }); res.Should().BeInvalidModel(); } { var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = "text", Text = null } }); res.Should().BeInvalidModel(); } { var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = "image", Data = null } }); res.Should().BeInvalidModel(); } { // image not base64 var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = "image", Data = "!!!" } }); res.Should().BeInvalidModel(); } { // image base64 not image var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = "image", Data = Convert.ToBase64String(new byte[] { 0x01, 0x02, 0x03 }) } }); res.Should().BeInvalidModel(); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task ImagePost_ShouldWork(Func generator) { var imageData = ImageHelper.CreatePngWithSize(100, 200); long postId; string postImageUrl; void AssertPostContent(TimelinePostContentInfo content) { content.Type.Should().Be(TimelinePostContentTypes.Image); content.Url.Should().EndWith(generator(1, $"posts/{postId}/data")); content.Text.Should().Be(null); } using var client = await CreateClientAsUser(); { var res = await client.PostAsJsonAsync(generator(1, "posts"), new TimelinePostCreateRequest { Content = new TimelinePostCreateRequestContent { Type = TimelinePostContentTypes.Image, Data = Convert.ToBase64String(imageData) } }); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; postId = body.Id; postImageUrl = body.Content.Url; AssertPostContent(body.Content); } { var res = await client.GetAsync(generator(1, "posts")); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Should().HaveCount(1); var post = body[0]; post.Id.Should().Be(postId); AssertPostContent(post.Content); } { var res = await client.GetAsync(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, generator(1, $"posts/{postId}/data")); } { var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().BeDelete(true); } { var res = await client.DeleteAsync(generator(1, $"posts/{postId}")); res.Should().BeDelete(false); } { var res = await client.GetAsync(generator(1, "posts")); res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.Should().BeEmpty(); } { using var scope = TestApp.Factory.Services.CreateScope(); var database = scope.ServiceProvider.GetRequiredService(); var count = await database.Data.CountAsync(); count.Should().Be(0); } } [Theory] [MemberData(nameof(TimelineUrlGeneratorData))] public async Task ImagePost_400(Func generator) { using var client = await CreateClientAsUser(); { var res = await client.GetAsync(generator(1, "posts/11234/data")); res.Should().HaveStatusCode(404) .And.HaveCommonBody(ErrorCodes.TimelineController.PostNotExist); } long postId; { var res = await client.PostAsJsonAsync(generator(1, "posts"), TimelineHelper.TextPostCreateRequest("aaa")); var body = res.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which; postId = body.Id; } { var res = await client.GetAsync(generator(1, $"posts/{postId}/data")); res.Should().HaveStatusCode(400) .And.HaveCommonBody(ErrorCodes.TimelineController.PostNoData); } } } }