AsyncAposUnescapedControlChars1750Test.java

package tools.jackson.core.unittest.json.async;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
import tools.jackson.core.unittest.async.AsyncTestBase;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.core.unittest.testutil.AsyncReaderWrapper;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Async variant of [jackson-core#1750] tests: {@code ALLOW_UNESCAPED_CONTROL_CHARS}
 * with single-quoted String values (and names).
 */
class AsyncAposUnescapedControlChars1750Test extends AsyncTestBase
{
    private final JsonFactory APOS_F = JsonFactory.builder()
            .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
            .build();

    private final JsonFactory APOS_CTRL_F = APOS_F.rebuild()
            .enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
            .build();

    private final static int[] READ_SIZES = new int[] { 1, 2, 3, 5, 99, 1000 };

    private final static String[] VALUES = new String[] {
            "a\tb", "\t", "a\u0001b", "x\ny\rz", "��\t���", "\t\uD83D\uDE00\t",
            _longValue()
    };

    @Test
    void controlCharsInAposValueAllowed() throws Exception
    {
        for (int readSize : READ_SIZES) {
            for (boolean byteBuffer : new boolean[] { false, true }) {
                for (String value : VALUES) {
                    try (AsyncReaderWrapper p = _parser(APOS_CTRL_F, byteBuffer, readSize,
                            "['" + value + "']")) {
                        assertToken(JsonToken.START_ARRAY, p.nextToken());
                        assertToken(JsonToken.VALUE_STRING, p.nextToken());
                        assertEquals(value, p.currentText(), "readSize " + readSize);
                        assertToken(JsonToken.END_ARRAY, p.nextToken());
                    }
                    try (AsyncReaderWrapper p = _parser(APOS_CTRL_F, byteBuffer, readSize,
                            "{'a':'" + value + "'}")) {
                        assertToken(JsonToken.START_OBJECT, p.nextToken());
                        assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
                        assertToken(JsonToken.VALUE_STRING, p.nextToken());
                        assertEquals(value, p.currentText(), "readSize " + readSize);
                        assertToken(JsonToken.END_OBJECT, p.nextToken());
                    }
                }
            }
        }
    }

    @Test
    void controlCharsInAposNameAllowed() throws Exception
    {
        for (int readSize : READ_SIZES) {
            for (String name : new String[] { "a\tb", "\t", "x\ny" }) {
                try (AsyncReaderWrapper p = _parser(APOS_CTRL_F, false, readSize,
                        "{'" + name + "':1}")) {
                    assertToken(JsonToken.START_OBJECT, p.nextToken());
                    assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
                    assertEquals(name, p.currentName(), "readSize " + readSize);
                    assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
                    assertToken(JsonToken.END_OBJECT, p.nextToken());
                }
            }
        }
    }

    @Test
    void controlCharsInAposDisallowed() throws Exception
    {
        for (int readSize : READ_SIZES) {
            for (String doc : new String[] { "['a\tb']", "{'a':'a\tb'}", "['" + _longValue() + "']" }) {
                try (AsyncReaderWrapper p = _parser(APOS_F, false, readSize, doc)) {
                    p.nextToken();
                    p.nextToken();
                    p.nextToken();
                    fail("Should not pass, readSize " + readSize);
                } catch (StreamReadException e) {
                    verifyException(e, "Illegal unquoted character");
                    verifyException(e, "string value");
                }
            }
            try (AsyncReaderWrapper p = _parser(APOS_F, false, readSize, "{'a\tb':1}")) {
                p.nextToken();
                p.nextToken();
                fail("Should not pass, readSize " + readSize);
            } catch (StreamReadException e) {
                verifyException(e, "Illegal unquoted character");
                verifyException(e, "name");
            }
        }
    }

    // Linefeeds in String values must update row number
    @Test
    void linefeedsUpdateRow() throws Exception
    {
        for (int readSize : READ_SIZES) {
            for (String lf : new String[] { "\n", "\r", "\r\n" }) {
                for (char q : new char[] { '\'', '"' }) {
                    final String doc = "[" + q + "x" + lf + "y" + q + ", 1]";
                    try (AsyncReaderWrapper p = _parser(APOS_CTRL_F, false, readSize, doc)) {
                        assertToken(JsonToken.START_ARRAY, p.nextToken());
                        assertToken(JsonToken.VALUE_STRING, p.nextToken());
                        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
                        assertEquals(2, p.parser().currentTokenLocation().getLineNr(),
                                "readSize " + readSize + ", doc "
                                + doc.replace("\r", "\\r").replace("\n", "\\n"));
                    }
                }
            }
        }
    }

    private AsyncReaderWrapper _parser(JsonFactory f, boolean byteBuffer, int readSize,
            String doc) throws Exception
    {
        byte[] data = _jsonDoc(doc);
        return byteBuffer ? asyncForByteBuffer(f, readSize, data, 0)
                : asyncForBytes(f, readSize, data, 0);
    }

    // Long enough to span input chunk and text buffer segment boundaries,
    // with control chars at varying offsets
    private static String _longValue() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 6000; ++i) {
            switch (i % 7) {
            case 0:
                sb.append('\t');
                break;
            case 3:
                sb.append('��');
                break;
            default:
                sb.append((char) ('a' + (i % 26)));
            }
        }
        return sb.toString();
    }
}