Skip to content

Commit

Permalink
ResourceEndpoints: 100% test coverage (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvyden authored Nov 8, 2023
2 parents 16c5d43 + 93d7717 commit a95bb2b
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 200 deletions.
21 changes: 6 additions & 15 deletions Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ public Response UploadAsset(RequestContext context, string hash, string type, by
if (!CommonPatterns.Sha1Regex().IsMatch(hash)) return BadRequest;

bool isPSP = context.IsPSP();
string assetPath = isPSP ? $"psp/{hash}" : hash;

string assetPath = hash;
if (isPSP)
assetPath = $"psp/{hash}";

if (dataStore.ExistsInStore(assetPath))
return Conflict;

Expand Down Expand Up @@ -97,20 +99,9 @@ public Response GetResource(RequestContext context, string hash, IDataStore data
[NullStatusCode(BadRequest)]
public SerializedResourceList? GetAssetsMissingFromStore(RequestContext context, SerializedResourceList body, IDataStore dataStore)
{
if (context.IsPSP())
{
//Iterate over all the items
for (int i = 0; i < body.Items.Count; i++)
{
string item = body.Items[i];
//Point them into the `psp` folder
body.Items[i] = $"psp/{item}";
}
}

if(body.Items.Any(hash => !CommonPatterns.Sha1Regex().IsMatch(hash)))
return null;
return new SerializedResourceList(body.Items.Where(r => !dataStore.ExistsInStore(r)));

return new SerializedResourceList(body.Items.Where(r => !dataStore.ExistsInStore(context.IsPSP() ? $"psp/{r}" : r)));
}
}
5 changes: 3 additions & 2 deletions RefreshTests.GameServer/GameServer/GameServerTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Bunkum.Core.Storage;
using Bunkum.Protocols.Http.Direct;
using NotEnoughLogs;
using RefreshTests.GameServer.Logging;
Expand All @@ -15,7 +16,7 @@ public class GameServerTest
});

// ReSharper disable once MemberCanBeMadeStatic.Global
protected TestContext GetServer(bool startServer = true)
protected TestContext GetServer(bool startServer = true, IDataStore? dataStore = null)
{
DirectHttpListener listener = new(Logger);
HttpClient client = listener.GetClient();
Expand All @@ -25,7 +26,7 @@ protected TestContext GetServer(bool startServer = true)

Lazy<TestRefreshGameServer> gameServer = new(() =>
{
TestRefreshGameServer gameServer = new(listener, () => provider);
TestRefreshGameServer gameServer = new(listener, () => provider, dataStore);
gameServer.Start();

return gameServer;
Expand Down
18 changes: 18 additions & 0 deletions RefreshTests.GameServer/GameServer/ReadFailingDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Bunkum.Core.Storage;

namespace RefreshTests.GameServer.GameServer;

/// <summary>
/// A data store that claims keys are always available, but returns failure when getting the data back
/// </summary>
public class ReadFailingDataStore : IDataStore
{
public bool ExistsInStore(string key) => true;
public bool WriteToStore(string key, byte[] data) => true;
public byte[] GetDataFromStore(string key) => throw new NotSupportedException();
public bool RemoveFromStore(string key) => throw new NotSupportedException();
public string[] GetKeysFromStore() => throw new NotSupportedException();
public bool WriteToStoreFromStream(string key, Stream data) => true;
public Stream GetStreamFromStore(string key) => throw new NotSupportedException();
public Stream OpenWriteStream(string key) => throw new NotSupportedException();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace RefreshTests.GameServer.GameServer;

public class TestRefreshGameServer : RefreshGameServer
{
public TestRefreshGameServer(BunkumHttpListener listener, Func<GameDatabaseProvider> provider) : base(listener, provider, null, new InMemoryDataStore())
public TestRefreshGameServer(BunkumHttpListener listener, Func<GameDatabaseProvider> provider, IDataStore? dataStore = null) : base(listener, provider, null, dataStore ?? new InMemoryDataStore())
{}

public BunkumHttpServer Server => this._server;
Expand Down
18 changes: 18 additions & 0 deletions RefreshTests.GameServer/GameServer/WriteFailingDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Bunkum.Core.Storage;

namespace RefreshTests.GameServer.GameServer;

/// <summary>
/// A data store that you are unable to write to, it always returns a failure operation
/// </summary>
public class WriteFailingDataStore : IDataStore
{
public bool ExistsInStore(string key) => false;
public bool WriteToStore(string key, byte[] data) => false;
public byte[] GetDataFromStore(string key) => throw new NotSupportedException();
public bool RemoveFromStore(string key) => throw new NotSupportedException();
public string[] GetKeysFromStore() => Array.Empty<string>();
public bool WriteToStoreFromStream(string key, Stream data) => false;
public Stream GetStreamFromStore(string key) => throw new NotSupportedException();
public Stream OpenWriteStream(string key) => throw new NotSupportedException();
}
205 changes: 199 additions & 6 deletions RefreshTests.GameServer/Tests/Assets/AssetUploadTests.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,89 @@
using System.Security.Cryptography;
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Services;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.UserData;
using RefreshTests.GameServer.Extensions;

namespace RefreshTests.GameServer.Tests.Assets;

public class AssetUploadTests : GameServerTest
{
private const string MissingHash = "6e4d252f247e3aa99ef846df8c65493393e79f4f";

[TestCase(false)]
[TestCase(true)]
public void CanUploadAsset(bool psp)
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);
if(psp)
client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT");

ReadOnlySpan<byte> data = "TEX a"u8;

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));
}

[Test]
public void CanUploadAsset()
public void CantUploadAssetWithInvalidHash()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

ReadOnlySpan<byte> data = "TEX a"u8;

HttpResponseMessage response = client.PostAsync($"/lbp/upload/{MissingHash}", new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(BadRequest));
}

[Test]
public void CantUploadBlockedAsset()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

ReadOnlySpan<byte> data = "FSHbthsa"u8;

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(Unauthorized));
}

[Test]
public void DataStoreWriteFailReturnsInternalServerError()
{
using TestContext context = this.GetServer(true, new WriteFailingDataStore());
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

ReadOnlySpan<byte> data = "TEX a"u8;

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(InternalServerError));
}

[Test]
public void CantUploadDuplicateAssets()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
Expand All @@ -22,31 +97,136 @@ public void CanUploadAsset()
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.IsSuccessStatusCode, Is.True);
Assert.That(response.StatusCode, Is.EqualTo(OK));

response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(Conflict));
}

[Test]
public void CantUploadTooLarge()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

//create 5mb array
ReadOnlySpan<byte> data = new byte[5000000];

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(RequestEntityTooLarge));
}

[Test]
public void CanRetrieveAsset()
public void InvalidAssetHashUploadFails()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

ReadOnlySpan<byte> data = "TEX a"u8;

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/I_AM_NOT_REAL", new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(BadRequest));
}

[TestCase(false)]
[TestCase(true)]
public void CanRetrieveAsset(bool psp)
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);
if (psp)
client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT");

ReadOnlySpan<byte> data = "TEX a"u8;
string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Wait();
HttpResponseMessage response = client.GetAsync("/lbp/r/" + hash).Result;
Assert.That(response.IsSuccessStatusCode, Is.True);
HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));

response = client.GetAsync("/lbp/r/" + hash).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));

byte[] returnedData = response.Content.ReadAsByteArrayAsync().Result;

Assert.That(data.SequenceEqual(returnedData), Is.True);
}

[TestCase(false)]
[TestCase(true)]
public void CheckForMissingAssets(bool psp)
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);
if(psp)
client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT");

ReadOnlySpan<byte> data = "TEX a"u8;
string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

//Check the list initially, should have 1 item
HttpResponseMessage response = client.PostAsync("/lbp/filterResources", new StringContent(new SerializedResourceList(new[] { hash }).AsXML())).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));

SerializedResourceList missingList = response.Content.ReadAsXML<SerializedResourceList>();
Assert.That(missingList.Items, Has.Count.EqualTo(1));
Assert.That(missingList.Items[0], Is.EqualTo(hash));

//Upload an asset
response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));

//Check the list after uploading, should now only have 0 items returned
response = client.PostAsync("/lbp/filterResources", new StringContent(new SerializedResourceList(new[] { hash }).AsXML())).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));

missingList = response.Content.ReadAsXML<SerializedResourceList>();
Assert.That(missingList.Items, Has.Count.EqualTo(0));
}

[Test]
public void CantRetrieveMissingAsset()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage response = client.GetAsync($"/lbp/r/{MissingHash}").Result;
Assert.That(response.StatusCode, Is.EqualTo(NotFound));
}

[Test]
public void DataStoreReadFailReturnsInternalServerError()
{
using TestContext context = this.GetServer(true, new ReadFailingDataStore());
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage response = client.GetAsync($"/lbp/r/{MissingHash}").Result;
Assert.That(response.StatusCode, Is.EqualTo(InternalServerError));
}

[Test]
public void InvalidHashFails()
{
Expand All @@ -61,4 +241,17 @@ public void InvalidHashFails()
response = client.GetAsync("/lbp/r/..%2Frpc.json").Result;
Assert.That(response.StatusCode, Is.EqualTo(BadRequest));
}

[Test]
public void CantCheckForInvalidMissingAssets()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

//Check the list initially, should have 1 item
HttpResponseMessage response = client.PostAsync("/lbp/filterResources", new StringContent(new SerializedResourceList(new[] { "I_AM_NOT_HASH" }).AsXML())).Result;
Assert.That(response.StatusCode, Is.EqualTo(BadRequest));
}
}
Loading

0 comments on commit a95bb2b

Please sign in to comment.