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