1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.unittesting.request;
7 
8 import diamond.core.apptype;
9 
10 static if (isWeb && isTesting)
11 {
12   import std.conv : to;
13 
14   import vibe.d : HTTPClientRequest, HTTPClientResponse, requestHTTP, HTTPMethod, Json;
15 
16   public import diamond.http.method;
17   public import diamond.http.status;
18 
19   /// Wrapper around a http unittet result.
20   final class HttpUnitTestResult
21   {
22     private:
23     /// The response.
24     HTTPClientResponse _response;
25 
26     /// The body data.
27     string _bodyData;
28 
29     /**
30     * Creates a new http unittest result.
31     * Params:
32     *   response = The response.
33     */
34     this(HTTPClientResponse response)
35     {
36       _response = response;
37     }
38 
39     public:
40     final:
41     @property
42     {
43       /// Gets the raw vibe.d response.
44       HTTPClientResponse rawResponse() { return _response; }
45 
46       /// Gets the raw vibe.d response stream.
47       auto responseStream() { return _response.bodyReader; }
48 
49       /// Gets the status code.
50       HttpStatus statusCode() { return cast(HttpStatus)_response.statusCode; }
51 
52       /// Gets the content type.
53       string contentType() { return _response.contentType; }
54 
55       /// Gets the json.
56       auto json() { return _response.readJson(); }
57 
58       /// Gets the body data.
59       auto bodyData()
60       {
61         import vibe.stream.operations : readAllUTF8;
62 
63         if (!_bodyData)
64         {
65           _bodyData = _response.bodyReader.readAllUTF8();
66         }
67 
68         return _bodyData;
69       }
70     }
71 
72     /**
73     * Gets a cookie.
74     * Params:
75     *   name =         The name of the cookie.
76     *   defaultValue = The default value.
77     * Returns:
78     *   The value of the cookie if present, default value otherwise.
79     */
80     string getCookie(string name, lazy string defaultValue = null)
81     {
82       if (name !in _response.cookies)
83       {
84         return defaultValue;
85       }
86 
87       return _response.cookies[name].value;
88     }
89 
90     /**
91     * Checks whether a cookie is present or not.
92     * Params:
93     *   name = The name of the cookie.
94     * Returns:
95     *   True if the cookie is present, false otherwise.
96     */
97     bool hasCookie(string name)
98     {
99       return cast(bool)(name in _response.cookies);
100     }
101 
102     /**
103     * Gets a header.
104     * Params:
105     *   name = The name of the header.
106     *   defaultValue = The default value.
107     * Returns:
108     *   The value if present, default value otherwise.
109     */
110     string getHeader(string name, lazy string defaultValue)
111     {
112       return _response.headers.get(name, defaultValue);
113     }
114 
115     /**
116     * Checks whether a header is present or not.
117     * Params:
118     *   name = The name of the header.
119     * Returns:
120     *   True if the header is present, false otherwise.
121     */
122     bool hasHeader(string name)
123     {
124       return getHeader(name, null) !is null;
125     }
126 
127     /// Gets a model from the response's json.
128     T getModelFromJson(T, CTORARGS...)(CTORARGS args)
129     {
130       import vibe.data.json;
131 
132       static if (is(T == struct))
133       {
134         T value;
135 
136         value.deserializeJson(json);
137 
138         return value;
139       }
140       else static if (is(T == class))
141       {
142         auto value = new T(args);
143 
144         value.deserializeJson(json);
145 
146         return value;
147       }
148       else
149       {
150         static assert(0);
151       }
152     }
153   }
154 
155   /**
156   * Creates an internal test request.
157   * Params:
158   *   route =     The route (not URL!) to call.
159   *   method =    The method to use for thr request.
160   *   responder = The handler for the unittest result.
161   *   requester = Custom handler for the raw vibe.d request. Can be used to setup the request data etc.
162   */
163   void testRequest
164   (
165     string route, HttpMethod method,
166     scope void delegate(scope HttpUnitTestResult) responder,
167     scope void delegate(scope HTTPClientRequest) requester = null,
168   )
169   {
170     import diamond.errors.checks;
171 
172     enforce(responder !is null, "No responder defined.");
173 
174     import diamond.core.webconfig;
175     auto address = webConfig.addresses[0];
176 
177     if (route[0] != '/')
178     {
179       route = "/" ~ route;
180     }
181 
182     auto ipAddress = address.ipAddresses[0];
183 
184     if (ipAddress == "::1")
185     {
186       ipAddress = "127.0.0.1";
187     }
188 
189     auto url = "http://" ~ ipAddress ~ ":" ~ to!string(address.port) ~ route;
190 
191     requestHTTP
192     (
193       url,
194       (scope request)
195       {
196         request.method = cast(HTTPMethod)method;
197 
198         if (requester !is null)
199         {
200           requester(request);
201         }
202       },
203       (scope response)
204       {
205         responder(new HttpUnitTestResult(response));
206       }
207     );
208   }
209 }