package org.mockserver.matchers;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.mockserver.configuration.ConfigurationProperties;
import org.mockserver.logging.MockServerLogger;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import static junit.framework.TestCase.*;
import static org.mockserver.character.Character.NEW_LINE;
import static org.mockserver.matchers.NotMatcher.notMatcher;

/**
 * @author jamesdbloom
 */
public class JsonStringMatcherTest {

    @Test
    public void shouldMatchExactMatchingJson() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldMatchExactMatchingJsonWithPlaceholder() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"optional\": true," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"optional\": \"${json-unit.any-boolean}\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"${json-unit.ignore-element}\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldNotMatchExactMatchingJson() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertFalse(notMatcher(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS)).matches(null, matched));
    }

    @Test
    public void shouldMatchMatchingSubJson() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldMatchMatchingSubJsonWithSomeSubJsonFields() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"glossary\": {" + NEW_LINE +
            "        \"title\": \"example glossary\"," + NEW_LINE +
            "        \"GlossDiv\": {" + NEW_LINE +
            "            \"title\": \"S\"," + NEW_LINE +
            "            \"GlossList\": {" + NEW_LINE +
            "                \"GlossEntry\": {" + NEW_LINE +
            "                    \"ID\": \"SGML\"," + NEW_LINE +
            "                    \"SortAs\": \"SGML\"," + NEW_LINE +
            "                    \"GlossTerm\": \"Standard Generalized Markup Language\"," + NEW_LINE +
            "                    \"Acronym\": \"SGML\"," + NEW_LINE +
            "                    \"Abbrev\": \"ISO 8879:1986\"," + NEW_LINE +
            "                    \"GlossDef\": {" + NEW_LINE +
            "                        \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\"," + NEW_LINE +
            "                        \"GlossSeeAlso\": [" + NEW_LINE +
            "                            \"GML\"," + NEW_LINE +
            "                            \"XML\"" + NEW_LINE +
            "                        ]" + NEW_LINE +
            "                    }, " + NEW_LINE +
            "                    \"GlossSee\": \"markup\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            }" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"glossary\": {" + NEW_LINE +
            "        \"GlossDiv\": {" + NEW_LINE +
            "            \"title\": \"S\"," + NEW_LINE +
            "            \"GlossList\": {" + NEW_LINE +
            "                \"GlossEntry\": {" + NEW_LINE +
            "                    \"ID\": \"SGML\"," + NEW_LINE +
            "                    \"Abbrev\": \"ISO 8879:1986\"," + NEW_LINE +
            "                    \"GlossDef\": {" + NEW_LINE +
            "                        \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\"" + NEW_LINE +
            "                    }, " + NEW_LINE +
            "                    \"GlossSee\": \"markup\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            }" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldNotMatchNotMatchingSubJsonWithSomeSubJsonFields() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"glossary\": {" + NEW_LINE +
            "        \"title\": \"example glossary\"," + NEW_LINE +
            "        \"GlossDiv\": {" + NEW_LINE +
            "            \"title\": \"S\"," + NEW_LINE +
            "            \"GlossList\": {" + NEW_LINE +
            "                \"GlossEntry\": {" + NEW_LINE +
            "                    \"ID\": \"SGML\"," + NEW_LINE +
            "                    \"SortAs\": \"SGML\"," + NEW_LINE +
            "                    \"GlossTerm\": \"Standard Generalized Markup Language\"," + NEW_LINE +
            "                    \"Acronym\": \"SGML\"," + NEW_LINE +
            "                    \"Abbrev\": \"ISO 8879:1986\"," + NEW_LINE +
            "                    \"GlossDef\": {" + NEW_LINE +
            "                        \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\"," + NEW_LINE +
            "                        \"GlossSeeAlso\": [" + NEW_LINE +
            "                            \"GML\"," + NEW_LINE +
            "                            \"XML\"" + NEW_LINE +
            "                        ]" + NEW_LINE +
            "                    }, " + NEW_LINE +
            "                    \"GlossSee\": \"markup\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            }" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"glossary\": {" + NEW_LINE +
            "        \"GlossDiv\": {" + NEW_LINE +
            "            \"title\": \"S\"," + NEW_LINE +
            "            \"GlossList\": {" + NEW_LINE +
            "                \"GlossEntry\": {" + NEW_LINE +
            "                    \"ID\": \"SGML\"," + NEW_LINE +
            "                    \"Abbrev\": \"ISO 8879:1986\"," + NEW_LINE +
            "                    \"GlossDef\": {" + NEW_LINE +
            "                        \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\"" + NEW_LINE +
            "                    }, " + NEW_LINE +
            "                    \"GlossSee\": \"markup\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            }" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.STRICT).matches(null, matched));
    }

    @Test
    public void shouldMatchNotMatchingSubJsonWithSomeSubJsonFields() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"glossary\": {" + NEW_LINE +
            "        \"title\": \"example glossary\"," + NEW_LINE +
            "        \"GlossDiv\": {" + NEW_LINE +
            "            \"title\": \"S\"," + NEW_LINE +
            "            \"GlossList\": {" + NEW_LINE +
            "                \"GlossEntry\": {" + NEW_LINE +
            "                    \"ID\": \"SGML\"," + NEW_LINE +
            "                    \"SortAs\": \"SGML\"," + NEW_LINE +
            "                    \"GlossTerm\": \"Standard Generalized Markup Language\"," + NEW_LINE +
            "                    \"Acronym\": \"SGML\"," + NEW_LINE +
            "                    \"Abbrev\": \"ISO 8879:1986\"," + NEW_LINE +
            "                    \"GlossDef\": {" + NEW_LINE +
            "                        \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\"," + NEW_LINE +
            "                        \"GlossSeeAlso\": [" + NEW_LINE +
            "                            \"GML\"," + NEW_LINE +
            "                            \"XML\"" + NEW_LINE +
            "                        ]" + NEW_LINE +
            "                    }, " + NEW_LINE +
            "                    \"GlossSee\": \"markup\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            }" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertTrue(notMatcher(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"glossary\": {" + NEW_LINE +
            "        \"GlossDiv\": {" + NEW_LINE +
            "            \"title\": \"S\"," + NEW_LINE +
            "            \"GlossList\": {" + NEW_LINE +
            "                \"GlossEntry\": {" + NEW_LINE +
            "                    \"ID\": \"SGML\"," + NEW_LINE +
            "                    \"Abbrev\": \"ISO 8879:1986\"," + NEW_LINE +
            "                    \"GlossDef\": {" + NEW_LINE +
            "                        \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\"" + NEW_LINE +
            "                    }, " + NEW_LINE +
            "                    \"GlossSee\": \"markup\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            }" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.STRICT)).matches(null, matched));
    }

    @Test
    public void shouldMatchMatchingSubJsonWithDifferentArrayOrder() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldNotMatchMatchingSubJsonWithDifferentArrayOrder() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.STRICT).matches(null, matched));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.STRICT).matches(null, matched));
    }

    @Test
    public void shouldNotMatchIllegalJson() {
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "illegal_json", MatchType.ONLY_MATCHING_FIELDS).matches(null, "illegal_json"));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "illegal_json", MatchType.ONLY_MATCHING_FIELDS).matches(null, "some_other_illegal_json"));
    }

    @Test
    public void shouldNotMatchNullExpectation() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), null, MatchType.ONLY_MATCHING_FIELDS).matches(null, "some_value"));
    }

    @Test
    public void shouldNotMatchEmptyExpectation() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "", MatchType.ONLY_MATCHING_FIELDS).matches(null, "some_value"));
    }

    @Test
    public void shouldNotMatchNonMatchingJson() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"wrong_value\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldMatchJsonForIncorrectArrayOrder() {
        // given
        String matched = "{id:1,pets:[\"dog\",\"cat\",\"fish\"]}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{id:1,pets:[\"cat\",\"dog\",\"fish\"]}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldNotMatchJsonForIncorrectArrayOrder() {
        // given
        String matched = "{id:1,pets:[\"dog\",\"cat\",\"fish\"]}";

        // then
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{id:1,pets:[\"cat\",\"dog\",\"fish\"]}", MatchType.STRICT).matches(null, matched));
    }

    @Test
    public void shouldMatchJsonForExtraField() {
        // given
        String matched = "{id:1,pets:[\"dog\",\"cat\",\"fish\"],extraField:\"extraValue\"}";

        // then
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{id:1,pets:[\"dog\",\"cat\",\"fish\"]}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldNotMatchJsonForExtraField() {
        // given
        String matched = "{id:1,pets:[\"dog\",\"cat\",\"fish\"],extraField:\"extraValue\"}";

        // then
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{id:1,pets:[\"dog\",\"cat\",\"fish\"]}", MatchType.STRICT).matches(null, matched));
    }

    @Test
    public void shouldNotMatchNonMatchingSubJson() {
        // given
        String matched = "" +
            "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"File\"," + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"id\": \"file\"," + NEW_LINE +
            "        \"value\": \"other_value\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{" + NEW_LINE +
            "    \"menu\": {" + NEW_LINE +
            "        \"popup\": {" + NEW_LINE +
            "            \"menuitem\": [" + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"New\"," + NEW_LINE +
            "                    \"onclick\": \"CreateNewDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Open\"," + NEW_LINE +
            "                    \"onclick\": \"OpenDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }, " + NEW_LINE +
            "                {" + NEW_LINE +
            "                    \"value\": \"Close\"," + NEW_LINE +
            "                    \"onclick\": \"CloseDoc()\"" + NEW_LINE +
            "                }" + NEW_LINE +
            "            ]" + NEW_LINE +
            "        }" + NEW_LINE +
            "    }" + NEW_LINE +
            "}", MatchType.ONLY_MATCHING_FIELDS).matches(null, matched));
    }

    @Test
    public void shouldNotMatchNullTest() {
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "some_value", MatchType.ONLY_MATCHING_FIELDS).matches(null, null));
    }

    @Test
    public void shouldNotMatchEmptyTest() {
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "some_value", MatchType.ONLY_MATCHING_FIELDS).matches(null, ""));
    }

    @Test
    public void shouldMatchBasicValues() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "null", MatchType.ONLY_MATCHING_FIELDS).matches(null, "null"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "1", MatchType.ONLY_MATCHING_FIELDS).matches(null, "1"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "true", MatchType.ONLY_MATCHING_FIELDS).matches(null, "true"));
    }

    @Test
    public void shouldNotMatchIntegerAndDoubleByDefault() {
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.ONLY_MATCHING_FIELDS).matches(null, "{\"value\":1.0}"));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1.0}", MatchType.ONLY_MATCHING_FIELDS).matches(null, "{\"value\":1}"));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.STRICT).matches(null, "{\"value\":1.0}"));
    }

    @Test
    public void shouldMatchIntegerAndDoubleWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1.0}"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1.0}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1}"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.STRICT, true).matches(null, "{\"value\":1.0}"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1.0}", MatchType.STRICT, true).matches(null, "{\"value\":1}"));
    }

    @Test
    public void shouldMatchIdenticalNumbersWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1}"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1.5}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1.5}"));
    }

    @Test
    public void shouldNotMatchDifferentNumbersWithMatchNumbersAsStrings() {
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":2}"));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1.0}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1.1}"));
    }

    @Test
    public void shouldMatchNestedIntegerAndDoubleWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"outer\":{\"value\":1}}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"outer\":{\"value\":1.0}}"));
    }

    @Test
    public void shouldMatchArrayNumbersWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"values\":[1,2,3]}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"values\":[1.0,2.0,3.0]}"));
    }

    @Test
    public void shouldMatchNegativeNumbersWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":-1}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":-1.0}"));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":-1}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":-2}"));
    }

    @Test
    public void shouldMatchZeroWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":0}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":0.0}"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":0.0}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":0}"));
    }

    @Test
    public void shouldMatchScientificNotationWithMatchNumbersAsStrings() {
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":100}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1e2}"));
        assertTrue(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1e2}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":100.0}"));
    }

    @Test
    public void shouldNotMatchStringNumberAgainstNumericWithMatchNumbersAsStrings() {
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":\"1\"}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":1}"));
        assertFalse(new JsonStringMatcher(new MockServerLogger(), "{\"value\":1}", MatchType.ONLY_MATCHING_FIELDS, true).matches(null, "{\"value\":\"1\"}"));
    }

    @Test
    public void showHaveCorrectEqualsBehaviour() {
        MockServerLogger mockServerLogger = new MockServerLogger();
        assertEquals(new JsonStringMatcher(mockServerLogger, "some_value", MatchType.ONLY_MATCHING_FIELDS), new JsonStringMatcher(mockServerLogger, "some_value", MatchType.ONLY_MATCHING_FIELDS));
    }

    @Test
    public void shouldMatchUsingCustomJsonUnitMatcher() {
        String previous = ConfigurationProperties.customJsonUnitMatchersClass();
        try {
            ConfigurationProperties.customJsonUnitMatchersClass(LargerThanMatcherProvider.class.getName());
            CustomJsonUnitMatcherLoader.reset();

            assertTrue(new JsonStringMatcher(
                new MockServerLogger(),
                "{\"price\":\"${json-unit.matches:largerThan}\"}",
                MatchType.ONLY_MATCHING_FIELDS
            ).matches(null, "{\"price\":250}"));
        } finally {
            ConfigurationProperties.customJsonUnitMatchersClass(previous);
            CustomJsonUnitMatcherLoader.reset();
        }
    }

    @Test
    public void shouldNotMatchWhenCustomJsonUnitMatcherRejects() {
        String previous = ConfigurationProperties.customJsonUnitMatchersClass();
        try {
            ConfigurationProperties.customJsonUnitMatchersClass(LargerThanMatcherProvider.class.getName());
            CustomJsonUnitMatcherLoader.reset();

            assertFalse(new JsonStringMatcher(
                new MockServerLogger(),
                "{\"price\":\"${json-unit.matches:largerThan}\"}",
                MatchType.ONLY_MATCHING_FIELDS
            ).matches(null, "{\"price\":50}"));
        } finally {
            ConfigurationProperties.customJsonUnitMatchersClass(previous);
            CustomJsonUnitMatcherLoader.reset();
        }
    }

    @Test
    public void shouldFallBackWhenCustomMatcherClassMisconfigured() {
        String previous = ConfigurationProperties.customJsonUnitMatchersClass();
        try {
            ConfigurationProperties.customJsonUnitMatchersClass("org.mockserver.does.not.Exist");
            CustomJsonUnitMatcherLoader.reset();

            // a plain JSON body that references no custom matcher still matches normally
            assertTrue(new JsonStringMatcher(
                new MockServerLogger(),
                "{\"price\":250}",
                MatchType.ONLY_MATCHING_FIELDS
            ).matches(null, "{\"price\":250}"));
        } finally {
            ConfigurationProperties.customJsonUnitMatchersClass(previous);
            CustomJsonUnitMatcherLoader.reset();
        }
    }

    public static class LargerThanMatcherProvider implements CustomJsonUnitMatcherProvider {

        @Override
        public Map<String, Matcher<?>> jsonUnitMatchers() {
            Map<String, Matcher<?>> matchers = new HashMap<>();
            matchers.put("largerThan", new BaseMatcher<Object>() {
                @Override
                public boolean matches(Object item) {
                    if (item == null) {
                        return false;
                    }
                    try {
                        return new BigDecimal(item.toString()).compareTo(BigDecimal.valueOf(100)) > 0;
                    } catch (NumberFormatException e) {
                        return false;
                    }
                }

                @Override
                public void describeTo(Description description) {
                    description.appendText("a number larger than 100");
                }
            });
            return matchers;
        }
    }
}
