Moderacja wyrazow: slownik i wstrzymywanie wiadomosci do akceptacji
Deploy PROD / deploy (push) Successful in 33s
Deploy PROD / deploy (push) Successful in 33s
Wiadomosc czatu i pytanie uczestnika przechodza przez slownik przed publikacja. Trafienie wstrzymuje je (status NEW, moderation_hit), widzi je tylko panel prowadzenia z przyciskami Opublikuj / Odrzuc, a autor dostaje informacje, ze wiadomosc czeka. Wiadomosci prowadzacych nie sa filtrowane. Normalizacja rozpoznaje ogonki, wielkie litery, cyfry zamiast liter, powtorzenia i rozstrzelenia. Slownik startowy w V31: wulgaryzmy, obelgi, tresci zwiazane z pedofilia. WordFilterTest czyta slownik z migracji i sprawdza obie strony, takze slownictwo wodociagowe. Ekran "Moderacja wyrazow" w menu: lista z wyszukiwaniem i kategoriami, dodawanie, edycja, wylaczanie, usuwanie z potwierdzeniem, sprawdzanie tekstu. Dostep ADMIN i MODERATOR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package com.softspm.webinar.moderation;
|
||||
|
||||
import com.softspm.webinar.domain.ModerationCategory;
|
||||
import com.softspm.webinar.domain.ModerationMatchMode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Filtr slownika moderacji, sprawdzany na slowniku startowym z migracji.
|
||||
*
|
||||
* Slownik czytany jest wprost z pliku V31, a nie przepisany do testu: test
|
||||
* sprawdza dokladnie to, co trafi na kazda nowa instancje.
|
||||
*
|
||||
* Obie strony sa rownie wazne. Filtr, ktory przepuszcza "k.u.r.w.a", nie
|
||||
* chroni sali; filtr, ktory wstrzymuje "duplikat faktury" albo "ciotka",
|
||||
* zasypuje prowadzacego falszywymi alarmami, az przestanie je czytac.
|
||||
*/
|
||||
class WordFilterTest {
|
||||
|
||||
private static final List<WordFilter.Entry> SEED = loadSeed();
|
||||
|
||||
private static List<WordFilter.Entry> loadSeed() {
|
||||
try {
|
||||
String sql = Files.readString(
|
||||
Path.of("src/main/resources/db/migration/V31__moderation_words.sql"),
|
||||
StandardCharsets.UTF_8);
|
||||
// tylko czesc INSERT: wyzej sa listy wartosci ograniczen CHECK
|
||||
String inserts = sql.substring(sql.indexOf("INSERT INTO moderation_words"));
|
||||
Matcher m = Pattern.compile("\\('([^']+)', '(\\w+)', '(\\w+)'\\)").matcher(inserts);
|
||||
List<WordFilter.Entry> entries = new ArrayList<>();
|
||||
while (m.find()) {
|
||||
entries.add(WordFilter.Entry.of(m.group(1),
|
||||
ModerationCategory.valueOf(m.group(2)),
|
||||
ModerationMatchMode.valueOf(m.group(3))));
|
||||
}
|
||||
return entries;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void seedIsLoaded() {
|
||||
assertThat(SEED).hasSizeGreaterThan(50);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"ty kurwo",
|
||||
"KURWA",
|
||||
"Kurwą jesteś",
|
||||
"kuuuurwa",
|
||||
"k.u.r.w.a",
|
||||
"k u r w a",
|
||||
"kurw4",
|
||||
"to jest zajebiste",
|
||||
"spierdalaj stąd",
|
||||
"wypierdalaj",
|
||||
"ty debilu",
|
||||
"chuj z tym",
|
||||
"ch_uj",
|
||||
"sp13rd0l",
|
||||
"pizda",
|
||||
})
|
||||
void holdsVulgarAndInsults(String text) {
|
||||
assertThat(WordFilter.find(text, SEED)).as(text).isNotNull();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"pedofil",
|
||||
"Pedofilia to przestępstwo",
|
||||
"pornografia dziecięca",
|
||||
"szukam pornografii dziecięcej",
|
||||
"seks z dziećmi",
|
||||
})
|
||||
void holdsChildAbuse(String text) {
|
||||
WordFilter.Hit hit = WordFilter.find(text, SEED);
|
||||
assertThat(hit).as(text).isNotNull();
|
||||
assertThat(hit.category()).isEqualTo(ModerationCategory.CHILD_ABUSE);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"Dzień dobry, mam pytanie o przyłącze wodociągowe",
|
||||
"Proszę o duplikat faktury",
|
||||
"Moja ciotka też pracuje w wodociągach",
|
||||
"Chodzi głównie o przepompownię ścieków",
|
||||
"Gnojowica i osady ściekowe",
|
||||
"Jaki jest dopuszczalny poziom chloru?",
|
||||
"Hydrant przy ulicy Kurkowej",
|
||||
"principal i sprawozdanie finansowe",
|
||||
"Szkolenie z kanalizacji deszczowej",
|
||||
"Czy będzie nagranie?",
|
||||
"Dziękuję, świetny webinar!",
|
||||
})
|
||||
void letsOrdinaryMessagesThrough(String text) {
|
||||
assertThat(WordFilter.find(text, SEED)).as(text).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void speakerWordsAreNotSpecialHere() {
|
||||
// filtr nie wie, kto pisze: pomijanie prowadzacych robi InteractionService
|
||||
assertThat(WordFilter.find("kurwa", SEED)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyTextPasses() {
|
||||
assertThat(WordFilter.find("", SEED)).isNull();
|
||||
assertThat(WordFilter.find(" ", SEED)).isNull();
|
||||
assertThat(WordFilter.find(null, SEED)).isNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user