Skip to content

Commit 0b93bad

Browse files
committed
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

+5-10
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

+242
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 <[email protected]>" ""
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)

0 commit comments

Comments
 (0)