The API had no tests. These run against the real application through WebApplicationFactory — same pipeline, same Identity configuration, same JWT validation — with only the SQLite file, upload folder and signing key swapped, so a passing test says something about what ships. The ownership tests pin the rule the old PHP API got wrong: a second user sees an empty library, gets 404 (not 403, which would confirm the id exists) when reading, updating or deleting someone else's game, and cannot reassign ownership by putting ownerId or userId in the request body. Also covered: the password policy, that login is indistinguishable between a wrong password and an absent user, that the availability endpoint leaks no row data, that a token signed with an untrusted key is refused, that the sort parameter is allow-listed rather than interpolated, and that uploads must decode as an image regardless of extension or content type. 36 tests, ~1s. Program is now declared public partial so the test host can reach it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
2.7 KiB
C#
91 lines
2.7 KiB
C#
using System.IO.Compression;
|
|
|
|
namespace LudosData.Api.Tests;
|
|
|
|
/// <summary>
|
|
/// Builds a real PNG in memory, so upload tests exercise the actual decode path
|
|
/// rather than a fixture file that could drift or go missing.
|
|
/// </summary>
|
|
public static class TestImages
|
|
{
|
|
public static byte[] Png(int width, int height)
|
|
{
|
|
// One filter byte per scanline, then RGB triples.
|
|
var raw = new byte[height * (1 + width * 3)];
|
|
var offset = 0;
|
|
for (var y = 0; y < height; y++)
|
|
{
|
|
raw[offset++] = 0; // filter: none
|
|
for (var x = 0; x < width; x++)
|
|
{
|
|
raw[offset++] = (byte)(x * 8 % 256);
|
|
raw[offset++] = (byte)(y * 8 % 256);
|
|
raw[offset++] = 128;
|
|
}
|
|
}
|
|
|
|
using var output = new MemoryStream();
|
|
output.Write([0x89, (byte)'P', (byte)'N', (byte)'G', 0x0D, 0x0A, 0x1A, 0x0A]);
|
|
|
|
var header = new byte[13];
|
|
WriteBigEndian(header, 0, width);
|
|
WriteBigEndian(header, 4, height);
|
|
header[8] = 8; // bit depth
|
|
header[9] = 2; // colour type: truecolour
|
|
WriteChunk(output, "IHDR", header);
|
|
|
|
WriteChunk(output, "IDAT", ZlibCompress(raw));
|
|
WriteChunk(output, "IEND", []);
|
|
|
|
return output.ToArray();
|
|
}
|
|
|
|
private static void WriteBigEndian(byte[] buffer, int index, int value)
|
|
{
|
|
buffer[index] = (byte)(value >> 24);
|
|
buffer[index + 1] = (byte)(value >> 16);
|
|
buffer[index + 2] = (byte)(value >> 8);
|
|
buffer[index + 3] = (byte)value;
|
|
}
|
|
|
|
private static void WriteChunk(Stream stream, string type, byte[] data)
|
|
{
|
|
var length = new byte[4];
|
|
WriteBigEndian(length, 0, data.Length);
|
|
stream.Write(length);
|
|
|
|
var typeBytes = System.Text.Encoding.ASCII.GetBytes(type);
|
|
stream.Write(typeBytes);
|
|
stream.Write(data);
|
|
|
|
var crc = Crc32([.. typeBytes, .. data]);
|
|
var crcBytes = new byte[4];
|
|
WriteBigEndian(crcBytes, 0, unchecked((int)crc));
|
|
stream.Write(crcBytes);
|
|
}
|
|
|
|
private static byte[] ZlibCompress(byte[] data)
|
|
{
|
|
using var output = new MemoryStream();
|
|
using (var deflate = new ZLibStream(output, CompressionLevel.Fastest, leaveOpen: true))
|
|
{
|
|
deflate.Write(data);
|
|
}
|
|
return output.ToArray();
|
|
}
|
|
|
|
private static uint Crc32(byte[] data)
|
|
{
|
|
var crc = 0xFFFFFFFFu;
|
|
foreach (var b in data)
|
|
{
|
|
crc ^= b;
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
crc = (crc & 1) != 0 ? (crc >> 1) ^ 0xEDB88320u : crc >> 1;
|
|
}
|
|
}
|
|
return crc ^ 0xFFFFFFFFu;
|
|
}
|
|
}
|