/*************************************************************************** * Copyright (C) 2025 by Nicolas Keita - nicolaskeita2@@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "TMxpElementRegistry.h" #include "TMxpProcessor.h" #include "TMxpStubClient.h" #include #include #include class TmxpElementDefinitionHandlerTest : public QObject { Q_OBJECT private slots: void testMxpElementRegistration_data() { QTest::addColumn("name"); QTest::addColumn("inputString"); QTest::addColumn("expectedAttrs"); QTest::addColumn("expectedFlags"); QTest::addColumn("expectedOpen"); QTest::addColumn("expectedEmpty"); QTest::addColumn("expectedParsedDefinitionCount"); QStringList attrs1; QTest::newRow("Test1") << "Test1" << R"()" << attrs1 << QString("Weird & Co") << false << false << 0; QStringList attrs2 = {"name", "type"}; QTest::newRow("Test2") << "Test2" << R"()" << attrs2 << QString("SpecialFlag") << true << true << 0; QStringList attrs3; QTest::newRow("Test3") << "Test3" << R"('>)" << attrs3 << QString() << false << false << 3; QStringList attrs4 = {"class", "level", "race"}; QTest::newRow("Test4_MultipleAttrsAndFlag") << "Player" << R"()" << attrs4 << QString("Hero") << false << false << 0; QStringList attrs5; QTest::newRow("Test5_EmptyFlag") << "EmptyFlag" << R"()" << attrs5 << QString("") << false << false << 0; QStringList attrs6; QTest::newRow("Test6_AutoClosingTag") << "AutoClose" << R"(' EMPTY>)" << attrs6 << QString() << false << true << 1; QStringList attrs7; QTest::newRow("Test7_NameWithDigitsAndUnderscore") << "Enemy_123" << R"()" << attrs7 << QString("Dangerous") << false << false << 0; QStringList attrs8; QTest::newRow("Test8_FlagWithChevronsAndAmpersand") << "Weird" << R"( & that">)" << attrs8 << QString("Use & that") << false << false << 0; } void testMxpElementRegistration() { QFETCH(QString, name); QFETCH(QString, inputString); QFETCH(QStringList, expectedAttrs); QFETCH(QString, expectedFlags); QFETCH(bool, expectedOpen); QFETCH(bool, expectedEmpty); QFETCH(int, expectedParsedDefinitionCount); TMxpStubClient client; TMxpProcessor mxpProcessor(&client); mxpProcessor.setMode(MXP_MODE_CODE_LOCK_SECURE); const QByteArray utf8Data = inputString.toUtf8(); for (char ch : utf8Data) { mxpProcessor.processMxpInput(ch, true); } const TMxpElementRegistry ®istry = mxpProcessor.getMxpTagProcessor().getElementRegistry(); QVERIFY(registry.containsElement(name)); const TMxpElement &element = registry.getElement(name); QCOMPARE(element.attrs, expectedAttrs); QCOMPARE(element.flags, expectedFlags); QCOMPARE(element.open, expectedOpen); QCOMPARE(element.empty, expectedEmpty); QCOMPARE(element.parsedDefinition.size(), expectedParsedDefinitionCount); } void testParserDoesNotFreezeOnAmpersand_data() { QTest::addColumn("messageWithAmpersand"); QTest::newRow("no amp") << "abc"; QTest::newRow("solo amp") << "&\n"; QTest::newRow("amp followed by space") << "& followed by space\n"; QTest::newRow("amp at start no semicolon") << "&start\n"; QTest::newRow("amp + newline") << "line with &\nnext line\n"; QTest::newRow("amp + numbers") << "version &123;\n"; QTest::newRow("amp + special chars") << "weird &entity-._#;\n"; QTest::newRow("multiple consecutive amps") << "text &&&& more text\n"; QTest::newRow("only amps") << "&&&&\n"; QTest::newRow("amp + emoji") << "smile &😊;\n"; QTest::newRow("html_entity") << "Hello <World>\r"; QTest::newRow("known_custom_entity") << "Use &custom;\r"; QTest::newRow("incomplete_entity") << "Broken &ent\r"; QTest::newRow("empty_entity") << "Edge case &;\r"; QTest::newRow("long_entity") << "&thisisaverylongentitynamethatmightbreak;\r"; } void testParserDoesNotFreezeOnAmpersand() { TMxpStubClient stub; TMxpProcessor mxpProcessor(&stub); TMxpProcessingResult lastResult = HANDLER_FALL_THROUGH; QFETCH(QString, messageWithAmpersand); const QByteArray utf8Data = messageWithAmpersand.toUtf8(); for (char c : utf8Data) { lastResult = mxpProcessor.processMxpInput(c, true); } QVERIFY(lastResult != HANDLER_NEXT_CHAR); } void testParserDoesNotFreezeOnUnescapedTagStart_data() { QTest::addColumn("inputString"); QTest::newRow("starts_with_less_than") << ""; QTest::newRow("less_than_second_char") << "a"; QTest::newRow("multiple_less_than_before_valid_tag") << "<<"; QTest::newRow("unclosed_tag") << ""; QTest::newRow("closed_without_open") << ""; QTest::newRow("mixed_with_text") << "foobaz"; QTest::newRow("doctype_like") << ""; QTest::newRow("comment_tag") << ""; QTest::newRow("comment_with_less_than") << "\r"; } void testParserDoesNotFreezeOnUnescapedTagStart() { TMxpStubClient stub; TMxpProcessor mxpProcessor(&stub); mxpProcessor.setMode(MXP_MODE_CODE_LOCK_SECURE); QFETCH(QString, inputString); const QByteArray utf8Data = inputString.toUtf8(); for (char c : utf8Data) { mxpProcessor.processMxpInput(c, true); } QCOMPARE( mxpProcessor.getMxpTagProcessor().getEntityResolver().getResolution( "&ob;"), "lamp"); } }; #include "TMxpElementDefinitionHandlerTest.moc" QTEST_MAIN(TmxpElementDefinitionHandlerTest)