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