PD Guard: модуль безопасности персональных данных
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package ru.pdguard.detect;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/** Денилист известных людей: встроенный список и дозагрузка сверху без пересборки. */
|
||||
class NameDictionaryTest {
|
||||
|
||||
/** Возвращает состояние словаря к встроенному списку — не течёт в остальные тесты. */
|
||||
@AfterEach
|
||||
void resetToBundledList() {
|
||||
NameDictionary.useExternalFile(Path.of("config/well-known.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bundledListCoversClassicFigures() {
|
||||
// Сравнение по началу слова покрывает обычную русскую флексию («Пушкин» →
|
||||
// «Пушкина»), но не прилагательное склонение («Толстой» → «Толстого», где
|
||||
// «-ой» меняется на «-ого» целиком, а не дописывается) — известное
|
||||
// ограничение самого приёма, не завязанное на список имён.
|
||||
assertTrue(NameDictionary.isWellKnown("Стихи Пушкина"));
|
||||
assertTrue(NameDictionary.isWellKnown("Портрет Толстой"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bundledListCoversCurrentPublicFigures() {
|
||||
assertTrue(NameDictionary.isWellKnown("Заявление Набиуллиной"));
|
||||
assertTrue(NameDictionary.isWellKnown("Интервью Путина"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownSurnameIsNotWellKnown() {
|
||||
assertFalse(NameDictionary.isWellKnown("Заявление Смирнова"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Фамилии, в честь которых чаще всего называют улицы в России (Росреестр).
|
||||
* Само по себе «улица Ленина» никогда не попало бы под ФИО — для этого
|
||||
* правила нужны два слова, — но денилист должен покрывать и составные
|
||||
* названия («Феликса Дзержинского»), и вариации написания («Будённый»/
|
||||
* «Буденный»).
|
||||
*/
|
||||
@Test
|
||||
void bundledListCoversCommemorativeStreetNames() {
|
||||
assertTrue(NameDictionary.isWellKnown("улица Кирова"));
|
||||
// «-ский» склоняется целиком («Дзержинский» → «Дзержинского»), для таких
|
||||
// основа обрезается сразу до «ск» — см. Declension.
|
||||
assertTrue(NameDictionary.isWellKnown("проспект Дзержинского"));
|
||||
assertTrue(NameDictionary.isWellKnown("улица Донского"));
|
||||
// «Будённый» — чистое прилагательное без «-ский» (как «Толстой»): та же
|
||||
// известная граница приёма, родительный падеж («Будённого») им не ловится.
|
||||
assertTrue(NameDictionary.isWellKnown("улица Будённый"));
|
||||
assertTrue(NameDictionary.isWellKnown("улица Буденный"), "написание без «ё» тоже должно ловиться");
|
||||
assertTrue(NameDictionary.isWellKnown("улица Жукова"));
|
||||
assertTrue(NameDictionary.isWellKnown("улица Островского"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void externalFileAddsNamesWithoutRebuild(@TempDir Path dir) throws IOException {
|
||||
Path file = dir.resolve("well-known.txt");
|
||||
Files.writeString(file, "Кастомов\n", StandardCharsets.UTF_8);
|
||||
|
||||
NameDictionary.useExternalFile(file);
|
||||
|
||||
assertTrue(NameDictionary.isWellKnown("Интервью Кастомова"),
|
||||
"дописанное сверху имя должно распознаваться наравне со встроенными");
|
||||
assertTrue(NameDictionary.isWellKnown("Стихи Пушкина"),
|
||||
"встроенный список не должен теряться при дозагрузке");
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingExternalFileFallsBackToBundledListOnly(@TempDir Path dir) {
|
||||
NameDictionary.useExternalFile(dir.resolve("нет-такого-файла.txt"));
|
||||
|
||||
assertTrue(NameDictionary.isWellKnown("Стихи Пушкина"));
|
||||
assertFalse(NameDictionary.isWellKnown("Заявление Смирнова"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ru.pdguard.detect;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/** Словарь населённых пунктов — не только города, но и сёла, посёлки, деревни, хутора. */
|
||||
class ToponymDictionaryTest {
|
||||
|
||||
@Test
|
||||
void recognisesNominativeCase() {
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Москва"));
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Казань"));
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Санкт-Петербург"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void recognisesInflectedForms() {
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Москве"), "дательный падеж города на гласную");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Тамбове"), "предложный падеж города на согласную");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Казани"), "родительный падеж");
|
||||
}
|
||||
|
||||
@Test
|
||||
void recognisesCisCapitals() {
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Минск"));
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Алматы"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMadeUpWord() {
|
||||
assertFalse(ToponymDictionary.isKnownSettlement("Ерунда"));
|
||||
assertFalse(ToponymDictionary.isKnownSettlement("Бла-бла"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isCaseInsensitive() {
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("МОСКВА"));
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("москва"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Из переписи 2020–2021, не из ручного списка городов: сёла, посёлки,
|
||||
* деревни, хутора, станицы, аулы, аалы — ровно то, чего не было, пока
|
||||
* словарь ограничивался официальными городами. Примеры из ТЗ («рп.
|
||||
* Ильинское», «с. Кукуево») и по одному реальному названию на тип
|
||||
* населённого пункта.
|
||||
*/
|
||||
@Test
|
||||
void recognisesSettlementsFromCensusNotJustOfficialCities() {
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Ильинское"), "рп. Ильинское — пример из ТЗ");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Кукуево"), "с. Кукуево — пример из ТЗ");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Прогресс"), "хутор");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Гиагинская"), "станица");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Блечепсин"), "аул");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Энем"), "посёлок городского типа");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Аксеновка"), "деревня");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Екатериновка"), "слобода");
|
||||
assertTrue(ToponymDictionary.isKnownSettlement("Сартыков"), "аал (Хакасия)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ru.pdguard.detect;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/** Контрольные суммы ОГРН/ОГРНИП: первые 12/14 цифр по модулю 11/13, младший разряд остатка. */
|
||||
class ValidatorsTest {
|
||||
|
||||
@Test
|
||||
void validOgrnPassesChecksum() {
|
||||
assertTrue(Validators.ogrn("1027700123450"));
|
||||
assertTrue(Validators.ogrn("1025000678900"));
|
||||
assertTrue(Validators.ogrn("1045002233440"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidOgrnChecksumFails() {
|
||||
assertFalse(Validators.ogrn("1027700123451"), "последняя цифра изменена — сумма не сходится");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ogrnWrongLengthFails() {
|
||||
assertFalse(Validators.ogrn("102770012345"), "12 цифр — не хватает контрольной");
|
||||
assertFalse(Validators.ogrn("10277001234500"), "14 цифр — лишняя");
|
||||
}
|
||||
|
||||
/** Остаток от деления на 11 может быть 10 — тогда контрольная цифра 0, не 10. */
|
||||
@Test
|
||||
void ogrnRemainderTenMapsToZero() {
|
||||
assertTrue(Validators.ogrn("1000000000000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validOgrnipPassesChecksum() {
|
||||
assertTrue(Validators.ogrnip("304500116000157"));
|
||||
assertTrue(Validators.ogrnip("312500000000013"));
|
||||
assertTrue(Validators.ogrnip("305500112233041"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidOgrnipChecksumFails() {
|
||||
assertFalse(Validators.ogrnip("304500116000158"), "последняя цифра изменена — сумма не сходится");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ogrnipWrongLengthFails() {
|
||||
assertFalse(Validators.ogrnip("30450011600015"), "14 цифр — не хватает контрольной");
|
||||
assertFalse(Validators.ogrnip("30450011600015700"), "18 цифр — лишние");
|
||||
}
|
||||
|
||||
/** Остаток от деления на 13 может быть 10 — тогда контрольная цифра 0, не 10. */
|
||||
@Test
|
||||
void ogrnipRemainderTenMapsToZero() {
|
||||
assertTrue(Validators.ogrnip("100000000000000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonDigitCharactersAreIgnored() {
|
||||
assertTrue(Validators.ogrn("10-27700-123450"), "разделители в номере не мешают счёту цифр");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyValueIsNotValid() {
|
||||
assertFalse(Validators.ogrn(""));
|
||||
assertFalse(Validators.ogrnip(""));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user