-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from CodebreakerApp/9-create-client-library
9 create client library
- Loading branch information
Showing
17 changed files
with
637 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
name: Client lib | ||
|
||
on: | ||
|
||
# Automatically trigger it when detected changes in repo | ||
push: | ||
branches: | ||
[ main ] | ||
paths: | ||
- 'src/clients/common/Codebreaker.GameAPIs.Client/**' | ||
- 'src/services/gameapi/Codebreaker.GameAPIs.Analyzers.Tests/**' | ||
- 'src/Codebreaker.GameA.sln' | ||
- '.github/workflows/codebreaker-lib-client.yml' | ||
- '.github/workflows/createnuget-withbuildnumber.yml' | ||
- '.github/workflows/publishnuget-azuredevops.yml' | ||
- '.github/workflows/publishnuget-nugetserver.yml' | ||
|
||
# Allow manually trigger | ||
workflow_dispatch: | ||
|
||
jobs: | ||
build: | ||
uses: CodebreakerApp/Codebreaker.Backend/.github/workflows/createnuget-withbuildnumber.yml@main | ||
with: | ||
version-suffix: beta. | ||
version-number: ${{ github.run_number }} | ||
version-offset: 10 | ||
solutionfile-path: src/Codebreaker.GameAPIs.Client.sln | ||
projectfile-path: src/clients/common/Codebreaker.GameAPIs.Client/Codebreaker.GameAPIs.Client.csproj | ||
dotnet-version: '8.0.x' | ||
artifact-name: codebreaker-clientlib | ||
branch-name: main | ||
|
||
publishdevops: | ||
uses: CodebreakerApp/Codebreaker.Backend/.github/workflows/publishnuget-azuredevops.yml@main | ||
needs: build | ||
with: | ||
artifact-name: codebreaker-clientlib | ||
secrets: inherit | ||
|
||
publishnuget: | ||
uses: CodebreakerApp/Codebreaker.Backend/.github/workflows/publishnuget-nugetserver.yml@main | ||
needs: publishdevops | ||
with: | ||
artifact-name: codebreaker-clientlib | ||
secrets: inherit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.7.33913.275 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Codebreaker.GameAPIs.Client", "clients\common\Codebreaker.GameAPIs.Client\Codebreaker.GameAPIs.Client.csproj", "{87B3D527-0BB4-4B7E-9A29-525F86836FBD}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Codebreaker.GameAPIs.Client.Tests", "clients\common\Codebreaker.GameAPIs.Client.Tests\Codebreaker.GameAPIs.Client.Tests.csproj", "{D6A22A41-9853-4EDE-8B25-033308390292}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{87B3D527-0BB4-4B7E-9A29-525F86836FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{87B3D527-0BB4-4B7E-9A29-525F86836FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{87B3D527-0BB4-4B7E-9A29-525F86836FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{87B3D527-0BB4-4B7E-9A29-525F86836FBD}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{D6A22A41-9853-4EDE-8B25-033308390292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{D6A22A41-9853-4EDE-8B25-033308390292}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{D6A22A41-9853-4EDE-8B25-033308390292}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{D6A22A41-9853-4EDE-8B25-033308390292}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {81F354FE-BC60-4FE4-93D7-7F93B1982EA3} | ||
EndGlobalSection | ||
EndGlobal |
31 changes: 31 additions & 0 deletions
31
...clients/common/Codebreaker.GameAPIs.Client.Tests/Codebreaker.GameAPIs.Client.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0-preview.6.23329.7" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> | ||
<PackageReference Include="Moq" Version="4.18.4" /> | ||
<PackageReference Include="xunit" Version="2.5.0" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="6.0.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Codebreaker.GameAPIs.Client\Codebreaker.GameAPIs.Client.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
1 change: 1 addition & 0 deletions
1
src/clients/common/Codebreaker.GameAPIs.Client.Tests/GlobalUsings.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
global using Xunit; |
82 changes: 82 additions & 0 deletions
82
src/clients/common/Codebreaker.GameAPIs.Client.Tests/TestGamesClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
using System.Net; | ||
|
||
using Microsoft.Extensions.Configuration; | ||
|
||
using Moq; | ||
using Moq.Protected; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Codebreaker.GameAPIs.Client.Tests; | ||
|
||
public class TestGamesClient | ||
{ | ||
private readonly ITestOutputHelper _outputHelper; | ||
|
||
public TestGamesClient(ITestOutputHelper outputHelper) | ||
{ | ||
_outputHelper = outputHelper; | ||
} | ||
|
||
[Fact] | ||
public async Task TestStartGame6x4Async() | ||
{ | ||
// Arrange | ||
var configMock = new Mock<IConfiguration>(); | ||
configMock.Setup(x => x[It.IsAny<string>()]).Returns("http://localhost:5000"); | ||
|
||
Mock<HttpMessageHandler> handlerMock = new (MockBehavior.Strict); | ||
string returnMessage = """ | ||
{ | ||
"gameId": "af8dd39f-6e16-41ef-9155-dcd3cf081e87", | ||
"gameType": "Game6x4", | ||
"playerName": "test", | ||
"numberCodes": 4, | ||
"maxMoves": 12, | ||
"fieldValues": { | ||
"colors": [ | ||
"Red", | ||
"Green", | ||
"Blue", | ||
"Yellow", | ||
"Purple", | ||
"Orange" | ||
] | ||
} | ||
} | ||
"""; | ||
handlerMock.Protected() | ||
.Setup<Task<HttpResponseMessage>>( | ||
"SendAsync", | ||
ItExpr.IsAny<HttpRequestMessage>(), | ||
ItExpr.IsAny<CancellationToken>()) | ||
.ReturnsAsync(new HttpResponseMessage | ||
{ | ||
StatusCode = HttpStatusCode.OK, | ||
Content = new StringContent(returnMessage) | ||
}).Verifiable(); | ||
|
||
HttpClient httpClient = new(handlerMock.Object) | ||
{ | ||
BaseAddress = new System.Uri(configMock.Object["GameAPIs"] ?? throw new InvalidOperationException()) | ||
}; | ||
|
||
var gamesClient = new GamesClient(httpClient); | ||
|
||
// Act | ||
var response = await gamesClient.StartGameAsync(Models.GameType.Game6x4, "test"); | ||
|
||
// Assert | ||
Assert.Equal(4, response.NumberCodes); | ||
Assert.Equal(12, response.MaxMoves); | ||
Assert.Single(response.FieldValues.Keys); | ||
Assert.Equal("colors", response.FieldValues.Keys.First()); | ||
Assert.Equal(6, response.FieldValues["colors"].Length); | ||
|
||
handlerMock.Protected().Verify( | ||
"SendAsync", | ||
Times.Once(), | ||
ItExpr.IsAny<HttpRequestMessage>(), | ||
ItExpr.IsAny<CancellationToken>()); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/clients/common/Codebreaker.GameAPIs.Client/Codebreaker.GameAPIs.Client.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<PackageId>CNinnovation.Codebreaker.GamesClient</PackageId> | ||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<LangVersion>preview</LangVersion> | ||
<PackageTags> | ||
Codebreaker;CNinnovation;GamesClient; | ||
</PackageTags> | ||
<Description> | ||
This library contains client code to access the Codebreaker Games APIp. Reference this library to create a client application to access the Codebreaker Games API. | ||
See https://github.com/codebreakerapp for more information on the complete solution. | ||
</Description> | ||
<PackageReadmeFile>readme.md</PackageReadmeFile> | ||
<PackageIcon>codebreaker.jpeg</PackageIcon> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<None Include="docs/readme.md" Pack="true" PackagePath="\" /> | ||
<None Include="Images/codebreaker.jpeg" Pack="true" PackagePath="\" /> | ||
</ItemGroup> | ||
|
||
</Project> |
103 changes: 103 additions & 0 deletions
103
src/clients/common/Codebreaker.GameAPIs.Client/GamesClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
using System.Net; | ||
using System.Net.Http.Json; | ||
using System.Text.Json; | ||
|
||
using Codebreaker.GameAPIs.Client.Models; | ||
|
||
namespace Codebreaker.GameAPIs.Client; | ||
|
||
/// <summary> | ||
/// Client to interact with the Codebreaker Game API. | ||
/// </summary> | ||
public class GamesClient | ||
{ | ||
private readonly HttpClient _httpClient; | ||
private readonly static JsonSerializerOptions s_jsonOptions = new() | ||
{ | ||
PropertyNameCaseInsensitive = true | ||
}; | ||
|
||
public GamesClient(HttpClient httpClient) | ||
{ | ||
_httpClient = httpClient; | ||
} | ||
|
||
/// <summary> | ||
/// Starts a new game | ||
/// </summary> | ||
/// <param name="gameType">The game type with one of the <see cref="GameType"/>enum values</param> | ||
/// <param name="playerName">The name of the player</param> | ||
/// <param name="cancellationToken">Optional cancellation token to cancel the request early</param> | ||
/// <returns>A tuple with the unique game id, the number of codes that need to be filled, the maximum available moves, and possible field values for guesses</returns> | ||
/// <exception cref="InvalidOperationException"></exception> | ||
/// <exception cref="HttpRequestException"></exception>" | ||
public async Task<(Guid GameId, int NumberCodes, int MaxMoves, IDictionary<string, string[]> FieldValues)> | ||
StartGameAsync(GameType gameType, string playerName, CancellationToken cancellationToken = default) | ||
{ | ||
CreateGameRequest createGameRequest = new(gameType, playerName); | ||
var response = await _httpClient.PostAsJsonAsync("/games", createGameRequest, s_jsonOptions, cancellationToken); | ||
response.EnsureSuccessStatusCode(); | ||
var gameResponse = await response.Content.ReadFromJsonAsync<CreateGameResponse>(s_jsonOptions, cancellationToken) ?? throw new InvalidOperationException(); | ||
return (gameResponse.GameId, gameResponse.NumberCodes, gameResponse.MaxMoves, gameResponse.FieldValues); | ||
} | ||
|
||
/// <summary> | ||
/// Set a game move by supplying guess pegs. This method returns the results of the move (the key pegs), and whether the game ended, and whether the game was won. | ||
/// </summary> | ||
/// <param name="gameId">The game id received from StartGameAsync</param> | ||
/// <param name="playerName">The player name (needs to be the same as received). This must match with the game started.</param> | ||
/// <param name="gameType">The game type with one of the <see cref="GameType"/>enum values. This must match with the game started.</param> | ||
/// <param name="moveNumber">The incremented move number. The game analyzer returns an error if this does not match the state of the game.</param> | ||
/// <param name="guessPegs">The guess pegs for this move. The number of guess pegs must conform to the number codes returned when creating the game.</param> | ||
/// <param name="cancellationToken">Optional cancellation token to cancel the request early.</param> | ||
/// <returns></returns> | ||
/// <exception cref="HttpRequestException"></exception>" | ||
/// <exception cref="InvalidOperationException"></exception> | ||
public async Task<(string[] Results, bool Ended, bool IsVictory)> SetMoveAsync(Guid gameId, string playerName, GameType gameType, int moveNumber, string[] guessPegs, CancellationToken cancellationToken = default) | ||
{ | ||
UpdateGameRequest updateGameRequest = new(gameId, gameType, playerName, moveNumber) | ||
{ | ||
GuessPegs = guessPegs | ||
}; | ||
var response = await _httpClient.PatchAsJsonAsync($"/games/{gameId}", updateGameRequest, s_jsonOptions, cancellationToken); | ||
response.EnsureSuccessStatusCode(); | ||
var moveResponse = await response.Content.ReadFromJsonAsync<UpdateGameResponse>(s_jsonOptions, cancellationToken) | ||
?? throw new InvalidOperationException(); | ||
(_, _, _, bool ended, bool isVictory, string[] results) = moveResponse; | ||
return (results, ended, isVictory); | ||
} | ||
|
||
/// <summary> | ||
/// Retrieves a game by ID. | ||
/// </summary> | ||
/// <param name="gameId">The unique identifier of a game.</param> | ||
/// <param name="cancellationToken">Optional cancellation token to cancel the request early.</param> | ||
/// <returns>The <see cref="Game"/> if it exists, otherwise null.</returns> | ||
/// <exception cref="HttpRequestException"></exception> | ||
public async Task<Game?> GetGameAsync(Guid gameId, CancellationToken cancellationToken = default) | ||
{ | ||
Game? game = default; | ||
try | ||
{ | ||
game = await _httpClient.GetFromJsonAsync<Game>($"/games/{gameId}", s_jsonOptions, cancellationToken); | ||
} | ||
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound) | ||
{ | ||
return default; | ||
} | ||
return game; | ||
} | ||
|
||
/// <summary> | ||
/// Retrieves a list of games matching a specified query. | ||
/// </summary> | ||
/// <param name="query">The games query object containing parameters to filter games.</param> | ||
/// <param name="cancellationToken">Cancellation token to cancel the request early.</param> | ||
/// <returns>An IEnumerable collection of Game objects that match the specified query.</returns> | ||
/// <exception cref="HttpRequestException"></exception> | ||
public async Task<IEnumerable<Game>> GetGamesAsync(GamesQuery query, CancellationToken cancellationToken = default) | ||
{ | ||
IEnumerable<Game> games = (await _httpClient.GetFromJsonAsync<IEnumerable<Game>>($"/games/{query.AsUrlQuery()}", s_jsonOptions, cancellationToken)) ?? Enumerable.Empty<Game>(); | ||
return games; | ||
} | ||
} |
Binary file added
BIN
+11.4 KB
src/clients/common/Codebreaker.GameAPIs.Client/Images/codebreaker.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.