1 //System Includes
2 #include <map>
3 #include <thread>
4 #include <string>
5 #include <memory>
6 #include <ciso646>
7 #include <utility>
8 #include <stdexcept>
9 #include <functional>
10 
11 //Project Includes
12 #include <restbed>
13 #include "content_type_rule.hpp"
14 #include "content_length_rule.hpp"
15 
16 //External Includes
17 #include <catch.hpp>
18 
19 //System Namespaces
20 using std::thread;
21 using std::string;
22 using std::function;
23 using std::multimap;
24 using std::make_pair;
25 using std::shared_ptr;
26 using std::make_shared;
27 
28 //Project Namespaces
29 using namespace restbed;
30 
31 //External Namespaces
32 
get_method_handler(const shared_ptr<Session> session)33 void get_method_handler( const shared_ptr< Session > session )
34 {
35     session->close( OK, "Hello, World!", { { "Content-Length", "13" } } );
36 }
37 
38 SCENARIO( "service rules engine", "[service]" )
39 {
40     auto resource = make_shared< Resource >( );
41     resource->set_path( "/resources" );
42     resource->set_method_handler( "POST", get_method_handler );
43 
44     auto settings = make_shared< Settings >( );
45     settings->set_port( 1984 );
46     settings->set_default_header( "Connection", "close" );
47 
48     shared_ptr< thread > worker = nullptr;
49     const auto content_type = make_shared< ContentTypeRule >( );
50     const auto content_length = make_shared< ContentLengthRule >( );
51 
52     Service service;
53     service.publish( resource );
54     service.add_rule( content_type );
55     service.add_rule( content_length );
56     service.set_ready_handler( [ &worker ]( Service & service )
__anon509902700102( Service & service ) 57     {
58         worker = make_shared< thread >( [ &service ] ( )
59         {
60             GIVEN( "I publish a resource at '/resources' with a HTTP 'POST' method handler" )
61             {
62                 WHEN( "I perform an HTTP 'POST' request to '/resources' with headers 'Content-Type: application/csv, Content-Length: 0'" )
63                 {
64                     auto request = make_shared< Request >( );
65                     request->set_port( 1984 );
66                     request->set_host( "localhost" );
67                     request->set_path( "/resources" );
68                     request->set_method( "POST" );
69 
70                     multimap< string, string > headers;
71                     headers.insert( make_pair( "Content-Length", "0" ) );
72                     headers.insert( make_pair( "Content-Type", "application/csv" ) );
73                     request->set_headers( headers );
74 
75                     auto response = Http::sync( request );
76 
77                     THEN( "I should see a '200' (OK) status code" )
78                     {
79                         REQUIRE( 200 == response->get_status_code( ) );
80                         REQUIRE( "OK" == response->get_status_message( ) );
81                     }
82 
83                     AND_THEN( "I should see a response body of 'Hello, World!'" )
84                     {
85                         auto actual = Http::fetch( 13, response );
86                         Bytes expectation { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!' };
87                         REQUIRE( actual == expectation );
88                     }
89 
90                     headers = response->get_headers( );
91 
92                     AND_THEN( "I should see a 'Connection' header value of 'close'" )
93                     {
94                         auto header = headers.find( "Connection" );
95                         REQUIRE( header not_eq headers.end( ) );
96                         REQUIRE( "close" == headers.find( "Connection" )->second );
97                     }
98 
99                     AND_THEN( "I should see a 'Content-Length' header value of '13'" )
100                     {
101                         auto header = headers.find( "Content-Length" );
102                         REQUIRE( header not_eq headers.end( ) );
103                         REQUIRE( "13" == headers.find( "Content-Length" )->second );
104                     }
105                 }
106 
107                 WHEN( "I perform an HTTP 'POST' request to '/resources' with headers 'Content-Type: application/json, Content-Length: 0'" )
108                 {
109                     auto request = make_shared< Request >( );
110                     request->set_port( 1984 );
111                     request->set_host( "localhost" );
112                     request->set_path( "/resources" );
113                     request->set_method( "POST" );
114 
115                     multimap< string, string > headers;
116                     headers.insert( make_pair( "Content-Length", "0" ) );
117                     headers.insert( make_pair( "Content-Type", "application/json" ) );
118                     request->set_headers( headers );
119 
120                     auto response = Http::sync( request );
121 
122                     THEN( "I should see a '415' (Unsupported Media Type) status code" )
123                     {
124                         REQUIRE( 415 == response->get_status_code( ) );
125                         REQUIRE( "Unsupported Media Type" == response->get_status_message( ) );
126                     }
127 
128                     AND_THEN( "I should see a response body of 'Unsupported Media Type, must be 'application/csv'.'" )
129                     {
130                         auto actual = Http::fetch( 50, response );
131                         string body( actual.begin( ), actual.end( ) );
132                         REQUIRE( body == "Unsupported Media Type, must be 'application/csv'." );
133                     }
134 
135                     headers = response->get_headers( );
136 
137                     AND_THEN( "I should see a 'Connection' header value of 'close'" )
138                     {
139                         auto header = headers.find( "Connection" );
140                         REQUIRE( header not_eq headers.end( ) );
141                         REQUIRE( "close" == headers.find( "Connection" )->second );
142                     }
143 
144                     AND_THEN( "I should see a 'Content-Type' header value of 'text/plain'" )
145                     {
146                         auto header = headers.find( "Content-Type" );
147                         REQUIRE( header not_eq headers.end( ) );
148                         REQUIRE( "text/plain" == headers.find( "Content-Type" )->second );
149                     }
150 
151                     AND_THEN( "I should see a 'Content-Length' header value of '50'" )
152                     {
153                         auto header = headers.find( "Content-Length" );
154                         REQUIRE( header not_eq headers.end( ) );
155                         REQUIRE( "50" == headers.find( "Content-Length" )->second );
156                     }
157                 }
158 
159                 WHEN( "I perform an HTTP 'POST' request to '/resources' with headers 'Content-Type: application/csv, Content-Length: 4' and body 'data'" )
160                 {
161                     auto request = make_shared< Request >( );
162                     request->set_port( 1984 );
163                     request->set_host( "localhost" );
164                     request->set_path( "/resources" );
165                     request->set_body( Bytes( { 'd', 'a', 't', 'a' } ) );
166                     request->set_method( "POST" );
167 
168                     multimap< string, string > headers;
169                     headers.insert( make_pair( "Content-Length", "4" ) );
170                     headers.insert( make_pair( "Content-Type", "application/csv" ) );
171                     request->set_headers( headers );
172 
173                     auto response = Http::sync( request );
174 
175                     THEN( "I should see a '200' (OK) status code" )
176                     {
177                         REQUIRE( 200 == response->get_status_code( ) );
178                         REQUIRE( "OK" == response->get_status_message( ) );
179                     }
180 
181                     AND_THEN( "I should see a response body of 'Hello, World!'" )
182                     {
183                         auto actual = Http::fetch( 13, response );
184                         Bytes expectation { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!' };
185                         REQUIRE( actual == expectation );
186                     }
187 
188                     headers = response->get_headers( );
189 
190                     AND_THEN( "I should see a 'Connection' header value of 'close'" )
191                     {
192                         auto header = headers.find( "Connection" );
193                         REQUIRE( header not_eq headers.end( ) );
194                         REQUIRE( "close" == headers.find( "Connection" )->second );
195                     }
196 
197                     AND_THEN( "I should see a 'Content-Length' header value of '13'" )
198                     {
199                         auto header = headers.find( "Content-Length" );
200                         REQUIRE( header not_eq headers.end( ) );
201                         REQUIRE( "13" == headers.find( "Content-Length" )->second );
202                     }
203                 }
204 
205                 WHEN( "I perform an HTTP 'POST' request to '/resources' with headers 'Content-Type: application/csv' and body 'data'" )
206                 {
207                     auto request = make_shared< Request >( );
208                     request->set_port( 1984 );
209                     request->set_host( "localhost" );
210                     request->set_path( "/resources" );
211                     request->set_body( Bytes( { 'd', 'a', 't', 'a' } ) );
212                     request->set_method( "POST" );
213 
214                     multimap< string, string > headers;
215                     headers.insert( make_pair( "Content-Type", "application/csv" ) );
216                     request->set_headers( headers );
217 
218                     auto response = Http::sync( request );
219 
220                     THEN( "I should see a '411' (Length Required) status code" )
221                     {
222                         REQUIRE( 411 == response->get_status_code( ) );
223                         REQUIRE( "Length Required" == response->get_status_message( ) );
224                     }
225 
226                     AND_THEN( "I should see a response body of 'Length Required.'" )
227                     {
228                         auto actual = Http::fetch( 16, response );
229                         Bytes expectation { 'L', 'e', 'n', 'g', 't', 'h', ' ', 'R', 'e', 'q', 'u', 'i', 'r', 'e', 'd', '.' };
230                         REQUIRE( actual == expectation );
231                     }
232 
233                     headers = response->get_headers( );
234 
235                     AND_THEN( "I should see a 'Connection' header value of 'close'" )
236                     {
237                         auto header = headers.find( "Connection" );
238                         REQUIRE( header not_eq headers.end( ) );
239                         REQUIRE( "close" == headers.find( "Connection" )->second );
240                     }
241 
242                     AND_THEN( "I should see a 'Content-Type' header value of 'text/plain'" )
243                     {
244                         auto header = headers.find( "Content-Type" );
245                         REQUIRE( header not_eq headers.end( ) );
246                         REQUIRE( "text/plain" == headers.find( "Content-Type" )->second );
247                     }
248 
249                     AND_THEN( "I should see a 'Content-Length' header value of '16'" )
250                     {
251                         auto header = headers.find( "Content-Length" );
252                         REQUIRE( header not_eq headers.end( ) );
253                         REQUIRE( "16" == headers.find( "Content-Length" )->second );
254                     }
255                 }
256 
257                 service.stop( );
258             }
259         } );
260     } );
261 
262     service.start( settings );
263     worker->join( );
264 }
265