mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
296 lines
11 KiB
C#
296 lines
11 KiB
C#
using System.Security.Claims;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Accounts;
|
|
using RadicadeServer.API.Notifications;
|
|
using RadicadeServer.Clubs;
|
|
using RadicadeServer.Clubs.Announcements;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Auth;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// Player subscriptions (`clubs.rec.net/subscription/*`) and the personal club id they hand back.
|
|
///
|
|
/// The subscribe route only ever existed as a GET, so the client's `POST /subscription/{id}` fell off
|
|
/// the route table, and `GET /subscription/details/{accountId}` answered `clubId: 0`. That zero is the
|
|
/// id the client then posts its announcements to, which is why `POST /announcements/club/0` answered
|
|
/// 404 - the club record it named did not exist. These tests pin the whole loop: subscribe, read the
|
|
/// personal club id back, and post an announcement to it.
|
|
/// </summary>
|
|
[Collection(DbFactoryCollection.Name)]
|
|
public class ClubSubscriptionTests
|
|
{
|
|
private const long SubscriberId = 71;
|
|
private const long CreatorId = 72;
|
|
private const long StrangerId = 73;
|
|
|
|
private sealed class TestDbContextFactory : IDbContextFactory<RecDBContext>
|
|
{
|
|
private readonly DbContextOptions<RecDBContext> options = TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
public RecDBContext CreateDbContext() => new(options);
|
|
}
|
|
|
|
private static async Task<(RecDBContext Db, IDbContextFactory<RecDBContext> Previous)> CreateContextAsync()
|
|
{
|
|
var factory = new TestDbContextFactory();
|
|
var previous = Notify.DbFactory;
|
|
Notify.DbFactory = factory;
|
|
|
|
var db = factory.CreateDbContext();
|
|
db.Profiles.AddRange(ProfileFor(SubscriberId), ProfileFor(CreatorId), ProfileFor(StrangerId));
|
|
await db.SaveChangesAsync();
|
|
return (db, previous);
|
|
}
|
|
|
|
private static Profile ProfileFor(long id) => new()
|
|
{
|
|
accountId = id,
|
|
username = $"player{id}",
|
|
displayName = $"Player {id}",
|
|
profileImage = string.Empty,
|
|
createdAt = DateTime.UtcNow
|
|
};
|
|
|
|
private static void SetUser(Controller controller, long userId)
|
|
{
|
|
var identity = new ClaimsIdentity("TestAuth");
|
|
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToString()));
|
|
controller.ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(identity) }
|
|
};
|
|
}
|
|
|
|
private static void SetFormBody(Controller controller, string body)
|
|
{
|
|
var request = controller.HttpContext.Request;
|
|
request.ContentType = "application/x-www-form-urlencoded";
|
|
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
|
|
request.ContentLength = request.Body.Length;
|
|
}
|
|
|
|
private static void SetJsonBody(Controller controller, string body)
|
|
{
|
|
var request = controller.HttpContext.Request;
|
|
request.ContentType = "application/json";
|
|
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
|
|
request.ContentLength = request.Body.Length;
|
|
}
|
|
|
|
private static T Property<T>(object? value, string name)
|
|
{
|
|
if (value is IDictionary<string, object?> dictionary && dictionary.TryGetValue(name, out var entry))
|
|
return (T)entry!;
|
|
|
|
var property = value?.GetType().GetProperty(name);
|
|
Assert.NotNull(property);
|
|
return (T)property!.GetValue(value)!;
|
|
}
|
|
|
|
private static IEnumerable<long> Ids(object? value) => Assert.IsAssignableFrom<IEnumerable<long>>(value);
|
|
|
|
[Fact]
|
|
public async Task SubscribeRecordsTheRelationshipAndReportsTheCreatorsPersonalClub()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
var api = new ClubSubscriptionAPI(db);
|
|
SetUser(api, SubscriberId);
|
|
|
|
var personalClubId = PlayerSubscriptions.PersonalClubId(CreatorId);
|
|
Assert.True(personalClubId > 0);
|
|
|
|
var subscribed = Assert.IsType<OkObjectResult>(await api.Subscribe(CreatorId));
|
|
Assert.Equal(personalClubId, Property<long>(subscribed.Value, "value"));
|
|
|
|
var subscriptions = Assert.IsType<OkObjectResult>(await api.GetMySubscriptions());
|
|
Assert.Equal(new[] { CreatorId }, Ids(subscriptions.Value).ToArray());
|
|
|
|
var details = Assert.IsType<OkObjectResult>(await api.GetAccountSubscriptionDetails(CreatorId));
|
|
Assert.Equal(personalClubId, Property<long>(details.Value, "clubId"));
|
|
Assert.Equal(CreatorId, Property<long>(details.Value, "accountId"));
|
|
Assert.Equal(1, Property<int>(details.Value, "subscriberCount"));
|
|
|
|
var subscribers = Assert.IsType<OkObjectResult>(await api.GetMySubscribers());
|
|
Assert.Empty(Ids(subscribers.Value));
|
|
|
|
var creatorView = new ClubSubscriptionAPI(db);
|
|
SetUser(creatorView, CreatorId);
|
|
var creatorSubscribers = Assert.IsType<OkObjectResult>(await creatorView.GetMySubscribers());
|
|
Assert.Equal(new[] { SubscriberId }, Ids(creatorSubscribers.Value).ToArray());
|
|
|
|
Assert.IsType<OkObjectResult>(await api.Unsubscribe(CreatorId));
|
|
Assert.Empty(Ids(Assert.IsType<OkObjectResult>(await api.GetMySubscriptions()).Value));
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BareSubscribeReadsThePlayerIdFromTheBody()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
var api = new ClubSubscriptionAPI(db);
|
|
SetUser(api, SubscriberId);
|
|
SetJsonBody(api, $"{{\"playerId\":{CreatorId}}}");
|
|
|
|
var subscribed = Assert.IsType<OkObjectResult>(await api.SubscribeBare());
|
|
Assert.Equal(PlayerSubscriptions.PersonalClubId(CreatorId), Property<long>(subscribed.Value, "value"));
|
|
|
|
var subscriptions = Assert.IsType<OkObjectResult>(await api.GetMySubscriptions());
|
|
Assert.Equal(new[] { CreatorId }, Ids(subscriptions.Value).ToArray());
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnnouncementToClubZeroLandsOnTheCallersPersonalClub()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
var api = new ClubAnnouncementsAPI(db);
|
|
SetUser(api, CreatorId);
|
|
SetFormBody(api, "title=Hello&body=World");
|
|
|
|
var posted = Assert.IsType<OkObjectResult>(await api.PostClubAnnouncements(0));
|
|
var announcementId = Property<long>(posted.Value, "value");
|
|
Assert.True(announcementId > 0);
|
|
|
|
var personalClubId = PlayerSubscriptions.PersonalClubId(CreatorId);
|
|
var feed = Assert.IsType<OkObjectResult>(api.GetClubAnnouncements(personalClubId));
|
|
using var document = JsonDocument.Parse(JsonSerializer.Serialize(feed.Value));
|
|
|
|
var value = document.RootElement.GetProperty("value");
|
|
Assert.Equal(personalClubId, value.GetProperty("ClubId").GetInt64());
|
|
var announcements = value.GetProperty("Announcements");
|
|
Assert.Equal(1, announcements.GetArrayLength());
|
|
Assert.Equal(announcementId, announcements[0].GetProperty("AnnouncementId").GetInt64());
|
|
Assert.Equal(announcementId, value.GetProperty("LastAnnouncementId").GetInt64());
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnnouncementCannotBePostedToAnotherAccountsPersonalClub()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
var api = new ClubAnnouncementsAPI(db);
|
|
SetUser(api, StrangerId);
|
|
SetFormBody(api, "title=Hi&body=There");
|
|
|
|
var result = await api.PostClubAnnouncements(PlayerSubscriptions.PersonalClubId(CreatorId));
|
|
|
|
Assert.Equal(StatusCodes.Status403Forbidden, Assert.IsType<ObjectResult>(result).StatusCode);
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BannerImageAcceptsTheNameFromAJsonBody()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
var api = new AccountsAPI(db);
|
|
SetUser(api, CreatorId);
|
|
SetJsonBody(api, "{\"imageName\":\"banner-abc.jpg\"}");
|
|
|
|
var result = Assert.IsType<OkObjectResult>(await api.SetBannerImage(null));
|
|
|
|
Assert.True(Property<bool>(result.Value, "success"));
|
|
Assert.Equal("banner-abc.jpg",
|
|
(await db.Profiles.AsNoTracking().SingleAsync(x => x.accountId == CreatorId)).bannerImage);
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BannerImageWithoutAReadableNameIsStillRefusedAndKeepsTheStoredBanner()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
db.Profiles.Single(x => x.accountId == CreatorId).bannerImage = "keep-me.jpg";
|
|
await db.SaveChangesAsync();
|
|
|
|
var api = new AccountsAPI(db);
|
|
SetUser(api, CreatorId);
|
|
SetJsonBody(api, "{}");
|
|
|
|
var result = await api.SetBannerImage(null);
|
|
|
|
Assert.IsType<BadRequestObjectResult>(result);
|
|
Assert.Equal("keep-me.jpg",
|
|
(await db.Profiles.AsNoTracking().SingleAsync(x => x.accountId == CreatorId)).bannerImage);
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InvitingAnExistingMemberChangesTheirRoleInsteadOfRefusing()
|
|
{
|
|
var (db, previous) = await CreateContextAsync();
|
|
await using var disposeDb = db;
|
|
try
|
|
{
|
|
var club = await ClubStore.CreateAsync(db, CreatorId, "Test Club", string.Empty);
|
|
await ClubStore.InviteMemberAsync(db, club.clubId, StrangerId, ClubMembershipTypes.Member, CreatorId);
|
|
var accepted = await ClubStore.AcceptInvitationAsync(db, club.clubId, StrangerId);
|
|
Assert.NotNull(accepted);
|
|
Assert.Equal(ClubMembershipTypes.Member, ClubStore.GetMembershipType(accepted!, StrangerId));
|
|
|
|
var api = new ClubsAPI(db);
|
|
SetUser(api, CreatorId);
|
|
SetFormBody(api, $"accountId={StrangerId}&membershipType={ClubMembershipTypes.Moderator}");
|
|
|
|
var result = await api.InviteMember(club.clubId);
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
var updated = await ClubStore.GetAsync(db, club.clubId);
|
|
Assert.Equal(ClubMembershipTypes.Moderator, ClubStore.GetMembershipType(updated!, StrangerId));
|
|
}
|
|
finally
|
|
{
|
|
Notify.DbFactory = previous;
|
|
}
|
|
}
|
|
}
|