Skip to content

Commit 5e1c97d

Browse files
author
HugoFara
committed
test(backend): add unit tests for Tier 3 and Tier 4 services
Add comprehensive tests for low-coverage backend services: Tier 3: - FeedOptionsTest: 71 tests for value object methods - ExportServiceTest: extended with 70+ edge case tests - MySqlArticleRepositoryTest: 33 tests for repository mapping - LanguageControllerTest: 22 tests for wizard methods - ExpressionServiceTest: 44 tests for expression handling Tier 4: - DictionaryAdapterTest: 43 tests for URL encoding and HTML generation - ArticleExtractorTest: 52 tests for HTML parsing and extraction - NlpServiceHandlerTest: 47 tests for NLP API methods - WordDiscoveryServiceTest: extended with method signatures - TextParsingTest: 20 new tests for checkText() and edge cases Also extends existing tests: - ApiV1RoutingTest, RouterTest, SentenceServiceTest - FeedApiHandlerTest, TextApiHandlerTest, TermCrudApiHandlerTest - LemmaServiceTest Coverage improved from ~31% to 32.32% lines, 51.37% methods.
1 parent f5f663f commit 5e1c97d

File tree

20 files changed

+9148
-0
lines changed

20 files changed

+9148
-0
lines changed

tests/backend/Api/V1/ApiV1RoutingTest.php

Lines changed: 824 additions & 0 deletions
Large diffs are not rendered by default.

tests/backend/Api/V1/Handlers/NlpServiceHandlerTest.php

Lines changed: 485 additions & 0 deletions
Large diffs are not rendered by default.

tests/backend/Core/Database/TextParsingTest.php

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,4 +624,344 @@ public function testSplitIntoSentencesWithRtlLanguage(): void
624624
// Clean up
625625
Connection::query("DELETE FROM $languages WHERE LgID = $rtlLangId");
626626
}
627+
628+
// ===== checkText() tests =====
629+
630+
public function testCheckTextReturnsArrayWithStats(): void
631+
{
632+
if (!self::$dbConnected) {
633+
$this->markTestSkipped('Database connection required');
634+
}
635+
636+
$text = "Hello world. This is a test sentence.";
637+
$result = TextParsing::checkText($text, self::$testLanguageId);
638+
639+
$this->assertIsArray($result);
640+
$this->assertArrayHasKey('sentences', $result);
641+
$this->assertArrayHasKey('words', $result);
642+
$this->assertArrayHasKey('unknownPercent', $result);
643+
$this->assertArrayHasKey('preview', $result);
644+
}
645+
646+
public function testCheckTextReturnsSentenceCount(): void
647+
{
648+
if (!self::$dbConnected) {
649+
$this->markTestSkipped('Database connection required');
650+
}
651+
652+
$text = "First sentence. Second sentence. Third sentence.";
653+
$result = TextParsing::checkText($text, self::$testLanguageId);
654+
655+
// At least 1 sentence should be parsed
656+
$this->assertGreaterThanOrEqual(1, $result['sentences']);
657+
}
658+
659+
public function testCheckTextReturnsWordCount(): void
660+
{
661+
if (!self::$dbConnected) {
662+
$this->markTestSkipped('Database connection required');
663+
}
664+
665+
$text = "One two three four five.";
666+
$result = TextParsing::checkText($text, self::$testLanguageId);
667+
668+
$this->assertGreaterThanOrEqual(5, $result['words']);
669+
}
670+
671+
public function testCheckTextReturnsUnknownPercent(): void
672+
{
673+
if (!self::$dbConnected) {
674+
$this->markTestSkipped('Database connection required');
675+
}
676+
677+
$text = "Hello world.";
678+
$result = TextParsing::checkText($text, self::$testLanguageId);
679+
680+
// Without any words in the database, all words should be unknown
681+
$this->assertIsFloat($result['unknownPercent']);
682+
$this->assertGreaterThanOrEqual(0, $result['unknownPercent']);
683+
$this->assertLessThanOrEqual(100, $result['unknownPercent']);
684+
}
685+
686+
public function testCheckTextReturnsPreview(): void
687+
{
688+
if (!self::$dbConnected) {
689+
$this->markTestSkipped('Database connection required');
690+
}
691+
692+
$text = "First sentence. Second sentence. Third sentence.";
693+
$result = TextParsing::checkText($text, self::$testLanguageId);
694+
695+
$this->assertIsString($result['preview']);
696+
$this->assertNotEmpty($result['preview']);
697+
}
698+
699+
public function testCheckTextPreviewWithMultipleSentences(): void
700+
{
701+
if (!self::$dbConnected) {
702+
$this->markTestSkipped('Database connection required');
703+
}
704+
705+
$text = "Sentence one. Sentence two. Sentence three. Sentence four. Sentence five.";
706+
$result = TextParsing::checkText($text, self::$testLanguageId);
707+
708+
// Preview should contain some of the text
709+
$this->assertIsString($result['preview']);
710+
$this->assertNotEmpty($result['preview']);
711+
}
712+
713+
public function testCheckTextWithEmptyText(): void
714+
{
715+
if (!self::$dbConnected) {
716+
$this->markTestSkipped('Database connection required');
717+
}
718+
719+
$result = TextParsing::checkText('', self::$testLanguageId);
720+
721+
$this->assertIsArray($result);
722+
$this->assertEquals(0, $result['words']);
723+
}
724+
725+
public function testCheckTextWithInvalidLanguage(): void
726+
{
727+
if (!self::$dbConnected) {
728+
$this->markTestSkipped('Database connection required');
729+
}
730+
731+
$result = TextParsing::checkText('Test text.', 99999);
732+
733+
$this->assertIsArray($result);
734+
$this->assertEquals(0, $result['sentences']);
735+
$this->assertEquals(0, $result['words']);
736+
$this->assertEquals(100.0, $result['unknownPercent']);
737+
$this->assertEquals('', $result['preview']);
738+
}
739+
740+
// ===== parseAndSave() error handling tests =====
741+
742+
public function testParseAndSaveThrowsForZeroTextId(): void
743+
{
744+
if (!self::$dbConnected) {
745+
$this->markTestSkipped('Database connection required');
746+
}
747+
748+
$this->expectException(\InvalidArgumentException::class);
749+
$this->expectExceptionMessage('Text ID must be positive');
750+
751+
TextParsing::parseAndSave("Test text.", self::$testLanguageId, 0);
752+
}
753+
754+
public function testParseAndSaveThrowsForNegativeTextId(): void
755+
{
756+
if (!self::$dbConnected) {
757+
$this->markTestSkipped('Database connection required');
758+
}
759+
760+
$this->expectException(\InvalidArgumentException::class);
761+
$this->expectExceptionMessage('Text ID must be positive');
762+
763+
TextParsing::parseAndSave("Test text.", self::$testLanguageId, -1);
764+
}
765+
766+
public function testParseAndSaveThrowsForInvalidLanguage(): void
767+
{
768+
if (!self::$dbConnected) {
769+
$this->markTestSkipped('Database connection required');
770+
}
771+
772+
$this->expectException(\Lwt\Core\Exception\DatabaseException::class);
773+
774+
TextParsing::parseAndSave("Test text.", 99999, 1);
775+
}
776+
777+
// ===== parseAndDisplayPreview() error handling tests =====
778+
779+
public function testParseAndDisplayPreviewThrowsForInvalidLanguage(): void
780+
{
781+
if (!self::$dbConnected) {
782+
$this->markTestSkipped('Database connection required');
783+
}
784+
785+
$this->expectException(\Lwt\Core\Exception\DatabaseException::class);
786+
787+
ob_start();
788+
try {
789+
TextParsing::parseAndDisplayPreview("Test text.", 99999);
790+
} finally {
791+
ob_end_clean();
792+
}
793+
}
794+
795+
// ===== Multi-word expression tests =====
796+
797+
public function testParseAndSaveWithMultiWordExpression(): void
798+
{
799+
if (!self::$dbConnected) {
800+
$this->markTestSkipped('Database connection required');
801+
}
802+
803+
$texts = Globals::table('texts');
804+
$sentences = Globals::table('sentences');
805+
$word_occurrences = Globals::table('word_occurrences');
806+
$words = Globals::table('words');
807+
808+
// Create a multi-word expression (lowercase to match parsed text)
809+
$sql = "INSERT INTO $words (WoLgID, WoText, WoTextLC, WoTranslation, WoStatus, WoWordCount)
810+
VALUES (" . self::$testLanguageId . ", 'test word', 'test word', 'translation', 1, 2)";
811+
Connection::query($sql);
812+
$wordId = mysqli_insert_id(Globals::getDbConnection());
813+
814+
// Create a test text
815+
$sql = "INSERT INTO $texts (TxLgID, TxTitle, TxText, TxAudioURI)
816+
VALUES (" . self::$testLanguageId . ", 'MW Test', 'This is a test word example.', '')";
817+
Connection::query($sql);
818+
$textId = mysqli_insert_id(Globals::getDbConnection());
819+
820+
// Parse and save
821+
TextParsing::parseAndSave("This is a test word example.", self::$testLanguageId, $textId);
822+
823+
// Check that word occurrences were created
824+
$itemCount = Connection::fetchValue(
825+
"SELECT COUNT(*) as value FROM $word_occurrences WHERE Ti2TxID = $textId"
826+
);
827+
$this->assertGreaterThan(0, (int)$itemCount, 'Should have word occurrences');
828+
829+
// Clean up
830+
Connection::query("DELETE FROM $word_occurrences WHERE Ti2TxID = $textId");
831+
Connection::query("DELETE FROM $sentences WHERE SeTxID = $textId");
832+
Connection::query("DELETE FROM $texts WHERE TxID = $textId");
833+
Connection::query("DELETE FROM $words WHERE WoID = $wordId");
834+
}
835+
836+
// ===== Additional edge case tests =====
837+
838+
public function testSplitIntoSentencesWithAbbreviations(): void
839+
{
840+
if (!self::$dbConnected) {
841+
$this->markTestSkipped('Database connection required');
842+
}
843+
844+
// Test that abbreviations don't split sentences incorrectly
845+
$text = "Dr. Smith works at Mr. Jones Corp. He is great.";
846+
$result = $this->callSplitIntoSentences($text, self::$testLanguageId);
847+
848+
$this->assertIsArray($result);
849+
// Should not split at "Dr." or "Mr."
850+
$this->assertGreaterThanOrEqual(1, count($result));
851+
}
852+
853+
public function testSplitIntoSentencesWithMixedPunctuation(): void
854+
{
855+
if (!self::$dbConnected) {
856+
$this->markTestSkipped('Database connection required');
857+
}
858+
859+
$text = "What? How! Sure... Go on. Yes!";
860+
$result = $this->callSplitIntoSentences($text, self::$testLanguageId);
861+
862+
$this->assertIsArray($result);
863+
$this->assertGreaterThanOrEqual(4, count($result));
864+
}
865+
866+
public function testSplitIntoSentencesPreservesParagraphMarkers(): void
867+
{
868+
if (!self::$dbConnected) {
869+
$this->markTestSkipped('Database connection required');
870+
}
871+
872+
$text = "Para one.\n\nPara two.\n\nPara three.";
873+
$result = $this->callSplitIntoSentences($text, self::$testLanguageId);
874+
875+
$this->assertIsArray($result);
876+
// Should have paragraph markers (¶)
877+
$joined = implode('', $result);
878+
$this->assertStringContainsString('', $joined);
879+
}
880+
881+
public function testCheckTextWithKnownWord(): void
882+
{
883+
if (!self::$dbConnected) {
884+
$this->markTestSkipped('Database connection required');
885+
}
886+
887+
$words = Globals::table('words');
888+
889+
// Create a known word
890+
$sql = "INSERT INTO $words (WoLgID, WoText, WoTextLC, WoTranslation, WoStatus, WoWordCount)
891+
VALUES (" . self::$testLanguageId . ", 'known', 'known', 'bekannt', 99, 1)";
892+
Connection::query($sql);
893+
$wordId = mysqli_insert_id(Globals::getDbConnection());
894+
895+
$result = TextParsing::checkText('known word test.', self::$testLanguageId);
896+
897+
// At least one word should be known now (lower unknown %)
898+
$this->assertIsFloat($result['unknownPercent']);
899+
// Can't assert exact percentage since "word" and "test" are still unknown
900+
901+
// Clean up
902+
Connection::query("DELETE FROM $words WHERE WoID = $wordId");
903+
}
904+
905+
public function testParseAndSaveMultipleSentences(): void
906+
{
907+
if (!self::$dbConnected) {
908+
$this->markTestSkipped('Database connection required');
909+
}
910+
911+
$texts = Globals::table('texts');
912+
$sentences = Globals::table('sentences');
913+
$word_occurrences = Globals::table('word_occurrences');
914+
915+
// Create a test text with multiple sentences
916+
$sql = "INSERT INTO $texts (TxLgID, TxTitle, TxText, TxAudioURI)
917+
VALUES (" . self::$testLanguageId . ", 'Multi Sentence Test', 'Sentence one. Sentence two. Sentence three.', '')";
918+
Connection::query($sql);
919+
$textId = mysqli_insert_id(Globals::getDbConnection());
920+
921+
TextParsing::parseAndSave("Sentence one. Sentence two. Sentence three.", self::$testLanguageId, $textId);
922+
923+
// Check that at least 1 sentence was created
924+
$sentenceCount = Connection::fetchValue(
925+
"SELECT COUNT(*) as value FROM $sentences WHERE SeTxID = $textId"
926+
);
927+
$this->assertGreaterThanOrEqual(1, (int)$sentenceCount, 'Should create at least 1 sentence');
928+
929+
// Clean up
930+
Connection::query("DELETE FROM $word_occurrences WHERE Ti2TxID = $textId");
931+
Connection::query("DELETE FROM $sentences WHERE SeTxID = $textId");
932+
Connection::query("DELETE FROM $texts WHERE TxID = $textId");
933+
}
934+
935+
public function testParseAndDisplayPreviewOutputsHtml(): void
936+
{
937+
if (!self::$dbConnected) {
938+
$this->markTestSkipped('Database connection required');
939+
}
940+
941+
ob_start();
942+
TextParsing::parseAndDisplayPreview("Test sentence one. Test sentence two.", self::$testLanguageId);
943+
$output = ob_get_clean();
944+
945+
// Should output HTML structure
946+
$this->assertStringContainsString('<h4>', $output);
947+
$this->assertStringContainsString('Sentences', $output);
948+
$this->assertStringContainsString('<ol>', $output);
949+
$this->assertStringContainsString('<li>', $output);
950+
}
951+
952+
public function testParseAndDisplayPreviewOutputsJson(): void
953+
{
954+
if (!self::$dbConnected) {
955+
$this->markTestSkipped('Database connection required');
956+
}
957+
958+
ob_start();
959+
TextParsing::parseAndDisplayPreview("Hello world.", self::$testLanguageId);
960+
$output = ob_get_clean();
961+
962+
// Should output JSON config scripts
963+
$this->assertStringContainsString('text-check-words-config', $output);
964+
$this->assertStringContainsString('text-check-config', $output);
965+
$this->assertStringContainsString('application/json', $output);
966+
}
627967
}

0 commit comments

Comments
 (0)