Skip to content

Commit

Permalink
Merge branch 'next' into feature/start-team-coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
sei-bstein committed Dec 5, 2024
2 parents 467e828 + 301fd46 commit ffaddc7
Show file tree
Hide file tree
Showing 100 changed files with 9,742 additions and 558 deletions.
2 changes: 1 addition & 1 deletion .vscode/gameboard.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"scope": "csharp",
"description": "Create a Gameboard unit test suite",
"prefix": "test-suite-unit",
"isFileTemplate": true,
"body": [
"namespace Gameboard.Api.Tests.Unit;",
"",
Expand All @@ -17,7 +18,6 @@
"scope": "csharp",
"description": "Start a new Gameboard unit test",
"prefix": "test-unit",
"isFileTemplate": true,
"body": [
"[${0:Theory}, ${1:GameboardAutoData}]",
"public async Task ${TM_FILENAME/Tests\\.cs//g}_$2_$3(IFixture fixture)",
Expand Down
36 changes: 28 additions & 8 deletions .vscode/launch.json.recommended
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (web)",
"name": ".NET Core Launch (dev)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Gameboard.Api/bin/Debug/net7.0/Gameboard.Api.dll",
"program": "${workspaceFolder}/src/Gameboard.Api/bin/Debug/net8.0/Gameboard.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Gameboard.Api",
"stopAtEntry": false,
Expand All @@ -22,15 +21,36 @@
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:5002"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"name": ".NET Core Launch (test)",
"type": "coreclr",
"request": "attach"
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Gameboard.Api/bin/Debug/net8.0/Gameboard.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Gameboard.Api",
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Test"
}
},
{
"name": ".NET Core Launch (db only)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Gameboard.Api/bin/Debug/net8.0/Gameboard.Api.dll",
"args": ["--dbonly"],
"cwd": "${workspaceFolder}/src/Gameboard.Api",
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:5002"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

namespace Gameboard.Api.Tests.Integration.Fixtures
namespace Gameboard.Api.Tests.Integration.Fixtures;

internal class TestClaimsTransformation : IClaimsTransformation
{
internal class TestClaimsTransformation : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
return Task.FromResult(principal);
}
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
=> Task.FromResult(principal);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ await _testContext.WithDataState

state.Add<Data.Game>(fixture, game =>
{
game.IsPublished = true;
game.PlayerMode = PlayerMode.Practice;

game.Specs = state.Build<Data.ChallengeSpec>(fixture, spec =>
{
game.PlayerMode = PlayerMode.Practice;
spec.Tags = tag;
}).ToCollection();
});
Expand Down Expand Up @@ -63,6 +65,9 @@ await _testContext.WithDataState
// note there are no suggested searches in this db
state.Add<Data.Game>(fixture, game =>
{
game.PlayerMode = PlayerMode.Practice;
game.IsPublished = true;

game.Specs = state.Build<Data.ChallengeSpec>(fixture, spec =>
{
game.PlayerMode = PlayerMode.Practice;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Gameboard.Api.Common;
using Gameboard.Api.Features.Teams;
using Gameboard.Api.Structure;

namespace Gameboard.Api.Tests.Integration.Teams;
Expand Down Expand Up @@ -78,6 +79,7 @@ await _testContext.WithDataState(state =>
p.Id = fixture.Create<string>();
p.Role = PlayerRole.Member;
p.TeamId = teamId;
p.User = state.Build<Data.User>(fixture, u => u.Id = fixture.Create<string>());
})
]
});
Expand All @@ -89,6 +91,116 @@ await _testContext.WithDataState(state =>
.PutAsync($"api/player/{playerId}/start", null)
.DeserializeResponseAs<Player>();

//
// then we should get a player back with a nonempty session start
result.SessionBegin.ShouldBeGreaterThan(DateTimeOffset.MinValue);
}

[Theory, GbIntegrationAutoData]
public async Task TeamGame_WithCaptainPromotion_CanStart
(
string finalCaptainPlayerId,
string finalCaptainUserId,
string initialCaptainPlayerId,
string initialCaptainUserId,
string teamId,
IFixture fixture
)
{
// given a team game and a registered player with no teammates
await _testContext.WithDataState(state =>
{
state.Add(new Data.Game
{
Id = fixture.Create<string>(),
MinTeamSize = 2,
MaxTeamSize = 5,
GameStart = DateTimeOffset.UtcNow,
GameEnd = DateTimeOffset.UtcNow.AddDays(1),
Mode = GameEngineMode.Standard,
Players =
[
state.Build<Data.Player>(fixture, p =>
{
p.Id = initialCaptainPlayerId;
p.Role = PlayerRole.Manager;
p.TeamId = teamId;
p.User = state.Build<Data.User>(fixture, u => u.Id = initialCaptainUserId);
}),
state.Build<Data.Player>(fixture, p =>
{
p.Id = finalCaptainPlayerId;
p.Role = PlayerRole.Member;
p.TeamId = teamId;
p.User = state.Build<Data.User>(fixture, u => u.Id = finalCaptainUserId);
})
]
});
});

// when they promote a new captain and then start
var httpClient = _testContext.CreateHttpClientWithActingUser(u => u.Id = initialCaptainUserId);

await httpClient
.PutAsync($"api/team/{teamId}/manager/{finalCaptainPlayerId}", new PromoteToManagerRequest
{
CurrentCaptainId = initialCaptainPlayerId,
NewManagerPlayerId = finalCaptainPlayerId,
TeamId = teamId
}.ToJsonBody());

var result = await _testContext
.CreateHttpClientWithActingUser(u => u.Id = finalCaptainUserId)
.PutAsync($"api/player/{finalCaptainPlayerId}/start", null)
.DeserializeResponseAs<Player>();

// then we should get a player back with a nonempty session start
result.SessionBegin.ShouldBeGreaterThan(DateTimeOffset.MinValue);
}

// Users can team up, leave the team, join a different team, then start sessions on the original and the new team
// Admins can start sessions for non-admins
// Non-admins can't start sessions for other teams
[Theory, GbIntegrationAutoData]
public async Task Team_WhenStartingOtherTeamSession_FailsValidation
(
string actingTeamId,
string actingUserId,
string targetPlayerId,
IFixture fixture
)
{
// given two players registered for the same game
await _testContext.WithDataState(state =>
{
state.Add<Data.Game>(fixture, game =>
{
game.Players =
[
// the person who's starting a session
state.Build<Data.Player>(fixture, p =>
{
p.Id = fixture.Create<string>();
p.Role = PlayerRole.Manager;
p.TeamId = actingTeamId;
p.User = state.Build<Data.User>(fixture, u => u.Id = actingUserId);
}),
state.Build<Data.Player>(fixture, p =>
{
p.Id = targetPlayerId;
p.Role = PlayerRole.Manager;
p.TeamId = actingTeamId;
p.User = state.Build<Data.User>(fixture);
})
];
});
});

// when the first player tries to start the second's session
var response = await _testContext
.CreateHttpClientWithActingUser(u => u.Id = actingUserId)
.PutAsync($"api/player/{targetPlayerId}/start", null);

// then the response should have a failure code
response.IsSuccessStatusCode.ShouldBeFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Gameboard.Api.Features.Users;
using Gameboard.Api.Services;
using MediatR;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace Gameboard.Api.Tests.Unit;
Expand Down Expand Up @@ -113,7 +112,6 @@ string userId
A.Fake<ILogger<ChallengeService>>(),
A.Fake<IMapper>(),
A.Fake<IMediator>(),
A.Fake<IMemoryCache>(),
A.Fake<INowService>(),
A.Fake<IPracticeService>(),
A.Fake<IUserRolePermissionsService>(),
Expand Down Expand Up @@ -237,7 +235,6 @@ string userId
A.Fake<ILogger<ChallengeService>>(),
A.Fake<IMapper>(),
A.Fake<IMediator>(),
A.Fake<IMemoryCache>(),
A.Fake<INowService>(),
A.Fake<IPracticeService>(),
A.Fake<IUserRolePermissionsService>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,6 @@ namespace Gameboard.Api.Tests.Unit;

public class PlayerServiceTests
{
[Theory, GameboardAutoData]
public async Task Standings_WhenGameIdIsEmpty_ReturnsEmptyArray(IFixture fixture)
{
// arrange
var sut = fixture.Create<PlayerService>();
var filterParams = A.Fake<PlayerDataFilter>();

// act
var result = await sut.Standings(filterParams);

// assert
result.ShouldBe(Array.Empty<Standing>());
}

[Theory, GameboardAutoData]
public async Task MakeCertificates_WhenScoreZero_ReturnsEmptyArray(IFixture fixture)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using AutoMapper;
using Gameboard.Api.Common;
using Gameboard.Api.Common.Services;
using Gameboard.Api.Data;
using Gameboard.Api.Features.Challenges;
using Gameboard.Api.Features.Practice;
using Gameboard.Api.Features.Users;
using Microsoft.EntityFrameworkCore;

namespace Gameboard.Api.Tests.Unit;
Expand Down Expand Up @@ -32,9 +32,8 @@ public async Task SearchPracticeChallenges_WithDisabled_ReturnsEmpty(IFixture fi
var sut = GetSutWithResults(fixture, disabledSpec);

// when a query for all challenges is issued
var result = await sut
.BuildQuery(string.Empty, Array.Empty<string>())
.ToArrayAsync(CancellationToken.None);
var query = await sut.BuildQuery(string.Empty, []);
var result = await query.ToArrayAsync(CancellationToken.None);

// then we expect no results
result.Length.ShouldBe(0);
Expand All @@ -53,6 +52,7 @@ public async Task SearchPracticeChallenges_WithEnabled_Returns(IFixture fixture)
Disabled = false,
Game = new Data.Game
{
IsPublished = true,
Name = fixture.Create<string>(),
PlayerMode = PlayerMode.Practice
}
Expand All @@ -61,9 +61,8 @@ public async Task SearchPracticeChallenges_WithEnabled_Returns(IFixture fixture)
var sut = GetSutWithResults(fixture, enabledSpec);

// when a query for all challenges is issued
var result = await sut
.BuildQuery(string.Empty, Array.Empty<string>())
.ToArrayAsync(CancellationToken.None);
var query = await sut.BuildQuery(string.Empty, []);
var result = await query.ToArrayAsync(CancellationToken.None);

// then we expect one result
result.Length.ShouldBe(1);
Expand All @@ -81,8 +80,8 @@ private SearchPracticeChallengesHandler GetSutWithResults(IFixture fixture, para
var sut = new SearchPracticeChallengesHandler
(
A.Fake<IChallengeDocsService>(),
A.Fake<IMapper>(),
A.Fake<IPagingService>(),
A.Fake<IUserRolePermissionsService>(),
A.Fake<IPracticeService>(),
A.Fake<ISlugService>(),
store
Expand Down
16 changes: 16 additions & 0 deletions src/Gameboard.Api/Data/Entities/FeedbackTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;

namespace Gameboard.Api.Data;

public sealed class FeedbackTemplate : IEntity
{
public string Id { get; set; }
public string HelpText { get; set;}
public required string Name { get; set; }
public required string Content { get; set; }

public required string CreatedByUserId { get; set; }
public Data.User CreatedByUser { get; set; }
public required ICollection<Data.Game> UseAsFeedbackTemplateForGames { get; set; } = [];
public required ICollection<Data.Game> UseAsFeedbackTemplateForGameChallenges { get; set; } = [];
}
4 changes: 4 additions & 0 deletions src/Gameboard.Api/Data/Entities/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public class Game : IEntity
public bool RequireSynchronizedStart { get; set; } = false;
public bool ShowOnHomePageInPracticeMode { get; set; } = false;

// feedback
public FeedbackTemplate GameChallengesFeedbackTemplate { get; set; }
public FeedbackTemplate GameFeedbackTemplate { get; set; }

public ICollection<Player> AdvancedPlayers { get; set; }
public ICollection<ChallengeSpec> Specs { get; set; } = new List<ChallengeSpec>();
public ICollection<DenormalizedTeamScore> DenormalizedTeamScores = new List<DenormalizedTeamScore>();
Expand Down
1 change: 1 addition & 0 deletions src/Gameboard.Api/Data/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class User : IEntity
public string SponsorId { get; set; }
public Sponsor Sponsor { get; set; }
public ICollection<ApiKey> ApiKeys { get; set; } = [];
public ICollection<FeedbackTemplate> CreatedFeedbackTemplates { get; set; } = [];
public ICollection<SystemNotification> CreatedSystemNotifications { get; set; } = [];
public ICollection<Player> Enrollments { get; set; } = [];
public ICollection<ManualBonus> EnteredManualBonuses { get; set; } = [];
Expand Down
Loading

0 comments on commit ffaddc7

Please sign in to comment.