Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

ChunkedTest.pudu

Pudu67 lines4.4 KB

GitHub ↗
1/** @Test.Domain.Chunked.Suite — chunked bodies decoded as their bytes arrive */2module PuduLangHttpClient.Domain.ChunkedTest34import Std.Io as Io5import Std.Test as Test6import PuduLangHttpClient.Domain.Chunked as Chunked78/// The decoder and every decoded byte after feeding each piece in turn, or the problem.9fn fed(pieces: &Array[Str], limit: Int) -> Result[(Chunked.Decoder, Str), Chunked.Problem] {10  var decoder = Chunked.decoder()11  var body = ""12  for piece in pieces {13    let (next, decoded) = Chunked.feed(&decoder, &piece.toBytes(), limit) ?14    decoder = next15    body = body + textOf(&decoded)16  }17  Ok((decoder, body))18}1920/// Text from bytes that are text, or empty text.21fn textOf(bytes: &Bytes) -> Str {22  match bytes.toText() {23    case Some(text) => text24    case None => ""25  }26}2728/// The body decoded, whether decoding finished, the trailers, and what was left over.29fn summary(pieces: &Array[Str], limit: Int) -> Result[(Str, Bool, Array[(Str, Str)], Str), Chunked.Problem] {30  let (decoder, body) = fed(pieces, limit) ?31  Ok((body, Chunked.isDone(&decoder), decoder.trailers, textOf(&Chunked.leftover(&decoder))))32}3334/// Runs the suite.35fn main() -> Int {36  let whole = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n"37  let pieces = whole.chars().map(|c: Char| c.toText())38  let checks = Test.suite("Domain.Chunked", &[39      Test.equals("a whole body decodes", &summary(&[whole], 100), &Ok(("Wikipedia in\r\n\r\nchunks.", true, [], ""))),40      Test.equals("a body arriving one character at a time decodes the same", &summary(&pieces, 100), &Ok(("Wikipedia in\r\n\r\nchunks.", true, [], ""))),41      Test.equals("extensions are ignored and trailers kept", &summary(&["3;name=v\r\nabc\r\n0\r\nExpires: never\r\nX-A:  b \r\n\r\n"], 100), &Ok(("abc", true, [("Expires", "never"), ("X-A", "b")], ""))),42      Test.equals("bytes after the body are left over", &summary(&["1\r\nx\r\n0\r\n\r\nHTTP"], 100), &Ok(("x", true, [], "HTTP"))),43      Test.equals("an unfinished body is not done", &summary(&["5\r\nab"], 100), &Ok(("ab", false, [], ""))),44      Test.equals("an empty body is done at once", &summary(&["0\r\n\r\n"], 0), &Ok(("", true, [], ""))),45      Test.equals("a body exactly at the limit decodes", &summary(&["3\r\nabc\r\n0\r\n\r\n"], 3), &Ok(("abc", true, [], ""))),46      Test.equals("a body one byte over the limit is exceeded", &summary(&["4\r\nabcd\r\n0\r\n\r\n"], 3), &Err(Chunked.Exceeded)),47      Test.equals("upper-case hexadecimal sizes are read", &summary(&["A\r\n0123456789\r\n0\r\n\r\n"], 100), &Ok(("0123456789", true, [], ""))),48      Test.equals("a size that is not hexadecimal is malformed", &summary(&["xyz\r\n"], 100), &Err(Chunked.Malformed("a chunk size is not hexadecimal: xyz"))),49      Test.equals("an empty size is malformed", &summary(&[";x\r\n"], 100), &Err(Chunked.Malformed("a chunk size is missing or too long"))),50      Test.equals("a size of sixteen digits is malformed", &summary(&["1000000000000000\r\n"], 100), &Err(Chunked.Malformed("a chunk size is missing or too long"))),51      Test.equals("a chunk not followed by a line break is malformed", &summary(&["1\r\nxyz"], 100), &Err(Chunked.Malformed("a chunk is not followed by a line break"))),52      Test.equals("a trailer without a name is malformed", &summary(&["0\r\n: v\r\n\r\n"], 100), &Err(Chunked.Malformed("a trailer field has no name"))),53      Test.equals("a framing line past the limit is malformed", &summary(&["1".repeat(8193)], 100), &Err(Chunked.Malformed("a framing line is longer than 8192 bytes"))),54      Test.equals("a long line with its break is malformed too", &summary(&["0;" + "x".repeat(8191) + "\r\n"], 100), &Err(Chunked.Malformed("a framing line is longer than 8192 bytes"))),55      Test.equals("a line at the limit is accepted", &summary(&["0;" + "x".repeat(8190) + "\r\n\r\n"], 100), &Ok(("", true, [], ""))),56      Test.equals("an unfinished line exactly at the limit waits for more", &summary(&["1".repeat(8192)], 100), &Ok(("", false, [], ""))),57      Test.equals("a size of fifteen digits is read", &summary(&["00000000000000A\r\n0123456789\r\n0\r\n\r\n"], 100), &Ok(("0123456789", true, [], ""))),58      Test.equals("a trailer name of one letter is a name", &summary(&["0\r\nX:1\r\n\r\n"], 100), &Ok(("", true, [("X", "1")], ""))),59      Test.equals("nothing is left over before the end", &textOf(&Chunked.leftover(&Chunked.decoder())), &"")60    ])61  let ran = Test.run(&checks)62  for failure in Test.failuresOf(&ran) {63    let _reported = Io.writeErrorLine(failure)64  }65  Test.report(&ran)66}67