From c17f94ad73b31baf8c4a158b15032612a0d027ce Mon Sep 17 00:00:00 2001 From: Christian Kauer Date: Wed, 27 Dec 2023 17:20:35 +0100 Subject: [PATCH] dev --- GameData.UnitTests/GameData.UnitTests.csproj | 24 ++ GameData.UnitTests/GlobalUsings.cs | 1 + GameData.UnitTests/UnitTest1.cs | 21 ++ GameData/ApiClient.cs | 260 ++++++++++++++++++ GameData/ApiSettings.cs | 16 ++ GameData/ContainerSetup.cs | 25 ++ .../{ => Dummy}/DummyExpenseRepository.cs | 2 +- GameData/GameData.csproj | 5 + GameData/Repository/ExpenseRepository.cs | 27 ++ .../Extensions/PinThrowExtensionTests.cs | 2 +- .../GameHandler/FreeGameHandlerTests.cs | 9 +- GameHandler/Extensions/PinThrowExtension.cs | 2 +- GameHandler/GameHandler/FreeGameHandler.cs | 5 + GameModel/Contracts/IExpenseRepository.cs | 2 +- GameModel/Expense.cs | 2 +- GameModel/ExpenseTrigger.cs | 2 +- GameModel/MemberExpense.cs | 4 +- GameModel/Settings/AppSettings.cs | 13 + KoogleCli/KoogleCli.csproj | 16 +- KoogleCli/Program.cs | 66 ++++- KoogleCli/appsettings.json | 5 + KoogleV4.sln | 9 +- 22 files changed, 498 insertions(+), 20 deletions(-) create mode 100644 GameData.UnitTests/GameData.UnitTests.csproj create mode 100644 GameData.UnitTests/GlobalUsings.cs create mode 100644 GameData.UnitTests/UnitTest1.cs create mode 100644 GameData/ApiClient.cs create mode 100644 GameData/ApiSettings.cs create mode 100644 GameData/ContainerSetup.cs rename GameData/{ => Dummy}/DummyExpenseRepository.cs (90%) create mode 100644 GameData/Repository/ExpenseRepository.cs create mode 100644 GameModel/Settings/AppSettings.cs create mode 100644 KoogleCli/appsettings.json diff --git a/GameData.UnitTests/GameData.UnitTests.csproj b/GameData.UnitTests/GameData.UnitTests.csproj new file mode 100644 index 0000000..d82adde --- /dev/null +++ b/GameData.UnitTests/GameData.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/GameData.UnitTests/GlobalUsings.cs b/GameData.UnitTests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/GameData.UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/GameData.UnitTests/UnitTest1.cs b/GameData.UnitTests/UnitTest1.cs new file mode 100644 index 0000000..19e71d1 --- /dev/null +++ b/GameData.UnitTests/UnitTest1.cs @@ -0,0 +1,21 @@ +using GameData.Repository; +using GameModel; +using System.Data.Common; + +namespace GameData.UnitTests +{ + public class Tests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + var client = new ExpenseRepository(); + var test = client.GetAll(); + } + } +} \ No newline at end of file diff --git a/GameData/ApiClient.cs b/GameData/ApiClient.cs new file mode 100644 index 0000000..9629a36 --- /dev/null +++ b/GameData/ApiClient.cs @@ -0,0 +1,260 @@ +using GameData.Repository; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace GameData +{ + public sealed class ApiClient : IDisposable + { + private class Wrapper + { + public T[] Data { get; set; } + } + + private readonly HttpClient client; + private readonly string _apiKey = "9wIqngBjwR_l2f89D0EEAN1ldrcXUl6c"; + private readonly string Api = "https://directus.straso.com/"; + public ApiClient() + { + client = new HttpClient + { + Timeout = TimeSpan.FromMinutes(1) + }; + + client.DefaultRequestHeaders + .Accept + .Add(new MediaTypeWithQualityHeaderValue("application/json")); + + client.DefaultRequestHeaders + .Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + + //client.DefaultRequestHeaders.Add("Api-AppInfo", "KrahApp;" + Assembly.GetExecutingAssembly().GetName().Version.ToString()); + + if (!Api.EndsWith("/")) + { + Api += "/"; + } + } + + public void Dispose() + { + client.Dispose(); + } + + public string UrlEncode(string s) + { + return HttpUtility.UrlEncode(s); + } + + private string GetFullUrl(string url) + { + if (url[0].Equals('/')) + { + url = url.Remove(0, 1); + } + + url = Api + url; + return url; + } + + private HttpRequestMessage GetHttpRequestMessage(HttpMethod method, string serviceUrl, object payload = null) + { + StringContent content = null; + if (payload != null) + { + var payloadString = JsonConvert.SerializeObject(payload); + content = new StringContent(payloadString, Encoding.UTF8, "application/json"); + } + + var url = serviceUrl; + if (!serviceUrl.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) + { + url = GetFullUrl(serviceUrl); + } + + var res = new HttpRequestMessage + { + Content = content, + Method = method, + RequestUri = new Uri(url), + }; + //res.Headers.Add("Authorization: Bearer", _apiKey); + return res; + } + + public async Task DownloadFile(string serviceUrl, string targetFile) + { + var res = false; + try + { + var request = GetHttpRequestMessage(HttpMethod.Get, serviceUrl); + request.Headers.Add("Accept", "application/json"); + request.Headers.Add("Accept", "application/octet-stream"); + request.Headers.Add("Accept", "file/download"); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + using (var responseStream = await response.Content.ReadAsStreamAsync()) + { + using (var fileStream = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) + { + await responseStream.CopyToAsync(fileStream); + res = true; + } + } + } + else + { + var errorResult = await response.Content.ReadAsStringAsync(); + //_logger.LogError("error downloading from service '{service}' with code '{statuscode}' - Result: '{result}', TargetFile: '{payload}'", serviceUrl, response.StatusCode, errorResult, targetFile); + } + } + catch (Exception ex) + { + //_logger.LogError(ex, "error at DownloadFile"); + } + + return res; + } + + public async Task Post(object payload, string serviceUrl, bool suppressExceptions = true) + { + T res = default; + + try + { + var response = await client.SendAsync(GetHttpRequestMessage(HttpMethod.Post, serviceUrl, payload)); + + if (response.IsSuccessStatusCode) + { + using (var responseStream = await response.Content.ReadAsStreamAsync()) + { + using (var streamReader = new StreamReader(responseStream)) + { + using (var jsonTextReader = new JsonTextReader(streamReader)) + { + var serializer = new JsonSerializer(); + res = serializer.Deserialize(jsonTextReader); + } + } + } + } + else + { + var errorResult = await response.Content.ReadAsStringAsync(); + //_logger.LogError("error posting to service '{service}' with code '{statuscode}' - Result: '{result}', Payload: '{payload}'", serviceUrl, response.StatusCode, errorResult, JsonConvert.SerializeObject(payload)); + } + } + catch (Exception ex) + { + //_logger.LogError(ex, "error at post"); + if (!suppressExceptions) + { + throw ex; + } + } + + return res; + } + + public async Task Delete(object payload, string serviceUrl, bool suppressExceptions = true) + { + T res = default; + + try + { + var response = await client.SendAsync(GetHttpRequestMessage(HttpMethod.Delete, serviceUrl, payload)); + + if (response.IsSuccessStatusCode) + { + using (var responseStream = await response.Content.ReadAsStreamAsync()) + { + using (var streamReader = new StreamReader(responseStream)) + { + using (var jsonTextReader = new JsonTextReader(streamReader)) + { + var serializer = new JsonSerializer(); + res = serializer.Deserialize(jsonTextReader); + } + } + } + } + else + { + var errorResult = await response.Content.ReadAsStringAsync(); + //_logger.LogError("error deleting to service '{service}' with code '{statuscode}' - Result: '{result}', Payload: '{payload}'", serviceUrl, response.StatusCode, errorResult, JsonConvert.SerializeObject(payload)); + } + } + catch (Exception ex) + { + //_logger.LogError(ex, "error at Delete"); + if (!suppressExceptions) + { + throw ex; + } + } + + return res; + } + + public T[] Get(string serviceUrl, bool suppressExceptions = true) + { + Wrapper res = default; + + try + { + //_logger.LogDebug($"calling service url {serviceUrl}"); + var response = client.Send(GetHttpRequestMessage(HttpMethod.Get, serviceUrl)); + + if (response.IsSuccessStatusCode) + { + using (var responseStream = response.Content.ReadAsStream()) + { + using (var streamReader = new StreamReader(responseStream)) + { + string content; + if (typeof(T).Equals(typeof(string))) + { + content = streamReader.ReadToEnd(); + res = (Wrapper)Convert.ChangeType(content, typeof(T)); + } + else + { + using (var jsonTextReader = new JsonTextReader(streamReader)) + { + var serializer = new JsonSerializer(); + //serializer. + res = serializer.Deserialize>(jsonTextReader); + } + } + } + } + } + else + { + var errorResult = response.Content.ReadAsStringAsync(); + //_logger.LogError("error getting from service '{service}' with code '{statuscode}' - Result: '{result}'", serviceUrl, response.StatusCode, errorResult); + } + } + catch (Exception ex) + { + //_logger.LogError(ex, "error at Get"); + if (!suppressExceptions) + { + throw ex; + } + } + + return res.Data; + } + } +} diff --git a/GameData/ApiSettings.cs b/GameData/ApiSettings.cs new file mode 100644 index 0000000..cc59cab --- /dev/null +++ b/GameData/ApiSettings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameData +{ + public class ApiSettings + { + public string BaseUrl => "https://directus.straso.com/"; + public string ApiToken => "9wIqngBjwR_l2f89D0EEAN1ldrcXUl6c"; + + + } +} diff --git a/GameData/ContainerSetup.cs b/GameData/ContainerSetup.cs new file mode 100644 index 0000000..b608e6f --- /dev/null +++ b/GameData/ContainerSetup.cs @@ -0,0 +1,25 @@ +using Autofac; +using GameData.Dummy; +using GameData.Repository; +using GameModel.Contract; +using GameModel.Settings; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameData +{ + //public static class ContainerSetup + //{ + // public static Autofac.IContainer GetContainer() + // { + // var builder = new ContainerBuilder(); + + // builder.RegisterType().As(); + // return builder.Build(); + // } + //} +} diff --git a/GameData/DummyExpenseRepository.cs b/GameData/Dummy/DummyExpenseRepository.cs similarity index 90% rename from GameData/DummyExpenseRepository.cs rename to GameData/Dummy/DummyExpenseRepository.cs index 61e8329..2954827 100644 --- a/GameData/DummyExpenseRepository.cs +++ b/GameData/Dummy/DummyExpenseRepository.cs @@ -1,7 +1,7 @@ using GameModel; using GameModel.Contract; -namespace GameData +namespace GameData.Dummy { public class DummyExpenseRepository : IExpenseRepository { diff --git a/GameData/GameData.csproj b/GameData/GameData.csproj index 044def4..8d810f8 100644 --- a/GameData/GameData.csproj +++ b/GameData/GameData.csproj @@ -6,6 +6,11 @@ enable + + + + + diff --git a/GameData/Repository/ExpenseRepository.cs b/GameData/Repository/ExpenseRepository.cs new file mode 100644 index 0000000..4049940 --- /dev/null +++ b/GameData/Repository/ExpenseRepository.cs @@ -0,0 +1,27 @@ +using GameModel; +using GameModel.Contract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameData.Repository +{ + public class ExpenseRepository : IExpenseRepository + { + private ApiClient _client; + + string Url => "items/expense"; + + public ExpenseRepository() + { + _client = new ApiClient(); + } + + public IEnumerable GetAll() + { + return _client.Get(Url); + } + } +} diff --git a/GameHandler.UnitTests/Extensions/PinThrowExtensionTests.cs b/GameHandler.UnitTests/Extensions/PinThrowExtensionTests.cs index d9b3300..dab2ca5 100644 --- a/GameHandler.UnitTests/Extensions/PinThrowExtensionTests.cs +++ b/GameHandler.UnitTests/Extensions/PinThrowExtensionTests.cs @@ -65,7 +65,7 @@ namespace GameHandler.UnitTests.Extensions var bs = BoardState.Create(ThrowMode.Decrease); var pt = PinThrow.Create(1, PinPicture.CreateAllPins(), false, true); var tirgger = pt.GetExpenseTriggers(bs); - CollectionAssert.AreEqual(tirgger, new[] { ExpenseTrigger.FullSink }); + CollectionAssert.AreEqual(tirgger, new[] { ExpenseTrigger.SinkAtAll }); } } } diff --git a/GameHandler.UnitTests/GameHandler/FreeGameHandlerTests.cs b/GameHandler.UnitTests/GameHandler/FreeGameHandlerTests.cs index 0a9e49d..fedd3f6 100644 --- a/GameHandler.UnitTests/GameHandler/FreeGameHandlerTests.cs +++ b/GameHandler.UnitTests/GameHandler/FreeGameHandlerTests.cs @@ -29,11 +29,16 @@ namespace GameHandler.UnitTests.GameHandler Assert.That(model.Throws.Count(), Is.EqualTo(4)); } + [Test] + public void Update_ThrowsExceptionWhenModelIsNull() + { + Assert.That(() => _gh.Update(PinThrow.Create(2, false, true), null, BoardState.Create()), Throws.TypeOf()); + } + [Test] public void Update_ThrowsExceptionWhenModelIsNotInitialized() { - Assert.That(() => _gh.Update(PinThrow.Create(2, false, true), BoardState.Create()), Throws.TypeOf()); - + Assert.That(() => _gh.Update(PinThrow.Create(2, false, true), new FreeGameModel(null,0,Array.Empty()), BoardState.Create()), Throws.TypeOf()); } } } diff --git a/GameHandler/Extensions/PinThrowExtension.cs b/GameHandler/Extensions/PinThrowExtension.cs index 21e624e..d8a7561 100644 --- a/GameHandler/Extensions/PinThrowExtension.cs +++ b/GameHandler/Extensions/PinThrowExtension.cs @@ -18,7 +18,7 @@ namespace GameHandler.Extensions { if (boardState.PinPicture.AllUp && boardState.ThrowMode == ThrowMode.Decrease) { - res.Add(ExpenseTrigger.FullSink); + res.Add(ExpenseTrigger.SinkAtAll); } else { diff --git a/GameHandler/GameHandler/FreeGameHandler.cs b/GameHandler/GameHandler/FreeGameHandler.cs index 4c8ed8b..f8bfadb 100644 --- a/GameHandler/GameHandler/FreeGameHandler.cs +++ b/GameHandler/GameHandler/FreeGameHandler.cs @@ -32,6 +32,11 @@ namespace GameHandler.GameHandler public IGameModel Update(PinThrow pinThrow, IGameModel gameModel, BoardState boardStateBeforeUpdate) { + if (gameModel == null) + { + throw new ArgumentNullException("gameModel cannto be null"); + } + var gm = gameModel as FreeGameModel; var throws = gm.Throws.ToList(); diff --git a/GameModel/Contracts/IExpenseRepository.cs b/GameModel/Contracts/IExpenseRepository.cs index 89831a9..aaacb4d 100644 --- a/GameModel/Contracts/IExpenseRepository.cs +++ b/GameModel/Contracts/IExpenseRepository.cs @@ -7,7 +7,7 @@ namespace GameModel.Contract static Expense[] TestData = new[] { Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Sink }, "Gosse", 0.5m, false), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Sink }, "Gosse2", 1.5m, false), - Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.FullSink }, "Gosse bei Anwurf", 1m, false), + Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.SinkAtAll }, "Gosse bei Anwurf", 1m, false), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Circle }, "Kranz", 1m, true), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.NinePins }, "alle Neune", 1m, true), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Eliminated }, "Ausgeschieden", 0.5m, false), diff --git a/GameModel/Expense.cs b/GameModel/Expense.cs index 4691fef..82d9250 100644 --- a/GameModel/Expense.cs +++ b/GameModel/Expense.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace GameModel { - public record Expense(Guid Guid, ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse) + public record Expense(Guid Id, ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse) { public static Expense Create(ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse) { diff --git a/GameModel/ExpenseTrigger.cs b/GameModel/ExpenseTrigger.cs index e75ae70..8e21dd2 100644 --- a/GameModel/ExpenseTrigger.cs +++ b/GameModel/ExpenseTrigger.cs @@ -17,7 +17,7 @@ namespace GameModel /// /// Sink at first throw in round /// - FullSink, // Gosse beim Anwurf + SinkAtAll, // Gosse beim Anwurf Eliminated, // Ausgeschieden Absent, // Am Spieltag nicht teilgenommen ExpensePoint // Strafpunkt, z.B. im "Scheißspiel" diff --git a/GameModel/MemberExpense.cs b/GameModel/MemberExpense.cs index fc7604e..0ef6735 100644 --- a/GameModel/MemberExpense.cs +++ b/GameModel/MemberExpense.cs @@ -6,11 +6,11 @@ using System.Threading.Tasks; namespace GameModel { - public record MemberExpense(Guid ExpenseGuid, int MemberId, ExpenseType ExpenseType, DateTime Created, decimal Price, string Name) + public record MemberExpense(Guid Id, int MemberId, Guid ExpenseId, ExpenseType ExpenseType, DateTime Created, decimal Price, string Name) { public static MemberExpense Create(int MemberId, Expense Expense) { - return new MemberExpense(Guid.NewGuid(), MemberId, Expense.ExpenseType, DateTime.Now, Expense.Price, Expense.Name); + return new MemberExpense(Guid.NewGuid(), MemberId, Expense.Id, Expense.ExpenseType, DateTime.Now, Expense.Price, Expense.Name); } } } diff --git a/GameModel/Settings/AppSettings.cs b/GameModel/Settings/AppSettings.cs new file mode 100644 index 0000000..e033ce6 --- /dev/null +++ b/GameModel/Settings/AppSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameModel.Settings +{ + public class AppSettings + { + public string Test { get; set; } + } +} diff --git a/KoogleCli/KoogleCli.csproj b/KoogleCli/KoogleCli.csproj index caaf8eb..8255d0c 100644 --- a/KoogleCli/KoogleCli.csproj +++ b/KoogleCli/KoogleCli.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,8 +7,22 @@ enable + + + + + + + PreserveNewest + + + + + + + diff --git a/KoogleCli/Program.cs b/KoogleCli/Program.cs index d1043d0..1a2d38c 100644 --- a/KoogleCli/Program.cs +++ b/KoogleCli/Program.cs @@ -2,18 +2,75 @@ using Autofac; using CommandLine; using GameData; +using GameData.Repository; using GameHandler; using GameHandler.DeathGame; using GameHandler.Extensions; using GameModel; using GameModel.Contract; using GameModel.DeathGame; +using GameModel.Settings; using KoogleCli.Model; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Spectre.Console; using Spectre.Console.Json; using static System.Runtime.InteropServices.JavaScript.JSType; + +const string RootLifetimeTag = "MyIsolatedRoot"; + + + AnsiConsole.MarkupLine("Welcome to [green]koogle[/]"); + + +var container = Register(); + +var serviceCollection = new ServiceCollection(); +//serviceCollection.AddLogging(); + + +var scope = container.BeginLifetimeScope(RootLifetimeTag, b => +{ + //b.Populate(serviceCollection, RootLifetimeTag); +}); + + +Autofac.IContainer Register() +{ + + var configurationBuilder = new ConfigurationBuilder() + .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + //.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true) + //.AddEnvironmentVariables() + //.AddCommandLine(args); + ; + + // Add your custom configuration provider here if needed + // .Add(new CustomConfigurationSource()); + + IConfiguration configuration = configurationBuilder.Build(); + + + + var someSettings = configuration.GetSection(typeof(AppSettings).Name).Get(); + + + var builder = new ContainerBuilder(); + builder.RegisterInstance(someSettings); + builder.RegisterType().As(); + return builder.Build(); +} + + + +var test = container.Resolve(); +Console.WriteLine( test.Test); +Console.ReadLine(); + ShowMainMenu(); void ShowMainMenu() { @@ -78,7 +135,7 @@ void NewGameAction() void StartGameAction(string gameName) { - _gs = new GameService(GetContainer()); + _gs = new GameService(); var bs = _gs.Start(new[] { 1,2,3,4}, GetGameSettings(gameName), gameName ); @@ -120,13 +177,6 @@ void StartGameAction(string gameName) ShowMainMenu(); } -IContainer GetContainer() -{ - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - return builder.Build(); -} - IGameSettings GetGameSettings(string gameName) { if (gameName == DeathGameHandler.GAMENAME_DEATHBOX) diff --git a/KoogleCli/appsettings.json b/KoogleCli/appsettings.json new file mode 100644 index 0000000..7700910 --- /dev/null +++ b/KoogleCli/appsettings.json @@ -0,0 +1,5 @@ +{ + "AppSettings": { + "Test": "value" + } +} diff --git a/KoogleV4.sln b/KoogleV4.sln index 970ed25..13421f8 100644 --- a/KoogleV4.sln +++ b/KoogleV4.sln @@ -13,10 +13,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameModel.UnitTests", "Game EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoogleCli", "KoogleCli\KoogleCli.csproj", "{3FF45A02-42F9-4E75-993B-6582DD2A22BF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameData", "GameData\GameData.csproj", "{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameData", "GameData\GameData.csproj", "{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameData.UnitTests", "GameData.UnitTests\GameData.UnitTests.csproj", "{910C00EB-06CE-4EBC-9B76-DD290DC03AE8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,6 +49,10 @@ Global {D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Release|Any CPU.Build.0 = Release|Any CPU + {910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -54,6 +60,7 @@ Global GlobalSection(NestedProjects) = preSolution {E2F3CE36-0051-4C9A-B3FF-0BB44292B756} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B} {C752388E-815A-4911-AC75-B6C27337D81A} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B} + {910C00EB-06CE-4EBC-9B76-DD290DC03AE8} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {00DAFA57-4F14-4807-886E-392D4E67BA46}