Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions LikeServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

public class Class1
{
public Class1()
{
}
}
43 changes: 43 additions & 0 deletions SocialPluse.IntegrationTests/Authentication/TestAuthHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text.Encodings.Web;

namespace SocialPluse.IntegrationTests.Authentication
{
public class TestAuthHandler
: AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string TestUserId =
"00000000-0000-0000-0000-000000000001";

public TestAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder)
: base(options, logger, encoder)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, TestUserId),
new Claim("sub", TestUserId)
};

var identity = new ClaimsIdentity(claims, "Test");

var principal = new ClaimsPrincipal(identity);

var ticket = new AuthenticationTicket(
principal,
"Test");

return Task.FromResult(
AuthenticateResult.Success(ticket));
}
}
}
21 changes: 21 additions & 0 deletions SocialPluse.IntegrationTests/BaseIntegrationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Net.Http.Headers;
using Xunit;

namespace SocialPluse.IntegrationTests
{
// 1. We remove IClassFixture<SocialPluseApiFactory>
// 2. We add the Collection attribute linking it to the shared infrastructure
[Collection("Shared API Collection")]
public abstract class BaseIntegrationTest
{
protected readonly HttpClient Client;

protected BaseIntegrationTest(SocialPluseApiFactory factory)
{
Client = factory.CreateClient();

Client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Test");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using FluentAssertions;
using System.Net;

namespace SocialPluse.IntegrationTests.ControllersTests
{
public class AnalyticsControllerTests : BaseIntegrationTest
{
public AnalyticsControllerTests(SocialPluseApiFactory factory) : base(factory) { }

[Fact]
public async Task GetMyAnalytics_ShouldReturnOk()
{
// Act
var response = await Client.GetAsync("/api/analytics/me");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task GetTrendingTopics_ShouldReturnOk()
{
// Act
var response = await Client.GetAsync("/api/analytics/trending?limit=5");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using FluentAssertions;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using SocialPluse.Persistence.IdentityData.Entities;
using SocialPluse.Shared.DTOs.Auth;
using System.Net;
using System.Net.Http.Json;

namespace SocialPluse.IntegrationTests.ControllersTests
{
public class AuthControllerTests : BaseIntegrationTest
{
private readonly IServiceProvider _services;
private readonly HttpClient _unauthenticatedClient;

public AuthControllerTests(SocialPluseApiFactory factory) : base(factory)
{
_services = factory.Services;
// A clean client that bypasses the BaseIntegrationTest's default Auth header
_unauthenticatedClient = factory.CreateClient();
}

[Fact]
public async Task Register_WhenValidPayload_ShouldReturnAuthResponse_WithRealJwt()
{
// Arrange
// Take only the first 8 characters of the GUID to stay under the 20-character limit
var uniqueId = Guid.NewGuid().ToString("N").Substring(0, 8);

var request = new RegisterRequest
{
Username = $"usr_{uniqueId}", // "usr_" (4) + 8 = 12 characters. Perfect.
Email = $"{uniqueId}@test.com",
Password = "StrongPassword123!"
};

// Act - Using the unauthenticated client
var response = await _unauthenticatedClient.PostAsJsonAsync("/api/auth/register", request);

// Assert
var responseContent = await response.Content.ReadAsStringAsync();

response.IsSuccessStatusCode.Should().BeTrue(
$"because the API should accept the registration, but it returned: {responseContent}");

var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
authResponse.Should().NotBeNull();
authResponse!.AccessToken.Should().NotBeNullOrEmpty();
}

[Fact]
public async Task Login_WhenCredentialsValid_ShouldReturnOk()
{
// Arrange - The SENIOR way: Seed the user directly via Identity UserManager
var uniqueId = Guid.NewGuid().ToString("N");
var email = $"login_{uniqueId}@test.com";
var password = "StrongPassword123!";

using (var scope = _services.CreateScope())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AppUser>>();
var user = new AppUser { UserName = $"login_{uniqueId}", Email = email };
await userManager.CreateAsync(user, password);
}

var loginReq = new LoginRequest { Email = email, Password = password };

// Act
var response = await _unauthenticatedClient.PostAsJsonAsync("/api/auth/login", loginReq);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using SocialPluse.Domain.Entities;
using SocialPluse.IntegrationTests.Authentication;
using SocialPluse.Persistence.DbContexts;
using SocialPluse.Persistence.IdentityData.Entities;
using System.Net;

namespace SocialPluse.IntegrationTests.ControllersTests
{
public class BlocksControllerTests : BaseIntegrationTest
{
private readonly IServiceProvider _services;

public BlocksControllerTests(SocialPluseApiFactory factory) : base(factory)
{
_services = factory.Services;
}

private async Task<Guid> SeedTargetUserAsync()
{
var targetId = Guid.NewGuid();
using var scope = _services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

dbContext.Users.Add(new AppUser
{
Id = targetId,
UserName = $"block_target_{targetId:N}",
Email = $"block_{targetId:N}@test.com"
});

await dbContext.SaveChangesAsync();
return targetId;
}

[Fact]
public async Task BlockUser_WhenUserExists_ShouldReturnOk()
{
// Arrange
var targetId = await SeedTargetUserAsync();

// Act
var response = await Client.PostAsync($"/api/blocks/{targetId}", null);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task UnblockUser_WhenBlocked_ShouldReturnNoContent()
{
// Arrange - Seed the target user AND the block relationship directly
var targetId = await SeedTargetUserAsync();
using (var scope = _services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Blocks.Add(new Block
{
BlockerId = Guid.Parse(TestAuthHandler.TestUserId),
BlockedId = targetId,
CreatedAt = DateTime.UtcNow
});
await dbContext.SaveChangesAsync();
}

// Act
var response = await Client.DeleteAsync($"/api/blocks/{targetId}");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using SocialPluse.Domain.Entities;
using SocialPluse.Persistence.DbContexts;
using SocialPluse.IntegrationTests.Authentication;
using System.Net;

namespace SocialPluse.IntegrationTests.ControllersTests
{
public class BookmarksControllerTests : BaseIntegrationTest
{
private readonly IServiceProvider _services;

public BookmarksControllerTests(SocialPluseApiFactory factory) : base(factory)
{
_services = factory.Services;
}

private async Task<Guid> SeedPostDirectlyAsync(string text)
{
var postId = Guid.NewGuid();
using var scope = _services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

dbContext.Posts.Add(new Post
{
Id = postId,
// The TestUser is seeded in the Factory, so we can use them as the author
AuthorId = Guid.Parse(TestAuthHandler.TestUserId),
Text = text,
CreatedAt = DateTime.UtcNow
});

await dbContext.SaveChangesAsync();
return postId;
}

[Fact]
public async Task ToggleBookmark_WhenPostExists_ShouldReturnOk()
{
// Arrange - 100% isolated from the PostsController
var postId = await SeedPostDirectlyAsync("A post to bookmark.");

// Act
var response = await Client.PostAsync($"/api/posts/{postId}/bookmark?shouldBookmark=true", null);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task GetBookmarks_WhenBookmarked_ShouldReturnList()
{
// Arrange
var postId = await SeedPostDirectlyAsync("My favorite post.");

// We can use the endpoint here because ToggleBookmark is the feature we need to pre-load the DB,
// though a true purist would seed the Bookmark entity directly too!
await Client.PostAsync($"/api/posts/{postId}/bookmark?shouldBookmark=true", null);

// Act
var response = await Client.GetAsync("/api/bookmarks?limit=10");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
content.Should().Contain("My favorite post.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using FluentAssertions;
using SocialPluse.Shared.DTOs.Comments;
using SocialPluse.Shared.DTOs.Posts;
using System.Net;
using System.Net.Http.Json;

namespace SocialPluse.IntegrationTests.ControllersTests
{
public class CommentsControllerTests : BaseIntegrationTest
{
public CommentsControllerTests(SocialPluseApiFactory factory) : base(factory) { }

private async Task<Guid> CreateTestPostAsync()
{
var request = new CreatePostRequest { Text = "A post to be commented on." };
var response = await Client.PostAsJsonAsync("/api/posts", request);
response.EnsureSuccessStatusCode();

var post = await response.Content.ReadFromJsonAsync<PostDto>();
return post!.Id;
}

[Fact]
public async Task AddComment_WhenValidPayload_ShouldReturnOk()
{
// Arrange
var postId = await CreateTestPostAsync();
var request = new CreateCommentRequest { Text = "This is a test comment!" };

// Act
var response = await Client.PostAsJsonAsync($"/api/posts/{postId}/comments", request);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task GetComments_WhenCommentsExist_ShouldReturnList()
{
// Arrange
var postId = await CreateTestPostAsync();
var request = new CreateCommentRequest { Text = "My first comment." };
await Client.PostAsJsonAsync($"/api/posts/{postId}/comments", request);

// Act
var response = await Client.GetAsync($"/api/posts/{postId}/comments?limit=10");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);

// If your GetComments API returns a wrapper (like a PaginatedList), you might need to adjust this type
var responseContent = await response.Content.ReadAsStringAsync();
responseContent.Should().Contain("My first comment.");
}
}
}
Loading
Loading