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 ////