mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using RadicadeServer.Images;
|
||||
using Xunit;
|
||||
|
||||
namespace RadicadeServer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The image route answers by name, and the name a client asks for is not always the name on
|
||||
/// disk. Two real cases drove this: a custom-avatar design is stored as
|
||||
/// <c><guid>.bin</c> while its bytes are a PNG, and the default room image ships as
|
||||
/// <c>DefaultRoomImage.png</c> while the server asks for the bare <c>DefaultRoomImage</c>.
|
||||
/// Both resolved to nothing, and nothing is not "no picture" -- it is an empty 404 body, which
|
||||
/// the 2023 client turns into a broken surface (and, for a room image, a fatal load failure).
|
||||
/// </summary>
|
||||
public class ImageAssetResolutionTests : IDisposable
|
||||
{
|
||||
private readonly string root = Directory.CreateTempSubdirectory("radicade-images-test-").FullName;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private string Write(string name, string content = "x")
|
||||
{
|
||||
var path = Path.Join(root, name);
|
||||
File.WriteAllText(path, content);
|
||||
return path;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheRequestedNameIsFoundFirst()
|
||||
{
|
||||
var expected = Write("photo.png");
|
||||
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("photo.png", new[] { root }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ADesignStoredAsBinIsFoundByTheNameTheClientAsksFor()
|
||||
{
|
||||
// Exactly what the customizer wrote before designs moved to the image CDN: the bytes
|
||||
// are a PNG, the name on disk ends in .bin, and the client asks for that name.
|
||||
var expected = Write("a07188bb-9f99-442d-a2ff-2aebad2ca7fe.bin");
|
||||
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("a07188bb-9f99-442d-a2ff-2aebad2ca7fe.bin", new[] { root }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AMissingExtensionIsTriedOnTheSameStem()
|
||||
{
|
||||
// The default-image case: the server asks for "DefaultRoomImage", the deployment ships
|
||||
// "DefaultRoomImage.png", and the extension search used to be skipped for this name.
|
||||
var expected = Write("DefaultRoomImage.png");
|
||||
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("DefaultRoomImage", new[] { root }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnExtensionThatDoesNotDescribeTheContentStillFindsTheFile()
|
||||
{
|
||||
var expected = Write("design.png");
|
||||
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("design.bin", new[] { root }));
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("design.jpeg", new[] { root }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RootsAreSearchedInOrder()
|
||||
{
|
||||
var first = Directory.CreateDirectory(Path.Join(root, "images")).FullName;
|
||||
var second = Directory.CreateDirectory(Path.Join(root, "custom")).FullName;
|
||||
var expected = Path.Join(first, "shirt.png");
|
||||
File.WriteAllText(expected, "x");
|
||||
File.WriteAllText(Path.Join(second, "shirt.png"), "x");
|
||||
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("shirt.png", new[] { first, second }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ANamedRootIsPreferredOverALaterRootsVariant()
|
||||
{
|
||||
var images = Directory.CreateDirectory(Path.Join(root, "images")).FullName;
|
||||
var custom = Directory.CreateDirectory(Path.Join(root, "custom")).FullName;
|
||||
var expected = Path.Join(custom, "shirt.bin");
|
||||
File.WriteAllText(expected, "x");
|
||||
File.WriteAllText(Path.Join(images, "shirt.png"), "x");
|
||||
|
||||
Assert.Equal(expected, ImageAPI.ResolveAsset("shirt.bin", new[] { images, custom }));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(".")]
|
||||
[InlineData("..")]
|
||||
[InlineData("../outside.png")]
|
||||
[InlineData("..\\outside.png")]
|
||||
[InlineData("sub/../outside.png")]
|
||||
public void NamesThatDoNotNameAFileInsideARootResolveToNothing(string name)
|
||||
{
|
||||
Assert.Null(ImageAPI.ResolveAsset(name, new[] { root }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnUnknownNameResolvesToNothing()
|
||||
{
|
||||
Assert.Null(ImageAPI.ResolveAsset("definitely-missing.png", new[] { root }));
|
||||
Assert.Null(ImageAPI.ResolveAsset("definitely-missing.bin", new[] { root }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheServedRootsStillIncludeTheDesignRoot()
|
||||
{
|
||||
// A design written before designs moved to the image CDN only stays reachable while
|
||||
// CDN/Custom is still searched, so dropping it here would silently blank old shirts.
|
||||
var roots = ImageAPI.AssetRoots();
|
||||
|
||||
Assert.Contains(roots, r => r.EndsWith(Path.Join("CDN", "Images")));
|
||||
Assert.Contains(roots, r => r.EndsWith(Path.Join("CDN", "Custom")));
|
||||
Assert.Contains(roots, r => r.EndsWith(Path.Join("CDN", "Storage")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user