diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c154806 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,158 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false + +[*.cs] +csharp_indent_case_contents = true:warning +csharp_indent_switch_labels = true:warning +csharp_space_after_cast = false:warning +csharp_space_after_keywords_in_control_flow_statements = true:warning +csharp_space_between_method_declaration_parameter_list_parentheses = false:warning +csharp_space_between_method_call_parameter_list_parentheses = false:warning +csharp_space_before_colon_in_inheritance_clause = false:warning +csharp_space_after_colon_in_inheritance_clause = true:warning +csharp_space_around_binary_operators = before_and_after:warning +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning +csharp_space_between_method_call_name_and_opening_parenthesis = false:warning +csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning +csharp_preserve_single_line_statements = false:warning +csharp_preserve_single_line_blocks = true:warning + +############################### +# .NET Coding Conventions # +############################### + +[*.cs] +# Organize usings +dotnet_sort_system_directives_first = true:warning +dotnet_separate_import_directive_groups = false:warning + +# this. preferences +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = always:warning +dotnet_style_readonly_field = true:warning + +# Expression-level preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true +dotnet_style_coalesce_expression = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_return = true:silent + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +############################### +# C# Code Style Rules # +############################### + +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent + +# Pattern-matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning + +# Null-checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:information +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_inlined_variable_declaration = true:warning + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = false +csharp_space_after_colon_in_inheritance_clause = true + +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# Wrapping preferences +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f6790d2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf +*.png binary +*.ttf binary +*.jpg binary +*.jpeg binary +*.pdf binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index dfcfd56..f76e58e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files -*.rsuser *.suo *.user *.userosscache @@ -13,9 +10,6 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -# Mono auto generated files -mono_crash.* - # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -23,58 +17,41 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ -[Ll]ogs/ -# Visual Studio 2015/2017 cache/options directory +# Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ -# Visual Studio 2017 auto generated files -Generated\ Files/ - # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUnit +# NUNIT *.VisualState.xml TestResult.xml -nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core +# DNX project.lock.json -project.fragment.lock.json artifacts/ -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio *_i.c *_p.c -*_h.h +*_i.h *.ilk *.meta *.obj -*.iobj *.pch *.pdb -*.ipdb *.pgc *.pgd *.rsp @@ -84,7 +61,6 @@ StyleCopReport.xml *.tlh *.tmp *.tmp_proj -*_wpftmp.csproj *.log *.vspscc *.vssscc @@ -113,9 +89,6 @@ ipch/ *.vspx *.sap -# Visual Studio Trace Files -*.e2e - # TFS 2012 Local Workspace $tf/ @@ -127,20 +100,15 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user +# JustCode is a .NET coding add-in +.JustCode + # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -172,7 +140,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -184,15 +152,13 @@ PublishScripts/ # NuGet Packages *.nupkg -# NuGet Symbol Packages -*.snupkg # The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* +# **/packages/* # except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ +!**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -209,15 +175,12 @@ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt -*.appx -*.appxbundle -*.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!?*.[Cc]ache/ +!*.[Cc]ache/ # Others ClientBin/ @@ -225,15 +188,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ @@ -248,22 +207,15 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ @@ -273,7 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ # Visual Studio 6 build log *.plg @@ -281,9 +232,6 @@ node_modules/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -299,52 +247,8 @@ paket-files/ # FAKE - F# Make .fake/ -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ +# JetBrains Rider +.idea/ +*.sln.iml -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ +**/out \ No newline at end of file diff --git a/README.md b/README.md index 36c43a2..eb1c5a8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,36 @@ # Ogooreck + Sneaky Test library + +## API Testing + +### Get + + + +```cs +[Fact] +public Task GetProducts() => + API.Given(URI("/api/products")) + .When(GET) + .Then(OK); +``` +snippet source | anchor + + +### Post + + + +```cs +[Fact] +public Task RegisterProduct() => + API.Given( + URI("/api/products"), + BODY(new RegisterProductRequest("abc-123", "Ogooreck")) + ) + .When(POST) + .Then(CREATED()); +``` +snippet source | anchor + diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..1660818 --- /dev/null +++ b/build.cmd @@ -0,0 +1,11 @@ +@echo off +FOR /f %%v IN ('dotnet --version') DO set version=%%v +set target_framework= +IF "%version:~0,2%"=="6." (set target_framework=net6.0) + +IF [%target_framework%]==[] ( + echo "BUILD FAILURE: .NET 6 SDK required to run build" + exit /b 1 +) + +dotnet run --project src/Ogooreck.Build/Ogooreck.Build.csproj -f %target_framework% -c Release -- %* diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..559cede --- /dev/null +++ b/build.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = "Stop"; +$version = dotnet --version; +if ($version.StartsWith("6.")) { + $target_framework="net6.0" +} else { + Write-Output "BUILD FAILURE: .NET 6 SDK required to run build" + exit 1 +} + +dotnet run --project src/Ogooreck.Build/Ogooreck.Build.csproj -f $target_framework -c Release -- $args diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..753250c --- /dev/null +++ b/build.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +version="$(dotnet --version)" +if [[ $version = 6.* ]]; then + target_framework="net6.0" +else + echo "BUILD FAILURE: .NET 6 SDK required to run build" + exit 1 +fi + +dotnet run --project src/Ogooreck.Build/Ogooreck.Build.csproj -f $target_framework -c Release -- "$@" diff --git a/mdsnippets.json b/mdsnippets.json new file mode 100644 index 0000000..2dea16f --- /dev/null +++ b/mdsnippets.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json", + "ReadOnly": true, + "TocLevel": 3, + "Convention": "SourceTransform", + "MaxWidth": 80, + "TreatMissingAsWarning": true, + "ValidateContent": true, + "OmitSnippetLinks": false, + "WriteHeader": false +} diff --git a/mdsource/README.source.md b/mdsource/README.source.md new file mode 100644 index 0000000..c25f267 --- /dev/null +++ b/mdsource/README.source.md @@ -0,0 +1,13 @@ +# Ogooreck + +Sneaky Test library + +## API Testing + +### Get + +snippet: ApiGetSample + +### Post + +snippet: ApiPostSample diff --git a/src/Ogooreck.Build/Ogooreck.Build.csproj b/src/Ogooreck.Build/Ogooreck.Build.csproj new file mode 100644 index 0000000..519f973 --- /dev/null +++ b/src/Ogooreck.Build/Ogooreck.Build.csproj @@ -0,0 +1,17 @@ + + + + Exe + net6.0 + false + + + + + + + + + + + diff --git a/src/Ogooreck.Build/Program.cs b/src/Ogooreck.Build/Program.cs new file mode 100644 index 0000000..c06d150 --- /dev/null +++ b/src/Ogooreck.Build/Program.cs @@ -0,0 +1,25 @@ +using static Bullseye.Targets; +using static SimpleExec.Command; + +Target("install-mdsnippets", IgnoreIfFailed(() => + Run("dotnet", $"tool install -g MarkdownSnippets.Tool") +)); + +Target("docs", DependsOn("install-mdsnippets"), () => { + // Run docs site + Run("mdsnippets"); +}); + +await RunTargetsAndExitAsync(args); + +Action IgnoreIfFailed(Action action) => () => +{ + try + { + action(); + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + } +}; diff --git a/src/Ogooreck.Sample.Api.Tests/ApiTests.cs b/src/Ogooreck.Sample.Api.Tests/ApiTests.cs index f1e82d7..6b6749b 100644 --- a/src/Ogooreck.Sample.Api.Tests/ApiTests.cs +++ b/src/Ogooreck.Sample.Api.Tests/ApiTests.cs @@ -9,15 +9,27 @@ public class Tests: IClassFixture> private ApiSpecification API; public Tests(ApiSpecification api) => API = api; + #region ApiGetSample + [Fact] public Task GetProducts() => - API.Given(URL("/api/products")) - .When(GET()) - .Then(OK()); - - // [Fact] - // public Task RegisterProduct() => - // API.Given(URL("/api/products")) - // .When(GET()) - // .Then(OK()); + API.Given(URI("/api/products")) + .When(GET) + .Then(OK); + + #endregion ApiGetSample + + + #region ApiPostSample + + [Fact] + public Task RegisterProduct() => + API.Given( + URI("/api/products"), + BODY(new RegisterProductRequest("abc-123", "Ogooreck")) + ) + .When(POST) + .Then(CREATED()); + + #endregion ApiPostSample } diff --git a/src/Ogooreck.Sample.Api.Tests/Ogooreck.Sample.Api.Tests.csproj b/src/Ogooreck.Sample.Api.Tests/Ogooreck.Sample.Api.Tests.csproj index 76f777e..3da30cf 100644 --- a/src/Ogooreck.Sample.Api.Tests/Ogooreck.Sample.Api.Tests.csproj +++ b/src/Ogooreck.Sample.Api.Tests/Ogooreck.Sample.Api.Tests.csproj @@ -1,23 +1,31 @@ - - net6.0 - + + net6.0 + - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + diff --git a/src/Ogooreck.sln b/src/Ogooreck.sln index f0b8416..c5306f0 100644 --- a/src/Ogooreck.sln +++ b/src/Ogooreck.sln @@ -9,6 +9,22 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F617812B-14C9-45AB-BDE1-5CDA13C339FA}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props + ..\build.cmd = ..\build.cmd + ..\build.ps1 = ..\build.ps1 + ..\build.sh = ..\build.sh + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{30269866-4EC5-46FE-8822-35E70583FFE9}" + ProjectSection(SolutionItems) = preProject + ..\mdsnippets.json = ..\mdsnippets.json + ..\README.md = ..\README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ogooreck.Build", "Ogooreck.Build\Ogooreck.Build.csproj", "{5D676ACB-C70F-447A-94AF-4547889CA100}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mdsource", "mdsource", "{2BA9001A-3DA6-4588-A192-6127114E1495}" + ProjectSection(SolutionItems) = preProject + ..\mdsource\README.source.md = ..\mdsource\README.source.md EndProjectSection EndProject Global @@ -29,5 +45,13 @@ Global {1A428941-1D1B-4C36-96EF-CAF1B991C76D}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A428941-1D1B-4C36-96EF-CAF1B991C76D}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A428941-1D1B-4C36-96EF-CAF1B991C76D}.Release|Any CPU.Build.0 = Release|Any CPU + {5D676ACB-C70F-447A-94AF-4547889CA100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D676ACB-C70F-447A-94AF-4547889CA100}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D676ACB-C70F-447A-94AF-4547889CA100}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D676ACB-C70F-447A-94AF-4547889CA100}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5D676ACB-C70F-447A-94AF-4547889CA100} = {F617812B-14C9-45AB-BDE1-5CDA13C339FA} + {2BA9001A-3DA6-4588-A192-6127114E1495} = {30269866-4EC5-46FE-8822-35E70583FFE9} EndGlobalSection EndGlobal diff --git a/src/Ogooreck/API/ApiSpecification.cs b/src/Ogooreck/API/ApiSpecification.cs index 78eb9d0..a8f200a 100644 --- a/src/Ogooreck/API/ApiSpecification.cs +++ b/src/Ogooreck/API/ApiSpecification.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http.Json; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; @@ -9,29 +10,51 @@ public static class ApiSpecification /////////////////// //// GIVEN //// /////////////////// + public static Func URI(string uri) => + URI(new Uri(uri, UriKind.RelativeOrAbsolute)); - public static Func URL(string url) => () => url; + public static Func URI(Uri uri) => + request => + { + request.RequestUri = uri; + return request; + }; + + public static Func BODY(T body) => + request => + { + request.Content = JsonContent.Create(body); + + return request; + }; /////////////////// //// WHEN //// /////////////////// + public static Func> GET = SEND(HttpMethod.Post); - public static Func> GET() => GET(""); + public static Func> POST = SEND(HttpMethod.Post); - public static Func> GET(string urlSuffix) => - (api, request) => api.GetAsync($"{request}/{urlSuffix}"); + public static Func> PUT = SEND(HttpMethod.Put); + + public static Func> DELETE = SEND(HttpMethod.Delete); + + public static Func> SEND(HttpMethod httpMethod) => + (api, request) => + { + request.Method = httpMethod; + return api.SendAsync(request); + }; /////////////////// //// THEN //// /////////////////// + public static Action OK = HTTP_STATUS(HttpStatusCode.OK); - public static Action OK() => AssertResponseStatus(HttpStatusCode.OK); - - public static Action CREATED(string apiPrefix) => + public static Action CREATED() => response => { - //response.RequestMessage.RequestUri.AbsolutePath - AssertResponseStatus(HttpStatusCode.Created); + HTTP_STATUS(HttpStatusCode.Created); var locationHeader = response.Headers.Location; @@ -39,24 +62,17 @@ public static Action CREATED(string apiPrefix) => var location = locationHeader!.OriginalString; - location.Should().StartWith(apiPrefix); - //assertDoesNotThrow(() => UUID.fromString(location.substring(apiPrefix.length() + 1))); + location.Should().StartWith(response.RequestMessage!.RequestUri!.AbsolutePath); }; - // public Action BAD_REQUEST = AssertResponseStatus(HttpStatus.BAD_REQUEST); - // - // public Action NOT_FOUND = AssertResponseStatus(HttpStatus.NOT_FOUND); - // - // public Action CONFLICT = AssertResponseStatus(HttpStatus.CONFLICT); - // - // public Action PRECONDITION_FAILED = AssertResponseStatus(HttpStatus.PRECONDITION_FAILED); - // - // public Action METHOD_NOT_ALLOWED = AssertResponseStatus(HttpStatus.METHOD_NOT_ALLOWED); - - public static Action AssertResponseStatus(HttpStatusCode status) - { - return response => response.StatusCode.Should().Be(status); - } + public static Action BAD_REQUEST = HTTP_STATUS(HttpStatusCode.BadRequest); + public static Action NOT_FOUND = HTTP_STATUS(HttpStatusCode.NotFound); + public static Action CONFLICT = HTTP_STATUS(HttpStatusCode.Conflict); + public static Action PRECONDITION_FAILED = HTTP_STATUS(HttpStatusCode.PreconditionFailed); + public static Action METHOD_NOT_ALLOWED = HTTP_STATUS(HttpStatusCode.MethodNotAllowed); + + public static Action HTTP_STATUS(HttpStatusCode status) => + response => response.StatusCode.Should().Be(status); } public class ApiSpecification: IDisposable where TProgram : class @@ -64,86 +80,29 @@ public class ApiSpecification: IDisposable where TProgram : class private readonly WebApplicationFactory applicationFactory; private readonly HttpClient client; - public ApiSpecification() + public ApiSpecification(): this(new WebApplicationFactory()) { - applicationFactory = new WebApplicationFactory(); - client = applicationFactory.CreateClient(); } - public GivenApiSpecificationBuilder Given(Func define) + public ApiSpecification(WebApplicationFactory applicationFactory) { - return new GivenApiSpecificationBuilder(client, define); + this.applicationFactory = applicationFactory; + client = applicationFactory.CreateClient(); } - // public Func POST = POST(""); - // - // public Func POST(string urlSuffix) { - // return (api, request) => this.restTemplate - // .postForEntity(getApiUrl() + urlSuffix, request, Void.class); - // } - // - // public Func POST(string urlSuffix, ETag eTag) { - // return (api, request) => this.restTemplate - // .postForEntity( - // getApiUrl() + urlSuffix, - // new HttpEntity<>(request, getIfMatchHeader(eTag)), - // Void.class - // ); - // } - // - // public Func PUT(ETag eTag) { - // return PUT("", eTag); - // } - // - // public Func PUT(string urlSuffix, ETag eTag) { - // return PUT(urlSuffix, eTag, true); - // } - // - // public Func PUT(string urlSuffix, ETag eTag, boolean withEmptyBody) { - // return (api, request) => this.restTemplate - // .exchange( - // getApiUrl() + urlSuffix + (withEmptyBody ? request : ""), - // HttpMethod.PUT, - // new HttpEntity<>(!withEmptyBody ? request : null, getIfMatchHeader(eTag)), - // Void.class - // ); - // } - // - // public Func DELETE(ETag eTag) { - // return DELETE("", eTag); - // } - // - // public Func DELETE(string urlSuffix, ETag eTag) { - // return (api, request) => this.restTemplate - // .exchange( - // getApiUrl() + urlSuffix + request, - // HttpMethod.DELETE, - // new HttpEntity<>(null, getIfMatchHeader(eTag)), - // Void.class - // ); - // } - // - // HttpHeaders getHeaders(Consumer consumer) { - // var headers = new HttpHeaders(); - // - // headers.setContentType(MediaType.APPLICATION_JSON); - // consumer.accept(headers); - // - // return headers; - // } - // - // HttpHeaders getIfMatchHeader(ETag eTag) { - // return getHeaders(headers => headers.setIfMatch(eTag.value())); - // } - // - // HttpHeaders getIfNoneMatchHeader(ETag eTag) { - // return getHeaders(headers => { - // if (eTag != null) - // headers.setIfNoneMatch(eTag.value()); - // }); - // } - // - // + public GivenApiSpecificationBuilder Given( + params Func[] builders) + { + var define = () => new HttpRequestMessage(); + + foreach (var current in builders) + { + var previous = define; + define = () => current(previous()); + } + + return new GivenApiSpecificationBuilder(client, define); + } ///////////////////// //// BUILDER ////