using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Yavsc.Blogspot; using Yavsc.Models; using Yavsc.Models.Access; using Yavsc.Models.Blog; using Yavsc.Models.Relationship; using Yavsc.Server.Helpers; namespace Yavsc.Blogs.Tests; /// /// Tests that (the /// "posts-by-author-for-this-reader" query) honours the same /// Visibility rules as . /// /// The two code paths duplicate the ACL/Visibility filter /// (one in the listing query, one in the per-author query); /// these tests catch the case where the two diverge — the kind /// of regression that's easy to miss in a code review because /// both filters look correct in isolation. /// /// Uses the same in-memory ApplicationDbContext /// scaffold as but /// exercises the helper directly, without going through HTTP, /// because is the unit /// under test. /// [Collection("JwtClaimMapping")] public sealed class UserHelpersVisibilityTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; public UserHelpersVisibilityTests(BlogsWebServerFixture fixture) { _fixture = fixture; } private void ResetDatabase() { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureDeleted(); db.Database.EnsureCreated(); db.Users.Add(new ApplicationUser { Id = "alice", UserName = "alice", Email = "alice@example.com", EmailConfirmed = true, }); db.Users.Add(new ApplicationUser { Id = "bob", UserName = "bob", Email = "bob@example.com", EmailConfirmed = true, }); db.SaveChanges(); } private long SeedPost(string authorId, Visibility visibility, params long[] aclCircleIds) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var post = new BlogPost { AuthorId = authorId, Title = $"post-by-{authorId}", Article = "test article", Visibility = visibility, DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, }; db.BlogSpot.Add(post); db.SaveChanges(); foreach (var cid in aclCircleIds) { db.CircleAuthorizationToBlogPost.Add(new CircleAuthorizationToBlogPost { BlogPostId = post.Id, CircleId = cid, Comment = false, }); } db.SaveChanges(); return post.Id; } private long SeedCircle(string ownerId, string name) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var circle = new Circle { OwnerId = ownerId, Name = name }; db.Circle.Add(circle); db.SaveChanges(); return circle.Id; } private void AddMember(long circleId, string memberId) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); db.CircleMembers.Add(new CircleMember { CircleId = circleId, MemberId = memberId }); db.SaveChanges(); } private List UserPostsIds(string posterId, string readerId) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); return db.UserPosts(posterId, readerId).Select(p => p.Id).ToList(); } [Fact] public void UserPosts_returns_only_private_posts_to_their_author() { ResetDatabase(); SeedPost("alice", Visibility.Private); var aliceSees = UserPostsIds("alice", "alice"); var bobSees = UserPostsIds("alice", "bob"); Assert.Single(aliceSees); Assert.Empty(bobSees); } [Fact] public void UserPosts_returns_public_posts_with_empty_ACL_to_anyone() { ResetDatabase(); SeedPost("alice", Visibility.Public); var bobSees = UserPostsIds("alice", "bob"); Assert.Single(bobSees); } [Fact] public void UserPosts_narrows_public_posts_with_nonempty_ACL() { ResetDatabase(); var circleId = SeedCircle("alice", "Famille"); AddMember(circleId, "alice"); // AddMember above adds alice, but we want bob NOT in // the circle, so we add bob to a different circle only: var otherCircleId = SeedCircle("alice", "Travail"); AddMember(otherCircleId, "bob"); // Make the post readable only to Famille: SeedPost("alice", Visibility.Public, circleId); var bobSees = UserPostsIds("alice", "bob"); Assert.Empty(bobSees); } [Fact] public void UserPosts_lets_acl_members_read_public_posts_even_if_not_author() { ResetDatabase(); var circleId = SeedCircle("alice", "Famille"); AddMember(circleId, "bob"); SeedPost("alice", Visibility.Public, circleId); var bobSees = UserPostsIds("alice", "bob"); Assert.Single(bobSees); } }