Skip to content

Commit

Permalink
Road to 100% endpoint test coverage (Part 1/?) (#202)
Browse files Browse the repository at this point in the history
This PR adds 100% test coverage for LeaderboardEndpoints
  • Loading branch information
jvyden authored Oct 18, 2023
2 parents 719b8dd + 0c239cf commit 7d7036d
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 12 deletions.
1 change: 1 addition & 0 deletions Refresh.GameServer/Database/GameDatabaseContext.Levels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public GameLevel GetStoryLevelById(int id)
Publisher = null,
Location = GameLocation.Zero,
Source = GameLevelSource.Story,
StoryId = id,
};

//Add the new story level to the database
Expand Down
23 changes: 19 additions & 4 deletions Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,23 @@ public Response PlayLevel(RequestContext context, GameUser user, GameDatabaseCon

int count = 1;
//If we are on PSP and it has sent a `count` parameter...
if (context.IsPSP() && context.QueryString.Get("count") != null)
if (context.QueryString.Get("count") != null)
{
//Count parameters are invalid on non-PSP clients
if (!context.IsPSP()) return BadRequest;

//Parse the count
if (!int.TryParse(context.QueryString["count"], out count))
{
//If it fails, send a bad request back to the client
return BadRequest;
}

//Sanitize against invalid values
if (count < 1)
{
return BadRequest;
}
}

database.PlayLevel(level, user, count);
Expand All @@ -49,6 +58,12 @@ public Response PlayLevel(RequestContext context, GameUser user, GameDatabaseCon
[RateLimitSettings(RequestTimeoutDuration, MaxRequestAmount, RequestBlockDuration, BucketName)]
public Response GetDeveloperScores(RequestContext context, GameUser user, GameDatabaseContext database, int id, Token token)
{
//No story levels have an ID < 0
if (id < 0)
{
return BadRequest;
}

GameLevel level = database.GetStoryLevelById(id);

MultiLeaderboard multiLeaderboard = new(database, level, token.TokenGame);
Expand Down Expand Up @@ -119,12 +134,12 @@ public Response SubmitScore(RequestContext context, GameUser user, GameDatabaseC
[GameEndpoint("topscores/user/{id}/{type}", ContentType.Xml)]
[MinimumRole(GameUserRole.Restricted)]
[RateLimitSettings(RequestTimeoutDuration, MaxRequestAmount, RequestBlockDuration, BucketName)]
public SerializedScoreList? GetTopScoresForLevel(RequestContext context, GameDatabaseContext database, int id, int type)
public Response GetTopScoresForLevel(RequestContext context, GameDatabaseContext database, int id, int type)
{
GameLevel? level = database.GetLevelById(id);
if (level == null) return null;
if (level == null) return NotFound;

(int skip, int count) = context.GetPageData();
return SerializedScoreList.FromSubmittedEnumerable(database.GetTopScoresForLevel(level, count, skip, (byte)type).Items);
return new Response(SerializedScoreList.FromSubmittedEnumerable(database.GetTopScoresForLevel(level, count, skip, (byte)type).Items), ContentType.Xml);
}
}
14 changes: 14 additions & 0 deletions RefreshTests.GameServer/HttpContentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Xml;
using System.Xml.Serialization;

namespace RefreshTests.GameServer;

public static class HttpContentExtensions
{
public async static Task<T> ReadAsXML<T>(this HttpContent content)
{
XmlSerializer serializer = new(typeof(T));

return (T)serializer.Deserialize(new XmlTextReader(await content.ReadAsStreamAsync()))!;
}
}
16 changes: 16 additions & 0 deletions RefreshTests.GameServer/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Xml.Serialization;

namespace RefreshTests.GameServer;

public static class ObjectExtensions
{
public static string AsXML(this object obj)
{
XmlSerializer serializer = new(obj.GetType());

TextWriter writer = new StringWriter();
serializer.Serialize(writer, obj);

return writer.ToString()!;
}
}
275 changes: 267 additions & 8 deletions RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using MongoDB.Bson;
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.UserData;
using Refresh.GameServer.Types.UserData.Leaderboard;

Expand All @@ -17,20 +18,201 @@ public async Task SubmitsScore()

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

string scorePayload = $@"<playRecord>
<host>true</host>
<type>1</type>
<playerIds>{user.Username}</playerIds>
<score>0</score>
</playRecord>";
SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = 5,
};

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/user/{level.LevelId}", new StringContent(score.AsXML()));
Assert.That(message.StatusCode, Is.EqualTo(OK));

message = await client.GetAsync($"/lbp/topscores/user/{level.LevelId}/1");
Assert.That(message.StatusCode, Is.EqualTo(OK));

SerializedScoreList scores = await message.Content.ReadAsXML<SerializedScoreList>();
Assert.That(scores.Scores, Has.Count.EqualTo(1));
Assert.That(scores.Scores[0].Player, Is.EqualTo(user.Username));
Assert.That(scores.Scores[0].Score, Is.EqualTo(5));

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/user/{level.LevelId}", new StringContent(scorePayload));
message = await client.GetAsync($"/lbp/scoreboard/user/{level.LevelId}");
Assert.That(message.StatusCode, Is.EqualTo(OK));

SerializedMultiLeaderboardResponse scoresMulti = await message.Content.ReadAsXML<SerializedMultiLeaderboardResponse>();
SerializedPlayerLeaderboardResponse singleplayerScores = scoresMulti.Scoreboards.First(s => s.PlayerCount == 1);
Assert.That(singleplayerScores.Scores, Has.Count.EqualTo(1));
Assert.That(singleplayerScores.Scores[0].Player, Is.EqualTo(user.Username));
Assert.That(singleplayerScores.Scores[0].Score, Is.EqualTo(5));
}

[Test]
public async Task SubmitsDeveloperScore()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = 5,
};

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/developer/1", new StringContent(score.AsXML()));
Assert.That(message.StatusCode, Is.EqualTo(OK));

context.Database.Refresh();

// message = await client.GetAsync($"/lbp/topscores/developer/{level.LevelId}/1");
// Assert.That(message.StatusCode, Is.EqualTo(OK));
//
// SerializedScoreList scores = await message.Content.ReadAsXML<SerializedScoreList>();
// Assert.That(scores.Scores, Has.Count.EqualTo(1));
// Assert.That(scores.Scores[0].Player, Is.EqualTo(user.Username));
// Assert.That(scores.Scores[0].Score, Is.EqualTo(5));

message = await client.GetAsync($"/lbp/scoreboard/developer/1");
Assert.That(message.StatusCode, Is.EqualTo(OK));

SerializedMultiLeaderboardResponse scoresMulti = await message.Content.ReadAsXML<SerializedMultiLeaderboardResponse>();
SerializedPlayerLeaderboardResponse singleplayerScores = scoresMulti.Scoreboards.First(s => s.PlayerCount == 1);
Assert.That(singleplayerScores.Scores, Has.Count.EqualTo(1));
Assert.That(singleplayerScores.Scores[0].Player, Is.EqualTo(user.Username));
Assert.That(singleplayerScores.Scores[0].Score, Is.EqualTo(5));
}

[Test]
public async Task DosentGetLeaderboardForInvalidLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage message2 = await client.GetAsync($"/lbp/topscores/user/{int.MaxValue}/1");
Assert.That(message2.StatusCode, Is.EqualTo(NotFound));
}

[Test]
public async Task DosentGetMultiLeaderboardForInvalidLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage message = await client.GetAsync($"/lbp/scoreboard/user/{int.MaxValue}");
Assert.That(message.StatusCode, Is.EqualTo(NotFound));
}

[Test]
public async Task DoesntSubmitInvalidScore()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
GameLevel level = context.CreateLevel(user);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = -1,
};

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/user/{level.LevelId}", new StringContent(score.AsXML()));
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));

context.Database.Refresh();

List<GameSubmittedScore> scores = context.Database.GetTopScoresForLevel(level, 1, 0, 1).Items.ToList();
Assert.That(scores, Has.Count.EqualTo(0));
}

[Test]
public async Task DoesntSubmitDeveloperInvalidScore()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
GameLevel level = context.CreateLevel(user);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = -1,
};

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/developer/{level.LevelId}", new StringContent(score.AsXML()));
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));

context.Database.Refresh();

List<GameSubmittedScore> scores = context.Database.GetTopScoresForLevel(level, 1, 0, 1).Items.ToList();
Assert.That(scores, Has.Count.EqualTo(1));
Assert.That(scores, Has.Count.EqualTo(0));
}

[Test]
public async Task DoesntSubmitsScoreToInvalidLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = 0,
};

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/user/{int.MaxValue}", new StringContent(score.AsXML()));
Assert.That(message.StatusCode, Is.EqualTo(NotFound));
}

[Test]
public async Task DoesntSubmitDeveloperScoreToInvalidLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = 0,
};

HttpResponseMessage message = await client.PostAsync($"/lbp/scoreboard/developer/-1", new StringContent(score.AsXML()));
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));
}

[Test]
public async Task DoesntGetDeveloperScoresForInvalidLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

SerializedScore score = new()
{
Host = true,
ScoreType = 1,
Score = 0,
};

HttpResponseMessage message = await client.GetAsync($"/lbp/scoreboard/developer/-1");
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));
}

/// <param name="count">The number of scores to try to fetch from the database</param>
Expand Down Expand Up @@ -103,4 +285,81 @@ public void FailsWithInvalidNumber()

Assert.That(() => context.Database.GetRankedScoresAroundScore(score, 2), Throws.ArgumentException);
}

[Test]
public async Task PlayLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
GameLevel level = context.CreateLevel(user);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage message = await client.PostAsync($"/lbp/play/user/{level.LevelId}", new ReadOnlyMemoryContent(Array.Empty<byte>()));
Assert.That(message.StatusCode, Is.EqualTo(OK));

context.Database.Refresh();

Assert.That(level.AllPlays.Count(), Is.EqualTo(1));
}

[Test]
public async Task DoesntPlayInvalidLevel()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage message = await client.PostAsync($"/lbp/play/user/{int.MaxValue}", new ReadOnlyMemoryContent(Array.Empty<byte>()));
Assert.That(message.StatusCode, Is.EqualTo(NotFound));
}

[Test]
public async Task PlayLevelWithCount()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
GameLevel level = context.CreateLevel(user);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);
client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT");

HttpResponseMessage message = await client.PostAsync($"/lbp/play/user/{level.LevelId}?count=2", new ReadOnlyMemoryContent(Array.Empty<byte>()));
Assert.That(message.StatusCode, Is.EqualTo(OK));

context.Database.Refresh();

Assert.That(level.AllPlays.AsEnumerable().Sum(p => p.Count), Is.EqualTo(2));
}

[Test]
public async Task DoesntPlayLevelWithInvalidCount()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
GameLevel level = context.CreateLevel(user);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);
client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT");

HttpResponseMessage message = await client.PostAsync($"/lbp/play/user/{level.LevelId}?count=gtgnyegth", new ReadOnlyMemoryContent(Array.Empty<byte>()));
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));

HttpResponseMessage message2 = await client.PostAsync($"/lbp/play/user/{level.LevelId}?count=-5", new ReadOnlyMemoryContent(Array.Empty<byte>()));
Assert.That(message2.StatusCode, Is.EqualTo(BadRequest));
}

[Test]
public async Task DoesntPlayLevelWithCountOnMainline()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
GameLevel level = context.CreateLevel(user);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

HttpResponseMessage message = await client.PostAsync($"/lbp/play/user/{level.LevelId}?count=3", new ReadOnlyMemoryContent(Array.Empty<byte>()));
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));
}
}

0 comments on commit 7d7036d

Please sign in to comment.