mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
150 lines
5.5 KiB
C#
150 lines
5.5 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API;
|
|
using RadicadeServer.Admin;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Config;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// The announcement feed lives as one JSON array in the ServerConfig "Announcement" row, and the
|
|
/// panel's save re-derives the next id from the ids already in it. A row saved by an older panel can
|
|
/// carry its id as a JSON string ("5"); reading that with GetValue<long>() threw, the route's
|
|
/// catch answered HTTP 200 with an error body and no write, and the panel therefore looked like it
|
|
/// had saved while nothing reached the client feed. These tests pin the tolerant read.
|
|
/// </summary>
|
|
public class AdminAnnouncementTests
|
|
{
|
|
private const string Secret = "test-bot-secret";
|
|
|
|
private static RecDBContext NewDb() => new(
|
|
TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options);
|
|
|
|
private static JsonElement Payload(string json) => JsonDocument.Parse(json).RootElement;
|
|
|
|
private static string StoredValue(RecDBContext db) =>
|
|
db.ServerConfig.First(x => x.Key == "Announcement").Value;
|
|
|
|
private static async Task<T> AsAdmin<T>(Func<Task<T>> work)
|
|
{
|
|
var originalKey = DBConfig.AdminApiKey;
|
|
var originalLogs = DBConfig.PersistentLogs;
|
|
DBConfig.AdminApiKey = Secret;
|
|
DBConfig.PersistentLogs = Path.Join(Path.GetTempPath(), "radicade-admin-announcement-tests-" + Guid.NewGuid().ToString("N"));
|
|
try
|
|
{
|
|
return await work();
|
|
}
|
|
finally
|
|
{
|
|
DBConfig.AdminApiKey = originalKey;
|
|
DBConfig.PersistentLogs = originalLogs;
|
|
}
|
|
}
|
|
|
|
private static async Task<JsonObject> SaveAsync(AdminPanelAPI api, string rowJson)
|
|
=> await AsAdmin(async () =>
|
|
{
|
|
var ok = Assert.IsType<OkObjectResult>(await api.Insert("announcements", Secret, Payload(rowJson)));
|
|
var created = Assert.IsType<JsonObject>(ok.Value);
|
|
Assert.False(created.ContainsKey("error"), created["error"]?.ToString());
|
|
return created;
|
|
});
|
|
|
|
[Fact]
|
|
public async Task ASaveOnAnEmptyConfigCreatesTheAnnouncementTheClientFeedServes()
|
|
{
|
|
var db = NewDb();
|
|
|
|
var created = await SaveAsync(
|
|
new AdminPanelAPI(db),
|
|
"{\"type\":\"Regular Announcement\",\"message\":\"Double XP all weekend.\",\"title\":\"Weekend event\"}");
|
|
|
|
Assert.Equal(1L, created["id"]!.GetValue<long>());
|
|
|
|
var feed = new AnnouncementAPI(db).GetAnnouncement();
|
|
var served = Assert.Single(feed!);
|
|
Assert.Equal(1L, served.AnnouncementId);
|
|
Assert.Equal("Weekend event", served.Title);
|
|
Assert.Equal("Double XP all weekend.", served.Body);
|
|
Assert.True(served.IsMostRecent);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ASaveStillLandsWhenAnExistingRowCarriesItsIdAsAString()
|
|
{
|
|
var db = NewDb();
|
|
db.ServerConfig.Add(new ServerConfigVal("Announcement",
|
|
"[{\"id\":\"5\",\"type\":\"Regular Announcement\",\"message\":\"legacy row\",\"title\":\"Legacy\"}]"));
|
|
await db.SaveChangesAsync();
|
|
|
|
var created = await SaveAsync(
|
|
new AdminPanelAPI(db),
|
|
"{\"type\":\"Regular Announcement\",\"message\":\"second\",\"title\":\"Second\"}");
|
|
|
|
// The save numbers the new row after the legacy one, and normalises that id on the way
|
|
// through - GetValue<long>() on the stored row is what used to throw.
|
|
Assert.Equal(6L, created["id"]!.GetValue<long>());
|
|
|
|
var stored = JsonNode.Parse(StoredValue(db))!.AsArray();
|
|
Assert.Equal(2, stored.Count);
|
|
Assert.Equal(5L, stored[0]!["id"]!.GetValue<long>());
|
|
Assert.Equal(6L, stored[1]!["id"]!.GetValue<long>());
|
|
|
|
// Both rows are now servable: the legacy entry keeps the fields it was saved with.
|
|
var feed = new AnnouncementAPI(db).GetAnnouncement();
|
|
Assert.Equal(2, feed!.Count);
|
|
Assert.Equal("Second", feed[0].Title);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ADeleteStillLandsWhenTheRowCarriesItsIdAsAString()
|
|
{
|
|
var db = NewDb();
|
|
db.ServerConfig.Add(new ServerConfigVal("Announcement",
|
|
"[{\"id\":\"9\",\"type\":\"Regular Announcement\",\"message\":\"legacy row\"}]"));
|
|
await db.SaveChangesAsync();
|
|
|
|
var api = new AdminPanelAPI(db);
|
|
var removed = await AsAdmin(async () =>
|
|
{
|
|
var ok = Assert.IsType<OkObjectResult>(
|
|
await api.Delete("announcements", Secret, null, "9", null, null, null, null, null));
|
|
return Assert.IsType<List<object>>(ok.Value);
|
|
});
|
|
|
|
Assert.Single(removed);
|
|
Assert.Equal("[]", StoredValue(db));
|
|
}
|
|
|
|
// The panel posts back every field of its row, so the boxes the admin left empty arrive as an
|
|
// explicit JSON null rather than as missing keys. JsonElement.TryGetInt32 throws on a null token
|
|
// (it only returns false for numbers it cannot represent), which made "minutes": null and
|
|
// "link_type": null fail the whole save with "The requested operation requires an element of
|
|
// type 'Number', but the target element has type 'Null'."
|
|
[Fact]
|
|
public async Task ASaveLandsWhenThePanelSendsItsEmptyFieldsAsNull()
|
|
{
|
|
var db = NewDb();
|
|
|
|
var created = await SaveAsync(
|
|
new AdminPanelAPI(db),
|
|
"{\"id\":null,\"type\":\"Regular Announcement\",\"message\":\"hi\",\"image\":null,\"minutes\":null," +
|
|
"\"expires_at\":null,\"created_at\":null,\"title\":\"Hi\",\"link_name\":null,\"link_type\":null," +
|
|
"\"link_uri\":null,\"AnnouncementId\":null,\"Body\":\"hi\",\"IsMostRecent\":null,\"Platform\":null}");
|
|
|
|
Assert.Equal(1L, created["id"]!.GetValue<long>());
|
|
Assert.Null(created["minutes"]);
|
|
Assert.Equal("Learn More", created["link_name"]!.GetValue<string>());
|
|
|
|
var feed = new AnnouncementAPI(db).GetAnnouncement();
|
|
Assert.Equal("Hi", Assert.Single(feed!).Title);
|
|
}
|
|
}
|