From bd9dacf0566ca542ecab913acbc244f474c6a752 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 1 Jun 2021 23:22:47 +0800 Subject: feat: Add ways to reset timeline or post color. --- .../Timeline.Tests/IntegratedTests/TimelineTest.cs | 12 ++++++++ .../Models/Http/HttpTimelinePatchRequest.cs | 2 +- .../Models/Http/HttpTimelinePostPatchRequest.cs | 2 +- .../Timeline/Models/Validation/ColorValidator.cs | 34 ++++++++++++++++++++++ BackEnd/Timeline/Models/Validation/Validator.cs | 2 +- .../Services/Timeline/TimelinePostService.cs | 20 +++++++++---- .../Timeline/Services/Timeline/TimelineService.cs | 11 +++++-- 7 files changed, 73 insertions(+), 10 deletions(-) diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs index bbfe4ab3..d7832b60 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs @@ -438,5 +438,17 @@ namespace Timeline.Tests.IntegratedTests timeline.Postable.Should().Be(false); } } + + [Theory] + [InlineData("")] + [InlineData("default")] + public async Task Patch_Timeline_Color_Default(string value) + { + using var client = await CreateClientAsUser(); + var timeline = await client.TestPatchAsync("timelines/t1", new HttpTimelinePatchRequest { Color = "#111111" }); + timeline.Color.Should().NotBeNull(); + var timeline2 = await client.TestPatchAsync("timelines/t1", new HttpTimelinePatchRequest { Color = value }); + timeline2.Color.Should().BeNull(); + } } } diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs index 9accb6fc..35667af2 100644 --- a/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs @@ -31,7 +31,7 @@ namespace Timeline.Models.Http /// /// New color. Null for not change. /// - [Color] + [Color(PermitDefault = true, PermitEmpty = true)] public string? Color { get; set; } } } diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostPatchRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostPatchRequest.cs index 2c6edf66..cb576a74 100644 --- a/BackEnd/Timeline/Models/Http/HttpTimelinePostPatchRequest.cs +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostPatchRequest.cs @@ -13,7 +13,7 @@ namespace Timeline.Models.Http /// /// Change the color. Null for not change. /// - [Color] + [Color(PermitEmpty = true, PermitDefault = true)] public string? Color { get; set; } } } diff --git a/BackEnd/Timeline/Models/Validation/ColorValidator.cs b/BackEnd/Timeline/Models/Validation/ColorValidator.cs index c5ad833d..4f7accc5 100644 --- a/BackEnd/Timeline/Models/Validation/ColorValidator.cs +++ b/BackEnd/Timeline/Models/Validation/ColorValidator.cs @@ -4,8 +4,22 @@ namespace Timeline.Models.Validation { public class ColorValidator : Validator { + public bool PermitEmpty { get; set; } = false; + public bool PermitDefault { get; set; } = false; + public string DefaultValue { get; set; } = "default"; + protected override (bool, string) DoValidate(string value) { + if (PermitEmpty && value.Length == 0) + { + return (true, GetSuccessMessage()); + } + + if (PermitDefault && value == DefaultValue) + { + return (true, GetSuccessMessage()); + } + if (!value.StartsWith('#')) { return (false, "Color must starts with '#'."); @@ -32,9 +46,29 @@ namespace Timeline.Models.Validation [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] public class ColorAttribute : ValidateWithAttribute { + private ColorValidator Validator => (ColorValidator)_validator; + public ColorAttribute() : base(typeof(ColorValidator)) { } + + public bool PermitEmpty + { + get => Validator.PermitEmpty; + set => Validator.PermitEmpty = value; + } + + public bool PermitDefault + { + get => Validator.PermitDefault; + set => Validator.PermitDefault = value; + } + + public string DefaultValue + { + get => Validator.DefaultValue; + set => Validator.DefaultValue = value; + } } } diff --git a/BackEnd/Timeline/Models/Validation/Validator.cs b/BackEnd/Timeline/Models/Validation/Validator.cs index d334960e..0e1f7445 100644 --- a/BackEnd/Timeline/Models/Validation/Validator.cs +++ b/BackEnd/Timeline/Models/Validation/Validator.cs @@ -77,7 +77,7 @@ namespace Timeline.Models.Validation AllowMultiple = false)] public class ValidateWithAttribute : ValidationAttribute { - private readonly IValidator _validator; + protected readonly IValidator _validator; /// /// Create with a given validator. diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs index ee1002e0..ae034767 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs @@ -26,6 +26,7 @@ namespace Timeline.Services.Timeline private readonly IImageService _imageValidator; private readonly IClock _clock; private readonly ColorValidator _colorValidator = new ColorValidator(); + private readonly ColorValidator _colorValidatorAllowEmptyAndDefault = new ColorValidator() { PermitEmpty = true, PermitDefault = true }; public TimelinePostService(ILogger logger, DatabaseContext database, IBasicTimelineService basicTimelineService, IBasicUserService basicUserService, IDataManager dataManager, IImageService imageValidator, IClock clock) { @@ -38,9 +39,9 @@ namespace Timeline.Services.Timeline _clock = clock; } - private void CheckColor(string color, string paramName) + private void CheckColor(string color, string paramName, bool allowEmptyOrDefault) { - if (!_colorValidator.Validate(color, out var message)) + if (!(allowEmptyOrDefault ? _colorValidatorAllowEmptyAndDefault : _colorValidator).Validate(color, out var message)) throw new ArgumentException(string.Format(Resource.ExceptionColorInvalid, message), paramName); } @@ -166,7 +167,7 @@ namespace Timeline.Services.Timeline throw new ArgumentNullException(nameof(request)); if (request.Color is not null) - CheckColor(request.Color, nameof(request)); + CheckColor(request.Color, nameof(request), false); if (request.DataList is null) throw new ArgumentException(Resource.ExceptionDataListNull, nameof(request)); @@ -269,7 +270,7 @@ namespace Timeline.Services.Timeline throw new ArgumentNullException(nameof(request)); if (request.Color is not null) - CheckColor(request.Color, nameof(request)); + CheckColor(request.Color, nameof(request), true); request.Time = request.Time?.MyToUtc(); @@ -287,7 +288,16 @@ namespace Timeline.Services.Timeline entity.Time = request.Time.Value; if (request.Color is not null) - entity.Color = request.Color; + { + if (request.Color.Length == 0 || request.Color == "default") + { + entity.Color = null; + } + else + { + entity.Color = request.Color; + } + } entity.LastUpdated = _clock.GetCurrentTime(); diff --git a/BackEnd/Timeline/Services/Timeline/TimelineService.cs b/BackEnd/Timeline/Services/Timeline/TimelineService.cs index 920fcc74..6f22ff05 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelineService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelineService.cs @@ -23,7 +23,7 @@ namespace Timeline.Services.Timeline private readonly IClock _clock; private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); - private readonly ColorValidator _colorValidator = new ColorValidator(); + private readonly ColorValidator _colorValidator = new ColorValidator() { PermitDefault = true, PermitEmpty = true }; public TimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IBasicUserService userService, IClock clock) : base(loggerFactory, database, userService, clock) @@ -119,7 +119,14 @@ namespace Timeline.Services.Timeline if (newProperties.Color is not null) { changed = true; - entity.Color = newProperties.Color; + if (newProperties.Color.Length == 0 || newProperties.Color == "default") + { + entity.Color = null; + } + else + { + entity.Color = newProperties.Color; + } } if (changed) -- cgit v1.2.3 From 97824588a3e015671bf15408d2b1129567c0f12d Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Jun 2021 09:50:49 +0800 Subject: test: Prepare to split post tests. --- .../IntegratedTests/TimelinePostTest.cs | 607 +------------------- .../IntegratedTests/TimelinePostTest1.cs | 620 +++++++++++++++++++++ 2 files changed, 622 insertions(+), 605 deletions(-) create mode 100644 BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs index 749b2cfd..a6853c34 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -1,18 +1,8 @@ -using FluentAssertions; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using System; +using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; using System.Text; -using System.Threading.Tasks; using Timeline.Models; using Timeline.Models.Http; -using Timeline.Tests.Helpers; -using Xunit; using Xunit.Abstractions; namespace Timeline.Tests.IntegratedTests @@ -36,7 +26,7 @@ namespace Timeline.Tests.IntegratedTests }; } - private static HttpTimelinePostCreateRequest CreateMarkdownPostRequest(string text, DateTime? time = null, string? color = null) + public static HttpTimelinePostCreateRequest CreateMarkdownPostRequest(string text, DateTime? time = null, string? color = null) { return new HttpTimelinePostCreateRequest() { @@ -57,598 +47,5 @@ namespace Timeline.Tests.IntegratedTests { } - - [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 posts = new List(); - - for (int i = 0; i < 4; i++) - { - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); - posts.Add(post); - await Task.Delay(TimeSpan.FromSeconds(1)); - } - - 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.Id).Should().Equal(posts[1].Id, posts[3].Id); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task PostList_IncludeDeleted(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var posts = new List(); - - for (int i = 0; i < 4; i++) - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); - 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); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_ModifiedSince_And_IncludeDeleted(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var posts = new List(); - - for (int i = 0; i < 4; i++) - { - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); - posts.Add(post); - await Task.Delay(TimeSpan.FromSeconds(1)); - } - - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}"); - - { - posts = await client.TestGetAsync>($"timelines/{generator(1)}/posts?modifiedSince={posts[1].LastUpdated.ToString("s", CultureInfo.InvariantCulture)}&includeDeleted=true"); - posts.Should().HaveCount(3); - posts.Select(p => p.Deleted).Should().Equal(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", CreateTextPostRequest("aaa")); - } - - using (var client = await CreateClientAsUser()) - { - // post self's - await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("aaa")); - // post other not as a member should get 403 - await client.TestPostAssertForbiddenAsync($"timelines/{generator(0)}/posts", CreateTextPostRequest("aaa")); - } - - using (var client = await CreateClientAsAdministrator()) - { // post as admin - await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("aaa")); - } - - using (var client = await CreateClientAs(2)) - { // post as member - await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("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", CreateTextPostRequest("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 GetPost_Should_Ordered(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - async Task CreatePost(DateTime time) - { - var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("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 Color(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - await client.TestPostAssertInvalidModelAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a", color: "aa")); - - long id; - - { - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", - CreateTextPostRequest("a", color: "#aabbcc")); - post.Color.Should().Be("#aabbcc"); - id = post.Id; - } - - { - var post = await client.TestGetAsync($"timelines/{generator(1)}/posts/{id}"); - post.Color.Should().Be("#aabbcc"); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task GetPost(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - - await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/1"); - - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); - - var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); - post2.Should().BeEquivalentTo(post); - - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); - - await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/{post.Id}"); - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task PatchPost(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", - CreateTextPostRequest("a")); - - var date = new DateTime(2000, 10, 1); - - var post2 = await client.TestPatchAsync($"timelines/{generator(1)}/posts/{post.Id}", new HttpTimelinePostPatchRequest - { - Time = date, - Color = "#aabbcc" - }); - post2.Time.Should().Be(date); - post2.Color.Should().Be("#aabbcc"); - - var post3 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); - post3.Time.Should().Be(date); - post3.Color.Should().Be("#aabbcc"); - } - - public static IEnumerable CreatePost_InvalidModelTest_TestData() - { - var testDataList = new List?>() - { - null, - new List(), - Enumerable.Repeat(new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) - }, 200).ToList(), - }; - - var testData = new List() - { - null, - new HttpTimelinePostCreateRequestData - { - ContentType = null!, - Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = null! - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/xxxxxxx", - Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = "aaa" - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = Convert.ToBase64String(new byte[] {0xE4, 0x1, 0xA0}) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "image/jpeg", - Data = Convert.ToBase64String(ImageHelper.CreatePngWithSize(100, 100)) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "image/jpeg", - Data = Convert.ToBase64String(new byte[] { 100, 200 }) - } - - }; - - testDataList.AddRange(testData.Select(d => new List() { d! })); - - return TimelineNameGeneratorTestData().AppendTestData(testDataList); - } - - [Theory] - [MemberData(nameof(CreatePost_InvalidModelTest_TestData))] - public async Task CreatePost_InvalidModel(TimelineNameGenerator generator, List dataList) - { - using var client = await CreateClientAsUser(); - - await client.TestPostAssertInvalidModelAsync( - $"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - DataList = dataList - } - ); - } - - public static IEnumerable CreatePost_ShouldWork_TestData() - { - var testByteDatas = new List() - { - new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextPlain), - new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextMarkdown), - new ByteData(ImageHelper.CreateImageWithSize(100, 50, PngFormat.Instance), MimeTypes.ImagePng), - new ByteData(ImageHelper.CreateImageWithSize(100, 50, JpegFormat.Instance), MimeTypes.ImageJpeg), - new ByteData(ImageHelper.CreateImageWithSize(100, 50, GifFormat.Instance), MimeTypes.ImageGif), - }; - - return TimelineNameGeneratorTestData().AppendTestData(testByteDatas); - } - - [Theory] - [MemberData(nameof(CreatePost_ShouldWork_TestData))] - public async Task CreatePost_ShouldWork(TimelineNameGenerator generator, ByteData data) - { - using var client = await CreateClientAsUser(); - - var post = await client.TestPostAsync( - $"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - DataList = new List - { - new HttpTimelinePostCreateRequestData - { - ContentType = data.ContentType, - Data = Convert.ToBase64String(data.Data) - } - } - } - ); - - post.DataList.Should().NotBeNull().And.HaveCount(1); - var postData = post.DataList[0]; - postData.Should().NotBeNull(); - var postDataEtag = postData.ETag; - postDataEtag.Should().NotBeNullOrEmpty(); - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(data.Data); - } - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(data.Data); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task CreatePost_MultipleData_ShouldWork(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var textData = Encoding.UTF8.GetBytes("aaa"); - var imageData = ImageHelper.CreatePngWithSize(100, 50); - - var post = await client.TestPostAsync( - $"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - DataList = new List - { - new HttpTimelinePostCreateRequestData - { - ContentType = MimeTypes.TextMarkdown, - Data = Convert.ToBase64String(textData) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = MimeTypes.ImagePng, - Data = Convert.ToBase64String(imageData) - } - } - } - ); - - post.DataList.Should().NotBeNull().And.HaveCount(2); - - var postData0 = post.DataList[0]; - postData0.Should().NotBeNull(); - var postDataEtag0 = postData0.ETag; - postDataEtag0.Should().NotBeNullOrEmpty(); - - var postData1 = post.DataList[1]; - postData1.Should().NotBeNull(); - var postDataEtag1 = postData1.ETag; - postDataEtag1.Should().NotBeNullOrEmpty(); - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag0); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(textData); - } - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag0); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(textData); - } - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/1"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag1); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.ImagePng); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(imageData); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_Editable(TimelineNameGenerator generator) - { - HttpTimelinePost post; - - { - using var client = await CreateClientAsUser(); - post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); - - post.Editable.Should().BeTrue(); - } - - { - using var client = await CreateClientAs(2); - var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); - post2.Editable.Should().BeFalse(); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_Markdown_Url_Map(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](1) ![bbb](2)")); - - var res = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); - var markdown = await res.Content.ReadAsStringAsync(); - - markdown.Should().MatchRegex(@$"\[aaa\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/1\)"); - markdown.Should().MatchRegex(@$"\[bbb\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/2\)"); - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_Markdown_Delete_Test(TimelineNameGenerator generator) - { - { - using var client = await CreateClientAs(2); - var post = await client.TestPostAsync($"timelines/{generator(2)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); - await client.TestDeleteAsync($"timelines/{generator(2)}/posts/{post.Id}"); - } - - { - using var client = await CreateClientAsUser(); - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_List_Pagination_Test(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - var posts = new List(); - for (int i = 0; i < 50; i++) - { - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest(i.ToString())); - posts.Add(post); - } - - { - var p = await client.TestGetAsync>($"timelines/{generator(1)}/posts?page=2&numberPerPage=10"); - p.Should().BeEquivalentTo(posts.Skip(10).Take(10)); - } - } } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs new file mode 100644 index 00000000..091e87b9 --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs @@ -0,0 +1,620 @@ +using FluentAssertions; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests.IntegratedTests +{ + public class TimelinePostTest1 : TimelinePostTest + { + public TimelinePostTest1(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + + } + + [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 posts = new List(); + + for (int i = 0; i < 4; i++) + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); + posts.Add(post); + await Task.Delay(TimeSpan.FromSeconds(1)); + } + + 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.Id).Should().Equal(posts[1].Id, posts[3].Id); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task PostList_IncludeDeleted(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var posts = new List(); + + for (int i = 0; i < 4; i++) + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); + 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); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_ModifiedSince_And_IncludeDeleted(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var posts = new List(); + + for (int i = 0; i < 4; i++) + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); + posts.Add(post); + await Task.Delay(TimeSpan.FromSeconds(1)); + } + + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{posts[2].Id}"); + + { + posts = await client.TestGetAsync>($"timelines/{generator(1)}/posts?modifiedSince={posts[1].LastUpdated.ToString("s", CultureInfo.InvariantCulture)}&includeDeleted=true"); + posts.Should().HaveCount(3); + posts.Select(p => p.Deleted).Should().Equal(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", CreateTextPostRequest("aaa")); + } + + using (var client = await CreateClientAsUser()) + { + // post self's + await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("aaa")); + // post other not as a member should get 403 + await client.TestPostAssertForbiddenAsync($"timelines/{generator(0)}/posts", CreateTextPostRequest("aaa")); + } + + using (var client = await CreateClientAsAdministrator()) + { // post as admin + await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("aaa")); + } + + using (var client = await CreateClientAs(2)) + { // post as member + await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("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", CreateTextPostRequest("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 GetPost_Should_Ordered(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + async Task CreatePost(DateTime time) + { + var body = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("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 Color(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + await client.TestPostAssertInvalidModelAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a", color: "aa")); + + long id; + + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", + CreateTextPostRequest("a", color: "#aabbcc")); + post.Color.Should().Be("#aabbcc"); + id = post.Id; + } + + { + var post = await client.TestGetAsync($"timelines/{generator(1)}/posts/{id}"); + post.Color.Should().Be("#aabbcc"); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task GetPost(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + + await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/1"); + + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); + + var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); + post2.Should().BeEquivalentTo(post); + + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); + + await client.TestGetAssertNotFoundAsync($"timelines/{generator(1)}/posts/{post.Id}"); + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task PatchPost(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", + CreateTextPostRequest("a")); + + var date = new DateTime(2000, 10, 1); + + var post2 = await client.TestPatchAsync($"timelines/{generator(1)}/posts/{post.Id}", new HttpTimelinePostPatchRequest + { + Time = date, + Color = "#aabbcc" + }); + post2.Time.Should().Be(date); + post2.Color.Should().Be("#aabbcc"); + + var post3 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); + post3.Time.Should().Be(date); + post3.Color.Should().Be("#aabbcc"); + } + + public static IEnumerable CreatePost_InvalidModelTest_TestData() + { + var testDataList = new List?>() + { + null, + new List(), + Enumerable.Repeat(new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) + }, 200).ToList(), + }; + + var testData = new List() + { + null, + new HttpTimelinePostCreateRequestData + { + ContentType = null!, + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = null! + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/xxxxxxx", + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = "aaa" + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = Convert.ToBase64String(new byte[] {0xE4, 0x1, 0xA0}) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "image/jpeg", + Data = Convert.ToBase64String(ImageHelper.CreatePngWithSize(100, 100)) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "image/jpeg", + Data = Convert.ToBase64String(new byte[] { 100, 200 }) + } + + }; + + testDataList.AddRange(testData.Select(d => new List() { d! })); + + return TimelineNameGeneratorTestData().AppendTestData(testDataList); + } + + [Theory] + [MemberData(nameof(CreatePost_InvalidModelTest_TestData))] + public async Task CreatePost_InvalidModel(TimelineNameGenerator generator, List dataList) + { + using var client = await CreateClientAsUser(); + + await client.TestPostAssertInvalidModelAsync( + $"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + DataList = dataList + } + ); + } + + public static IEnumerable CreatePost_ShouldWork_TestData() + { + var testByteDatas = new List() + { + new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextPlain), + new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextMarkdown), + new ByteData(ImageHelper.CreateImageWithSize(100, 50, PngFormat.Instance), MimeTypes.ImagePng), + new ByteData(ImageHelper.CreateImageWithSize(100, 50, JpegFormat.Instance), MimeTypes.ImageJpeg), + new ByteData(ImageHelper.CreateImageWithSize(100, 50, GifFormat.Instance), MimeTypes.ImageGif), + }; + + return TimelineNameGeneratorTestData().AppendTestData(testByteDatas); + } + + [Theory] + [MemberData(nameof(CreatePost_ShouldWork_TestData))] + public async Task CreatePost_ShouldWork(TimelineNameGenerator generator, ByteData data) + { + using var client = await CreateClientAsUser(); + + var post = await client.TestPostAsync( + $"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + DataList = new List + { + new HttpTimelinePostCreateRequestData + { + ContentType = data.ContentType, + Data = Convert.ToBase64String(data.Data) + } + } + } + ); + + post.DataList.Should().NotBeNull().And.HaveCount(1); + var postData = post.DataList[0]; + postData.Should().NotBeNull(); + var postDataEtag = postData.ETag; + postDataEtag.Should().NotBeNullOrEmpty(); + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(data.Data); + } + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(data.Data); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task CreatePost_MultipleData_ShouldWork(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var textData = Encoding.UTF8.GetBytes("aaa"); + var imageData = ImageHelper.CreatePngWithSize(100, 50); + + var post = await client.TestPostAsync( + $"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + DataList = new List + { + new HttpTimelinePostCreateRequestData + { + ContentType = MimeTypes.TextMarkdown, + Data = Convert.ToBase64String(textData) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = MimeTypes.ImagePng, + Data = Convert.ToBase64String(imageData) + } + } + } + ); + + post.DataList.Should().NotBeNull().And.HaveCount(2); + + var postData0 = post.DataList[0]; + postData0.Should().NotBeNull(); + var postDataEtag0 = postData0.ETag; + postDataEtag0.Should().NotBeNullOrEmpty(); + + var postData1 = post.DataList[1]; + postData1.Should().NotBeNull(); + var postDataEtag1 = postData1.ETag; + postDataEtag1.Should().NotBeNullOrEmpty(); + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag0); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(textData); + } + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag0); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(textData); + } + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/1"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag1); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.ImagePng); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(imageData); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Editable(TimelineNameGenerator generator) + { + HttpTimelinePost post; + + { + using var client = await CreateClientAsUser(); + post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); + + post.Editable.Should().BeTrue(); + } + + { + using var client = await CreateClientAs(2); + var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); + post2.Editable.Should().BeFalse(); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Markdown_Url_Map(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](1) ![bbb](2)")); + + var res = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + var markdown = await res.Content.ReadAsStringAsync(); + + markdown.Should().MatchRegex(@$"\[aaa\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/1\)"); + markdown.Should().MatchRegex(@$"\[bbb\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/2\)"); + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Markdown_Delete_Test(TimelineNameGenerator generator) + { + { + using var client = await CreateClientAs(2); + var post = await client.TestPostAsync($"timelines/{generator(2)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); + await client.TestDeleteAsync($"timelines/{generator(2)}/posts/{post.Id}"); + } + + { + using var client = await CreateClientAsUser(); + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_List_Pagination_Test(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + var posts = new List(); + for (int i = 0; i < 50; i++) + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest(i.ToString())); + posts.Add(post); + } + + { + var p = await client.TestGetAsync>($"timelines/{generator(1)}/posts?page=2&numberPerPage=10"); + p.Should().BeEquivalentTo(posts.Skip(10).Take(10)); + } + } + } +} -- cgit v1.2.3 From 02502642313118b9bf44db01ecb6620232964353 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Jun 2021 12:44:42 +0800 Subject: test: Use ws protocol to test signalr hub. --- BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs index e8b774d8..0c4225c6 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs @@ -17,7 +17,7 @@ namespace Timeline.Tests.IntegratedTests private HubConnection CreateConnection(string? token) { - return new HubConnectionBuilder().WithUrl("http://localhost/api/hub/timeline", + return new HubConnectionBuilder().WithUrl("ws://localhost/api/hub/timeline", options => { options.HttpMessageHandlerFactory = _ => TestApp.Server.CreateHandler(); @@ -47,13 +47,11 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsUser(); await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("aaa")); - await Task.Delay(1); changed.Should().BeFalse(); await connection.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), generator(1)); await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("bbb")); - await Task.Delay(1); changed.Should().BeTrue(); changed = false; @@ -61,7 +59,6 @@ namespace Timeline.Tests.IntegratedTests await connection.InvokeAsync(nameof(TimelineHub.UnsubscribeTimelinePostChange), generator(1)); await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("ccc")); - await Task.Delay(1); changed.Should().BeFalse(); } -- cgit v1.2.3 From 14c8e37493e686822972e0372c845045569a485e Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Jun 2021 19:07:48 +0800 Subject: ... --- BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs index 0c4225c6..c9986177 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Microsoft.AspNetCore.SignalR.Client; using System; +using System.Threading; using System.Threading.Tasks; using Timeline.SignalRHub; using Xunit; @@ -33,12 +34,15 @@ namespace Timeline.Tests.IntegratedTests await using var connection = CreateConnection(token); + using SemaphoreSlim semaphore = new SemaphoreSlim(0); + var changed = false; connection.On(nameof(ITimelineClient.OnTimelinePostChanged), (timelineName) => { timelineName.Should().Be(generator(1)); changed = true; + semaphore.Release(); }); await connection.StartAsync(); @@ -47,11 +51,13 @@ namespace Timeline.Tests.IntegratedTests using var client = await CreateClientAsUser(); await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("aaa")); + await semaphore.WaitAsync(); changed.Should().BeFalse(); await connection.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), generator(1)); await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("bbb")); + await semaphore.WaitAsync(); changed.Should().BeTrue(); changed = false; -- cgit v1.2.3 From c616e7b6e4a6ed316cf243c141b1fbad67757ea0 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Jun 2021 19:48:06 +0800 Subject: test: Finally find the right way to test signalr. --- .../IntegratedTests/TimelineHubTest.cs | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs index c9986177..9b28a648 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs @@ -34,6 +34,9 @@ namespace Timeline.Tests.IntegratedTests await using var connection = CreateConnection(token); + await connection.StartAsync(); + connection.State.Should().Be(HubConnectionState.Connected); + using SemaphoreSlim semaphore = new SemaphoreSlim(0); var changed = false; @@ -45,27 +48,28 @@ namespace Timeline.Tests.IntegratedTests semaphore.Release(); }); - await connection.StartAsync(); - connection.State.Should().Be(HubConnectionState.Connected); + await Task.Run(async () => + { + using var client = await CreateClientAsUser(); - using var client = await CreateClientAsUser(); + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("aaa")); - await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("aaa")); - await semaphore.WaitAsync(); - changed.Should().BeFalse(); + changed.Should().BeFalse(); - await connection.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), generator(1)); + await connection.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), generator(1)); - await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("bbb")); - await semaphore.WaitAsync(); - changed.Should().BeTrue(); + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("bbb")); + await semaphore.WaitAsync(); + changed.Should().BeTrue(); - changed = false; + changed = false; - await connection.InvokeAsync(nameof(TimelineHub.UnsubscribeTimelinePostChange), generator(1)); + await connection.InvokeAsync(nameof(TimelineHub.UnsubscribeTimelinePostChange), generator(1)); - await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("ccc")); - changed.Should().BeFalse(); + await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("ccc")); + changed.Should().BeFalse(); + + }); } [Fact] -- cgit v1.2.3 From 634fc0454b81dc63e6dd31e69930d5eb5d744b47 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Jun 2021 21:57:16 +0800 Subject: test: Split post tests and write post patch default color test. --- .../IntegratedTests/TimelinePostTest1.cs | 293 ------------------ .../IntegratedTests/TimelinePostTest2.cs | 326 +++++++++++++++++++++ 2 files changed, 326 insertions(+), 293 deletions(-) create mode 100644 BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest2.cs diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs index 091e87b9..58efd74a 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest1.cs @@ -1,17 +1,11 @@ using FluentAssertions; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Net; -using System.Text; using System.Threading.Tasks; using Timeline.Models; using Timeline.Models.Http; -using Timeline.Tests.Helpers; using Xunit; using Xunit.Abstractions; @@ -329,292 +323,5 @@ namespace Timeline.Tests.IntegratedTests post3.Time.Should().Be(date); post3.Color.Should().Be("#aabbcc"); } - - public static IEnumerable CreatePost_InvalidModelTest_TestData() - { - var testDataList = new List?>() - { - null, - new List(), - Enumerable.Repeat(new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) - }, 200).ToList(), - }; - - var testData = new List() - { - null, - new HttpTimelinePostCreateRequestData - { - ContentType = null!, - Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = null! - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/xxxxxxx", - Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = "aaa" - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "text/plain", - Data = Convert.ToBase64String(new byte[] {0xE4, 0x1, 0xA0}) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "image/jpeg", - Data = Convert.ToBase64String(ImageHelper.CreatePngWithSize(100, 100)) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = "image/jpeg", - Data = Convert.ToBase64String(new byte[] { 100, 200 }) - } - - }; - - testDataList.AddRange(testData.Select(d => new List() { d! })); - - return TimelineNameGeneratorTestData().AppendTestData(testDataList); - } - - [Theory] - [MemberData(nameof(CreatePost_InvalidModelTest_TestData))] - public async Task CreatePost_InvalidModel(TimelineNameGenerator generator, List dataList) - { - using var client = await CreateClientAsUser(); - - await client.TestPostAssertInvalidModelAsync( - $"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - DataList = dataList - } - ); - } - - public static IEnumerable CreatePost_ShouldWork_TestData() - { - var testByteDatas = new List() - { - new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextPlain), - new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextMarkdown), - new ByteData(ImageHelper.CreateImageWithSize(100, 50, PngFormat.Instance), MimeTypes.ImagePng), - new ByteData(ImageHelper.CreateImageWithSize(100, 50, JpegFormat.Instance), MimeTypes.ImageJpeg), - new ByteData(ImageHelper.CreateImageWithSize(100, 50, GifFormat.Instance), MimeTypes.ImageGif), - }; - - return TimelineNameGeneratorTestData().AppendTestData(testByteDatas); - } - - [Theory] - [MemberData(nameof(CreatePost_ShouldWork_TestData))] - public async Task CreatePost_ShouldWork(TimelineNameGenerator generator, ByteData data) - { - using var client = await CreateClientAsUser(); - - var post = await client.TestPostAsync( - $"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - DataList = new List - { - new HttpTimelinePostCreateRequestData - { - ContentType = data.ContentType, - Data = Convert.ToBase64String(data.Data) - } - } - } - ); - - post.DataList.Should().NotBeNull().And.HaveCount(1); - var postData = post.DataList[0]; - postData.Should().NotBeNull(); - var postDataEtag = postData.ETag; - postDataEtag.Should().NotBeNullOrEmpty(); - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(data.Data); - } - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(data.Data); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task CreatePost_MultipleData_ShouldWork(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - - var textData = Encoding.UTF8.GetBytes("aaa"); - var imageData = ImageHelper.CreatePngWithSize(100, 50); - - var post = await client.TestPostAsync( - $"timelines/{generator(1)}/posts", - new HttpTimelinePostCreateRequest - { - DataList = new List - { - new HttpTimelinePostCreateRequestData - { - ContentType = MimeTypes.TextMarkdown, - Data = Convert.ToBase64String(textData) - }, - new HttpTimelinePostCreateRequestData - { - ContentType = MimeTypes.ImagePng, - Data = Convert.ToBase64String(imageData) - } - } - } - ); - - post.DataList.Should().NotBeNull().And.HaveCount(2); - - var postData0 = post.DataList[0]; - postData0.Should().NotBeNull(); - var postDataEtag0 = postData0.ETag; - postDataEtag0.Should().NotBeNullOrEmpty(); - - var postData1 = post.DataList[1]; - postData1.Should().NotBeNull(); - var postDataEtag1 = postData1.ETag; - postDataEtag1.Should().NotBeNullOrEmpty(); - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag0); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(textData); - } - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag0); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(textData); - } - - { - var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/1"); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.ETag.Should().NotBeNull(); - response.Headers.ETag!.Tag.Should().Be(postDataEtag1); - response.Content.Headers.ContentType.Should().NotBeNull(); - response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.ImagePng); - - var body = await response.Content.ReadAsByteArrayAsync(); - body.Should().Equal(imageData); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_Editable(TimelineNameGenerator generator) - { - HttpTimelinePost post; - - { - using var client = await CreateClientAsUser(); - post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); - - post.Editable.Should().BeTrue(); - } - - { - using var client = await CreateClientAs(2); - var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); - post2.Editable.Should().BeFalse(); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_Markdown_Url_Map(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](1) ![bbb](2)")); - - var res = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); - var markdown = await res.Content.ReadAsStringAsync(); - - markdown.Should().MatchRegex(@$"\[aaa\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/1\)"); - markdown.Should().MatchRegex(@$"\[bbb\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/2\)"); - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_Markdown_Delete_Test(TimelineNameGenerator generator) - { - { - using var client = await CreateClientAs(2); - var post = await client.TestPostAsync($"timelines/{generator(2)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); - await client.TestDeleteAsync($"timelines/{generator(2)}/posts/{post.Id}"); - } - - { - using var client = await CreateClientAsUser(); - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); - await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); - } - } - - [Theory] - [MemberData(nameof(TimelineNameGeneratorTestData))] - public async Task Post_List_Pagination_Test(TimelineNameGenerator generator) - { - using var client = await CreateClientAsUser(); - var posts = new List(); - for (int i = 0; i < 50; i++) - { - var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest(i.ToString())); - posts.Add(post); - } - - { - var p = await client.TestGetAsync>($"timelines/{generator(1)}/posts?page=2&numberPerPage=10"); - p.Should().BeEquivalentTo(posts.Skip(10).Take(10)); - } - } } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest2.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest2.cs new file mode 100644 index 00000000..78be068f --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest2.cs @@ -0,0 +1,326 @@ +using FluentAssertions; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Timeline.Models; +using Timeline.Models.Http; +using Timeline.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace Timeline.Tests.IntegratedTests +{ + public class TimelinePostTest2 : TimelinePostTest + { + public TimelinePostTest2(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + + } + + public static IEnumerable CreatePost_InvalidModelTest_TestData() + { + var testDataList = new List?>() + { + null, + new List(), + Enumerable.Repeat(new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) + }, 200).ToList(), + }; + + var testData = new List() + { + null, + new HttpTimelinePostCreateRequestData + { + ContentType = null!, + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = null! + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/xxxxxxx", + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("a")) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = "aaa" + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "text/plain", + Data = Convert.ToBase64String(new byte[] {0xE4, 0x1, 0xA0}) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "image/jpeg", + Data = Convert.ToBase64String(ImageHelper.CreatePngWithSize(100, 100)) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = "image/jpeg", + Data = Convert.ToBase64String(new byte[] { 100, 200 }) + } + + }; + + testDataList.AddRange(testData.Select(d => new List() { d! })); + + return TimelineNameGeneratorTestData().AppendTestData(testDataList); + } + + [Theory] + [MemberData(nameof(CreatePost_InvalidModelTest_TestData))] + public async Task CreatePost_InvalidModel(TimelineNameGenerator generator, List dataList) + { + using var client = await CreateClientAsUser(); + + await client.TestPostAssertInvalidModelAsync( + $"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + DataList = dataList + } + ); + } + + public static IEnumerable CreatePost_ShouldWork_TestData() + { + var testByteDatas = new List() + { + new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextPlain), + new ByteData(Encoding.UTF8.GetBytes("aaa"), MimeTypes.TextMarkdown), + new ByteData(ImageHelper.CreateImageWithSize(100, 50, PngFormat.Instance), MimeTypes.ImagePng), + new ByteData(ImageHelper.CreateImageWithSize(100, 50, JpegFormat.Instance), MimeTypes.ImageJpeg), + new ByteData(ImageHelper.CreateImageWithSize(100, 50, GifFormat.Instance), MimeTypes.ImageGif), + }; + + return TimelineNameGeneratorTestData().AppendTestData(testByteDatas); + } + + [Theory] + [MemberData(nameof(CreatePost_ShouldWork_TestData))] + public async Task CreatePost_ShouldWork(TimelineNameGenerator generator, ByteData data) + { + using var client = await CreateClientAsUser(); + + var post = await client.TestPostAsync( + $"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + DataList = new List + { + new HttpTimelinePostCreateRequestData + { + ContentType = data.ContentType, + Data = Convert.ToBase64String(data.Data) + } + } + } + ); + + post.DataList.Should().NotBeNull().And.HaveCount(1); + var postData = post.DataList[0]; + postData.Should().NotBeNull(); + var postDataEtag = postData.ETag; + postDataEtag.Should().NotBeNullOrEmpty(); + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(data.Data); + } + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(data.ContentType); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(data.Data); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task CreatePost_MultipleData_ShouldWork(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + var textData = Encoding.UTF8.GetBytes("aaa"); + var imageData = ImageHelper.CreatePngWithSize(100, 50); + + var post = await client.TestPostAsync( + $"timelines/{generator(1)}/posts", + new HttpTimelinePostCreateRequest + { + DataList = new List + { + new HttpTimelinePostCreateRequestData + { + ContentType = MimeTypes.TextMarkdown, + Data = Convert.ToBase64String(textData) + }, + new HttpTimelinePostCreateRequestData + { + ContentType = MimeTypes.ImagePng, + Data = Convert.ToBase64String(imageData) + } + } + } + ); + + post.DataList.Should().NotBeNull().And.HaveCount(2); + + var postData0 = post.DataList[0]; + postData0.Should().NotBeNull(); + var postDataEtag0 = postData0.ETag; + postDataEtag0.Should().NotBeNullOrEmpty(); + + var postData1 = post.DataList[1]; + postData1.Should().NotBeNull(); + var postDataEtag1 = postData1.ETag; + postDataEtag1.Should().NotBeNullOrEmpty(); + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag0); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(textData); + } + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/0"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag0); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.TextMarkdown); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(textData); + } + + { + var response = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data/1"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.ETag.Should().NotBeNull(); + response.Headers.ETag!.Tag.Should().Be(postDataEtag1); + response.Content.Headers.ContentType.Should().NotBeNull(); + response.Content.Headers.ContentType!.MediaType.Should().Be(MimeTypes.ImagePng); + + var body = await response.Content.ReadAsByteArrayAsync(); + body.Should().Equal(imageData); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Editable(TimelineNameGenerator generator) + { + HttpTimelinePost post; + + { + using var client = await CreateClientAsUser(); + post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest("a")); + + post.Editable.Should().BeTrue(); + } + + { + using var client = await CreateClientAs(2); + var post2 = await client.TestGetAsync($"timelines/{generator(1)}/posts/{post.Id}"); + post2.Editable.Should().BeFalse(); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Markdown_Url_Map(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](1) ![bbb](2)")); + + var res = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + var markdown = await res.Content.ReadAsStringAsync(); + + markdown.Should().MatchRegex(@$"\[aaa\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/1\)"); + markdown.Should().MatchRegex(@$"\[bbb\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/2\)"); + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Markdown_Delete_Test(TimelineNameGenerator generator) + { + { + using var client = await CreateClientAs(2); + var post = await client.TestPostAsync($"timelines/{generator(2)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); + await client.TestDeleteAsync($"timelines/{generator(2)}/posts/{post.Id}"); + } + + { + using var client = await CreateClientAsUser(); + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](https://crupest.life)")); + await client.TestDeleteAsync($"timelines/{generator(1)}/posts/{post.Id}"); + } + } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_List_Pagination_Test(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + var posts = new List(); + for (int i = 0; i < 50; i++) + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateTextPostRequest(i.ToString())); + posts.Add(post); + } + + { + var p = await client.TestGetAsync>($"timelines/{generator(1)}/posts?page=2&numberPerPage=10"); + p.Should().BeEquivalentTo(posts.Skip(10).Take(10)); + } + } + + [Theory] + [InlineData("")] + [InlineData("default")] + public async Task Post_Color_Patch_Default(string value) + { + using var client = await CreateClientAsUser(); + + var post = await client.TestPostAsync("timelines/t1/posts", CreateTextPostRequest("aaa", color: "#111111")); + + var post2 = await client.TestPatchAsync($"timelines/t1/posts/{post.Id}", new HttpTimelinePostPatchRequest { Color = value }); + post2.Color.Should().BeNull(); + } + } +} -- cgit v1.2.3