From 2c7418f333cc3ea809ee312c12ae24f56e3724d7 Mon Sep 17 00:00:00 2001 From: landrigun Date: Tue, 20 Sep 2022 09:06:19 +0000 Subject: [PATCH] feat: #2 impl an HTTPResponse + update tests --- src/handlers/mod.rs | 2 + src/handlers/response.rs | 98 +++++++++++++++++++++++++++++++++++ src/main.rs | 21 ++------ tests/bash/curling.bash | 4 +- tests/python/requirements.txt | 1 - tests/python/test_requests.py | 67 ++++++++++++++---------- 6 files changed, 147 insertions(+), 46 deletions(-) create mode 100644 src/handlers/response.rs diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 7bb6422..b5c833b 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,3 +1,5 @@ pub mod request; +pub mod response; pub use request::handle_request; +pub use response::HTTPResponse; diff --git a/src/handlers/response.rs b/src/handlers/response.rs new file mode 100644 index 0000000..c2a8d6f --- /dev/null +++ b/src/handlers/response.rs @@ -0,0 +1,98 @@ +//! response handles the incoming request parsed `HTTPRequest` +//! it will check if the `HTTPRequest` is valid and build an HTTPResponse corresponding to the HTTP +//! message specs. see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages +//! NOTE: only few parts of the specification has been implemented + +use json; + +use crate::handlers::request::{HTTPRequest, HTTPVersion}; + +enum HTTPStatusCode { + Http200, + Http400, + Http404, + Http500, +} + +impl Into for HTTPStatusCode { + fn into(self) -> String { + match self { + Self::Http200 => "200".to_string(), + Self::Http400 => "400".to_string(), + Self::Http404 => "404".to_string(), + Self::Http500 => "500".to_string(), + } + } +} + +pub struct HTTPStatusLine { + version: HTTPVersion, + status_code: HTTPStatusCode, +} + +impl Default for HTTPStatusLine { + fn default() -> HTTPStatusLine { + HTTPStatusLine { + version: HTTPVersion::Http1_1, + status_code: HTTPStatusCode::Http400, + } + } +} + +impl Into for HTTPStatusLine { + fn into(self) -> String { + let version: String = self.version.into(); + let status_code: String = self.status_code.into(); + format! {"{} {}", version, status_code} + } +} + +pub struct HTTPResponse { + status_line: HTTPStatusLine, + body: json::JsonValue, +} + +impl Default for HTTPResponse { + fn default() -> Self { + HTTPResponse { + status_line: HTTPStatusLine::default(), + body: json::parse(r#"{"error": "the incoming request is not valid"}"#).unwrap(), + } + } +} + +impl From for HTTPResponse { + fn from(request: HTTPRequest) -> Self { + let mut response = HTTPResponse::default(); + if !request.is_valid() { + return response; + } + + let body = json::parse( + r#"{"token": "header.payload.signature", "refresh": "header.payload.signature"}"#, + ) + .unwrap(); + + response.status_line.version = request.start_line.version; + response.status_line.status_code = HTTPStatusCode::Http200; + response.body = body; + + response + } +} + +impl Into for HTTPResponse { + fn into(self) -> String { + // move `self.body` into a new var + let b = self.body; + let body: String = json::stringify(b); + + let status_line: String = self.status_line.into(); + format!( + "{}\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", + status_line, + body.len(), + body + ) + } +} diff --git a/src/main.rs b/src/main.rs index 4c36b6b..aa6e660 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ mod handlers; -use std::io::prelude::*; - use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::{TcpListener, TcpStream}, }; -use handlers::handle_request; +use handlers::{handle_request, HTTPResponse}; const SERVER_URL: &str = "127.0.0.1:9000"; @@ -29,20 +27,9 @@ async fn handle_connection(mut stream: TcpStream) { let request_string = std::str::from_utf8(&buffer[0..n]).unwrap(); let request = handle_request(request_string); - if request.is_valid() { - let contents = "{\"status\": \"ok\"}"; - let response = format!( - "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", - contents.len(), - contents - ); + let response = HTTPResponse::from(request); + let response_str: String = response.into(); - stream.write(response.as_bytes()).await.unwrap(); - stream.flush().await.unwrap(); - return; - } - - let response = "HTTP/1.1 400 OK\r\n\r\n".to_string(); - stream.write(response.as_bytes()).await.unwrap(); + stream.write(response_str.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); } diff --git a/tests/bash/curling.bash b/tests/bash/curling.bash index 3e7e1db..3021842 100755 --- a/tests/bash/curling.bash +++ b/tests/bash/curling.bash @@ -16,8 +16,8 @@ do echo "bad http status code : ${http_response}, expect 200" exit 1 fi - - if [ $(cat response.txt | jq -r '.[]') != "ok" ] + + if [ "$(cat response.txt | jq -r '.token')" != "header.payload.signature" ] then echo "bad data returned, expect : ok" exit 1 diff --git a/tests/python/requirements.txt b/tests/python/requirements.txt index 9768453..0997272 100644 --- a/tests/python/requirements.txt +++ b/tests/python/requirements.txt @@ -12,7 +12,6 @@ platformdirs==2.5.2 pluggy==1.0.0 py==1.11.0 pyparsing==3.0.9 -pytest==7.1.3 requests==2.28.1 tomli==2.0.1 urllib3==1.26.12 diff --git a/tests/python/test_requests.py b/tests/python/test_requests.py index 66534c5..95ec7a7 100644 --- a/tests/python/test_requests.py +++ b/tests/python/test_requests.py @@ -1,34 +1,49 @@ import requests +from unittest import TestCase + URL = "https://dev.thegux.fr" -def test_get_target(): - resp = requests.post(URL + "/get/", json={"username": "toto", "password": "tata"}) - assert resp.status_code == 200, "bad status code returned" - assert resp.json() is not None, "response data can't be empty" - assert resp.json()["status"] == "ok", "bad status returned" +class TestResponse(TestCase): + def test_get_target(self): + resp = requests.post( + URL + "/get/", json={"username": "toto", "password": "tata"} + ) + self.assertEqual(resp.status_code, 200, "bad status code returned") + self.assertIsNotNone(resp.json(), "response data can't be empty") + self.assertEqual( + resp.json()["token"], "header.payload.signature", "bad status returned" + ) + def test_validate_target(self): + resp = requests.post( + URL + "/validate/", json={"username": "toto", "password": "tata"} + ) + self.assertEqual(resp.status_code, 200, "bad status code returned") + self.assertIsNotNone(resp.json(), "response data can't be empty") + self.assertEqual( + resp.json()["token"], "header.payload.signature", "bad status returned" + ) -def test_validate_target(): - resp = requests.post( - URL + "/validate/", json={"username": "toto", "password": "tata"} - ) - assert resp.status_code == 200, "bad status code returned" - assert resp.json() is not None, "response data can't be empty" - assert resp.json()["status"] == "ok", "bad status returned" + def test_refresh_target(self): + resp = requests.post( + URL + "/refresh/", json={"username": "toto", "password": "tata"} + ) + self.assertEqual(resp.status_code, 200, "bad status code returned") + self.assertIsNotNone(resp.json(), "response data can't be empty") + self.assertEqual( + resp.json()["token"], "header.payload.signature", "bad status returned" + ) - -def test_refresh_target(): - resp = requests.post( - URL + "/refresh/", json={"username": "toto", "password": "tata"} - ) - assert resp.status_code == 200, "bad status code returned" - assert resp.json() is not None, "response data can't be empty" - assert resp.json()["status"] == "ok", "bad status returned" - - -def test_bad_target(): - resp = requests.post(URL + "/token/", json={"username": "toto", "password": "tata"}) - assert resp.status_code == 400, "bad status code returned" - assert resp.text == "", "response data must be empty" + def test_bad_target(self): + resp = requests.post( + URL + "/token/", json={"username": "toto", "password": "tata"} + ) + self.assertEqual(resp.status_code, 400, "bad status code returned") + self.assertIsNotNone(resp.json(), "response data must not be empty") + self.assertEqual( + resp.json()["error"], + "the incoming request is not valid", + "invalid error message returned", + )