Replace custom socket implementation with SignalR.

Replace MSAL and custom cookie auth with Microsoft.Identity.EntityFramework
Also some UI redesign to accommodate different login experience.
This commit is contained in:
2024-08-25 03:46:44 +00:00
parent d688afaeae
commit 51d234d871
172 changed files with 3857 additions and 4045 deletions

View File

@@ -0,0 +1,346 @@
using FluentAssertions.Execution;
using Shogi.AcceptanceTests.TestSetup;
using Shogi.Contracts.Api;
using Shogi.Contracts.Types;
using System.Diagnostics.Tracing;
using System.Net;
using System.Net.Http.Json;
using Xunit.Abstractions;
namespace Shogi.AcceptanceTests;
//#pragma warning disable xUnit1033 // There is a bug which provides a false positive of xUnit1033.
//#pragma warning restore xUnit1033
public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClassFixture<AatTestFixture>
{
private readonly HttpClient httpClient = fixture.HttpClient;
private readonly HttpClient player2HttpClient = fixture.OtherHttpClient;
private string sessionId = string.Empty;
[Fact]
public async Task CreateSession()
{
try
{
// Arrange
await this.SetupTestSession();
// Act
var response = await this.ReadTestSession();
// Assert
response.Should().NotBeNull();
response!.BoardState.Board.Should().NotBeEmpty();
ValidateBoard(response.BoardState.Board);
response.BoardState.Player1Hand.Should().BeEmpty();
response.BoardState.Player2Hand.Should().BeEmpty();
response.BoardState.PlayerInCheck.Should().BeNull();
response.BoardState.WhoseTurn.Should().Be(WhichPlayer.Player1);
response.Player1.Should().NotBeNull();
response.Player2.Should().BeNullOrEmpty();
response.SessionId.Should().NotBeEmpty();
}
finally
{
// Annul
await this.DeleteTestSession();
}
static void ValidateBoard(Dictionary<string, Piece?> board)
{
using var scope = new AssertionScope();
board["A1"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["A1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["A1"]!.IsPromoted.Should().Be(false);
board["B1"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["B1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B1"]!.IsPromoted.Should().Be(false);
board["C1"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["C1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["C1"]!.IsPromoted.Should().Be(false);
board["D1"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["D1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["D1"]!.IsPromoted.Should().Be(false);
board["E1"]!.WhichPiece.Should().Be(WhichPiece.King);
board["E1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["E1"]!.IsPromoted.Should().Be(false);
board["F1"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["F1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["F1"]!.IsPromoted.Should().Be(false);
board["G1"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["G1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["G1"]!.IsPromoted.Should().Be(false);
board["H1"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["H1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H1"]!.IsPromoted.Should().Be(false);
board["I1"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["I1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["I1"]!.IsPromoted.Should().Be(false);
board["A2"].Should().BeNull();
board["B2"]!.WhichPiece.Should().Be(WhichPiece.Bishop);
board["B2"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B2"]!.IsPromoted.Should().Be(false);
board["C2"].Should().BeNull();
board["D2"].Should().BeNull();
board["E2"].Should().BeNull();
board["F2"].Should().BeNull();
board["G2"].Should().BeNull();
board["H2"]!.WhichPiece.Should().Be(WhichPiece.Rook);
board["H2"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H2"]!.IsPromoted.Should().Be(false);
board["I2"].Should().BeNull();
board["A3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["A3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["A3"]!.IsPromoted.Should().Be(false);
board["B3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["B3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B3"]!.IsPromoted.Should().Be(false);
board["C3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["C3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["C3"]!.IsPromoted.Should().Be(false);
board["D3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["D3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["D3"]!.IsPromoted.Should().Be(false);
board["E3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["E3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["E3"]!.IsPromoted.Should().Be(false);
board["F3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["F3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["F3"]!.IsPromoted.Should().Be(false);
board["G3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["G3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["G3"]!.IsPromoted.Should().Be(false);
board["H3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["H3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H3"]!.IsPromoted.Should().Be(false);
board["I3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["I3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["I3"]!.IsPromoted.Should().Be(false);
board["A4"].Should().BeNull();
board["B4"].Should().BeNull();
board["C4"].Should().BeNull();
board["D4"].Should().BeNull();
board["E4"].Should().BeNull();
board["F4"].Should().BeNull();
board["G4"].Should().BeNull();
board["H4"].Should().BeNull();
board["I4"].Should().BeNull();
board["A5"].Should().BeNull();
board["B5"].Should().BeNull();
board["C5"].Should().BeNull();
board["D5"].Should().BeNull();
board["E5"].Should().BeNull();
board["F5"].Should().BeNull();
board["G5"].Should().BeNull();
board["H5"].Should().BeNull();
board["I5"].Should().BeNull();
board["A6"].Should().BeNull();
board["B6"].Should().BeNull();
board["C6"].Should().BeNull();
board["D6"].Should().BeNull();
board["E6"].Should().BeNull();
board["F6"].Should().BeNull();
board["G6"].Should().BeNull();
board["H6"].Should().BeNull();
board["I6"].Should().BeNull();
board["A7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["A7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["A7"]!.IsPromoted.Should().Be(false);
board["B7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["B7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B7"]!.IsPromoted.Should().Be(false);
board["C7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["C7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["C7"]!.IsPromoted.Should().Be(false);
board["D7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["D7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["D7"]!.IsPromoted.Should().Be(false);
board["E7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["E7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["E7"]!.IsPromoted.Should().Be(false);
board["F7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["F7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["F7"]!.IsPromoted.Should().Be(false);
board["G7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["G7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["G7"]!.IsPromoted.Should().Be(false);
board["H7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["H7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H7"]!.IsPromoted.Should().Be(false);
board["I7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["I7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["I7"]!.IsPromoted.Should().Be(false);
board["A8"].Should().BeNull();
board["B8"]!.WhichPiece.Should().Be(WhichPiece.Rook);
board["B8"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B8"]!.IsPromoted.Should().Be(false);
board["C8"].Should().BeNull();
board["D8"].Should().BeNull();
board["E8"].Should().BeNull();
board["F8"].Should().BeNull();
board["G8"].Should().BeNull();
board["H8"]!.WhichPiece.Should().Be(WhichPiece.Bishop);
board["H8"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H8"]!.IsPromoted.Should().Be(false);
board["I8"].Should().BeNull();
board["A9"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["A9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["A9"]!.IsPromoted.Should().Be(false);
board["B9"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["B9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B9"]!.IsPromoted.Should().Be(false);
board["C9"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["C9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["C9"]!.IsPromoted.Should().Be(false);
board["D9"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["D9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["D9"]!.IsPromoted.Should().Be(false);
board["E9"]!.WhichPiece.Should().Be(WhichPiece.King);
board["E9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["E9"]!.IsPromoted.Should().Be(false);
board["F9"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["F9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["F9"]!.IsPromoted.Should().Be(false);
board["G9"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["G9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["G9"]!.IsPromoted.Should().Be(false);
board["H9"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["H9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H9"]!.IsPromoted.Should().Be(false);
board["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["I9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["I9"]!.IsPromoted.Should().Be(false);
}
}
[Fact]
public async Task ReadAllSessionsMetadata()
{
try
{
// Arrange
await this.SetupTestSession();
var testSession = await this.ReadTestSession();
// Act
var readAllResponse = await this.httpClient.GetFromJsonAsync<SessionMetadata[]>(
new Uri("Sessions", UriKind.Relative),
Contracts.ShogiApiJsonSerializerSettings.SystemTextJsonSerializerOptions);
// Assert
readAllResponse.Should().NotBeNull();
readAllResponse!.First().SessionId.Should().Be(testSession.SessionId);
}
finally
{
// Annul
await this.DeleteTestSession();
}
}
[Fact]
public async Task JoinSession_Player2IsNotSet_SetsPlayer2()
{
try
{
// Arrange
await this.SetupTestSession();
// Act
var joinResponse = await this.player2HttpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Join", UriKind.Relative), null);
// Assert
joinResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var readSessionResponse = await this.ReadTestSession();
readSessionResponse.Player2.Should().NotBeNullOrEmpty();
}
finally
{
await this.DeleteTestSession();
}
}
[Fact]
public async Task JoinSession_SessionIsFull_Conflict()
{
try
{
// Arrange
await this.SetupTestSession();
var joinResponse = await this.player2HttpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Join", UriKind.Relative), null);
joinResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var readSessionResponse = await this.ReadTestSession();
readSessionResponse.Player2.Should().NotBeNull();
// Act
joinResponse = await this.player2HttpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Join", UriKind.Relative), null);
// Assert
joinResponse.StatusCode.Should().Be(HttpStatusCode.Conflict);
}
finally
{
await this.DeleteTestSession();
}
}
[Fact]
public async Task MovePieceCommand_MovingPieceFromBoard_MovesThePiece()
{
try
{
// Arrange
await this.SetupTestSession();
var movePawnCommand = new MovePieceCommand
{
From = "A3",
To = "A4",
};
// Act
var response = await this.httpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Move", UriKind.Relative), JsonContent.Create(movePawnCommand));
response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync());
// Assert
var session = await this.ReadTestSession();
session.BoardState.Board.Should().ContainKey("A3");
session.BoardState.Board["A3"].Should().BeNull();
session.BoardState.Board["A4"].Should().NotBeNull();
session.BoardState.Board["A4"]!.IsPromoted.Should().BeFalse();
session.BoardState.Board["A4"]!.Owner.Should().Be(WhichPlayer.Player1);
session.BoardState.Board["A4"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
}
finally
{
// Annul
await this.DeleteTestSession();
}
}
private async Task SetupTestSession()
{
var createResponse = await this.httpClient.PostAsync(new Uri("Sessions", UriKind.Relative), null);
createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
this.sessionId = await createResponse.Content.ReadAsStringAsync();
}
private Task<Session> ReadTestSession()
{
return this.httpClient.GetFromJsonAsync<Session>(new Uri($"Sessions/{Uri.EscapeDataString(this.sessionId)}", UriKind.Relative))!;
}
private async Task DeleteTestSession()
{
var response = await this.httpClient.DeleteAsync(new Uri($"Sessions/{Uri.EscapeDataString(this.sessionId)}", UriKind.Relative));
response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync());
}
}

View File

@@ -1,362 +0,0 @@
using FluentAssertions.Execution;
using Shogi.AcceptanceTests.TestSetup;
using Shogi.Contracts.Api;
using Shogi.Contracts.Types;
using System.Net;
using System.Net.Http.Json;
using Xunit.Abstractions;
namespace Shogi.AcceptanceTests;
#pragma warning disable xUnit1033 // There is a bug which provides a false positive of xUnit1033.
public class GuestSessionTests : IClassFixture<GuestTestFixture>
#pragma warning restore xUnit1033
{
private readonly HttpClient guest1HttpClient;
private readonly HttpClient guest2HttpClient;
private readonly ITestOutputHelper console;
public GuestSessionTests(GuestTestFixture fixture, ITestOutputHelper console)
{
this.guest1HttpClient = fixture.Guest1ServiceClient;
this.guest2HttpClient = fixture.Guest2ServiceClient;
this.console = console;
}
[Fact]
public async Task JoinSession_Player2IsNotSet_SetsPlayer2()
{
try
{
// Arrange
await SetupTestSession();
// Act
var joinResponse = await guest2HttpClient.PatchAsync(new Uri("Sessions/Acceptance Tests/Join", UriKind.Relative), null);
// Assert
joinResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var readSessionResponse = await ReadTestSession();
readSessionResponse.Session.Player2.Should().NotBeNull();
readSessionResponse.Session.Player2!.Id.Should().NotBeNullOrEmpty();;
readSessionResponse.Session.Player2.Name.Should().NotBeNullOrEmpty(); ;
}
finally
{
await DeleteTestSession();
}
}
[Fact]
public async Task JoinSession_SessionIsFull_Conflict()
{
try
{
// Arrange
await SetupTestSession();
var joinResponse = await guest2HttpClient.PatchAsync(new Uri("Sessions/Acceptance Tests/Join", UriKind.Relative), null);
joinResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var readSessionResponse = await ReadTestSession();
readSessionResponse.Session.Player2.Should().NotBeNull();
// Act
joinResponse = await guest2HttpClient.PatchAsync(new Uri("Sessions/Acceptance Tests/Join", UriKind.Relative), null);
// Assert
joinResponse.StatusCode.Should().Be(HttpStatusCode.Conflict);
}
finally
{
await DeleteTestSession();
}
}
[Fact]
public async Task ReadSessionsPlayerCount()
{
try
{
// Arrange
await SetupTestSession();
// Act
var readAllResponse = await guest1HttpClient
.GetFromJsonAsync<ReadSessionsPlayerCountResponse>(new Uri("Sessions/PlayerCount", UriKind.Relative),
Contracts.ShogiApiJsonSerializerSettings.SystemTextJsonSerializerOptions);
// Assert
readAllResponse.Should().NotBeNull();
readAllResponse!
.PlayerHasJoinedSessions
.Should()
.ContainSingle(session => session.Name == "Acceptance Tests" && session.PlayerCount == 1);
readAllResponse.AllOtherSessions.Should().NotBeNull();
}
finally
{
// Annul
await DeleteTestSession();
}
}
[Fact]
public async Task CreateSession()
{
try
{
// Arrange
await SetupTestSession();
// Act
var response = await guest1HttpClient.GetFromJsonAsync<ReadSessionResponse>(
new Uri("Sessions/Acceptance Tests", UriKind.Relative),
Contracts.ShogiApiJsonSerializerSettings.SystemTextJsonSerializerOptions);
// Assert
response.Should().NotBeNull();
response!.Session.Should().NotBeNull();
response.Session.BoardState.Board.Should().NotBeEmpty();
ValidateBoard(response.Session.BoardState.Board);
response.Session.BoardState.Player1Hand.Should().BeEmpty();
response.Session.BoardState.Player2Hand.Should().BeEmpty();
response.Session.BoardState.PlayerInCheck.Should().BeNull();
response.Session.BoardState.WhoseTurn.Should().Be(WhichPlayer.Player1);
response.Session.Player1.Should().NotBeNull();
response.Session.Player2.Should().BeNull();
response.Session.SessionName.Should().Be("Acceptance Tests");
}
finally
{
// Annul
await DeleteTestSession();
}
static void ValidateBoard(Dictionary<string, Piece?> board)
{
using var scope = new AssertionScope();
board["A1"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["A1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["A1"]!.IsPromoted.Should().Be(false);
board["B1"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["B1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B1"]!.IsPromoted.Should().Be(false);
board["C1"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["C1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["C1"]!.IsPromoted.Should().Be(false);
board["D1"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["D1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["D1"]!.IsPromoted.Should().Be(false);
board["E1"]!.WhichPiece.Should().Be(WhichPiece.King);
board["E1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["E1"]!.IsPromoted.Should().Be(false);
board["F1"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["F1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["F1"]!.IsPromoted.Should().Be(false);
board["G1"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["G1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["G1"]!.IsPromoted.Should().Be(false);
board["H1"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["H1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H1"]!.IsPromoted.Should().Be(false);
board["I1"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["I1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["I1"]!.IsPromoted.Should().Be(false);
board["A2"].Should().BeNull();
board["B2"]!.WhichPiece.Should().Be(WhichPiece.Bishop);
board["B2"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B2"]!.IsPromoted.Should().Be(false);
board["C2"].Should().BeNull();
board["D2"].Should().BeNull();
board["E2"].Should().BeNull();
board["F2"].Should().BeNull();
board["G2"].Should().BeNull();
board["H2"]!.WhichPiece.Should().Be(WhichPiece.Rook);
board["H2"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H2"]!.IsPromoted.Should().Be(false);
board["I2"].Should().BeNull();
board["A3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["A3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["A3"]!.IsPromoted.Should().Be(false);
board["B3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["B3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B3"]!.IsPromoted.Should().Be(false);
board["C3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["C3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["C3"]!.IsPromoted.Should().Be(false);
board["D3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["D3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["D3"]!.IsPromoted.Should().Be(false);
board["E3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["E3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["E3"]!.IsPromoted.Should().Be(false);
board["F3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["F3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["F3"]!.IsPromoted.Should().Be(false);
board["G3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["G3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["G3"]!.IsPromoted.Should().Be(false);
board["H3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["H3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H3"]!.IsPromoted.Should().Be(false);
board["I3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["I3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["I3"]!.IsPromoted.Should().Be(false);
board["A4"].Should().BeNull();
board["B4"].Should().BeNull();
board["C4"].Should().BeNull();
board["D4"].Should().BeNull();
board["E4"].Should().BeNull();
board["F4"].Should().BeNull();
board["G4"].Should().BeNull();
board["H4"].Should().BeNull();
board["I4"].Should().BeNull();
board["A5"].Should().BeNull();
board["B5"].Should().BeNull();
board["C5"].Should().BeNull();
board["D5"].Should().BeNull();
board["E5"].Should().BeNull();
board["F5"].Should().BeNull();
board["G5"].Should().BeNull();
board["H5"].Should().BeNull();
board["I5"].Should().BeNull();
board["A6"].Should().BeNull();
board["B6"].Should().BeNull();
board["C6"].Should().BeNull();
board["D6"].Should().BeNull();
board["E6"].Should().BeNull();
board["F6"].Should().BeNull();
board["G6"].Should().BeNull();
board["H6"].Should().BeNull();
board["I6"].Should().BeNull();
board["A7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["A7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["A7"]!.IsPromoted.Should().Be(false);
board["B7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["B7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B7"]!.IsPromoted.Should().Be(false);
board["C7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["C7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["C7"]!.IsPromoted.Should().Be(false);
board["D7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["D7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["D7"]!.IsPromoted.Should().Be(false);
board["E7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["E7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["E7"]!.IsPromoted.Should().Be(false);
board["F7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["F7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["F7"]!.IsPromoted.Should().Be(false);
board["G7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["G7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["G7"]!.IsPromoted.Should().Be(false);
board["H7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["H7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H7"]!.IsPromoted.Should().Be(false);
board["I7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["I7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["I7"]!.IsPromoted.Should().Be(false);
board["A8"].Should().BeNull();
board["B8"]!.WhichPiece.Should().Be(WhichPiece.Rook);
board["B8"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B8"]!.IsPromoted.Should().Be(false);
board["C8"].Should().BeNull();
board["D8"].Should().BeNull();
board["E8"].Should().BeNull();
board["F8"].Should().BeNull();
board["G8"].Should().BeNull();
board["H8"]!.WhichPiece.Should().Be(WhichPiece.Bishop);
board["H8"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H8"]!.IsPromoted.Should().Be(false);
board["I8"].Should().BeNull();
board["A9"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["A9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["A9"]!.IsPromoted.Should().Be(false);
board["B9"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["B9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B9"]!.IsPromoted.Should().Be(false);
board["C9"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["C9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["C9"]!.IsPromoted.Should().Be(false);
board["D9"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["D9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["D9"]!.IsPromoted.Should().Be(false);
board["E9"]!.WhichPiece.Should().Be(WhichPiece.King);
board["E9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["E9"]!.IsPromoted.Should().Be(false);
board["F9"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["F9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["F9"]!.IsPromoted.Should().Be(false);
board["G9"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["G9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["G9"]!.IsPromoted.Should().Be(false);
board["H9"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["H9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H9"]!.IsPromoted.Should().Be(false);
board["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["I9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["I9"]!.IsPromoted.Should().Be(false);
}
}
[Fact]
public async Task MovePieceCommand_MovingPieceFromBoard_MovesThePiece()
{
try
{
// Arrange
await SetupTestSession();
var movePawnCommand = new MovePieceCommand
{
From = "A3",
To = "A4",
};
// Act
var response = await guest1HttpClient.PatchAsync(new Uri("Sessions/Acceptance Tests/Move", UriKind.Relative), JsonContent.Create(movePawnCommand));
response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync());
// Assert
var session = (await ReadTestSession()).Session;
session.BoardState.Board["A3"].Should().BeNull();
session.BoardState.Board["A4"].Should().NotBeNull();
session.BoardState.Board["A4"]!.IsPromoted.Should().BeFalse();
session.BoardState.Board["A4"]!.Owner.Should().Be(WhichPlayer.Player1);
session.BoardState.Board["A4"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
}
finally
{
// Annul
await DeleteTestSession();
}
}
private async Task SetupTestSession()
{
var createResponse = await guest1HttpClient.PostAsJsonAsync(
new Uri("Sessions", UriKind.Relative),
new CreateSessionCommand { Name = "Acceptance Tests" },
Contracts.ShogiApiJsonSerializerSettings.SystemTextJsonSerializerOptions);
createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
}
private Task<ReadSessionResponse> ReadTestSession()
{
return guest1HttpClient.GetFromJsonAsync<ReadSessionResponse>(new Uri("Sessions/Acceptance Tests", UriKind.Relative))!;
}
private async Task DeleteTestSession()
{
var response = await guest1HttpClient.DeleteAsync(new Uri("Sessions/Acceptance Tests", UriKind.Relative));
response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync());
}
}

View File

@@ -11,10 +11,14 @@
</PropertyGroup>
<ItemGroup>
<None Remove="appsettings.Development.json" />
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -27,7 +31,6 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.59.0" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.6.6" />

View File

@@ -0,0 +1,114 @@
using Microsoft.Extensions.Configuration;
using System.Net.Http.Json;
using System.Text.Json;
namespace Shogi.AcceptanceTests.TestSetup;
/// <summary>
/// Acceptance Test fixture for tests which assert features for Microsoft accounts.
/// </summary>
public class AatTestFixture : IAsyncLifetime, IDisposable
{
protected static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
private readonly string testAccountPassword;
private bool disposedValue;
public AatTestFixture()
{
this.Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
var baseUrl = this.Configuration["ServiceUrl"] ?? throw new InvalidOperationException();
this.HttpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl, UriKind.Absolute)
};
this.OtherHttpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl, UriKind.Absolute)
};
this.testAccountPassword = this.Configuration["TestUserPassword"]!;
if (string.IsNullOrWhiteSpace(this.testAccountPassword))
{
throw new InvalidOperationException("TestUserPassword is not configured.");
}
}
public IConfiguration Configuration { get; private set; }
public HttpClient HttpClient { get; }
public HttpClient OtherHttpClient { get; }
protected async Task LoginToTestAccounts()
{
var response = await this.HttpClient.PostAsJsonAsync(
RelativeUri("login"),
new
{
email = "aat-account",
password = this.testAccountPassword,
},
options: SerializerOptions);
response.IsSuccessStatusCode.Should().BeTrue(because: "The test account should exist. If it does not, use the /Account/TestAccount route to create it.");
var bearerToken = (await response.Content.ReadFromJsonAsync<LoginResponse>())?.AccessToken;
this.HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bearerToken);
response = await this.HttpClient.PostAsJsonAsync(
RelativeUri("login"),
new
{
email = "aat-account-2",
password = this.testAccountPassword,
},
options: SerializerOptions);
response.IsSuccessStatusCode.Should().BeTrue(because: "The test account should exist. If it does not, use the /Account/TestAccount route to create it.");
bearerToken = (await response.Content.ReadFromJsonAsync<LoginResponse>())?.AccessToken;
this.OtherHttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bearerToken);
}
public async Task InitializeAsync()
{
await this.LoginToTestAccounts();
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
this.HttpClient.Dispose();
}
this.disposedValue = true;
}
}
public Task DisposeAsync()
{
this.Dispose(true);
return Task.CompletedTask;
}
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected static Uri RelativeUri(string s) => new(s, UriKind.Relative);
private class LoginResponse
{
public string AccessToken { get; set; } = string.Empty;
}
}

View File

@@ -1,74 +0,0 @@
using Microsoft.Extensions.Configuration;
namespace Shogi.AcceptanceTests.TestSetup;
/// <summary>
/// Acceptance Test fixture for tests which assert features for Microsoft accounts.
/// </summary>
public class GuestTestFixture : IAsyncLifetime, IDisposable
{
private bool disposedValue;
public GuestTestFixture()
{
Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var baseUrl = Configuration["ServiceUrl"] ?? throw new InvalidOperationException("ServiceUrl configuration missing.");
var baseAddress = new Uri(baseUrl, UriKind.Absolute);
Guest1ServiceClient = new HttpClient
{
BaseAddress = baseAddress
};
Guest2ServiceClient = new HttpClient
{
BaseAddress = baseAddress
};
}
public IConfiguration Configuration { get; private set; }
public HttpClient Guest2ServiceClient { get; }
public HttpClient Guest1ServiceClient { get; }
public async Task InitializeAsync()
{
// Log in as some guest accounts and retain the session cookie for future requests.
var guestLoginUri = new Uri("User/LoginAsGuest", UriKind.Relative);
var loginResponse = await Guest1ServiceClient.GetAsync(guestLoginUri);
loginResponse.IsSuccessStatusCode.Should().BeTrue(because: "Guest accounts should work");
var guestSessionCookie = loginResponse.Headers.GetValues("Set-Cookie").Single();
Guest1ServiceClient.DefaultRequestHeaders.Add("Set-Cookie", guestSessionCookie);
loginResponse = await Guest2ServiceClient.GetAsync(guestLoginUri);
loginResponse.IsSuccessStatusCode.Should().BeTrue(because: "Guest accounts should work twice");
guestSessionCookie = loginResponse.Headers.GetValues("Set-Cookie").Single();
Guest2ServiceClient.DefaultRequestHeaders.Add("Set-Cookie", guestSessionCookie);
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
Guest1ServiceClient.Dispose();
}
disposedValue = true;
}
}
public Task DisposeAsync()
{
Dispose(true);
return Task.CompletedTask;
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@@ -1,100 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
namespace Shogi.AcceptanceTests.TestSetup
{
/// <summary>
/// Acceptance Test fixture for tests which assert features for Microsoft accounts.
/// </summary>
public class MsalTestFixture : IAsyncLifetime, IDisposable
{
private bool disposedValue;
private readonly IConfidentialClientApplication app;
public MsalTestFixture()
{
Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.AddUserSecrets<MsalTestFixture>()
.Build();
var azure = Configuration.GetSection("Auth");
app = ConfidentialClientApplicationBuilder
.Create(azure["ClientId"])
.WithTenantId(azure["TenantId"])
.WithClientSecret(azure["SecretValue"])
.Build();
Service = new HttpClient
{
BaseAddress = new Uri(Configuration["ServiceUrl"], UriKind.Absolute)
};
publicApp = PublicClientApplicationBuilder
.Create(azure["ClientId"])
.WithTenantId("common")
.Build();
}
public IConfiguration Configuration { get; private set; }
public HttpClient Service { get; }
private IPublicClientApplication publicApp;
public async Task InitializeAsync()
{
var scopes = Configuration.GetSection("Auth:Scopes").Get<string[]>();
var authResult = await app
.AcquireTokenForClient(scopes)
.ExecuteAsync();
authResult.Should().NotBeNull();
authResult.AccessToken.Should().NotBeNullOrEmpty();
Service.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
var accounts = await publicApp.GetAccountsAsync();
var password = new System.Security.SecureString();
foreach (var c in "some password")
{
password.AppendChar(c);
}
var th = await publicApp
.AcquireTokenByIntegratedWindowsAuth(scopes)
.ExecuteAsync();
//var thing = await publicApp.AcquireTokenByUsernamePassword(scopes, "Need to create a test email.", password)
//.ExecuteAsync();
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
Service.Dispose();
}
disposedValue = true;
}
}
public Task DisposeAsync()
{
Dispose(true);
return Task.CompletedTask;
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,3 @@
{
"TestUserPassword": "I'mAToysRUsK1d"
}

View File

@@ -1,12 +1,4 @@
{
"ServiceUrl": "https://localhost:5001",
"Auth": {
"TenantId": "d6019544-c403-415c-8e96-50009635b6aa",
"ClientId": "78b12a47-440c-4cc7-9402-f573a2802951",
"SecretValue": "REDACTED",
"Comment": "Go make a secrete in azure portal when it is time to deploy.",
"Scopes": [
"api://c1e94676-cab0-42ba-8b6c-9532b8486fff/.default"
]
}
"TestUserPassword": ""
}