#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%require-types
%enable-all-warnings
%new-style
%strict-args

%requires ../../../../qlib/QUnit.qm
%requires ../../../../qlib/Mime.qm

%exec-class MimeTest

public class MimeTest inherits QUnit::Test {
    private {
        string testString = "This is a test: æéìœü";

        string printableEncoded = "This is a test: =C3=A6=C3=A9=C3=AC=C5=93=C3=BC";
        string base64Encoded = "VGhpcyBpcyBhIHRlc3Q6IMOmw6nDrMWTw7w=";
        string headerWordQEncoded = "=?UTF-8?Q?This_is_a_test:_=C3=A6=C3=A9=C3=AC=C5=93=C3=BC?=";
        string headerWordBEncoded = "=?UTF-8?B?VGhpcyBpcyBhIHRlc3Q6IMOmw6nDrMWTw7w=?=";

        const UrlEncodingSource = "this,is!a\"test\"&it really is$-_.*'()";
        const UrlEncodingTarget = "this,is!a%22test%22%26it%20really%20is$-_.*'()";

        const FormDataBoundary = "---------------------------735323031399963166993862150";
        const FormDataBody = "-----------------------------735323031399963166993862150\r
Content-Disposition: form-data; name=\"text1\"\r
\r
text default\r
-----------------------------735323031399963166993862150\r
Content-Disposition: form-data; name=\"text2\"\r
\r
aωb\r
-----------------------------735323031399963166993862150\r
Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r
Content-Type: text/plain\r
\r
Content of a.txt.\r
\r
-----------------------------735323031399963166993862150\r
Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r
Content-Type: text/html\r
\r
<!DOCTYPE html><title>Content of a.html.</title>\r
\r
-----------------------------735323031399963166993862150\r
Content-Disposition: form-data; name=\"file3\"; filename=\"binary\"\r
Content-Type: application/octet-stream\r
\r
aωb\r
-----------------------------735323031399963166993862150--\r
";
    }

    constructor() : Test("MimeTest", "1.0") {
        addTestCase("issue 2936", \issue2936());
        addTestCase("mime type from ext", \mimeTypeTest());
        addTestCase("Printable test", \printableTest());
        addTestCase("Base64 test", \base64Test());
        addTestCase("Header word q test", \wordQTest());
        addTestCase("Header word b test", \wordBTest());
        addTestCase("urlencoding", \urlEncodingTest());
        addTestCase("form data", \formDataTest());
        addTestCase("mime_parse_form_urlencoded_string", \parseFormUrlencodedTest());

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    issue2936() {
        # get random binary data
        list<binary> bl = map get_random_bytes(128), 0..2;

        MultiPartFormDataMessage msg();
        map msg.addPart($1, "bin-" + $#, "binary-file-" + $# + ".bin", MimeTypeOctetStream), bl;

        string boundary = msg.getBoundary();
        hash<MessageInfo> h = MultiPartMessage::parseBody(boundary, msg.serializeBody(), True);

        map assertEq(MimeTypeOctetStream, $1.hdr."content-type"), h.part;
        map assertEq(bl[$#], $1.body), h.part;
    }

    mimeTypeTest() {
        assertEq(MimeTypeUnknown, get_mime_type_from_ext("/"));
        assertEq(MimeTypeHtml, get_mime_type_from_ext("/", MimeTypeHtml));
    }

    printableTest() {
        testAssertion("encode", \mime_encode_quoted_printable(), (testString,), new TestResultValue(printableEncoded));
        testAssertion("decode", \mime_decode_quoted_printable(), (printableEncoded,), new TestResultValue(testString));
    }

    base64Test() {
        testAssertion("encode", \mime_encode_base64(), (testString,), new TestResultValue(base64Encoded));
        testAssertion("decode", \mime_decode_base64_to_string(), (base64Encoded,), new TestResultValue(testString));
    }

    wordQTest() {
        testAssertion("encode", \mime_encode_header_word_q(), (testString,), new TestResultValue(headerWordQEncoded));
        testAssertion("decode", \mime_decode_header(), (headerWordQEncoded,), new TestResultValue(testString));
    }

    wordBTest() {
        testAssertion("encode", \mime_encode_header_word_b(), (testString,), new TestResultValue(headerWordBEncoded));
        testAssertion("decode", \mime_decode_header(), (headerWordBEncoded,), new TestResultValue(testString));
    }

    urlEncodingTest() {
        assertEq(UrlEncodingTarget, mime_get_urlencoded_string(UrlEncodingSource));
        assertEq("a=b&a%20x=c", mime_get_form_urlencoded_string(("a": "b", "a x": "c")));
        assertEq(UrlEncodingSource, mime_decode_urlencoded_string(UrlEncodingTarget));
    }

    formDataTest() {
        hash<MessageInfo> mh = MultiPartMessage::parseBody(FormDataBoundary, FormDataBody);
        assertEq("", mh.body);
        assertEq(5, mh.part.size());
        #printf("mh: %N\n", mh);

        hash<string, hash<FormDataMessageInfo>> h = MultiPartFormDataMessage::parseMessage("multipart/form-data; boundary=---------------------------735323031399963166993862150", FormDataBody);
        assertEq(5, h.size());
        assertEq("hash<FormDataMessageInfo>", h.firstValue().fullType());

        MultiPartFormDataMessage msg = MultiPartFormDataMessage::makeMessage(h);
        assertEq(5, msg.size());
        #printf("str: %s\n%s <- boundary\n", msg.serializeBody().toString(), msg.getBoundary());
        h = MultiPartFormDataMessage::parseMessage(MimeTypeMultipartFormData + ";boundary=" + msg.getBoundary(), msg.serializeBody().toString());
        assertEq(5, h.size());
    }

    parseFormUrlencodedTest() {
        hash test_data = {
            "foo=bar&baz=1" : { "foo" : "bar", "baz" : "1", },
            "foo=bar&baz=" : { "foo" : "bar", "baz" : NOTHING, },
            "0=1&1=0" : { "0" : "1", "1" : "0", },
            "foo=bar&baz=1&foo=barber": {"foo": ("bar", "barber"), "baz": "1"},
        };

        HashIterator it(test_data);
        while (it.next()) {
            hash res = mime_parse_form_urlencoded_string(it.getKey());
            assertEq(it.getValue(), res, "mime_parse_form_urlencoded_string " + it.getKey());
        }
    }
}
