Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0b93bad

Browse files
committedMay 23, 2018
net461 -> net471, remove App.configs (auto now), Suave.Testing inside projs instead
1 parent 6a43878 commit 0b93bad

20 files changed

+1507
-1704
lines changed
 

‎HttpFs.IntegrationTests/HttpFs.IntegrationTests.fsproj

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,22 @@
44
<Name>HttpFs.IntegrationTests</Name>
55
<AssemblyName>HttpFs.IntegrationTests</AssemblyName>
66
<OutputType>Exe</OutputType>
7-
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
7+
<TargetFrameworks>netcoreapp2.0;net471</TargetFrameworks>
8+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
89
</PropertyGroup>
10+
911
<ItemGroup>
12+
<Compile Include="Testing.fs" />
1013
<Compile Include="HttpServer.fs" />
1114
<Compile Include="Tests.fs" />
1215
<Compile Include="Program.fs" />
1316
<None Include="sample-form-post.fsx" />
14-
<None Include="app.config" />
1517
<None Include="paket.references" />
1618
<Content Include="cat-stare.gif">
1719
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1820
</Content>
19-
</ItemGroup>
20-
<ItemGroup>
2121
<ProjectReference Include="..\HttpFs\HttpFs.fsproj" />
2222
</ItemGroup>
23-
<ItemGroup Condition="$(TargetFramework) == 'net461'">
24-
<Reference Include="System.Net.Http">
25-
<HintPath>..\..\packages\System.Net.Http\lib\net46\System.Net.Http.dll</HintPath>
26-
<Private>True</Private>
27-
</Reference>
28-
</ItemGroup>
23+
2924
<Import Project="..\.paket\Paket.Restore.targets" />
3025
</Project>

‎HttpFs.IntegrationTests/Testing.fs

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
module Suave.Testing
2+
3+
(** For testing suave applications easily
4+
5+
Example:
6+
7+
open Suave
8+
open Suave.Web
9+
open Suave.Types
10+
open Suave.Testing
11+
12+
open Expecto
13+
14+
let runWithConfig = runWith defaultConfig
15+
16+
testCase "parsing a large multipart form" <| fun _ ->
17+
18+
let res =
19+
runWithConfig testMultipartForm
20+
|> req HttpMethod.POST "/" (Some byteArrayContent)
21+
22+
Expect.equal "Bob <bob@wishfulcoding.mailgun.org>" ""
23+
24+
*)
25+
26+
open System
27+
open System.Diagnostics
28+
open System.Threading
29+
open System.Net
30+
open System.Net.Http
31+
open System.Net.Http.Headers
32+
open Expecto
33+
open Suave
34+
open Suave.Logging
35+
open Suave.Logging.Message
36+
open Suave.Http
37+
38+
[<AutoOpen>]
39+
module ResponseData =
40+
let responseHeaders (response : HttpResponseMessage) =
41+
response.Headers
42+
43+
let contentHeaders (response : HttpResponseMessage) =
44+
response.Content.Headers
45+
46+
let statusCode (response : HttpResponseMessage) =
47+
response.StatusCode
48+
49+
let contentString (response : HttpResponseMessage) =
50+
response.Content.ReadAsStringAsync().Result
51+
52+
let contentByteArray (response : HttpResponseMessage) =
53+
response.Content.ReadAsByteArrayAsync().Result
54+
55+
module Utilities =
56+
57+
/// Utility function for mapping from Suave.Types.HttpMethod to
58+
/// System.Net.Http.HttpMethod.
59+
let toHttpMethod = function
60+
| HttpMethod.GET -> HttpMethod.Get
61+
| HttpMethod.POST -> HttpMethod.Post
62+
| HttpMethod.DELETE -> HttpMethod.Delete
63+
| HttpMethod.PUT-> HttpMethod.Put
64+
| HttpMethod.HEAD -> HttpMethod.Head
65+
| HttpMethod.TRACE -> HttpMethod.Trace
66+
| HttpMethod.OPTIONS -> HttpMethod.Options
67+
| HttpMethod.PATCH -> failwithf "PATCH not a supported method in HttpClient"
68+
| HttpMethod.CONNECT -> failwithf "CONNECT not a supported method in the unit tests"
69+
| HttpMethod.OTHER x -> failwithf "%A not a supported method" x
70+
71+
open Utilities
72+
73+
/// This test context is a holder for the runtime values of the web
74+
/// server of suave, as well as the cancellation token that is
75+
/// threaded throughout the web server and will shut down all
76+
/// concurrently running async operations.
77+
///
78+
/// When you are done with it, you should call `dispose_context` to
79+
/// cancel the token and dispose the server's runtime artifacts
80+
/// (like the listening socket etc).
81+
type SuaveTestCtx =
82+
{ cts : CancellationTokenSource
83+
suaveConfig : SuaveConfig }
84+
85+
/// Cancels the cancellation token source and disposes the server's
86+
/// resources.
87+
let disposeContext (ctx : SuaveTestCtx) =
88+
ctx.cts.Cancel()
89+
ctx.cts.Dispose()
90+
91+
/// Create a new test context from a factory that starts the web
92+
/// server, such as `web_server_async` from `Suave.Web`. Also pass
93+
/// in a `SuaveConfig` value and the web parts you'd like to test.
94+
///
95+
/// The factory needs to start two async's, one which this function
96+
/// can block on (listening) and another (server) which is the actual
97+
/// async value of the running server. The listening async value will
98+
/// be awaited inside this function but the server async value will
99+
/// be run on the thread pool.
100+
let runWithFactory factory config webParts : SuaveTestCtx =
101+
let binding = config.bindings.Head
102+
let baseUri = binding.ToString()
103+
let cts = new CancellationTokenSource()
104+
let config2 = { config with cancellationToken = cts.Token; bufferSize = 128; maxOps = 10 }
105+
106+
let listening, server = factory config webParts
107+
Async.Start(server, cts.Token)
108+
listening |> Async.RunSynchronously |> ignore // wait for the server to start listening
109+
110+
{ cts = cts
111+
suaveConfig = config2 }
112+
113+
/// Similar to run_with_factory, but uses the default suave factory.
114+
let runWith config webParts = runWithFactory startWebServerAsync config webParts
115+
116+
/// Ensures the context is disposed after 'f ctx' is called.
117+
let withContext f ctx =
118+
try
119+
f ctx
120+
finally disposeContext ctx
121+
122+
/// Create a new HttpRequestMessage towards the endpoint
123+
let createRequest methd resource query data (endpoint : Uri) =
124+
let uriBuilder = UriBuilder endpoint
125+
uriBuilder.Path <- resource
126+
uriBuilder.Query <- query
127+
128+
let request = new HttpRequestMessage(toHttpMethod methd, uriBuilder.Uri)
129+
request.Headers.ConnectionClose <- Nullable(true)
130+
data |> Option.iter (fun data -> request.Content <- data)
131+
request
132+
133+
/// Create a new disposable HttpClientHandler
134+
let createHandler decomp_method cookies =
135+
let handler = new Net.Http.HttpClientHandler(AllowAutoRedirect = false)
136+
handler.AutomaticDecompression <- decomp_method
137+
cookies |> Option.iter (fun cookies -> handler.CookieContainer <- cookies)
138+
handler
139+
140+
let createClient handler =
141+
new HttpClient(handler)
142+
143+
/// Send the request with the client - returning the result of the request
144+
let send (client : HttpClient) (timeout : TimeSpan) (ctx : SuaveTestCtx) (request : HttpRequestMessage) =
145+
ctx.suaveConfig.logger.verbose (
146+
eventX "Send"
147+
>> setFieldValue "method" request.Method.Method
148+
>> setFieldValue "uri" request.RequestUri)
149+
150+
let send = client.SendAsync(request, HttpCompletionOption.ResponseContentRead, ctx.cts.Token)
151+
152+
let completed = send.Wait (int timeout.TotalMilliseconds, ctx.cts.Token)
153+
if not completed && Debugger.IsAttached then Debugger.Break()
154+
else Expect.isTrue completed (sprintf "should finish request in %fms" timeout.TotalMilliseconds)
155+
156+
send.Result
157+
158+
let endpointUri (suaveConfig : SuaveConfig) =
159+
Uri(suaveConfig.bindings.Head.ToString())
160+
161+
/// This is the main function for the testing library; it lets you assert
162+
/// on the request/response values while ensuring deterministic
163+
/// disposal of suave.
164+
///
165+
/// Currently, it:
166+
///
167+
/// - doesn't automatically follow 301 FOUND redirects (nor 302, 307) to
168+
/// ensure you can assert on redirects.
169+
/// - only requests to the very first binding your web server has in use
170+
/// - only sets a HttpContent if you have given a value to the `data`
171+
/// parameter.
172+
/// - waits 5000 ms for a reply, then breaks into the debugger if you're
173+
/// attached, otherwise asserts a failure of the timeout
174+
/// - calls `f_result` with the HttpResponseMessage
175+
///
176+
let reqResp
177+
(methd : HttpMethod)
178+
(resource : string)
179+
(query : string)
180+
data
181+
(cookies : CookieContainer option)
182+
(decompMethod : DecompressionMethods)
183+
(fRequest : HttpRequestMessage -> HttpRequestMessage)
184+
fResult =
185+
186+
let defaultTimeout = TimeSpan.FromSeconds 10.
187+
188+
withContext <| fun ctx ->
189+
use handler = createHandler decompMethod cookies
190+
use client = createClient handler
191+
use request = createRequest methd resource query data (endpointUri ctx.suaveConfig) |> fRequest
192+
use result = request |> send client defaultTimeout ctx
193+
fResult result
194+
195+
let req methd resource data =
196+
reqResp methd resource "" data None DecompressionMethods.None id contentString
197+
198+
let reqQuery methd resource query =
199+
reqResp methd resource query None None DecompressionMethods.None id contentString
200+
201+
let reqBytes methd resource data =
202+
reqResp methd resource "" data None DecompressionMethods.None id contentByteArray
203+
204+
let reqGZip methd resource data =
205+
reqResp methd resource "" data None DecompressionMethods.GZip id contentString
206+
207+
let reqDeflate methd resource data =
208+
reqResp methd resource "" data None DecompressionMethods.Deflate id contentString
209+
210+
let reqGZipBytes methd resource data =
211+
reqResp methd resource "" data None DecompressionMethods.GZip id contentByteArray
212+
213+
let reqDeflateBytes methd resource data =
214+
reqResp methd resource "" data None DecompressionMethods.Deflate id contentByteArray
215+
216+
let reqHeaders methd resource data =
217+
reqResp methd resource "" data None DecompressionMethods.None id responseHeaders
218+
219+
let reqContentHeaders methd resource data =
220+
reqResp methd resource "" data None DecompressionMethods.None id contentHeaders
221+
222+
let reqStatusCode methd resource data =
223+
reqResp methd resource "" data None DecompressionMethods.None id statusCode
224+
225+
/// Test a request by looking at the cookies alone.
226+
let reqCookies methd resource data ctx =
227+
let cookies = new CookieContainer()
228+
reqResp
229+
methd resource "" data
230+
(Some cookies)
231+
DecompressionMethods.None
232+
id
233+
contentString
234+
ctx
235+
|> ignore // places stuff in the cookie container
236+
cookies
237+
238+
/// Returns the cookie collection for the default binding.
239+
let reqCookies' methd resource data ctx =
240+
reqCookies methd resource data ctx
241+
|> fun cookies ->
242+
cookies.GetCookies(endpointUri ctx.suaveConfig)

‎HttpFs.IntegrationTests/app.config

Lines changed: 0 additions & 160 deletions
This file was deleted.

‎HttpFs.IntegrationTests/paket.references

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ Hopac
33
System.Text.Encoding.CodePages
44
System.Net.Http
55
Expecto
6-
Suave.Testing
6+
Suave

‎HttpFs.SampleApplication/HttpFs.SampleApplication.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<OutputType>Exe</OutputType>
77
<TargetFramework>netcoreapp2.0</TargetFramework>
88
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
9+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
910
</PropertyGroup>
1011
<ItemGroup>
1112
<Compile Include="PageDownloader.fs" />

‎HttpFs.SampleApplication/app.config

Lines changed: 1 addition & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
33

4-
<runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
5-
<dependentAssembly>
6-
<Paket>True</Paket>
7-
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
8-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.4.1.0" />
9-
</dependentAssembly>
10-
<dependentAssembly>
11-
<Paket>True</Paket>
12-
<assemblyIdentity name="System.Collections.Concurrent" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
13-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.13.0" />
14-
</dependentAssembly>
15-
<dependentAssembly>
16-
<Paket>True</Paket>
17-
<assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
18-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
19-
</dependentAssembly>
20-
<dependentAssembly>
21-
<Paket>True</Paket>
22-
<assemblyIdentity name="System.Linq" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
23-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
24-
</dependentAssembly>
25-
<dependentAssembly>
26-
<Paket>True</Paket>
27-
<assemblyIdentity name="System.Linq.Expressions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
28-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
29-
</dependentAssembly>
30-
<dependentAssembly>
31-
<Paket>True</Paket>
32-
<assemblyIdentity name="System.Linq.Queryable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
33-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
34-
</dependentAssembly>
35-
<dependentAssembly>
36-
<Paket>True</Paket>
37-
<assemblyIdentity name="System.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
38-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.13.0" />
39-
</dependentAssembly>
40-
<dependentAssembly>
41-
<Paket>True</Paket>
42-
<assemblyIdentity name="System.Reflection.Emit" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
43-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
44-
</dependentAssembly>
45-
<dependentAssembly>
46-
<Paket>True</Paket>
47-
<assemblyIdentity name="System.Reflection.Emit.ILGeneration" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
48-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
49-
</dependentAssembly>
50-
<dependentAssembly>
51-
<Paket>True</Paket>
52-
<assemblyIdentity name="System.Reflection.Emit.Lightweight" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
53-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
54-
</dependentAssembly>
55-
<dependentAssembly>
56-
<Paket>True</Paket>
57-
<assemblyIdentity name="System.Reflection.TypeExtensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
58-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.2.0" />
59-
</dependentAssembly>
60-
<dependentAssembly>
61-
<Paket>True</Paket>
62-
<assemblyIdentity name="System.Runtime.Numerics" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
63-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
64-
</dependentAssembly>
65-
<dependentAssembly>
66-
<Paket>True</Paket>
67-
<assemblyIdentity name="System.Text.RegularExpressions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
68-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
69-
</dependentAssembly>
70-
<dependentAssembly>
71-
<Paket>True</Paket>
72-
<assemblyIdentity name="System.Threading" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
73-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.12.0" />
74-
</dependentAssembly>
75-
<dependentAssembly>
76-
<Paket>True</Paket>
77-
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
78-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
79-
</dependentAssembly>
80-
<dependentAssembly>
81-
<Paket>True</Paket>
82-
<assemblyIdentity name="System.Threading.Tasks.Parallel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
83-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
84-
</dependentAssembly>
85-
<dependentAssembly>
86-
<Paket>True</Paket>
87-
<assemblyIdentity name="System.Threading.Thread" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
88-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.1.0" />
89-
</dependentAssembly>
90-
<dependentAssembly>
91-
<Paket>True</Paket>
92-
<assemblyIdentity name="System.Threading.ThreadPool" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
93-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.11.0" />
94-
</dependentAssembly>
95-
<dependentAssembly>
96-
<Paket>True</Paket>
97-
<assemblyIdentity name="System.Xml.ReaderWriter" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
98-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.0.0" />
99-
</dependentAssembly>
100-
</assemblyBinding></runtime></configuration>
4+
</configuration>

‎HttpFs.SamplePostApplication/App.config

Lines changed: 1 addition & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,100 +4,4 @@
44
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
55
</startup>
66

7-
<runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
8-
<dependentAssembly>
9-
<Paket>True</Paket>
10-
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
11-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.4.1.0" />
12-
</dependentAssembly>
13-
<dependentAssembly>
14-
<Paket>True</Paket>
15-
<assemblyIdentity name="System.Collections.Concurrent" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
16-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.13.0" />
17-
</dependentAssembly>
18-
<dependentAssembly>
19-
<Paket>True</Paket>
20-
<assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
21-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
22-
</dependentAssembly>
23-
<dependentAssembly>
24-
<Paket>True</Paket>
25-
<assemblyIdentity name="System.Linq" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
26-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
27-
</dependentAssembly>
28-
<dependentAssembly>
29-
<Paket>True</Paket>
30-
<assemblyIdentity name="System.Linq.Expressions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
31-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
32-
</dependentAssembly>
33-
<dependentAssembly>
34-
<Paket>True</Paket>
35-
<assemblyIdentity name="System.Linq.Queryable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
36-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
37-
</dependentAssembly>
38-
<dependentAssembly>
39-
<Paket>True</Paket>
40-
<assemblyIdentity name="System.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
41-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.13.0" />
42-
</dependentAssembly>
43-
<dependentAssembly>
44-
<Paket>True</Paket>
45-
<assemblyIdentity name="System.Reflection.Emit" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
46-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
47-
</dependentAssembly>
48-
<dependentAssembly>
49-
<Paket>True</Paket>
50-
<assemblyIdentity name="System.Reflection.Emit.ILGeneration" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
51-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
52-
</dependentAssembly>
53-
<dependentAssembly>
54-
<Paket>True</Paket>
55-
<assemblyIdentity name="System.Reflection.Emit.Lightweight" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
56-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
57-
</dependentAssembly>
58-
<dependentAssembly>
59-
<Paket>True</Paket>
60-
<assemblyIdentity name="System.Reflection.TypeExtensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
61-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.2.0" />
62-
</dependentAssembly>
63-
<dependentAssembly>
64-
<Paket>True</Paket>
65-
<assemblyIdentity name="System.Runtime.Numerics" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
66-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
67-
</dependentAssembly>
68-
<dependentAssembly>
69-
<Paket>True</Paket>
70-
<assemblyIdentity name="System.Text.RegularExpressions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
71-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
72-
</dependentAssembly>
73-
<dependentAssembly>
74-
<Paket>True</Paket>
75-
<assemblyIdentity name="System.Threading" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
76-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.12.0" />
77-
</dependentAssembly>
78-
<dependentAssembly>
79-
<Paket>True</Paket>
80-
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
81-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.1.0" />
82-
</dependentAssembly>
83-
<dependentAssembly>
84-
<Paket>True</Paket>
85-
<assemblyIdentity name="System.Threading.Tasks.Parallel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
86-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.2.0" />
87-
</dependentAssembly>
88-
<dependentAssembly>
89-
<Paket>True</Paket>
90-
<assemblyIdentity name="System.Threading.Thread" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
91-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.1.0" />
92-
</dependentAssembly>
93-
<dependentAssembly>
94-
<Paket>True</Paket>
95-
<assemblyIdentity name="System.Threading.ThreadPool" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
96-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.0.11.0" />
97-
</dependentAssembly>
98-
<dependentAssembly>
99-
<Paket>True</Paket>
100-
<assemblyIdentity name="System.Xml.ReaderWriter" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
101-
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.1.0.0" />
102-
</dependentAssembly>
103-
</assemblyBinding></runtime></configuration>
7+
</configuration>

‎HttpFs.SamplePostApplication/HttpClient.SamplePostApplication.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
<Name>HttpFs.SamplePostApplication</Name>
55
<AssemblyName>HttpFs.SamplePostApplication</AssemblyName>
66
<OutputType>Exe</OutputType>
7-
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
7+
<TargetFrameworks>netcoreapp2.0;net471</TargetFrameworks>
88
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
9+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
910
</PropertyGroup>
1011
<ItemGroup>
1112
<Compile Include="Program.fs" />

‎HttpFs.UnitTests/HttpFs.UnitTests.fsproj

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,22 @@
44
<AssemblyName>HttpFs.UnitTests</AssemblyName>
55
<Name>HttpFs.UnitTests</Name>
66
<OutputType>Exe</OutputType>
7-
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
7+
<TargetFrameworks>netcoreapp2.0;net471</TargetFrameworks>
8+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
89
</PropertyGroup>
910
<ItemGroup>
1011
<Compile Include="AssemblyInfo.fs" />
12+
<Compile Include="Testing.fs" />
1113
<Compile Include="Expect.fs" />
1214
<Compile Include="Api.fs" />
1315
<Compile Include="RequestBody.fs" />
1416
<Compile Include="SendingStreams.fs" />
1517
<Compile Include="SSE.fs" />
1618
<Compile Include="Program.fs" />
17-
<None Include="app.config">
18-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
19-
</None>
2019
<None Include="paket.references" />
2120
<None Include="pix.gif">
2221
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2322
</None>
24-
</ItemGroup>
25-
<ItemGroup>
2623
<ProjectReference Include="..\HttpFs\HttpFs.fsproj" />
2724
</ItemGroup>
2825
<Import Project="..\.paket\Paket.Restore.targets" />

‎HttpFs.UnitTests/Testing.fs

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
module Suave.Testing
2+
3+
(** For testing suave applications easily
4+
5+
Example:
6+
7+
open Suave
8+
open Suave.Web
9+
open Suave.Types
10+
open Suave.Testing
11+
12+
open Expecto
13+
14+
let runWithConfig = runWith defaultConfig
15+
16+
testCase "parsing a large multipart form" <| fun _ ->
17+
18+
let res =
19+
runWithConfig testMultipartForm
20+
|> req HttpMethod.POST "/" (Some byteArrayContent)
21+
22+
Expect.equal "Bob <bob@wishfulcoding.mailgun.org>" ""
23+
24+
*)
25+
26+
open System
27+
open System.Diagnostics
28+
open System.Threading
29+
open System.Net
30+
open System.Net.Http
31+
open System.Net.Http.Headers
32+
open Expecto
33+
open Suave
34+
open Suave.Logging
35+
open Suave.Logging.Message
36+
open Suave.Http
37+
38+
[<AutoOpen>]
39+
module ResponseData =
40+
let responseHeaders (response : HttpResponseMessage) =
41+
response.Headers
42+
43+
let contentHeaders (response : HttpResponseMessage) =
44+
response.Content.Headers
45+
46+
let statusCode (response : HttpResponseMessage) =
47+
response.StatusCode
48+
49+
let contentString (response : HttpResponseMessage) =
50+
response.Content.ReadAsStringAsync().Result
51+
52+
let contentByteArray (response : HttpResponseMessage) =
53+
response.Content.ReadAsByteArrayAsync().Result
54+
55+
module Utilities =
56+
57+
/// Utility function for mapping from Suave.Types.HttpMethod to
58+
/// System.Net.Http.HttpMethod.
59+
let toHttpMethod = function
60+
| HttpMethod.GET -> HttpMethod.Get
61+
| HttpMethod.POST -> HttpMethod.Post
62+
| HttpMethod.DELETE -> HttpMethod.Delete
63+
| HttpMethod.PUT-> HttpMethod.Put
64+
| HttpMethod.HEAD -> HttpMethod.Head
65+
| HttpMethod.TRACE -> HttpMethod.Trace
66+
| HttpMethod.OPTIONS -> HttpMethod.Options
67+
| HttpMethod.PATCH -> failwithf "PATCH not a supported method in HttpClient"
68+
| HttpMethod.CONNECT -> failwithf "CONNECT not a supported method in the unit tests"
69+
| HttpMethod.OTHER x -> failwithf "%A not a supported method" x
70+
71+
open Utilities
72+
73+
/// This test context is a holder for the runtime values of the web
74+
/// server of suave, as well as the cancellation token that is
75+
/// threaded throughout the web server and will shut down all
76+
/// concurrently running async operations.
77+
///
78+
/// When you are done with it, you should call `dispose_context` to
79+
/// cancel the token and dispose the server's runtime artifacts
80+
/// (like the listening socket etc).
81+
type SuaveTestCtx =
82+
{ cts : CancellationTokenSource
83+
suaveConfig : SuaveConfig }
84+
85+
/// Cancels the cancellation token source and disposes the server's
86+
/// resources.
87+
let disposeContext (ctx : SuaveTestCtx) =
88+
ctx.cts.Cancel()
89+
ctx.cts.Dispose()
90+
91+
/// Create a new test context from a factory that starts the web
92+
/// server, such as `web_server_async` from `Suave.Web`. Also pass
93+
/// in a `SuaveConfig` value and the web parts you'd like to test.
94+
///
95+
/// The factory needs to start two async's, one which this function
96+
/// can block on (listening) and another (server) which is the actual
97+
/// async value of the running server. The listening async value will
98+
/// be awaited inside this function but the server async value will
99+
/// be run on the thread pool.
100+
let runWithFactory factory config webParts : SuaveTestCtx =
101+
let binding = config.bindings.Head
102+
let baseUri = binding.ToString()
103+
let cts = new CancellationTokenSource()
104+
let config2 = { config with cancellationToken = cts.Token; bufferSize = 128; maxOps = 10 }
105+
106+
let listening, server = factory config webParts
107+
Async.Start(server, cts.Token)
108+
listening |> Async.RunSynchronously |> ignore // wait for the server to start listening
109+
110+
{ cts = cts
111+
suaveConfig = config2 }
112+
113+
/// Similar to run_with_factory, but uses the default suave factory.
114+
let runWith config webParts = runWithFactory startWebServerAsync config webParts
115+
116+
/// Ensures the context is disposed after 'f ctx' is called.
117+
let withContext f ctx =
118+
try
119+
f ctx
120+
finally disposeContext ctx
121+
122+
/// Create a new HttpRequestMessage towards the endpoint
123+
let createRequest methd resource query data (endpoint : Uri) =
124+
let uriBuilder = UriBuilder endpoint
125+
uriBuilder.Path <- resource
126+
uriBuilder.Query <- query
127+
128+
let request = new HttpRequestMessage(toHttpMethod methd, uriBuilder.Uri)
129+
request.Headers.ConnectionClose <- Nullable(true)
130+
data |> Option.iter (fun data -> request.Content <- data)
131+
request
132+
133+
/// Create a new disposable HttpClientHandler
134+
let createHandler decomp_method cookies =
135+
let handler = new Net.Http.HttpClientHandler(AllowAutoRedirect = false)
136+
handler.AutomaticDecompression <- decomp_method
137+
cookies |> Option.iter (fun cookies -> handler.CookieContainer <- cookies)
138+
handler
139+
140+
let createClient handler =
141+
new HttpClient(handler)
142+
143+
/// Send the request with the client - returning the result of the request
144+
let send (client : HttpClient) (timeout : TimeSpan) (ctx : SuaveTestCtx) (request : HttpRequestMessage) =
145+
ctx.suaveConfig.logger.verbose (
146+
eventX "Send"
147+
>> setFieldValue "method" request.Method.Method
148+
>> setFieldValue "uri" request.RequestUri)
149+
150+
let send = client.SendAsync(request, HttpCompletionOption.ResponseContentRead, ctx.cts.Token)
151+
152+
let completed = send.Wait (int timeout.TotalMilliseconds, ctx.cts.Token)
153+
if not completed && Debugger.IsAttached then Debugger.Break()
154+
else Expect.isTrue completed (sprintf "should finish request in %fms" timeout.TotalMilliseconds)
155+
156+
send.Result
157+
158+
let endpointUri (suaveConfig : SuaveConfig) =
159+
Uri(suaveConfig.bindings.Head.ToString())
160+
161+
/// This is the main function for the testing library; it lets you assert
162+
/// on the request/response values while ensuring deterministic
163+
/// disposal of suave.
164+
///
165+
/// Currently, it:
166+
///
167+
/// - doesn't automatically follow 301 FOUND redirects (nor 302, 307) to
168+
/// ensure you can assert on redirects.
169+
/// - only requests to the very first binding your web server has in use
170+
/// - only sets a HttpContent if you have given a value to the `data`
171+
/// parameter.
172+
/// - waits 5000 ms for a reply, then breaks into the debugger if you're
173+
/// attached, otherwise asserts a failure of the timeout
174+
/// - calls `f_result` with the HttpResponseMessage
175+
///
176+
let reqResp
177+
(methd : HttpMethod)
178+
(resource : string)
179+
(query : string)
180+
data
181+
(cookies : CookieContainer option)
182+
(decompMethod : DecompressionMethods)
183+
(fRequest : HttpRequestMessage -> HttpRequestMessage)
184+
fResult =
185+
186+
let defaultTimeout = TimeSpan.FromSeconds 10.
187+
188+
withContext <| fun ctx ->
189+
use handler = createHandler decompMethod cookies
190+
use client = createClient handler
191+
use request = createRequest methd resource query data (endpointUri ctx.suaveConfig) |> fRequest
192+
use result = request |> send client defaultTimeout ctx
193+
fResult result
194+
195+
let req methd resource data =
196+
reqResp methd resource "" data None DecompressionMethods.None id contentString
197+
198+
let reqQuery methd resource query =
199+
reqResp methd resource query None None DecompressionMethods.None id contentString
200+
201+
let reqBytes methd resource data =
202+
reqResp methd resource "" data None DecompressionMethods.None id contentByteArray
203+
204+
let reqGZip methd resource data =
205+
reqResp methd resource "" data None DecompressionMethods.GZip id contentString
206+
207+
let reqDeflate methd resource data =
208+
reqResp methd resource "" data None DecompressionMethods.Deflate id contentString
209+
210+
let reqGZipBytes methd resource data =
211+
reqResp methd resource "" data None DecompressionMethods.GZip id contentByteArray
212+
213+
let reqDeflateBytes methd resource data =
214+
reqResp methd resource "" data None DecompressionMethods.Deflate id contentByteArray
215+
216+
let reqHeaders methd resource data =
217+
reqResp methd resource "" data None DecompressionMethods.None id responseHeaders
218+
219+
let reqContentHeaders methd resource data =
220+
reqResp methd resource "" data None DecompressionMethods.None id contentHeaders
221+
222+
let reqStatusCode methd resource data =
223+
reqResp methd resource "" data None DecompressionMethods.None id statusCode
224+
225+
/// Test a request by looking at the cookies alone.
226+
let reqCookies methd resource data ctx =
227+
let cookies = new CookieContainer()
228+
reqResp
229+
methd resource "" data
230+
(Some cookies)
231+
DecompressionMethods.None
232+
id
233+
contentString
234+
ctx
235+
|> ignore // places stuff in the cookie container
236+
cookies
237+
238+
/// Returns the cookie collection for the default binding.
239+
let reqCookies' methd resource data ctx =
240+
reqCookies methd resource data ctx
241+
|> fun cookies ->
242+
cookies.GetCookies(endpointUri ctx.suaveConfig)

‎HttpFs.UnitTests/app.config

Lines changed: 0 additions & 159 deletions
This file was deleted.

‎HttpFs.UnitTests/paket.references

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
Hopac
22
Expecto
3-
Suave.Testing
3+
Suave

‎HttpFs/HttpFs.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,13 +1171,13 @@ module Composition =
11711171

11721172
let timerFilter (state: HttpFsState): JobFilter<Request, Response> =
11731173
around (fun () -> Stopwatch.StartNew())
1174-
(fun sw -> sw.Stop(); Message.gauge sw.ElapsedTicks "ticks" |> state.logger.logSimple)
1174+
(fun sw -> sw.Stop(); Message.gauge sw.Elapsed.TotalSeconds "seconds" |> state.logger.logSimple)
11751175

11761176
let timerFilterNamed (state: HttpFsState) (name): JobFilter<Request, Response> =
11771177
around (fun () -> Stopwatch.StartNew())
11781178
(fun sw ->
11791179
sw.Stop()
1180-
Message.gauge sw.ElapsedTicks "ticks"
1180+
Message.gauge sw.Elapsed.TotalSeconds "seconds"
11811181
|> Message.setName name
11821182
|> state.logger.logSimple)
11831183

‎HttpFs/HttpFs.fsproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<AssemblyName>HttpFs</AssemblyName>
55
<PackageId>Http.fs</PackageId>
66
<Version>5.1.1</Version>
7-
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
7+
<TargetFrameworks>netstandard2.0;net471</TargetFrameworks>
88
</PropertyGroup>
99
<ItemGroup>
1010
<Compile Include="..\paket-files\haf\YoLo\YoLo.fs">
@@ -18,9 +18,8 @@
1818
<Compile Include="AssemblyInfo.fs" />
1919
<Compile Include="SSE.fs" />
2020
<Compile Include="HttpFs.fs" />
21-
<PackageReference Include="FSharp.Core" Version="4.1.18" />
2221
</ItemGroup>
23-
<ItemGroup Condition="'$(TargetFramework)'!='net461'">
22+
<ItemGroup Condition="'$(TargetFramework)'!='net471'">
2423
<PackageReference Include="System.Diagnostics.FileVersionInfo" Version="4.3.0" />
2524
</ItemGroup>
2625
<Import Project="..\.paket\Paket.Restore.targets" />

‎HttpFs/SSE.fs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ module SSE =
137137
let rec inner s =
138138
streamer
139139
>>- interpret s
140-
>>= function (s, eo) ->
141-
match eo with
142-
| Some e ->
143-
Job.result ( { read = inner s }, e)
144-
| None ->
145-
inner s
140+
>>= fun (s, eo) ->
141+
match eo with
142+
| Some e ->
143+
Job.result ( { read = inner s }, e)
144+
| None ->
145+
inner s
146146
inner initialState

‎RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#### 5.2.0 — 2018-05-20
2+
* Bump compilation target to net471 due to https://github.com/dotnet/corefx/issues/23306 and so many other errors stemming from this
3+
* Suave.Testing is now a file in each unit test project
4+
15
#### 5.1.1 — 2018-05-20
26
* SSE improvements and tests
37

‎Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ task :compile => [:versioning, :restore, :assembly_info] do |b|
5353
"dotnet build HttpFs -c #{Configuration} --no-restore --framework net45") or exit(1)
5454
system "dotnet", %W|build HttpFs -c #{Configuration} --no-restore --framework netstandard2.0|
5555
Kernel.system({"FrameworkPathOverride" => "#{ENV["MONO_BASE_PATH"]}/4.5/"},
56-
"dotnet build HttpFs.IntegrationTests -c #{Configuration} --no-restore --framework net461") or exit(1)
56+
"dotnet build HttpFs.IntegrationTests -c #{Configuration} --no-restore --framework net471") or exit(1)
5757
system "dotnet", %W|build HttpFs.UnitTests -c #{Configuration} --no-restore --framework netcoreapp2.0|
5858
system "dotnet", %W|build HttpFs.IntegrationTests -c #{Configuration} --no-restore --framework netcoreapp2.0|
5959
else
@@ -71,7 +71,7 @@ end
7171
namespace :tests do
7272
task :integration do
7373
Kernel.system({"TEST_PORT" => "2345"}, "dotnet run -p HttpFs.IntegrationTests -c #{Configuration} --no-restore --no-build --framework netcoreapp2.0")
74-
system "HttpFs.IntegrationTests/bin/#{Configuration}/net461/HttpFs.IntegrationTests.exe", clr_command: true
74+
system "HttpFs.IntegrationTests/bin/#{Configuration}/net471/HttpFs.IntegrationTests.exe", clr_command: true
7575
end
7676
task :unit do
7777
system "dotnet", %W|run -p HttpFs.UnitTests -c #{Configuration} --no-restore --no-build|

‎build.fsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,18 @@ let build project framework =
5757

5858
Target "BuildTest" (fun _ ->
5959
build "HttpFs.UnitTests/HttpFs.UnitTests.fsproj" "netcoreapp2.0"
60-
build "HttpFs.UnitTests/HttpFs.UnitTests.fsproj" "net461"
60+
build "HttpFs.UnitTests/HttpFs.UnitTests.fsproj" "net471"
6161
build "HttpFs.IntegrationTests/HttpFs.IntegrationTests.fsproj" "netcoreapp2.0"
62-
build "HttpFs.IntegrationTests/HttpFs.IntegrationTests.fsproj" "net461"
62+
build "HttpFs.IntegrationTests/HttpFs.IntegrationTests.fsproj" "net471"
6363
)
6464

6565
Target "RunTest" (fun _ ->
6666
DotNetCli.RunCommand id ("HttpFs.UnitTests/bin/"+configuration+"/netcoreapp2.0/HttpFs.UnitTests.dll --summary --sequenced")
67-
//Shell.Exec ("HttpFs.UnitTests/bin/"+configuration+"/net461/HttpFs.UnitTests.exe","--summary --sequenced")
67+
//Shell.Exec ("HttpFs.UnitTests/bin/"+configuration+"/net471/HttpFs.UnitTests.exe","--summary --sequenced")
6868
//|> fun r -> if r<>0 then failwith "HttpFs.UnitTests.exe failed"
6969

7070
DotNetCli.RunCommand id ("HttpFs.IntegrationTests/bin/"+configuration+"/netcoreapp2.0/HttpFs.IntegrationTests.dll --summary --sequenced")
71-
//Shell.Exec ("HttpFs.IntegrationTests/bin/"+configuration+"/net461/HttpFs.IntegrationTests.exe","--summary --sequenced")
71+
//Shell.Exec ("HttpFs.IntegrationTests/bin/"+configuration+"/net471/HttpFs.IntegrationTests.exe","--summary --sequenced")
7272
//|> fun r -> if r<>0 then failwith "HttpFs.IntegrationTests.exe failed"
7373
)
7474

‎paket.dependencies

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
source https://api.nuget.org/v3/index.json
2-
redirects: on
32

43
github haf/YoLo YoLo.fs
54
github logary/logary src/Logary.Facade/Facade.fs
65

76
nuget Hopac ~> 0.3.23
8-
nuget FSharp.Core ~> 4.2.3
9-
nuget System.Net.Http ~> 4.3.2
7+
nuget FSharp.Core
8+
nuget Suave
9+
nuget System.Net.Http
1010
nuget System.Text.Encoding.CodePages ~> 4.4
11-
nuget Expecto ~> 5.1.2
12-
nuget Suave.Testing ~> 2
11+
nuget Expecto
1312

1413
group build
1514
source https://api.nuget.org/v3/index.json
1615
nuget FAKE
1716
nuget FSharp.Compiler.Tools
18-
github fsharp/FAKE modules/Octokit/Octokit.fsx
17+
github fsharp/FAKE modules/Octokit/Octokit.fsx

‎paket.lock

Lines changed: 983 additions & 1149 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.