tii 0.0.6

A Low-Latency Web Server.
Documentation
mod mock_stream;

use crate::mock_stream::MockStream;
use tii::{
  AcceptQualityMimeType, Cookie, MimeCharset, MimeType, MimeTypeWithCharset, QValue,
  RequestContext, RequestHeadParsingError, TiiError, UserError,
};
use tii::{HttpHeader, HttpHeaderName};
use tii::{HttpMethod, TypeSystem};

use std::collections::VecDeque;
use std::iter::FromIterator;
use std::vec;
use tii::HttpVersion;
use tii::IntoConnectionStream;

#[allow(deprecated)]
#[test]
fn test_request_from_stream() {
  let test_data = b"GET /testpath?foo=bar HTTP/1.1\r\nHost: localhost\r\n\r\n";
  let stream = MockStream::with_data(VecDeque::from_iter(test_data.iter().cloned()));
  let raw_stream = stream.clone().into_connection_stream();

  let request = RequestContext::read(raw_stream.as_ref(), None, 8096, TypeSystem::empty());

  let request = request.unwrap();
  let expected_uri: String = "/testpath".into();
  assert_eq!(request.get_method(), &HttpMethod::Get);
  assert_eq!(request.get_path(), expected_uri);
  assert_eq!(request.get_query(), &[("foo".to_string(), "bar".to_string())]);
  assert_eq!(request.get_version(), HttpVersion::Http11);

  let mut expected_headers = Vec::new();
  expected_headers.push(HttpHeader::new(HttpHeaderName::Host, "localhost"));

  let collected_headers = request.iter_headers().cloned().collect::<Vec<_>>();
  assert_eq!(collected_headers, expected_headers);
}

#[test]
fn test_cookie_request() {
  let test_data = b"GET / HTTP/1.1\r\nHost: localhost\r\nCookie: foo=bar; baz=qux\r\n\r\n";
  let stream = MockStream::with_data(VecDeque::from_iter(test_data.iter().cloned()));
  let raw_stream = stream.clone().into_connection_stream();
  let request = RequestContext::read(raw_stream.as_ref(), None, 8096, TypeSystem::empty()).unwrap();

  let mut expected_cookies = vec![Cookie::new("foo", "bar"), Cookie::new("baz", "qux")];

  assert_eq!(request.get_cookies(), expected_cookies);

  assert_eq!(request.get_cookie("baz"), expected_cookies.pop());
  assert_eq!(request.get_cookie("foo"), expected_cookies.pop());
  assert_eq!(request.get_cookie("sus"), None);
}

#[test]
fn test_proxied_request_from_stream() {
  let test_data =
    b"GET /testpath HTTP/1.1\r\nHost: localhost\r\nX-Forwarded-For: 9.10.11.12,13.14.15.16\r\n\r\n";
  let stream = MockStream::with_data(VecDeque::from_iter(test_data.iter().cloned()));
  let raw_stream = stream.clone().into_connection_stream();

  let request = RequestContext::read(raw_stream.as_ref(), None, 8096, TypeSystem::empty());

  let request = request.unwrap();
  let expected_uri: String = "/testpath".into();
  assert_eq!(request.get_method(), &HttpMethod::Get);
  assert_eq!(request.get_path(), expected_uri);
  assert_eq!(request.get_version(), HttpVersion::Http11);

  let mut expected_headers = Vec::new();
  expected_headers.push(HttpHeader::new(HttpHeaderName::Host, "localhost"));
  expected_headers.push(HttpHeader::new("X-Forwarded-For", "9.10.11.12,13.14.15.16"));
  let collected: Vec<HttpHeader> = request.iter_headers().cloned().collect();

  assert_eq!(collected, expected_headers);
}

#[test]
fn test_mock_request_head() {
  let mock_head = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "/beep",
    vec![("bep", "bop")],
    Vec::new(),
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap();
  assert_eq!(mock_head.get_raw_status_line(), "GET /beep?bep=bop HTTP/1.1");
  let mock_head = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "/beepä/bo#ö!/",
    vec![("bepä", "büop")],
    Vec::new(),
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap();
  assert_eq!(
    mock_head.get_raw_status_line(),
    "GET /beep%C3%A4/bo%23%C3%B6%21/?bep%C3%A4=b%C3%BCop HTTP/1.1"
  );
  let mock_head = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "/beep/bop/",
    vec![("", "büop"), ("", "nop"), ("cop", "")],
    Vec::new(),
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap();
  assert_eq!(mock_head.get_raw_status_line(), "GET /beep/bop/?=b%C3%BCop&=nop&cop HTTP/1.1");
  let err = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "beep/bop/",
    vec![("", "büop"), ("", "nop"), ("cop", "")],
    Vec::new(),
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap_err();
  match err {
    TiiError::RequestHeadParsing(RequestHeadParsingError::InvalidPath(s)) => {
      assert_eq!(s, "beep/bop/")
    }
    _ => panic!("Unexpected error {err}"),
  }

  let mock_head = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http09,
    "/beep/bop/",
    vec![("mep", "mop")],
    Vec::new(),
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap();
  assert_eq!(mock_head.get_raw_status_line(), "GET /beep/bop/?mep=mop");

  let err = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Post,
    HttpVersion::Http09,
    "/beep/bop",
    Vec::<(&str, &str)>::new(),
    Vec::new(),
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap_err();
  match err {
    TiiError::RequestHeadParsing(RequestHeadParsingError::MethodNotSupportedByHttpVersion(
      v,
      m,
    )) => assert_eq!((v, m), (HttpVersion::Http09, HttpMethod::Post)),
    _ => panic!("Unexpected error {err}"),
  }

  let err = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http09,
    "/beep/bop",
    Vec::<(&str, &str)>::new(),
    vec![HttpHeader::new("abc", "def")],
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap_err();
  match err {
    TiiError::UserError(UserError::HeaderNotSupportedByHttpVersion(v)) => {
      assert_eq!(v, HttpVersion::Http09)
    }
    _ => panic!("Unexpected error {err}"),
  }

  let err = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "/beep/bop",
    Vec::<(&str, &str)>::new(),
    vec![HttpHeader::new("Accept", "*//")],
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap_err();
  match err {
    TiiError::UserError(UserError::IllegalAcceptHeaderValueSet(v)) => assert_eq!(v, "*//"),
    _ => panic!("Unexpected error {err}"),
  }

  let err = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "/beep/bop",
    Vec::<(&str, &str)>::new(),
    vec![HttpHeader::new("Content-Type", "*//")],
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap_err();
  match err {
    TiiError::UserError(UserError::IllegalContentTypeHeaderValueSet(v)) => assert_eq!(v, "*//"),
    _ => panic!("Unexpected error {err}"),
  }

  let mut mock_head = RequestContext::new(
    0,
    "localhost",
    "localhost",
    HttpMethod::Get,
    HttpVersion::Http11,
    "/beep/bop",
    Vec::<(&str, &str)>::new(),
    vec![
      HttpHeader::new("Content-Type", "text/plain"),
      HttpHeader::new("Accept", "application/json"),
    ],
    None,
    None,
    TypeSystem::empty(),
  )
  .unwrap();
  assert_eq!(
    mock_head.get_accept().first(),
    Some(&AcceptQualityMimeType::from_mime(
      MimeType::ApplicationJson,
      QValue::default(),
      MimeCharset::Unspecified
    ))
  );
  assert_eq!(
    mock_head.get_content_type(),
    Some(&MimeTypeWithCharset::from_mime(MimeType::TextPlain))
  );
  mock_head.set_content_type(MimeType::Application7Zip);
  assert_eq!(
    mock_head.get_content_type(),
    Some(&MimeTypeWithCharset::from_mime(MimeType::Application7Zip))
  );
  assert_eq!(mock_head.get_header("Content-Type"), Some(MimeType::Application7Zip.as_str()));
  mock_head.remove_headers("Content-Type").unwrap();
  assert_eq!(mock_head.get_header("Content-Type"), None);
  assert_eq!(mock_head.get_content_type(), None);
  mock_head.remove_headers("Accept").unwrap();
  assert_eq!(mock_head.get_header("Accept"), Some("*/*"));
  assert_eq!(
    mock_head.get_accept().first(),
    Some(&AcceptQualityMimeType::wildcard(QValue::default(), MimeCharset::Unspecified))
  );
  mock_head.set_header("meep", "moop").unwrap();
  assert_eq!(mock_head.get_header("meep"), Some("moop"));
  mock_head.remove_headers("meep").unwrap();
  assert_eq!(mock_head.get_header("meep"), None);
  mock_head.set_content_type(MimeType::Application7Zip);
  mock_head.set_accept(vec![
    MimeType::ApplicationJson.into_accept(QValue::default(), MimeCharset::Unspecified)
  ]);
  let err = mock_head.add_header(HttpHeaderName::ContentType, "application/zip").unwrap_err();
  match err {
    TiiError::UserError(UserError::MultipleContentTypeHeaderValuesSet(v1, v2)) => assert_eq!(
      (v1, v2),
      ("application/x-7z-compressed".to_string(), "application/zip".to_string())
    ),
    _ => panic!("Unexpected error {err}"),
  }
  let err = mock_head.add_header(HttpHeaderName::Accept, "*/*").unwrap_err();
  match err {
    TiiError::UserError(UserError::MultipleAcceptHeaderValuesSet(v1, v2)) => {
      assert_eq!((v1, v2), ("application/json".to_string(), "*/*".to_string()))
    }
    _ => panic!("Unexpected error {err}"),
  }

  let err = mock_head.remove_headers(HttpHeaderName::ContentLength).unwrap_err();
  match err {
    TiiError::UserError(UserError::ImmutableRequestHeaderRemoved(v)) => {
      assert_eq!(v, HttpHeaderName::ContentLength)
    }
    _ => panic!("Unexpected error {err}"),
  }
  let err = mock_head.remove_headers(HttpHeaderName::TransferEncoding).unwrap_err();
  match err {
    TiiError::UserError(UserError::ImmutableRequestHeaderRemoved(v)) => {
      assert_eq!(v, HttpHeaderName::TransferEncoding)
    }
    _ => panic!("Unexpected error {err}"),
  }
  let err = mock_head.set_header(HttpHeaderName::TransferEncoding, "bup").unwrap_err();
  match err {
    TiiError::UserError(UserError::ImmutableRequestHeaderModified(v, l)) => {
      assert_eq!((v, l), (HttpHeaderName::TransferEncoding, "bup".to_string()))
    }
    _ => panic!("Unexpected error {err}"),
  }
  let err = mock_head.add_header(HttpHeaderName::TransferEncoding, "bup").unwrap_err();
  match err {
    TiiError::UserError(UserError::ImmutableRequestHeaderModified(v, l)) => {
      assert_eq!((v, l), (HttpHeaderName::TransferEncoding, "bup".to_string()))
    }
    _ => panic!("Unexpected error {err}"),
  }
  let err = mock_head.set_header(HttpHeaderName::ContentLength, "bup").unwrap_err();
  match err {
    TiiError::UserError(UserError::ImmutableRequestHeaderModified(v, l)) => {
      assert_eq!((v, l), (HttpHeaderName::ContentLength, "bup".to_string()))
    }
    _ => panic!("Unexpected error {err}"),
  }
  let err = mock_head.add_header(HttpHeaderName::ContentLength, "bup").unwrap_err();
  match err {
    TiiError::UserError(UserError::ImmutableRequestHeaderModified(v, l)) => {
      assert_eq!((v, l), (HttpHeaderName::ContentLength, "bup".to_string()))
    }
    _ => panic!("Unexpected error {err}"),
  }
}