fluentasserts.vibe.request 201/205(98%) line coverage

      
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
2426
250
260
270
280
290
300
310
320
330
340
350
360
370
380
390
400
410
4226
430
4426
450
460
470
480
491
500
511
521
530
541
550
560
570
580
590
600
610
620
632
642
650
660
670
681
692
701
711
720
730
740
750
760
770
780
790
800
810
822
830
840
852
860
872
880
890
900
910
920
938
940
950
960
970
980
991
1000
1010
1020
1030
1040
1051
1060
1070
1080
1090
1100
1111
1120
1130
1140
1150
1160
11714
1180
1190
1200
1210
1220
12326
1240
1250
1260
1270
1280
12926
13026
1310
13226
1330
1340
1350
13626
1370
1380
1390
1400
1417
14221
1437
1440
1450
1467
1470
1487
1490
1500
1510
1520
1533
1540
1553
1566
1572
1580
1590
1600
1613
1620
1633
1640
1650
1660
1670
1682
1690
1702
1716
1722
1730
1740
1750
1762
1770
1782
1790
1800
1810
1820
1832
1844
1851
1862
1870
1880
1892
1901
1910
1920
1930
1940
1951
1960
1970
1980
1992
2000
2012
2020
2030
2040
2050
206107
20713
2080
2090
2100
2110
21226
2130
2140
2150
2160
2170
2180
2190
2200
22126
2220
2230
22426
2250
2260
2270
2280
22926
23026
2310
2320
23326
2340
2350
2360
2370
23826
2390
24026
24126
2420
24326
2440
24526
2460
24726
2480
2490
2500
25126
25213
2530
2540
25513
2560
2570
2580
2590
2600
2610
2620
2630
2640
2650
2660
2670
2680
2690
2700
2710
2720
27326
2740
27526
2760
27726
2780
27926
2800
28126
2820
28326
28426
2850
286171
2870
28831
28931
2900
2910
29226
2930
2940
2950
2960
2971
2980
2990
3001
3010
3020
3030
3040
3050
3061
3070
3080
3090
3100
3110
3120
3130
3140
3150
3160
3171
3180
3191
3200
3211
3220
3230
3241
3251
3260
3271
3282
3290
3300
3311
3320
3331
3342
3350
3360
3370
3380
3390
3401
3410
3421
3430
3441
3450
3460
3471
3481
3490
3501
3512
3520
3530
3541
3550
3561
3572
3580
3590
3600
3610
3620
3631
3640
3651
3660
3671
3680
3690
3701
3711
3720
3731
3742
3750
3760
3771
3780
3791
3802
3810
3820
3830
3840
3850
3861
3870
3881
3890
3901
3910
3920
3931
3941
3950
3961
3972
3980
3990
4001
4010
4021
4032
4040
4050
4060
4070
4080
4091
4100
4111
4120
4131
4140
4150
4161
4171
4180
4191
4202
4210
4220
4231
4240
4251
4262
4270
4280
4290
4300
4310
4321
4330
4341
4350
4361
4370
4380
4391
4400
4411
4420
4431
4442
4450
4460
4471
4480
4491
4502
4510
4520
4530
4540
4550
4561
4570
4581
4590
4602
4610
4620
4631
4640
4651
4660
4670
4680
4690
4700
4710
4720
4730
4740
4751
4760
4771
4780
4792
4800
4810
4821
4830
4841
4850
4860
4870
4880
4890
4900
4910
4921
4930
4941
4950
4962
4970
4983
4993
5000
5010
5021
5030
5041
5050
5060
5070
5080
5090
5100
5110
5121
5130
5141
5150
5162
5170
5180
5191
5200
5211
5220
5230
5240
5250
5260
5270
5280
5291
5300
5311
5320
5331
5340
5350
5361
5370
5381
5390
5401
5412
5420
5430
5440
5450
5460
5471
5480
5491
5500
5511
5521
5530
5540
5551
5560
5571
5580
5590
5600
5610
5620
5633
5641
5650
5660
5670
5681
5690
5700
5710
5720
5730
5741
5750
5761
5770
5785
5795
5800
5810
5821
5830
5840
5850
5861
5870
5880
5890
5900
5910
5923
5931
5940
5950
5960
5971
5980
5993
6001
6010
6020
6030
6041
6050
6060
6071
6080
6090
6100
6110
6120
6133
6141
6150
6160
6170
6181
6190
6200
6211
6220
6230
6240
6250
6263
6271
6280
6290
6300
6311
6320
module fluentasserts.vibe.request; version(Have_vibe_d_http): import vibe.inet.url; import vibe.http.router; import vibe.http.form; import vibe.data.json; import vibe.stream.memory; import std.conv, std.string, std.array; import std.algorithm, std.conv; import std.stdio; import std.exception; import fluentasserts.core.base; import fluentasserts.core.results; //@safe: RequestRouter request(URLRouter router) { return new RequestRouter(router); } final class RequestRouter { private { alias ExpectedCallback = void delegate(Response res); ExpectedCallback[] expected; URLRouter router; HTTPServerRequest preparedRequest; string[string] headers; string responseBody; string requestBody; } this(URLRouter router) { this.router = router; } /// Send a string[string] to the server as x-www-form-urlencoded data RequestRouter send(string[string] data) { auto dst = appender!string; dst.writeFormData(data); header("Content-Type", "application/x-www-form-urlencoded"); return send(dst.data); } /// Send data to the server. You can send strings, Json or any other object /// which will be serialized to Json RequestRouter send(T)(T data) { static if (is(T == string)) { requestBody = data; return this; } else static if (is(T == Json)) { requestBody = data.toPrettyString; () @trusted { preparedRequest.bodyReader = createMemoryStream(cast(ubyte[]) requestBody); }(); preparedRequest.json = data; return this; } else { return send(data.serializeToJson()); } } /// Add a header to the server request RequestRouter header(string name, string value) { if(preparedRequest is null) { headers[name] = value; } else { preparedRequest.headers[name] = value; } return this; } /// Send a POST request RequestRouter post(string host = "localhost", ushort port = 80)(string path) { return customMethod!(HTTPMethod.POST, host, port)(path); } /// Send a PATCH request RequestRouter patch(string host = "localhost", ushort port = 80)(string path) { return customMethod!(HTTPMethod.PATCH, host, port)(path); } /// Send a PUT request RequestRouter put(string host = "localhost", ushort port = 80)(string path) { return customMethod!(HTTPMethod.PUT, host, port)(path); } /// Send a DELETE request RequestRouter delete_(string host = "localhost", ushort port = 80)(string path) { return customMethod!(HTTPMethod.DELETE, host, port)(path); } /// Send a GET request RequestRouter get(string host = "localhost", ushort port = 80)(string path) { return customMethod!(HTTPMethod.GET, host, port)(path); } /// Send a custom method request RequestRouter customMethod(HTTPMethod method, string host = "localhost", ushort port = 80)(string path) { return customMethod!method(URL("http://" ~ host ~ ":" ~ port.to!string ~ path)); } /// ditto RequestRouter customMethod(HTTPMethod method)(URL url) { preparedRequest = createTestHTTPServerRequest(url, method); preparedRequest.host = url.host; foreach(name, value; headers) { preparedRequest.headers[name] = value; } return this; } RequestRouter expectHeaderExist(string name, const string file = __FILE__, const size_t line = __LINE__) { void localExpectHeaderExist(Response res) { auto result = res.headers.keys.should.contain(name, file, line); result.message = new MessageResult("Response header `" ~ name ~ "` is missing."); } expected ~= &localExpectHeaderExist; return this; } RequestRouter expectHeader(string name, string value, const string file = __FILE__, const size_t line = __LINE__) { expectHeaderExist(name, file, line); void localExpectedHeader(Response res) { auto result = res.headers[name].should.equal(value, file, line); result.message = new MessageResult("Response header `" ~ name ~ "` has an unexpected value. Expected `" ~ value ~ "` != `" ~ res.headers[name].to!string ~ "`"); } expected ~= &localExpectedHeader; return this; } RequestRouter expectHeaderContains(string name, string value, const string file = __FILE__, const size_t line = __LINE__) { expectHeaderExist(name, file, line); void expectHeaderContains(Response res) { auto result = res.headers[name].should.contain(value, file, line); result.message = new MessageResult("Response header `" ~ name ~ "` has an unexpected value. Expected `" ~ value ~ "` not found in `" ~ res.headers[name].to!string ~ "`"); } expected ~= &expectHeaderContains; return this; } RequestRouter expectStatusCode(int code, const string file = __FILE__, const size_t line = __LINE__) { void localExpectStatusCode(Response res) { if(code != 404 && res.statusCode == 404) { writeln("\n\nIs your route defined here?"); router.getAllRoutes.map!(a => a.method.to!string ~ " " ~ a.pattern).each!writeln; } if(code != res.statusCode) { IResult[] results = [ cast(IResult) new MessageResult("Invalid status code."), cast(IResult) new ExpectedActualResult(code.to!string ~ " - " ~ httpStatusText(code), res.statusCode.to!string ~ " - " ~ httpStatusText(res.statusCode)), cast(IResult) new SourceResult(file, line) ]; throw new TestException(results, file, line); } } expected ~= &localExpectStatusCode; return this; } private void performExpected(Response res) { foreach(func; expected) { func(res); } } void end() { end((Response response) => { }); } void end(T)(T callback) @trusted { import vibe.stream.operations : readAllUTF8; import vibe.inet.webform; import vibe.stream.memory; auto data = new ubyte[5000]; static if(__traits(compiles, createMemoryStream(data) )) { MemoryStream stream = createMemoryStream(data); } else { MemoryStream stream = new MemoryStream(data); } HTTPServerResponse res = createTestHTTPServerResponse(stream); res.statusCode = 404; static if(__traits(compiles, createMemoryStream(data) )) { preparedRequest.bodyReader = createMemoryStream(cast(ubyte[]) requestBody); } else { preparedRequest.bodyReader = new MemoryStream(cast(ubyte[]) requestBody); } router.handleRequest(preparedRequest, res); string responseString = (cast(string) data).toStringz.to!string; checkResponse(responseString); auto response = new Response(responseString); callback(response)(); performExpected(response); } void checkResponse(ref string data) { if(data.length > 0) { return; } data = "HTTP/1.1 404 No Content\r\n\r\n"; } } class Response { string bodyString; private { Json _bodyJson; string responseLine; string data; } string[string] headers; int statusCode; this(string data) { this.data = data; auto bodyIndex = data.indexOf("\r\n\r\n"); assert(bodyIndex != -1, "Invalid response data: \n" ~ data ~ "\n\n"); auto headers = data[0 .. bodyIndex].split("\r\n").array; responseLine = headers[0]; statusCode = headers[0].split(" ")[1].to!int; foreach (i; 1 .. headers.length) { auto header = headers[i].split(": "); this.headers[header[0]] = header[1]; } bodyString = data[bodyIndex + 4 .. $]; } @property Json bodyJson() { if (_bodyJson.type == Json.Type.undefined) { try { _bodyJson = bodyString.parseJson; } catch(Exception e) { writeln("`" ~ bodyString ~ "` is not a json string"); } } return _bodyJson; } override string toString() { return data; } } @("Mocking a GET Request") unittest { auto router = new URLRouter(); void sayHello(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("hello"); } router.get("*", &sayHello); request(router) .get("/") .end((Response response) => { response.bodyString.should.equal("hello"); }); request(router) .post("/") .end((Response response) => { response.bodyString.should.not.equal("hello"); }); } @("Mocking a POST Request") unittest { auto router = new URLRouter(); void sayHello(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("hello"); } router.post("*", &sayHello); request(router) .post("/") .end((Response response) => { response.bodyString.should.equal("hello"); }); request(router) .get("/") .end((Response response) => { response.bodyString.should.not.equal("hello"); }); } @("Mocking a PATCH Request") unittest { auto router = new URLRouter(); void sayHello(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("hello"); } router.patch("*", &sayHello); request(router) .patch("/") .end((Response response) => { response.bodyString.should.equal("hello"); }); request(router) .get("/") .end((Response response) => { response.bodyString.should.not.equal("hello"); }); } @("Mocking a PUT Request") unittest { auto router = new URLRouter(); void sayHello(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("hello"); } router.put("*", &sayHello); request(router) .put("/") .end((Response response) => { response.bodyString.should.equal("hello"); }); request(router) .get("/") .end((Response response) => { response.bodyString.should.not.equal("hello"); }); } @("Mocking a DELETE Request") unittest { auto router = new URLRouter(); void sayHello(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("hello"); } router.delete_("*", &sayHello); request(router) .delete_("/") .end((Response response) => { response.bodyString.should.equal("hello"); }); request(router) .get("/") .end((Response response) => { response.bodyString.should.not.equal("hello"); }); } @("Mocking a ACL Request") unittest { auto router = new URLRouter(); void sayHello(HTTPServerRequest, HTTPServerResponse res) { res.writeBody("hello"); } router.match(HTTPMethod.ACL, "*", &sayHello); request(router) .customMethod!(HTTPMethod.ACL)("/") .end((Response response) => { response.bodyString.should.equal("hello"); }); request(router) .get("/") .end((Response response) => { response.bodyString.should.not.equal("hello"); }); } @("Sending headers") unittest { auto router = new URLRouter(); void checkHeaders(HTTPServerRequest req, HTTPServerResponse) { req.headers["Accept"].should.equal("application/json"); } router.any("*", &checkHeaders); request(router) .get("/") .header("Accept", "application/json") .end(); } @("Sending raw string") unittest { import std.string; auto router = new URLRouter(); void checkStringData(HTTPServerRequest req, HTTPServerResponse) { req.bodyReader.peek.assumeUTF.to!string.should.equal("raw string"); } router.any("*", &checkStringData); request(router) .post("/") .send("raw string") .end(); } @("Sending form data") unittest { auto router = new URLRouter(); void checkFormData(HTTPServerRequest req, HTTPServerResponse) { req.headers["content-type"].should.equal("application/x-www-form-urlencoded"); req.form["key1"].should.equal("value1"); req.form["key2"].should.equal("value2"); } router.any("*", &checkFormData); request(router) .post("/") .send(["key1": "value1", "key2": "value2"]) .end(); } @("Sending json data") unittest { auto router = new URLRouter(); void checkJsonData(HTTPServerRequest req, HTTPServerResponse) { req.json["key"].to!string.should.equal("value"); } router.any("*", &checkJsonData); request(router) .post("/") .send(`{ "key": "value" }`.parseJsonString) .end(); } @("Receive json data") unittest { auto router = new URLRouter(); void respondJsonData(HTTPServerRequest, HTTPServerResponse res) { res.writeJsonBody(`{ "key": "value"}`.parseJsonString); } router.any("*", &respondJsonData); request(router) .get("/") .end((Response response) => { response.bodyJson["key"].to!string.should.equal("value"); }); } @("Expect status code") unittest { auto router = new URLRouter(); void respondStatus(HTTPServerRequest, HTTPServerResponse res) { res.statusCode = 200; res.writeBody(""); } router.get("*", &respondStatus); request(router) .get("/") .expectStatusCode(200) .end(); ({ request(router) .post("/") .expectStatusCode(200) .end(); }).should.throwException!TestException.msg.should.startWith("Invalid status code."); } @("Expect header") unittest { auto router = new URLRouter(); void respondHeader(HTTPServerRequest, HTTPServerResponse res) { res.headers["some-header"] = "some-value"; res.writeBody(""); } router.get("*", &respondHeader); // Check for the exact header value: request(router) .get("/") .expectHeader("some-header", "some-value") .end(); ({ request(router) .post("/") .expectHeader("some-header", "some-value") .end(); }).should.throwAnyException.msg.should.startWith("Response header `some-header` is missing."); ({ request(router) .get("/") .expectHeader("some-header", "other-value") .end(); }).should.throwAnyException.msg.should.startWith("Response header `some-header` has an unexpected value"); // Check if a header exists request(router) .get("/") .expectHeaderExist("some-header") .end(); ({ request(router) .post("/") .expectHeaderExist("some-header") .end(); }).should.throwAnyException.msg.should.startWith("Response header `some-header` is missing."); // Check if a header contains a string request(router) .get("/") .expectHeaderContains("some-header", "value") .end(); ({ request(router) .get("/") .expectHeaderContains("some-header", "other") .end(); }).should.throwAnyException.msg.should.contain("Response header `some-header` has an unexpected value."); }