fluentasserts.vibe.request 189/197(95%) 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
2425
250
260
270
280
290
300
310
320
330
340
350
360
370
380
390
400
410
4225
430
4425
450
460
470
480
490
500
510
520
530
540
550
560
570
580
590
600
611
621
630
640
650
661
672
681
691
700
710
720
730
740
750
760
770
780
791
800
810
821
830
841
850
860
870
880
897
900
910
920
930
941
950
960
970
980
991
1000
1010
1020
1030
1041
1050
1060
1070
1080
10914
1100
1110
1120
1130
1141
1150
1160
1170
1180
11925
12025
1210
12225
1230
1240
1250
12625
1270
1280
1290
1300
1317
1329
1330
1340
1357
1360
1377
1380
1390
1400
1410
1423
1430
1443
1452
1461
1470
1480
1490
1503
1510
1523
1530
1540
1550
1560
1572
1580
1592
1602
1611
1620
1630
1640
1652
1660
1672
1680
1690
1700
1710
1722
1734
1741
1752
1760
1770
1782
1791
1800
1810
1820
1830
1841
1850
1860
1870
1882
1890
1900
1912
1920
1930
1940
1950
196104
19713
1980
1990
2000
2010
20224
2030
2040
2050
2060
2070
2080
2090
2100
21125
2120
2130
21425
2150
2160
2170
2180
21925
22025
2210
2220
22325
2240
2250
2260
2270
22825
2290
23025
23125
2320
23325
2340
23525
2360
23725
2380
2390
2400
24125
24213
2430
2440
24512
2460
2470
2480
2490
2500
2510
2520
2530
2540
2550
2560
2570
2580
2590
2600
2610
2620
26325
2640
26525
2660
26725
2680
26925
2700
27125
2720
27325
27425
2750
276168
2770
27831
27931
2800
2810
28225
2830
2840
2850
2860
2871
2880
2890
2901
2910
2920
2930
2940
2950
2961
2970
2980
2990
3000
3010
3020
3030
3040
3050
3060
3071
3080
3091
3100
3111
3120
3130
3141
3151
3160
3171
3182
3190
3200
3211
3220
3231
3242
3250
3260
3270
3280
3290
3301
3310
3321
3330
3341
3350
3360
3371
3381
3390
3401
3412
3420
3430
3441
3450
3461
3472
3480
3490
3500
3510
3520
3531
3540
3551
3560
3571
3580
3590
3601
3611
3620
3631
3642
3650
3660
3671
3680
3691
3702
3710
3720
3730
3740
3750
3761
3770
3781
3790
3801
3810
3820
3831
3841
3850
3861
3872
3880
3890
3901
3910
3921
3932
3940
3950
3960
3970
3980
3991
4000
4011
4020
4031
4040
4050
4061
4071
4080
4091
4102
4110
4120
4131
4140
4151
4162
4170
4180
4190
4200
4210
4221
4230
4241
4250
4261
4270
4280
4291
4300
4311
4320
4331
4342
4350
4360
4371
4380
4391
4402
4410
4420
4430
4440
4450
4461
4470
4481
4490
4502
4510
4520
4531
4540
4551
4560
4570
4580
4590
4600
4610
4620
4630
4640
4651
4660
4671
4680
4692
4700
4710
4721
4730
4741
4750
4760
4770
4780
4790
4800
4810
4820
4830
4840
4850
4860
4870
4880
4890
4900
4910
4920
4930
4940
4950
4960
4970
4980
4990
5000
5010
5020
5031
5040
5051
5060
5072
5080
5090
5101
5110
5121
5130
5140
5150
5160
5170
5180
5190
5201
5210
5221
5230
5241
5250
5260
5271
5280
5291
5300
5311
5322
5330
5340
5350
5360
5370
5381
5390
5401
5410
5421
5431
5440
5450
5461
5470
5481
5490
5500
5510
5520
5530
5543
5551
5560
5570
5580
5591
5600
5610
5620
5630
5640
5651
5660
5671
5680
5695
5705
5710
5720
5731
5740
5750
5760
5771
5780
5790
5800
5810
5820
5833
5841
5850
5860
5870
5881
5890
5903
5911
5920
5930
5940
5951
5960
5970
5981
5990
6000
6010
6020
6030
6043
6051
6060
6070
6080
6091
6100
6110
6121
6130
6140
6150
6160
6173
6181
6190
6200
6210
6221
6230
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; } RequestRouter send(string[string] data) { auto dst = appender!string; dst.writeFormData(data); header("Content-Type", "application/x-www-form-urlencoded"); return send(dst.data); } 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()); } } RequestRouter header(string name, string value) { if(preparedRequest is null) { headers[name] = value; } else { preparedRequest.headers[name] = value; } return this; } RequestRouter post(string path) { return customMethod!(HTTPMethod.POST)(URL("http://localhost" ~ path)); } RequestRouter patch(string path) { return customMethod!(HTTPMethod.PATCH)(URL("http://localhost" ~ path)); } RequestRouter put(string path) { return customMethod!(HTTPMethod.PUT)(URL("http://localhost" ~ path)); } RequestRouter delete_(string path) { return customMethod!(HTTPMethod.DELETE)(URL("http://localhost" ~ path)); } RequestRouter get(string path) { return customMethod!(HTTPMethod.GET)(URL("http://localhost" ~ path)); } RequestRouter customMethod(HTTPMethod method)(string path) { return customMethod!method(URL("http://localhost" ~ path)); } RequestRouter customMethod(HTTPMethod method)(URL url) { preparedRequest = createTestHTTPServerRequest(url, method); preparedRequest.host = "localhost"; 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) { enforce(name in res.headers, "Response header `" ~ name ~ "` is missing.", file, line); } 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) { enforce(res.headers[name] == value, "Response header `" ~ name ~ "` has an unexpected value. Expected `" ~ value ~ "` != `" ~ res.headers[name].to!string ~ "`", file, line); } 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) { enforce(res.headers[name].indexOf(value) != -1, "Response header `" ~ name ~ "` has an unexpected value. Expected `" ~ value ~ "` not found in `" ~ res.headers[name].to!string ~ "`", file, line); } 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.equal("Response header `some-header` is missing."); ({ request(router) .get("/") .expectHeader("some-header", "other-value") .end(); }).should.throwAnyException.msg.should.contain("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.equal("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."); }