) Arrays.stream(elements, 0, size);
+ }
+
+ /**
+ * Добавляет все элементы из указанной коллекции в конец этой коллекции.
+ *
+ * Порядок элементов в целевой коллекции соответствует порядку,
+ * в котором они возвращаются итератором указанной коллекции.
+ *
+ *
Поведение этого метода не определено, если указанная коллекция изменяется
+ * в процессе выполнения операции.
+ *
+ * @param collection коллекция, содержащая элементы для добавления
+ */
+ public void addAll(CustomCollection extends T> collection) {
+ if (collection == null || collection.isEmpty()) {
+ return;
+ }
+
+ for (T element : collection) {
+ add(element);
+ }
}
@Override
public int size() {
- return 0;
+ return size;
}
@Override
- public boolean contains(Object car) {
- return false;
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new CustomArrayIterator();
+ }
+
+ /**
+ * Внутренний класс итератора для CustomCollection.
+ * Реализует fail-fast поведение: выбрасывает {@link IllegalStateException}
+ * при попытке удаления без предварительного вызова {@link #next()}.
+ */
+ class CustomArrayIterator implements Iterator {
+ /** Текущая позиция итератора в массиве элементов. */
+ private int cursor = 0;
+
+ /** Индекс последнего возвращенного элемента; -1 если элемент не был возвращен или был удален. */
+ private int lastRet = -1;
+
+ public CustomArrayIterator() {
+ }
+
+ /**
+ * Проверяет, существует ли следующий элемент для итерации.
+ *
+ * @return {@code true} если итерация содержит больше элементов,
+ * {@code false} в противном случае
+ */
+ public boolean hasNext() {
+ return cursor < size;
+ }
+
+ /**
+ * Возвращает следующий элемент в итерации.
+ *
+ * @return следующий элемент в итерации
+ * @throws NoSuchElementException если итерация не содержит больше элементов
+ */
+ public T next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException("Элемент отсутствует");
+ }
+ lastRet = cursor;
+ T element = (T) elements[cursor];
+ cursor++;
+
+ return element;
+ }
+
+ /**
+ * Удаляет из коллекции последний элемент, возвращенный этим итератором.
+ *
+ * @throws IllegalStateException если метод {@link #next} еще не был вызван,
+ * или метод {@link #remove} уже был вызван после последнего вызова {@link #next}
+ */
+ public void remove() {
+ if (lastRet == -1) {
+ throw new IllegalStateException();
+ }
+ removeByIndex(lastRet);
+ cursor = lastRet;
+ lastRet = -1;
+ }
+ }
+
+ /**
+ * Увеличивает емкость внутреннего массива, чтобы обеспечить минимальную указанную емкость.
+ * @param minCapacity минимальная требуемая емкость
+ */
+ private void increaseCapacity(int minCapacity) {
+ int newCapacity = (int) Math.max(elements.length * GROWTH_FACTOR + 1, minCapacity);
+ Object[] newElements = new Object[newCapacity];
+ System.arraycopy(elements, 0, newElements, 0, size);
+ elements = newElements;
}
}
diff --git a/src/main/java/input/FileReader.java b/src/main/java/input/FileReader.java
deleted file mode 100644
index 70d3383..0000000
--- a/src/main/java/input/FileReader.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package input;
-
-public class FileReader {
-}
diff --git a/src/main/java/input/InputManager.java b/src/main/java/input/InputManager.java
index b904efb..7cb8b2e 100644
--- a/src/main/java/input/InputManager.java
+++ b/src/main/java/input/InputManager.java
@@ -1,5 +1,110 @@
package input;
+import input.strategy.*;
+import dto.Client;
+
+import java.io.IOException;
+
+/**
+ * Менеджер ввода данных, реализующий паттерн Strategy.
+ * Выступает в роли Context для управления различными стратегиями ввода данных о клиентах.
+ *
+ * Основные функции:
+ *
+ * - Управление текущей стратегией ввода
+ * - Загрузка данных с использованием выбранной стратегии
+ * - Создание стратегий различных типов
+ *
+ *
+ * @see ClientInputStrategy
+ * @see FileReaderStrategy
+ * @see ManualInputReaderStrategy
+ * @see RandomDataGeneratorStrategy
+ * @see CustomCollection
+ * @see Client
+ */
+
public class InputManager {
- CustomCollection customCollection = new CustomCollection();
-}
+ private ClientInputStrategy currentStrategy;
+
+
+ public InputManager() {
+ // Инициализация без стратегии
+ }
+
+ public InputManager(ClientInputStrategy initialStrategy) {
+ if (initialStrategy == null) {
+ throw new IllegalArgumentException("Стратегия не может быть null");
+ }
+ this.currentStrategy = initialStrategy;
+ }
+
+ public void setStrategy(ClientInputStrategy strategy) {
+ if (strategy == null) {
+ throw new IllegalArgumentException("Стратегия не может быть null");
+ }
+ this.currentStrategy = strategy;
+ }
+
+ public ClientInputStrategy getCurrentStrategy() {
+ return currentStrategy;
+ }
+
+ /**
+ * Загружает данные о клиентах с использованием текущей стратегии.
+ *
+ * @return коллекция клиентов, загруженных с помощью текущей стратегии
+ * @throws IllegalStateException если стратегия не установлена
+ * @throws IOException если возникает ошибка ввода-вывода при загрузке данных
+ */
+ public CustomCollection loadData() throws IOException {
+ if (currentStrategy == null) {
+ throw new IllegalStateException("Стратегия ввода не установлена. " +
+ "Используйте setStrategy() перед вызовом loadData()");
+ }
+ return currentStrategy.getData();
+ }
+
+ /**
+ * Создает стратегию для чтения данных из файла.
+ *
+ * Файл должен содержать данные в формате:
+ *
+ * Имя|Телефон|ID
+ * Иван Иванов|+79991234567|1
+ *
+ *
+ * @param filePath путь к файлу с данными о клиентах
+ * @return стратегия чтения из файла
+ * @throws IllegalArgumentException если {@code filePath} равен {@code null} или пуст
+ */
+ public FileReaderStrategy createFileStrategy(String filePath) {
+ if (filePath == null || filePath.trim().isEmpty()) {
+ throw new IllegalArgumentException("Путь к файлу не может быть null или пустым");
+ }
+ return new FileReaderStrategy(filePath);
+ }
+
+ /**
+ * Создает стратегию для ручного ввода данных через консоль.
+ *
+ * Стратегия предоставляет интерактивный интерфейс для ввода данных
+ * о клиентах с валидацией и подтверждением.
+ *
+ * @return стратегия ручного ввода
+ */
+ public ManualInputReaderStrategy createManualStrategy() {
+ return new ManualInputReaderStrategy();
+ }
+
+ /**
+ * Создает стратегию для генерации случайных данных о клиентах.
+ *
+ * @param count количество клиентов для генерации
+ * @return стратегия генерации случайных данных
+ * @throws IllegalArgumentException если {@code count} меньше или равен 0
+ */
+ public RandomDataGeneratorStrategy createRandomStrategy(int count) {
+ return new RandomDataGeneratorStrategy(count);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/input/ManualInputReader.java b/src/main/java/input/ManualInputReader.java
deleted file mode 100644
index 00b0632..0000000
--- a/src/main/java/input/ManualInputReader.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package input;
-
-public class ManualInputReader {
-}
diff --git a/src/main/java/input/RandomDataGenerator.java b/src/main/java/input/RandomDataGenerator.java
deleted file mode 100644
index 804d222..0000000
--- a/src/main/java/input/RandomDataGenerator.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package input;
-
-public class RandomDataGenerator {
-}
diff --git a/src/main/java/input/strategy/ClientInputStrategy.java b/src/main/java/input/strategy/ClientInputStrategy.java
new file mode 100644
index 0000000..b58aa7f
--- /dev/null
+++ b/src/main/java/input/strategy/ClientInputStrategy.java
@@ -0,0 +1,17 @@
+package input.strategy;
+
+import dto.Client;
+import input.CustomCollection;
+
+import java.io.IOException;
+
+/**
+ * Интерфейс стратегии для ввода данных клиентов.
+ * Реализует паттерн Стратегия для различных способов получения объектов {@link Client}.
+ * Каждая реализация представляет конкретный способ получения данных
+ * Реализации должны гарантировать, что возвращаемые объекты {@link Client}
+ * являются валидными
+ */
+public interface ClientInputStrategy {
+ CustomCollection getData() throws IOException;
+}
diff --git a/src/main/java/input/strategy/FileReaderStrategy.java b/src/main/java/input/strategy/FileReaderStrategy.java
new file mode 100644
index 0000000..f0981d1
--- /dev/null
+++ b/src/main/java/input/strategy/FileReaderStrategy.java
@@ -0,0 +1,140 @@
+package input.strategy;
+
+import dto.Client;
+import input.CustomCollection;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * Стратегия для чтения данных о клиентах из текстового файла.
+ * Реализует интерфейс {@link ClientInputStrategy} для получения
+ * коллекции клиентов из внешнего файла.
+ *
+ * Формат файла:
+ *
+ * - Каждая строка представляет одного клиента
+ * - Формат строки: {@code Имя|Телефон|ID}
+ * - Разделитель полей: вертикальная черта ({@code |})
+ * - Кодировка файла: UTF-8
+ *
+ *
+ * Требования к данным:
+ *
+ * - Имя: непустая строка
+ * - Телефон: формат {@code +7XXXXXXXXXX} (11 цифр после {@code +7})
+ * - ID: целое число
+ *
+ *
+ * Обработка ошибок:
+ *
+ * - Пропускает некорректные строки с выводом сообщения об ошибке
+ * - Игнорирует пустые строки
+ * - Генерирует исключение при проблемах с файлом
+ *
+ *
+ * Пример содержимого файла:
+ *
+ * Иван Иванов|+79991234567|1
+ * Мария Петрова|+79997654321|2
+ *
+ *
+ * @see ClientInputStrategy
+ * @see Client
+ * @see CustomCollection
+ */
+public class FileReaderStrategy implements ClientInputStrategy {
+ private final String filePath;
+
+ public FileReaderStrategy(String filePath) {
+ this.filePath = filePath;
+ }
+
+ /**
+ * Читает данные о клиентах из файла и возвращает их в виде коллекции.
+ * Файл должен содержать строки в формате: Имя|Телефон|ID
+ * Телефон должен соответствовать формату: +7XXXXXXXXXX (11 цифр после +7)
+ *
+ * @return коллекция клиентов, прочитанных из файла
+ * @throws RuntimeException если файл не найден или произошла ошибка ввода-вывода
+ */
+ @Override
+ public CustomCollection getData() {
+ CustomCollection clients = new CustomCollection<>();
+ Path path = Paths.get(filePath);
+
+ if (!Files.exists(path)) {
+ throw new RuntimeException("Файл не найден: " + filePath);
+ }
+
+ try (Stream lines = Files.lines(path, StandardCharsets.UTF_8)) {
+ lines
+ .filter(line -> !line.trim().isEmpty())
+ .map(this::parseToClient)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(Client.ClientBuilder::build)
+ .forEach(clients::add);
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return clients;
+ }
+
+ /**
+ * Парсит строку из файла в объект ClientBuilder.
+ * Строка должна быть в формате: Имя|Телефон|ID
+ *
+ * @param line строка из файла для парсинга
+ * @return Optional содержащий ClientBuilder если строка валидна,
+ * или пустой Optional если строка содержит ошибки формата
+ */
+ public Optional parseToClient(String line) {
+ try {
+ String[] parts = line.split("\\|");
+ if (parts.length != 3) {
+ throw new IllegalArgumentException("неверный формат данных");
+ }
+ String name = parts[0].trim();
+ String phoneNumber = parts[1].trim();
+ int idNumber;
+
+ try {
+ idNumber = Integer.parseInt(parts[2].trim());
+ } catch (NumberFormatException e) {
+ System.out.println("ID не число: " + line);
+ return Optional.empty();
+ }
+
+ if (name.isEmpty()) {
+ System.out.println("Пустое имя в строке: " + line);
+ return Optional.empty();
+ }
+
+ if (!phoneNumber.matches("^\\+7\\d{10}$")) {
+ System.out.println("Неверный формат номера телефона в строке: " + line);
+ return Optional.empty();
+ }
+
+ Client.ClientBuilder builder = new Client.ClientBuilder()
+ .name(name)
+ .phoneNumber(phoneNumber)
+ .idNumber(idNumber);
+ return Optional.of(builder);
+
+ } catch (Exception e) {
+ System.out.println("Ошибка в строке: " + line);
+ return Optional.empty();
+ }
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+}
diff --git a/src/main/java/input/strategy/ManualInputReaderStrategy.java b/src/main/java/input/strategy/ManualInputReaderStrategy.java
new file mode 100644
index 0000000..0db8327
--- /dev/null
+++ b/src/main/java/input/strategy/ManualInputReaderStrategy.java
@@ -0,0 +1,261 @@
+package input.strategy;
+
+import dto.Client;
+import input.CustomCollection;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Set;
+
+/**
+ * Стратегия для ручного ввода данных клиентов через консоль.
+ * Реализует интерфейс {@link ClientInputStrategy} для интерактивного
+ * ввода данных о клиентах с валидацией и подтверждением.
+ *
+ * Обеспечивает:
+ *
+ * - Пошаговый ввод данных о клиентах
+ * - Валидацию введенных данных
+ * - Подтверждение корректности ввода
+ * - Ограничение на уникальность ID клиентов
+ * - Лимит на максимальное количество клиентов (1000)
+ *
+ *
+ * @see ClientInputStrategy
+ * @see Client
+ * @see CustomCollection
+ */
+public class ManualInputReaderStrategy implements ClientInputStrategy {
+ private Scanner scanner;
+ private Set userIds;
+
+ public ManualInputReaderStrategy() {
+ this.scanner = new Scanner(System.in);
+ this.userIds = new HashSet<>();
+ }
+
+
+ public ManualInputReaderStrategy(InputStream in) {
+ this.scanner = new Scanner(in); // создаём Scanner на переданном потоке
+ this.userIds = new HashSet<>();
+ }
+
+ /**
+ * Получает коллекцию клиентов через интерактивный ручной ввод.
+ * Метод последовательно запрашивает данные о каждом клиенте,
+ * выполняет валидацию и подтверждение, после чего добавляет
+ * клиента в результирующую коллекцию.
+ *
+ * Процесс ввода продолжается до тех пор, пока:
+ *
+ * - Пользователь не откажется от добавления нового клиента
+ * - Не будет достигнут лимит в 1000 клиентов
+ *
+ *
+ * @return коллекция введенных клиентов
+ * @throws IOException если возникает ошибка ввода-вывода
+ */
+ @Override
+ public CustomCollection getData() throws IOException {
+ CustomCollection clients = new CustomCollection<>();
+
+ System.out.println("=== Ручной ввод клиентов ===");
+ System.out.println("Вводите данные клиентов. Для завершения введите 'стоп'.\n");
+
+ int clientNumber = 1;
+
+ while (true) {
+ if (userIds.size() == 1000) {
+ System.out.println("Достигнут лимит клиентов");
+ break;
+ }
+
+ System.out.println("Ввод клиент №" + clientNumber);
+
+ try {
+ if (!promptForConfirmation("Добавляем клиента?")) {
+ break;
+ }
+
+ Client client = inputClient(clientNumber);
+ if (client != null) {
+ clients.add(client);
+ clientNumber++;
+ }
+ } catch (StopInputException e) {
+ System.out.println("\nВвод прерван пользователем. Текущий клиент не добавлен.");
+ break;
+ }
+ }
+ System.out.println("\n Ввод завершен. Добавлено клиентов: " + clients.size());
+ return clients;
+ }
+
+ /**
+ * Выполняет ввод данных для одного клиента.
+ * Запрашивает имя, телефонный номер и идентификатор,
+ * затем отображает введенные данные для подтверждения.
+ *
+ * @param clientNumber порядковый номер клиента (для отображения в интерфейсе)
+ * @return объект {@link Client} с введенными данными или {@code null},
+ * если пользователь отменил ввод
+ */
+ private Client inputClient(int clientNumber) {
+ try {
+ String name = promptForName();
+ String phone = promptForNumber();
+ int id = promptForId();
+
+ Client.ClientBuilder builder = new Client.ClientBuilder().name(name)
+ .phoneNumber(phone)
+ .idNumber(id);
+
+ System.out.println("\nВы ввели:");
+ System.out.println(" Имя: " + name);
+ System.out.println(" Телефон: " + phone);
+ System.out.println(" ID: " + id);
+
+ if (promptForConfirmation("Все верно?")) {
+ return builder.build();
+ } else {
+ System.out.println("Отмена ввода этого клиента");
+ return null;
+ }
+ } catch (StopInputException e) {
+ throw e;
+ }
+ }
+
+ private String promptForName() {
+ try {
+ return promptForString("Введите имя", true);
+ } catch (StopInputException e) {
+ throw e;
+ }
+ }
+
+ private String promptForNumber() {
+ while (true) {
+ try {
+ String phone = promptForString("Введите номер телефона в формате +7XXXXXXXXXX", true);
+
+ if (phone.matches("^\\+7\\d{10}$")) {
+ return phone;
+ } else {
+ System.out.println("⚠️ Неверный формат телефона! Пример: +79991234567");
+ System.out.println(" Должно начинаться с +7 и содержать 11 цифр");
+ }
+ } catch (StopInputException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private int promptForId() {
+ while (true) {
+ String input = promptForString("Введите ID в диапазоне от 0 до 1000", true);
+
+ try {
+ int id = Integer.parseInt(input);
+
+ if (id < 0 || id > 1000) {
+ System.out.println("ID должен быть в диапазоне от 0 до 1000");
+ continue;
+ }
+
+ if (userIds.contains(id)) {
+ System.out.println("Этот ID уже занят");
+ continue;
+ }
+
+ userIds.add(id);
+ return id;
+ } catch (StopInputException e) {
+ throw e;
+ } catch (NumberFormatException e) {
+ System.out.println("ID должен быть целым числом");
+ }
+ }
+ }
+
+ /**
+ * Запрашивает у пользователя подтверждение действия.
+ * Поддерживает ввод на русском и английском языках.
+ *
+ * Принимаемые варианты подтверждения:
+ *
+ * - Положительные: "да", "д", "y", "yes"
+ * - Отрицательные: "нет", "н", "n", "no"
+ *
+ *
+ * Метод продолжает запрашивать ввод до получения корректного ответа.
+ *
+ * @param message сообщение с вопросом для подтверждения
+ * @return {@code true} если пользователь подтвердил действие,
+ * {@code false} если отказался
+ */
+ private boolean promptForConfirmation(String message) {
+ while (true) {
+ System.out.print(message + " (да/д/yes/y или нет/н/no/n): ");
+ String input = scanner.nextLine().trim().toLowerCase();
+
+ if (input.equalsIgnoreCase("стоп")) {
+ throw new StopInputException();
+ }
+
+ if (input.equals("да") || input.equals("д") || input.equals("y") || input.equals("yes")) {
+ return true;
+ }
+ if (input.equals("нет") || input.equals("н") || input.equals("n") || input.equals("no")) {
+ return false;
+ }
+ System.out.println("⚠️ Пожалуйста, введите 'да/д/yes/y' или 'нет/н/no/n'");
+ }
+ }
+
+ /**
+ * Универсальный метод для запроса строкового ввода от пользователя.
+ * Обрабатывает обязательность поля и базовые ошибки ввода.
+ *
+ * Если поле является обязательным ({@code required = true}),
+ * метод будет продолжать запрашивать ввод до получения непустой строки.
+ *
+ * @param message сообщение с описанием запрашиваемых данных
+ * @param required флаг, указывающий является ли поле обязательным для заполнения
+ * @return введенная пользователем строка (без начальных и конечных пробелов)
+ */
+ private String promptForString(String message, boolean required) {
+ while (true) {
+ System.out.print(message + ": ");
+
+ try {
+ String input = scanner.nextLine().trim();
+
+ if (input.equalsIgnoreCase("стоп")) {
+ throw new StopInputException();
+ }
+
+ if (required && input.isEmpty()) {
+ System.out.println("⚠️ Это поле обязательно для заполнения!");
+ continue;
+ }
+
+ return input;
+ } catch (Exception e) {
+ if (e instanceof StopInputException) {
+ throw e; // Пробрасываем дальше
+ }
+ System.out.println("Ошибка ввода: " + e.getMessage());
+ scanner.nextLine(); // Очистка буфера
+ }
+ }
+ }
+
+ static class StopInputException extends RuntimeException {
+ public StopInputException() {
+ super("Ввод прерван пользователем");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/input/strategy/RandomDataGeneratorStrategy.java b/src/main/java/input/strategy/RandomDataGeneratorStrategy.java
new file mode 100644
index 0000000..c4b82bf
--- /dev/null
+++ b/src/main/java/input/strategy/RandomDataGeneratorStrategy.java
@@ -0,0 +1,130 @@
+package input.strategy;
+
+import com.github.javafaker.Faker;
+import dto.Client;
+import input.CustomCollection;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Стратегия для генерации случайных данных о клиентах.
+ * Реализует интерфейс {@link ClientInputStrategy} для создания
+ * коллекции клиентов со случайными данными.
+ *
+ * Использует библиотеку JavaFaker для генерации реалистичных данных:
+ *
+ * - Имена клиентов на русском языке
+ * - Телефонные номера в российском формате
+ * - Уникальные идентификаторы в заданном диапазоне
+ *
+ *
+ * Обеспечивает уникальность ID клиентов и соблюдение ограничений
+ * на диапазон значений (0-1000).
+ *
+ * @see ClientInputStrategy
+ * @see Client
+ * @see CustomCollection
+ * @see Faker
+ */
+public class RandomDataGeneratorStrategy implements ClientInputStrategy {
+ private int count;
+ private Faker faker;
+ private Set userIds;
+
+ public RandomDataGeneratorStrategy(int count) {
+ if (count <= 0) {
+ throw new IllegalArgumentException("Количество случайных записей должно быть больше нуля");
+ }
+ this.count = count;
+ this.faker = new Faker(new Locale("ru", "Ru"));
+ this.userIds = new HashSet<>();
+ }
+
+ /**
+ * Генерирует коллекцию клиентов со случайными данными.
+ * Создает указанное количество клиентов, используя случайные имена,
+ * телефонные номера и уникальные идентификаторы.
+ *
+ * Метод использует Stream API для эффективной генерации данных:
+ *
+ * - Генерирует поток объектов-строителей клиентов
+ * - Ограничивает поток заданным количеством элементов
+ * - Преобразует строителей в готовые объекты Client
+ * - Добавляет клиентов в результирующую коллекцию
+ *
+ *
+ * @return коллекция клиентов со случайными данными
+ * @throws IOException если возникает ошибка ввода-вывода (в данной реализации
+ * маловероятно, но требуется по контракту интерфейса)
+ * @throws IllegalStateException если невозможно сгенерировать уникальный ID
+ * (все возможные ID в диапазоне 0-1000 уже использованы)
+ */
+ @Override
+ public CustomCollection getData() throws IOException {
+ CustomCollection clients = new CustomCollection<>();
+
+ Stream.generate(this::randomClientBuilder)
+ .limit(count)
+ .map(Client.ClientBuilder::build)
+ .forEach(clients::add);
+
+ return clients;
+ }
+
+ /**
+ * Создает строитель клиента со случайными данными.
+ * Генерирует:
+ *
+ * - Случайное полное имя на русском языке
+ * - Случайный телефонный номер в формате +79XXXXXXXXX
+ * - Уникальный идентификатор в диапазоне 0-1000
+ *
+ *
+ * @return {@link Client.ClientBuilder} с заполненными случайными данными
+ */
+ private Client.ClientBuilder randomClientBuilder() {
+ return new Client.ClientBuilder()
+ .name(faker.name().fullName())
+ .phoneNumber("+7" + faker.numerify("9#########"))
+ .idNumber(generateUniqueId());
+ }
+
+ /**
+ * Генерирует уникальный идентификатор в диапазоне от 0 до 1000.
+ * Гарантирует, что каждый сгенерированный ID будет уникальным
+ * в рамках данного экземпляра стратегии.
+ *
+ * Алгоритм генерации:
+ *
+ * - Проверяет, не достигнут ли лимит уникальных ID
+ * - Генерирует случайное число в диапазоне 0-1000
+ * - Проверяет уникальность сгенерированного числа
+ * - Повторяет генерацию, если число уже использовано
+ *
+ *
+ * @return уникальный идентификатор клиента
+ * @throws IllegalStateException если все возможные ID в диапазоне 0-1000
+ * уже были сгенерированы
+ */
+ private int generateUniqueId() {
+ if (userIds.size() >= 1000) {
+ throw new IllegalStateException("Не осталось свободных ID");
+ }
+
+ int id;
+ do {
+ id = faker.random().nextInt(0, 1000);
+ } while (userIds.contains(id));
+
+ userIds.add(id);
+ return id;
+ }
+
+ public int getCount() {
+ return count;
+ }
+}
diff --git a/src/main/java/output/FileDataWriter.java b/src/main/java/output/FileDataWriter.java
new file mode 100644
index 0000000..c9a3e9c
--- /dev/null
+++ b/src/main/java/output/FileDataWriter.java
@@ -0,0 +1,30 @@
+package output;
+
+import java.io.*;
+
+public class FileDataWriter {
+
+ private final File file = new File("savedData.txt");
+
+ public void writeDataToFile(String data){
+ if(file.exists() && file.isDirectory()){
+ throw new RuntimeException("По пути сохранения находится директория");
+ }
+
+ if (!file.exists()) {
+ try {
+ if(!file.createNewFile()){
+ throw new RuntimeException("Не удалось создать файл");
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Ошибка создания файла", e);
+ }
+ }
+
+ try(Writer writer = new BufferedWriter(new FileWriter(file, true))){
+ writer.append(data);
+ } catch (IOException e){
+ throw new RuntimeException("Ошибка записи в файл", e);
+ }
+ }
+}
diff --git a/src/main/java/sorting/AbstractMergeSortStrategy.java b/src/main/java/sorting/AbstractMergeSortStrategy.java
new file mode 100644
index 0000000..d6df0d4
--- /dev/null
+++ b/src/main/java/sorting/AbstractMergeSortStrategy.java
@@ -0,0 +1,120 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+
+import java.util.Comparator;
+
+public abstract class AbstractMergeSortStrategy implements SortingStrategy {
+
+ public void sortWithComparator(CustomCollection clients, Comparator comparator) {
+ if (clients == null || clients.size() <= 1) {
+ return;
+ }
+
+ mergeSort(clients, 0, clients.size() - 1, comparator);
+ }
+
+ protected abstract Comparator getComparator();
+
+ @Override
+ public String getStrategyName() {
+ return "Abstract Merge Sort (сортировка слиянием)";
+ }
+
+ private void mergeSort(CustomCollection clients, int left, int right,
+ Comparator comparator) {
+
+ if (left < right) {
+ int mid = left + (right - left) / 2;
+
+ mergeSort(clients, left, mid, comparator);
+
+ mergeSort(clients, mid + 1, right, comparator);
+
+ merge(clients, left, mid, right, comparator);
+ }
+ }
+
+
+ private void merge(CustomCollection clients, int left, int mid, int right,
+ Comparator comparator) {
+
+ CustomCollection temp = new CustomCollection<>();
+
+ int i = left;
+ int j = mid + 1;
+
+
+ while (i <= mid && j <= right) {
+ Client leftClient = clients.get(i);
+ Client rightClient = clients.get(j);
+
+
+ int comparison = comparator.compare(leftClient, rightClient);
+
+
+ if (comparison <= 0) {
+ temp.add(leftClient);
+ i++;
+ } else {
+ temp.add(rightClient);
+ j++;
+ }
+ }
+
+ while (i <= mid) {
+ temp.add(clients.get(i));
+ i++;
+ }
+
+ while (j <= right) {
+ temp.add(clients.get(j));
+ j++;
+ }
+
+ for (int k = 0; k < temp.size(); k++) {
+ clients.set(left + k, temp.get(k));
+ }
+ }
+
+ @Override
+ public void sortEvenValuesOnly(CustomCollection clients) {
+ // Компаратор для сортировки по idNumber в натуральном порядке (по возрастанию)
+ Comparator idComparator = Comparator.comparing(Client::getIdNumber);
+ sortEvenValuesOnly(clients, idComparator);
+ }
+
+ public void sortEvenValuesOnly(CustomCollection clients, Comparator comparator) {
+ if (clients == null || clients.isEmpty()) {
+ return;
+ }
+
+ // Создаем список для хранения элементов с четными idNumber и их индексов
+ CustomCollection evenClients = new CustomCollection<>();
+ CustomCollection evenIndices = new CustomCollection<>();
+
+ // Собираем все элементы с четными idNumber и запоминаем их индексы
+ for (int i = 0; i < clients.size(); i++) {
+ Client client = clients.get(i);
+ if (client.getIdNumber() % 2 == 0) {
+ evenClients.add(client);
+ evenIndices.add(i);
+ }
+ }
+
+ // Если нет элементов с четными значениями, ничего не делаем
+ if (evenClients.isEmpty()) {
+ return;
+ }
+
+ // Сортируем только элементы с четными значениями по idNumber в натуральном порядке
+ sortWithComparator(evenClients, comparator);
+
+ // Возвращаем отсортированные элементы на исходные позиции
+ for (int i = 0; i < evenClients.size(); i++) {
+ int originalIndex = evenIndices.get(i);
+ clients.set(originalIndex, evenClients.get(i));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/sorting/BubbleSortingStrategy.java b/src/main/java/sorting/BubbleSortingStrategy.java
deleted file mode 100644
index 3b50a81..0000000
--- a/src/main/java/sorting/BubbleSortingStrategy.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package sorting;
-
-import dto.Client;
-
-import java.util.Comparator;
-import java.util.List;
-
-public class BubbleSortingStrategy implements Sortable {
-
- @Override
- public void sort(List clients, Comparator comparator) {
- }
-
-
-
- // @Override
-// public int[] bubbleIntegerSorting(int[] numbers) {
-// boolean isSwaped = true;
-// int lastIndex = numbers.length - 1;
-//
-// while (isSwaped) {
-// isSwaped = false;
-// int range = lastIndex;
-//
-// for (int i = 0; i < range; i++) {
-// if (numbers[i] > numbers[i + 1]) {
-// int number1 = numbers[i];
-// numbers[i] = numbers[i + 1];
-// numbers[i + 1] = number1;
-// isSwaped = true;
-// lastIndex = i + 1;
-// }
-// }
-// }
-// return numbers;
-// }
-}
diff --git a/src/main/java/sorting/MergeSortDefaultStrategy.java b/src/main/java/sorting/MergeSortDefaultStrategy.java
new file mode 100644
index 0000000..a1cde05
--- /dev/null
+++ b/src/main/java/sorting/MergeSortDefaultStrategy.java
@@ -0,0 +1,32 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+
+import java.util.Comparator;
+
+public class MergeSortDefaultStrategy extends AbstractMergeSortStrategy {
+
+ @Override
+ public void sort(CustomCollection clients) {
+ sortWithComparator(clients, getComparator());
+ }
+
+ protected Comparator getComparator() {
+ return (c1, c2) -> {
+ int nameComparison = c1.getName().compareTo(c2.getName());
+ if (nameComparison != 0) return nameComparison;
+
+ int idComparison = Integer.compare(c1.getIdNumber(), c2.getIdNumber());
+ if (idComparison != 0) return idComparison;
+
+ return c1.getPhoneNumber().compareTo(c2.getPhoneNumber());
+ };
+
+ }
+
+ @Override
+ public String getStrategyName() {
+ return "Merge Sort Default Strategy (сортировка по имени -> ID -> телефону)";
+ }
+}
diff --git a/src/main/java/sorting/MergeSortDynamicStrategy.java b/src/main/java/sorting/MergeSortDynamicStrategy.java
new file mode 100644
index 0000000..44eecd9
--- /dev/null
+++ b/src/main/java/sorting/MergeSortDynamicStrategy.java
@@ -0,0 +1,42 @@
+package sorting;
+
+import dto.Client;
+import enums.Field;
+import input.CustomCollection;
+
+import java.util.Comparator;
+
+public class MergeSortDynamicStrategy extends AbstractMergeSortStrategy{
+ private final Field field;
+ private final boolean ascending;
+
+ public MergeSortDynamicStrategy(Field field, boolean ascending) {
+ this.field = field;
+ this.ascending = ascending;
+ }
+
+ @Override
+ public void sort(CustomCollection clients) {
+ sortWithComparator(clients, getComparator());
+ }
+
+ public Comparator getComparator(){
+ Comparator comparator = switch (field){
+ case NAME -> Comparator.comparing(Client :: getName);
+ case ID_NUMBER -> Comparator.comparing(Client :: getIdNumber);
+ case PHONE_NUMBER -> Comparator.comparing(Client :: getPhoneNumber);
+ default -> throw new IllegalArgumentException("Неизвестное поле: " + field);
+ };
+
+ if (ascending) {
+ return comparator;
+ } else {
+ return comparator.reversed();
+ }
+ }
+
+ @Override
+ public String getStrategyName() {
+ return "Dynamic Merge Sort (Динамическая сортировка по: " + field + ")";
+ }
+}
diff --git a/src/main/java/sorting/Sortable.java b/src/main/java/sorting/Sortable.java
deleted file mode 100644
index 9009742..0000000
--- a/src/main/java/sorting/Sortable.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package sorting;
-
-import dto.Client;
-
-import java.util.Comparator;
-import java.util.List;
-
-public interface Sortable {
-
- void sort(List clients, Comparator comparator);
-
-}
diff --git a/src/main/java/sorting/SortingManager.java b/src/main/java/sorting/SortingManager.java
new file mode 100644
index 0000000..7b3b080
--- /dev/null
+++ b/src/main/java/sorting/SortingManager.java
@@ -0,0 +1,25 @@
+package sorting;
+
+public class SortingManager {
+ private SortingStrategy currentStrategy;
+
+ public SortingManager(){}
+
+ public SortingManager(SortingStrategy initialStrategy) {
+ if (initialStrategy == null) {
+ throw new IllegalArgumentException("Стратегия не может быть null");
+ }
+ currentStrategy = initialStrategy;
+ }
+
+ public void setStrategy(SortingStrategy strategy) {
+ if (strategy == null) {
+ throw new IllegalArgumentException("Стратегия не может быть null");
+ }
+ this.currentStrategy = strategy;
+ }
+
+ public SortingStrategy getCurrentStrategy() {
+ return currentStrategy;
+ }
+}
diff --git a/src/main/java/sorting/SortingStrategy.java b/src/main/java/sorting/SortingStrategy.java
new file mode 100644
index 0000000..f163805
--- /dev/null
+++ b/src/main/java/sorting/SortingStrategy.java
@@ -0,0 +1,11 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+
+public interface SortingStrategy {
+
+ void sort(CustomCollection clients);
+ String getStrategyName();
+ void sortEvenValuesOnly(CustomCollection clients);
+}
\ No newline at end of file
diff --git a/src/main/java/userInterface/AppController.java b/src/main/java/userInterface/AppController.java
new file mode 100644
index 0000000..b16cdd0
--- /dev/null
+++ b/src/main/java/userInterface/AppController.java
@@ -0,0 +1,120 @@
+package userInterface;
+
+import dto.Client;
+import enums.Field;
+import input.CustomCollection;
+import input.InputManager;
+import input.strategy.ManualInputReaderStrategy;
+import output.FileDataWriter;
+import sorting.MergeSortDefaultStrategy;
+import sorting.MergeSortDynamicStrategy;
+import sorting.SortingManager;
+
+import java.io.File;
+import java.io.IOException;
+
+public class AppController {
+
+ private final InputManager inputManager = new InputManager();
+ private final CustomCollection fullList = new CustomCollection<>();
+ private final SortingManager sortingManager = new SortingManager();
+ private final FileDataWriter fileDataWriter = new FileDataWriter();
+ ConcurrentCounter concurrentCounter = new ConcurrentCounter();
+
+ public void startDefaultSorting(){
+ sortingManager.setStrategy(new MergeSortDefaultStrategy());
+ sortingManager.getCurrentStrategy().sort(fullList);
+ showAndWriteAllClients();
+ }
+
+ public void startEvenIdsSorting(){
+ sortingManager.setStrategy(new MergeSortDefaultStrategy());
+ sortingManager.getCurrentStrategy().sortEvenValuesOnly(fullList);
+ showAndWriteAllClients();
+ }
+
+ public void startDynamicSorting(Field field){
+ sortingManager.setStrategy(new MergeSortDynamicStrategy(field, true));
+ sortingManager.getCurrentStrategy().sort(fullList);
+ showAndWriteAllClients();
+ }
+
+ public CustomCollection getFullList() {
+ return fullList;
+ }
+
+ public void showAndWriteAllClients(){
+ for(Client client : fullList){
+ System.out.println(client);
+ fileDataWriter.writeDataToFile(client + "\n");
+ }
+ fileDataWriter.writeDataToFile("\n");
+ }
+
+ public void startFileReaderStrategy(String path){
+ inputManager.setStrategy(inputManager.createFileStrategy(path));
+ try {
+ CustomCollection fromFileList = inputManager.loadData();
+ int countOfAlexes = concurrentCounter.countAlexes(fromFileList);
+ fullList.addAll(fromFileList);
+ for(Client client : fromFileList){
+ System.out.println(client);
+ }
+ System.out.println("Добавлено Алексеев: " + countOfAlexes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println("File path: " + path);
+ System.out.println("File exists: " + new File(path).exists());
+ }
+
+ public void startManualInputStrategy(){
+ inputManager.setStrategy(inputManager.createManualStrategy());
+ try {
+ CustomCollection manualList = inputManager.loadData();
+ int countOfAlexes = concurrentCounter.countAlexes(manualList);
+ fullList.addAll(manualList);
+ for(Client client : manualList){
+ System.out.println(client);
+ }
+ System.out.println("Добавлено Алексеев: " + countOfAlexes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void startRandomDataStrategy(int count){
+ inputManager.setStrategy(inputManager.createRandomStrategy(count));
+ try {
+ CustomCollection randomList = inputManager.loadData();
+ int countOfAlexes = concurrentCounter.countAlexes(randomList);
+ fullList.addAll(randomList);
+ for(Client client : randomList){
+ System.out.println(client);
+ }
+ System.out.println("Добавлено Алексеев: " + countOfAlexes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void startManualInputStrategy(ManualInputReaderStrategy strategy){
+ inputManager.setStrategy(strategy);
+ try {
+ CustomCollection manualList = inputManager.loadData();
+ int countOfAlexes = concurrentCounter.countAlexes(manualList);
+ fullList.addAll(manualList);
+ for(Client client : manualList){
+ System.out.println(client);
+ }
+ System.out.println("Добавлено Алексеев: " + countOfAlexes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public InputManager getInputManager() {
+ return inputManager;
+ }
+}
diff --git a/src/main/java/userInterface/ConcurrentCounter.java b/src/main/java/userInterface/ConcurrentCounter.java
new file mode 100644
index 0000000..d34ee09
--- /dev/null
+++ b/src/main/java/userInterface/ConcurrentCounter.java
@@ -0,0 +1,50 @@
+package userInterface;
+
+import dto.Client;
+import input.CustomCollection;
+
+import java.util.concurrent.*;
+
+public class ConcurrentCounter {
+
+ public int countAlexes(CustomCollection clients){
+ CustomCollection clients1 = new CustomCollection<>();
+ CustomCollection clients2 = new CustomCollection<>();
+
+ for (int i = 0; i < clients.size()/2 ; i++){
+ clients1.add(clients.get(i));
+ }
+
+ for (int i = clients.size()/2; i < clients.size(); i++){
+ clients2.add(clients.get(i));
+ }
+
+ ExecutorService executorService = Executors.newFixedThreadPool(2);
+
+ Future count1 = executorService.submit(new Callable() {
+ @Override
+ public Long call() throws Exception {
+ return clients1.stream().filter(c-> c.getName().contains("Алексей")).count();
+ }
+ });
+
+ Future count2 = executorService.submit(new Callable() {
+ @Override
+ public Long call() throws Exception {
+ return clients2.stream().filter(c-> c.getName().contains("Алексей")).count();
+ }
+ });
+
+ executorService.shutdown();
+
+ try {
+ long result = count1.get() + count2.get();
+ return Math.toIntExact(result);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
diff --git a/src/main/java/userInterface/MenuManager.java b/src/main/java/userInterface/MenuManager.java
index 1f36c4b..2e3a59d 100644
--- a/src/main/java/userInterface/MenuManager.java
+++ b/src/main/java/userInterface/MenuManager.java
@@ -1,30 +1,226 @@
package userInterface;
+import enums.Field;
+
import java.util.Scanner;
public class MenuManager implements Printable {
-
+ AppController appController = new AppController();
private final Scanner scanner = new Scanner(System.in);
+ private boolean running = true;
+
+ public void run() {
+ while (running) {
+ try {
+ printMainMenu();
+ String input = scanner.nextLine();
+ if (input.isEmpty()) {
+ System.out.println("Пожалуйста, выберите номер опции");
+ continue;
+ }
- public void run(){
+ int mainOption = Integer.parseInt(input);
+
+ switch (mainOption) {
+ case 1 -> printFillingDatabaseMenu();
+ case 2 -> printSortingMenu();
+ case 0 -> exitByChoice();
+ default -> System.out.println("Неверный выбор. Пожалуйста, выберите 1, 2 или 0");
+ }
+ }
+ catch (Exception e) {
+ System.out.println("Ошибка: " + e.getMessage());
+ System.out.println("Пожалуйста, введите корректный номер опции.");
+ }
+ }
+ }
+
+ @Override
+ public void printFillingDatabaseMenu() {
+ boolean backToMain = false;
+ while (!backToMain && running) {
+ Printable.super.printFillingDatabaseMenu();
+ String input = scanner.nextLine().trim();
+ if (input.isEmpty()) {
+ System.out.println("Пожалуйста, введите номер опции.");
+ continue;
+ }
+ try {
+ int fillOption = Integer.parseInt(input);
+
+ switch (fillOption) {
+ case 1 -> fillManually();
+ case 2 -> fillFromFile();
+ case 3 -> fillRandom();
+ case 0 -> {
+ System.out.println("Возврат в главное меню...");
+ backToMain = true;
+ }
+ default -> System.out.println("Неверный выбор. Пожалуйста, выберите от 0 до 3");
+ }
+ }
+ catch (NumberFormatException e) {
+ System.out.println("Пожалуйста, введите цифру для выбора действия");
+ }
+ }
+
+ }
- printMainMenu();
- String input = scanner.nextLine();
- int mainOption = Integer.parseInt(input);
+ @Override
+ public void printSortingMenu() {
+ boolean backToMain = false;
+ while (!backToMain && running) {
+ Printable.super.printSortingMenu();
+ String input = scanner.nextLine().trim();
+
+ if (input.isEmpty()) {
+ System.out.println("Пожалуйста, введите номер опции.");
+ continue;
+ }
+ try {
+ int sortOption = Integer.parseInt(input);
+
+ switch (sortOption) {
+ case 1 -> printDefaultOrder();
+ case 2 -> printNameSortingOptions();
+ case 3 -> printIdSortingOptions();
+ case 4 -> printPhoneSortingOptions();
+ case 0 -> {
+ System.out.println("Возврат в главное меню...");
+ backToMain = true;
+ }
+ default -> System.out.println("Неверный выбор. Пожалуйста, выберите от 0 до 4");
+ }
+ }
+ catch (NumberFormatException e) {
+ System.out.println("Пожалуйста, введите цифру для выбора действия.");
+ }
+ }
+ }
+
+ @Override
+ public void printIdSortingOptions() {
+ boolean backToMain = false;
+ while (!backToMain && running) {
+ Printable.super.printIdSortingOptions();
+ String input = scanner.nextLine().trim();
+
+ if (input.isEmpty()) {
+ System.out.println("Пожалуйста, введите номер опции.");
+ continue;
+ }
+
+ try {
+ int idSortOption = Integer.parseInt(input);
+
+ switch (idSortOption) {
+ case 1 -> sortIDByAscending();
+ case 2 -> sortIDbyEven();
+ case 0 -> {
+ System.out.println("Назад...");
+ backToMain = true;
+ }
+ default -> System.out.println("Неверный выбор. Пожалуйста, выберите от 0 до 3");
+ }
+ }
+ catch (NumberFormatException e) {
+ System.out.println("Пожалуйста, введите цифру для выбора действия.");
+ }
+ }
+ }
- switch (mainOption){
- case 1 -> printFillingDatabaseMenu();
- case 2 -> printSortingMenu();
+ @Override
+ public void printNameSortingOptions() {
+ appController.startDynamicSorting(Field.NAME);
+ boolean backToMain = false;
+ while (!backToMain && running) {
+ Printable.super.printNameSortingOptions();
+ backToMain = true;
}
+ }
- switch (mainOption){
- case 1 -> System.out.println("Future custom collection");
- case 2 -> printNameSortingOptions();
- case 3 -> printIdSortingOptions();
- case 4 -> printPhoneSortingOptions();
+ @Override
+ public void printPhoneSortingOptions() {
+ appController.startDynamicSorting(Field.PHONE_NUMBER);
+ boolean backToMain = false;
+ while (!backToMain && running) {
+ Printable.super.printPhoneSortingOptions();
+ backToMain = true;
}
+ }
+
+ private void sortIDByAscending() {
+ appController.startDynamicSorting(Field.ID_NUMBER);
+ System.out.println("Клиенты отсортированы по возрастанию ID");
+ }
+
+ private void sortIDbyEven() {
+ appController.startEvenIdsSorting();
+ System.out.println("Клиенты отсортированы по четным ID");
+ }
- // ПРОДОЛЖИТЬ
+ private void fillManually() {
+ System.out.println("Выбран способ ввода данных вручную");
+ System.out.println("Введите данные...");
+ appController.startManualInputStrategy();
+ System.out.println("Данные успешно сохранены");
+
+ }
+
+ private void fillFromFile() {
+ System.out.println("Выбран способ ввода данных из файла\n");
+ System.out.println("Введите путь к файлу: ");
+ String filePath = scanner.nextLine().trim();
+ appController.startFileReaderStrategy(filePath);
+ System.out.println("Загрузка из файла: " + filePath);
+
+ }
+
+ private void fillRandom() {
+ System.out.println("Выбран способ ввода случайных данных");
+ int count = inputLengthOfValue("Сколько записей создать? ", 10);
+ appController.startRandomDataStrategy(count);
+ System.out.println("Создано: " + count + " записей");
+ }
+
+ private void printDefaultOrder() {
+ System.out.println("Список клиентов");
+ appController.startDefaultSorting();
+ }
+
+ private void exitByChoice() {
+ System.out.println("Выход из программы...");
+ running = false;
+ scanner.close();
+ }
+
+ private int inputLengthOfValue(String prompt, int defaultValue) {
+ while (true) {
+ System.out.println(prompt + "(по умолчанию: " + defaultValue + "): ");
+ String input = scanner.nextLine().trim();
+
+ if (input.isEmpty()) {
+ System.out.println("Значение по умолчанию: " + defaultValue);
+ return defaultValue;
+ }
+
+ try {
+ int value = Integer.parseInt(input);
+ if (value > 0) {
+ return value;
+ }
+ else if (value == 0) {
+ System.out.println("Используется значение по умолчанию: " + defaultValue);
+ return defaultValue;
+ }
+ else {
+ System.out.println("Число должно быть положительным! Попробуйте еще раз.");
+ }
+ }
+ catch (NumberFormatException e) {
+ System.out.println("Пожалуйста, введите целое число! Попробуйте еще раз");
+ }
+ }
}
}
diff --git a/src/main/java/userInterface/Printable.java b/src/main/java/userInterface/Printable.java
index 57ebd24..a2f6554 100644
--- a/src/main/java/userInterface/Printable.java
+++ b/src/main/java/userInterface/Printable.java
@@ -4,69 +4,53 @@ public interface Printable {
default void printMainMenu(){
System.out.println(
- "1) Fill the client database\n" +
- "2) Show sorting options"
+ "1) Наполнить клиентскую базу\n" +
+ "2) Показать параметры сортировки\n" +
+ "0) Выход"
);
}
default void printFillingDatabaseMenu(){
- System.out.println(
- "Fill database: \n" +
- "1) Manual input\n" +
- "2) File\n" +
- "3) Random"
+
+ System.out.println("""
+ Заполнить базу данных:
+ 1) Ручной ввод
+ 2) Файлом
+ 3) Случайный набор
+ 0) Возврат в главное меню"""
);
}
default void printSortingMenu(){
- System.out.println(
- "Chose an option:\n" +
- "1) Show clients in default order\n" +
- "2) Sort by name\n" +
- "3) Sort by ID\n" +
- "4) Sort by phone number"
+ System.out.println("""
+ Выберите вариант:
+ 1) Базовая сортировка клиентов
+ 2) Сортировать клиентов по имени
+ 3) Сортировать клиентов по ID
+ 4) Сортировать клиентов по номеру телефона
+ 0) Возврат в главное меню"""
);
}
default void printNameSortingOptions(){
System.out.println(
- "Sort clients by:\n" +
- "1) Names alphabet order\n" +
- "2) Name length\n" +
- "3) Amount of vowels in name\n" +
- "4) Amount of consonants in name\n" +
- "5) Name unicode\n" +
- "6) Combine name criteria\n" + // Придумать что-то комбинированное
- "0) Go back"
+ "Клиенты отсортированы по умолчанию\n"
);
}
default void printIdSortingOptions(){
- System.out.println(
- "Sort clients by:\n" +
- "1) ID Ascending \n" +
- "2) ID Descending \n" +
- "3) Amount of numbers in ID\n" +
- "4) ID even number\n" + // ЭТО ДОП ЗАДАНИЕ
- "5) ID odd number\n" + // ЭТО ДОП ЗАДАНИЕ
- "6) Unicode\n" +
- "7) Combine criteria\n" +
- "9) ID creation date" // если сделать ID включающим дату
+ System.out.println("""
+ Сортировать клиентов:
+ 1) По возрастанию ID
+ 2) По четным ID
+ 0) Назад"""
);
}
default void printPhoneSortingOptions(){
System.out.println(
- "Sort clients by phone:\n" +
- "1) Normalized number\n" + // приведенный к международному формату
- "2) Country code\n" +
- "3) Operator code\n" +
- "4) Last numbers\n" +
- "5) Number length\n" +
- "6) Clean number\n" + // без знаков
- "7) Combine criteria\n"
-
+ "Клиенты отсортированы по умолчанию\n"
);
}
}
diff --git a/src/test/java/dto/ClientTest.java b/src/test/java/dto/ClientTest.java
new file mode 100644
index 0000000..f09dc17
--- /dev/null
+++ b/src/test/java/dto/ClientTest.java
@@ -0,0 +1,149 @@
+package dto;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ClientTest {
+
+ @Test
+ @DisplayName("Конструктор Client должен корректно инициализировать поля из ClientBuilder")
+ void testClientConstructorInitializesFieldsCorrectly() {
+ // Arrange
+ Client.ClientBuilder builder = new Client.ClientBuilder()
+ .name("Иван Иванов")
+ .phoneNumber("+79991234567")
+ .idNumber(1);
+
+ // Act
+ Client client = new Client(builder);
+
+ // Assert
+ assertEquals("Иван Иванов", client.getName());
+ assertEquals("+79991234567", client.getPhoneNumber());
+ assertEquals(1, client.getIdNumber());
+ }
+
+ @Test
+ @DisplayName("Геттеры должны возвращать корректные значения полей")
+ void testGettersReturnCorrectValues() {
+ // Arrange
+ Client.ClientBuilder builder = new Client.ClientBuilder()
+ .name("Петр Петров")
+ .phoneNumber("+78887654321")
+ .idNumber(2);
+ Client client = new Client(builder);
+
+ // Act & Assert
+ assertEquals("Петр Петров", client.getName());
+ assertEquals("+78887654321", client.getPhoneNumber());
+ assertEquals(2, client.getIdNumber());
+ }
+
+ @Test
+ @DisplayName("Метод build() ClientBuilder должен создавать корректный объект Client")
+ void testClientBuilderBuildCreatesCorrectClient() {
+ // Arrange
+ Client.ClientBuilder builder = new Client.ClientBuilder()
+ .name("Мария Сидорова")
+ .phoneNumber("+77776543210")
+ .idNumber(3);
+
+ // Act
+ Client client = builder.build();
+
+ // Assert
+ assertNotNull(client);
+ assertEquals("Мария Сидорова", client.getName());
+ assertEquals("+77776543210", client.getPhoneNumber());
+ assertEquals(3, client.getIdNumber());
+ }
+
+ @Test
+ @DisplayName("Цепочка методов в ClientBuilder должна работать корректно")
+ void testClientBuilderMethodChaining() {
+ // Act
+ Client client = new Client.ClientBuilder()
+ .name("Алексей Козлов")
+ .phoneNumber("+75554443322")
+ .idNumber(4)
+ .build();
+
+ // Assert
+ assertNotNull(client);
+ assertEquals("Алексей Козлов", client.getName());
+ assertEquals("+75554443322", client.getPhoneNumber());
+ assertEquals(4, client.getIdNumber());
+ }
+
+ @Test
+ @DisplayName("toString() должен возвращать строку в ожидаемом формате")
+ void testToStringReturnsExpectedFormat() {
+ // Arrange
+ Client.ClientBuilder builder = new Client.ClientBuilder()
+ .name("Анна Волкова")
+ .phoneNumber("+73332221100")
+ .idNumber(5);
+ Client client = builder.build();
+ String expectedString = "Client{name='Анна Волкова', phoneNumber='+73332221100', idNumber=5}";
+
+ // Act
+ String actualString = client.toString();
+
+ // Assert
+ assertEquals(expectedString, actualString);
+ }
+
+ @Test
+ @DisplayName("Поля Client должны быть final и не изменяться после создания")
+ void testClientFieldsAreImmutable() {
+ // Arrange
+ Client.ClientBuilder builder = new Client.ClientBuilder()
+ .name("Сергей Новиков")
+ .phoneNumber("+71112223344")
+ .idNumber(6);
+ Client client = builder.build();
+
+ // Act & Assert (проверяем, что геттеры возвращают те же значения)
+ assertEquals("Сергей Новиков", client.getName());
+ assertEquals("+71112223344", client.getPhoneNumber());
+ assertEquals(6, client.getIdNumber());
+
+ // Попытка изменить состояние через builder не должна повлиять на существующий объект
+ builder.name("Другое имя").idNumber(999);
+ Client anotherClient = builder.build();
+
+ // Проверяем, что первый клиент остался неизменным
+ assertEquals("Сергей Новиков", client.getName());
+ assertEquals(6, client.getIdNumber());
+
+ // Проверяем, что новый клиент имеет новые значения
+ assertEquals("Другое имя", anotherClient.getName());
+ assertEquals(999, anotherClient.getIdNumber());
+ }
+
+ @Test
+ @DisplayName("Два клиента с одинаковыми данными должны быть разными объектами")
+ void testClientsWithSameDataAreDifferentObjects() {
+ // Arrange
+ Client.ClientBuilder builder1 = new Client.ClientBuilder()
+ .name("Ольга Смирнова")
+ .phoneNumber("+79998887766")
+ .idNumber(7);
+ Client.ClientBuilder builder2 = new Client.ClientBuilder()
+ .name("Ольга Смирнова")
+ .phoneNumber("+79998887766")
+ .idNumber(7);
+
+ // Act
+ Client client1 = builder1.build();
+ Client client2 = builder2.build();
+
+ // Assert
+ assertNotSame(client1, client2); // Разные объекты
+ assertEquals(client1.getName(), client2.getName());
+ assertEquals(client1.getPhoneNumber(), client2.getPhoneNumber());
+ assertEquals(client1.getIdNumber(), client2.getIdNumber());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/input/CustomCollectionTest.java b/src/test/java/input/CustomCollectionTest.java
new file mode 100644
index 0000000..548dbf3
--- /dev/null
+++ b/src/test/java/input/CustomCollectionTest.java
@@ -0,0 +1,300 @@
+package input;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class CustomCollectionTest {
+
+ private CustomCollection collection;
+
+ @BeforeEach
+ void setUp() {
+ collection = new CustomCollection<>();
+ }
+
+ @Test
+ void testDefaultConstructorCapacity() {
+ assertEquals(10, collection.getElements().length);
+ }
+
+ @Test
+ void testInitialCapacityConstructor() {
+ CustomCollection coll = new CustomCollection<>(5);
+ assertEquals(5, coll.getElements().length);
+ }
+
+ @Test
+ void testInitialCapacityConstructorWithZero() {
+ assertThrows(IllegalArgumentException.class, () -> new CustomCollection<>(0));
+ }
+
+ @Test
+ void testInitialCapacityConstructorWithNegative() {
+ assertThrows(IllegalArgumentException.class, () -> new CustomCollection<>(-1));
+ }
+
+ @Test
+ void testAddElement() {
+ collection.add("test");
+ assertEquals(1, collection.size());
+ assertEquals("test", collection.get(0));
+ }
+
+ @Test
+ void testAddMultipleElements() {
+ collection.add("one");
+ collection.add("two");
+ collection.add("three");
+
+ assertEquals(3, collection.size());
+ assertEquals("one", collection.get(0));
+ assertEquals("two", collection.get(1));
+ assertEquals("three", collection.get(2));
+ }
+
+ @Test
+ void testAutoExpansion() {
+ // Заполняем до предела начальной ёмкости (10 элементов)
+ for (int i = 0; i < 10; i++) {
+ collection.add("item" + i);
+ }
+
+ // Добавляем ещё один — должно произойти расширение
+ collection.add("expanded");
+
+ assertEquals(11, collection.size());
+ assertEquals("expanded", collection.get(10));
+ assertTrue(collection.getElements().length > 10);
+ }
+
+ @Test
+ void testGetWithValidIndex() {
+ collection.add("first");
+ collection.add("second");
+
+ assertEquals("first", collection.get(0));
+ assertEquals("second", collection.get(1));
+ }
+
+ @Test
+ void testGetWithNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.get(-1));
+ }
+
+ @Test
+ void testGetWithIndexEqualToSize() {
+ collection.add("item");
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.get(1));
+ }
+
+ @Test
+ void testGetWithIndexGreaterThanSize() {
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.get(5));
+ }
+
+ @Test
+ void testSetWithValidIndex() {
+ collection.add("old");
+ String result = collection.set(0, "new");
+
+ assertEquals("old", result);
+ assertEquals("new", collection.get(0));
+ }
+
+ @Test
+ void testSetWithNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.set(-1, "value"));
+ }
+
+ @Test
+ void testSetWithIndexEqualToSize() {
+ collection.add("item");
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.set(1, "value"));
+ }
+
+ @Test
+ void testRemoveByIndexWithValidIndex() {
+ collection.add("one");
+ collection.add("two");
+ collection.add("three");
+
+ collection.removeByIndex(1);
+
+ assertEquals(2, collection.size());
+ assertEquals("one", collection.get(0));
+ assertEquals("three", collection.get(1));
+ }
+
+ @Test
+ void testRemoveByIndexFirstElement() {
+ collection.add("first");
+ collection.add("second");
+
+ collection.removeByIndex(0);
+
+ assertEquals(1, collection.size());
+ assertEquals("second", collection.get(0));
+ }
+
+ @Test
+ void testRemoveByIndexLastElement() {
+ collection.add("first");
+ collection.add("last");
+
+ collection.removeByIndex(1);
+
+ assertEquals(1, collection.size());
+ assertEquals("first", collection.get(0));
+ }
+
+ @Test
+ void testRemoveByIndexWithNegativeIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.removeByIndex(-1));
+ }
+
+ @Test
+ void testRemoveByIndexWithIndexEqualToSize() {
+ collection.add("item");
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.removeByIndex(1));
+ }
+
+ @Test
+ void testRemoveByIndexWithIndexGreaterThanSize() {
+ assertThrows(IndexOutOfBoundsException.class, () -> collection.removeByIndex(5));
+ }
+
+ @Test
+ void testSize() {
+ assertEquals(0, collection.size());
+
+ collection.add("item");
+ assertEquals(1, collection.size());
+
+ collection.add("another");
+ assertEquals(2, collection.size());
+ }
+
+ @Test
+ void testIsEmpty() {
+ assertTrue(collection.isEmpty());
+
+ collection.add("item");
+ assertFalse(collection.isEmpty());
+
+ collection.removeByIndex(0);
+ assertTrue(collection.isEmpty());
+ }
+
+ @Test
+ void testClear() {
+ collection.add("one");
+ collection.add("two");
+
+ collection.clear();
+
+ assertEquals(0, collection.size());
+ assertTrue(collection.isEmpty());
+ }
+
+ @Test
+ void testIteratorHasNext() {
+ assertFalse(collection.iterator().hasNext());
+
+ collection.add("item");
+ assertTrue(collection.iterator().hasNext());
+ }
+
+ @Test
+ void testIteratorNext() {
+ collection.add("first");
+ collection.add("second");
+
+ Iterator iterator = collection.iterator();
+ assertEquals("first", iterator.next());
+ assertEquals("second", iterator.next());
+ }
+
+ @Test
+ void testIteratorNextNoSuchElement() {
+ Iterator iterator = collection.iterator();
+ assertThrows(NoSuchElementException.class, iterator::next);
+ }
+
+ @Test
+ void testIteratorRemove() {
+ collection.add("one");
+ collection.add("two");
+ collection.add("three");
+
+ Iterator iterator = collection.iterator();
+ iterator.next(); // переходим к первому элементу
+ iterator.remove();
+
+ assertEquals(2, collection.size());
+ assertEquals("two", collection.get(0));
+ assertEquals("three", collection.get(1));
+ }
+
+ @Test
+ void testIteratorRemoveWithoutNext() {
+ Iterator iterator = collection.iterator();
+ assertThrows(IllegalStateException.class, iterator::remove);
+ }
+
+ @Test
+ void testIteratorRemoveTwice() {
+ collection.add("item");
+
+ Iterator iterator = collection.iterator();
+ iterator.next();
+ iterator.remove();
+
+ assertThrows(IllegalStateException.class, iterator::remove);
+ }
+
+ @Test
+ void testStream() {
+ collection.add("one");
+ collection.add("two");
+ collection.add("three");
+
+ long count = collection.stream().count();
+ assertEquals(3, count);
+
+ String joined = collection.stream()
+ .map(String::toUpperCase)
+ .reduce("", String::concat);
+ assertEquals("ONETWOTHREE", joined);
+ }
+
+ @Test
+ void testAddAllWithNullCollection() {
+ collection.addAll(null);
+ assertEquals(0, collection.size());
+ }
+
+ @Test
+ void testAddAllWithEmptyCollection() {
+ CustomCollection empty = new CustomCollection<>();
+ collection.addAll(empty);
+ assertEquals(0, collection.size());
+ }
+
+ @Test
+ void testAddAllWithNonEmptyCollection() {
+ CustomCollection source = new CustomCollection<>();
+ source.add("a");
+ source.add("b");
+
+ collection.addAll(source);
+
+ assertEquals(2, collection.size());
+ assertEquals("a", collection.get(0));
+ assertEquals("b", collection.get(1));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/input/InputManagerTest.java b/src/test/java/input/InputManagerTest.java
new file mode 100644
index 0000000..40666da
--- /dev/null
+++ b/src/test/java/input/InputManagerTest.java
@@ -0,0 +1,162 @@
+package input;
+
+import dto.Client;
+import input.strategy.ClientInputStrategy;
+import input.strategy.FileReaderStrategy;
+import input.strategy.ManualInputReaderStrategy;
+import input.strategy.RandomDataGeneratorStrategy;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class InputManagerTest {
+
+ private InputManager inputManager;
+
+ @BeforeEach
+ void setUp() {
+ inputManager = new InputManager();
+ }
+
+ @Test
+ @DisplayName("Конструктор без стратегии должен создавать InputManager с null-стратегией")
+ void testConstructorWithoutStrategy() {
+ assertNull(inputManager.getCurrentStrategy());
+ }
+
+ @Test
+ @DisplayName("Конструктор с null-стратегией должен выбрасывать IllegalArgumentException")
+ void testConstructorWithNullStrategyThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> new InputManager(null));
+ }
+
+ @Test
+ @DisplayName("Конструктор с валидной стратегией должен устанавливать её как текущую")
+ void testConstructorWithValidStrategy() {
+ ClientInputStrategy strategy = new TestClientInputStrategy();
+ InputManager manager = new InputManager(strategy);
+ assertEquals(strategy, manager.getCurrentStrategy());
+ }
+
+ @Test
+ @DisplayName("setStrategy с null должен выбрасывать IllegalArgumentException")
+ void testSetStrategyWithNullThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> inputManager.setStrategy(null));
+ }
+
+ @Test
+ @DisplayName("setStrategy должен корректно устанавливать новую стратегию")
+ void testSetStrategy() {
+ ClientInputStrategy strategy = new TestClientInputStrategy();
+ inputManager.setStrategy(strategy);
+ assertEquals(strategy, inputManager.getCurrentStrategy());
+ }
+
+ @Test
+ @DisplayName("loadData без установленной стратегии должен выбрасывать IllegalStateException")
+ void testLoadDataWithoutStrategyThrowsException() throws IOException {
+ assertThrows(IllegalStateException.class, inputManager::loadData);
+ }
+
+ @Test
+ @DisplayName("loadData должен возвращать данные от текущей стратегии")
+ void testLoadDataReturnsDataFromStrategy() throws IOException {
+ ClientInputStrategy strategy = new TestClientInputStrategy();
+ inputManager.setStrategy(strategy);
+
+ CustomCollection result = inputManager.loadData();
+
+ assertNotNull(result);
+ assertEquals(1, result.size()); // Ожидаем 1 клиента от тестовой стратегии
+ }
+
+ @Test
+ @DisplayName("loadData должен пробрасывать IOException от стратегии")
+ void testLoadDataPropagatesIOException() throws IOException {
+ ClientInputStrategy strategy = new ClientInputStrategy() {
+ @Override
+ public CustomCollection getData() throws IOException {
+ throw new IOException("Ошибка ввода-вывода");
+ }
+ };
+ inputManager.setStrategy(strategy);
+
+ assertThrows(IOException.class, inputManager::loadData);
+ }
+
+ @Test
+ @DisplayName("createFileStrategy с null-путём должен выбрасывать IllegalArgumentException")
+ void testCreateFileStrategyWithNullPathThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> inputManager.createFileStrategy(null));
+ }
+
+ @Test
+ @DisplayName("createFileStrategy с пустым путём должен выбрасывать IllegalArgumentException")
+ void testCreateFileStrategyWithEmptyPathThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> inputManager.createFileStrategy(""));
+ }
+
+ @Test
+ @DisplayName("createFileStrategy должен создавать FileReaderStrategy с указанным путём")
+ void testCreateFileStrategy() {
+ String filePath = "test.txt";
+ FileReaderStrategy strategy = inputManager.createFileStrategy(filePath);
+
+ assertNotNull(strategy);
+ // Если у FileReaderStrategy есть геттер для пути:
+ // assertEquals(filePath, strategy.getFilePath());
+ }
+
+ @Test
+ @DisplayName("createManualStrategy должен создавать ManualInputReaderStrategy")
+ void testCreateManualStrategy() {
+ ManualInputReaderStrategy strategy = inputManager.createManualStrategy();
+
+ assertNotNull(strategy);
+ }
+
+ @Test
+ @DisplayName("createRandomStrategy с отрицательным count должен выбрасывать IllegalArgumentException")
+ void testCreateRandomStrategyWithNegativeCountThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> inputManager.createRandomStrategy(-1));
+ }
+
+ @Test
+ @DisplayName("createRandomStrategy с нулевым count должен выбрасывать IllegalArgumentException")
+ void testCreateRandomStrategyWithZeroCountThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> inputManager.createRandomStrategy(0));
+ }
+
+ @Test
+ @DisplayName("createRandomStrategy должен создавать RandomDataGeneratorStrategy с указанным количеством")
+ void testCreateRandomStrategy() {
+ int count = 5;
+ RandomDataGeneratorStrategy strategy = inputManager.createRandomStrategy(count);
+
+ assertNotNull(strategy);
+ // Если у RandomDataGeneratorStrategy есть геттер для count:
+ // assertEquals(count, strategy.getCount());
+ }
+}
+
+
+// Тестовая реализация ClientInputStrategy для использования в тестах
+ class TestClientInputStrategy implements ClientInputStrategy {
+ @Override
+ public CustomCollection getData() throws IOException {
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Test Client")
+ .phoneNumber("+79991234567").idNumber(1).build());
+
+ // Предполагаем, что CustomCollection имеет конструктор от Collection
+ return clients;
+ }
+ }
\ No newline at end of file
diff --git a/src/test/java/input/strategy/FileReaderStrategyTest.java b/src/test/java/input/strategy/FileReaderStrategyTest.java
new file mode 100644
index 0000000..f5a57d5
--- /dev/null
+++ b/src/test/java/input/strategy/FileReaderStrategyTest.java
@@ -0,0 +1,271 @@
+package input.strategy;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FileReaderStrategyTest {
+
+ private static final String TEST_FILE_PATH = "test_clients.txt";
+ private Path testFilePath;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ testFilePath = Paths.get(TEST_FILE_PATH);
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Удаляем тестовый файл после каждого теста, если он существует
+ if (Files.exists(testFilePath)) {
+ try {
+ Files.delete(testFilePath);
+ } catch (IOException e) {
+ // Игнорируем ошибки удаления в tearDown
+ }
+ }
+ }
+
+ @Test
+ @DisplayName("getData должен прочитать корректные данные из файла")
+ void testGetDataReadsValidClients() throws IOException {
+ // Arrange: создаём файл с корректными данными
+ String content = "Иван Иванов|+79991234567|1\n" +
+ "Мария Петрова|+79997654321|2";
+ Files.write(testFilePath, content.getBytes());
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert
+ assertEquals(2, clients.size());
+ assertEquals("Иван Иванов", clients.get(0).getName());
+ assertEquals("+79991234567", clients.get(0).getPhoneNumber());
+ assertEquals(1, clients.get(0).getIdNumber());
+
+ assertEquals("Мария Петрова", clients.get(1).getName());
+ assertEquals("+79997654321", clients.get(1).getPhoneNumber());
+ assertEquals(2, clients.get(1).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("getData должен пропускать пустые строки")
+ void testGetDataSkipsEmptyLines() throws IOException {
+ // Arrange: файл с пустыми строками и одним корректным клиентом
+ String content = "\n\n" +
+ "Иван Иванов|+79991234567|1\n" +
+ "\n";
+ Files.write(testFilePath, content.getBytes());
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: должна быть прочитана только одна запись
+ assertEquals(1, clients.size());
+ assertEquals("Иван Иванов", clients.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getData должен пропускать строки с неверным форматом (не 3 поля)")
+ void testGetDataSkipsInvalidFormatLines() throws IOException {
+ // Arrange: строка с двумя полями вместо трёх
+ String content = "Иван Иванов|+79991234567\n" +
+ "Мария Петрова|+79997654321|2";
+ Files.write(testFilePath, content.getBytes());
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: только вторая строка должна быть прочитана
+ assertEquals(1, clients.size());
+ assertEquals("Мария Петрова", clients.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getData должен пропускать строки с невалидным ID (не число)")
+ void testGetDataSkipsInvalidId() throws IOException {
+ // Arrange
+ String content = "Иван Иванов|+79991234567|abc\n" +
+ "Мария Петрова|+79997654321|2";
+ Files.write(testFilePath, content.getBytes());
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: первая строка с невалидным ID пропускается
+ assertEquals(1, clients.size());
+ assertEquals("Мария Петрова", clients.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getData должен пропускать строки с пустым именем")
+ void testGetDataSkipsEmptyName() throws IOException {
+ // Arrange
+ String content = "|+79991234567|1\n" +
+ "Мария Петрова|+79997654321|2";
+ Files.write(testFilePath, content.getBytes());
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: первая строка пропускается изза пустого имени
+ assertEquals(1, clients.size());
+ assertEquals("Мария Петрова", clients.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getData должен пропускать строки с невалидным номером телефона")
+ void testGetDataSkipsInvalidPhone() throws IOException {
+ // Arrange: номер без +7 и неправильный формат
+ String content = "Иван Иванов|79991234567|1\n" + // без +7
+ "Мария Петрова|+7999765432|2\n" + // 9 цифр вместо 10
+ "Анна Сидорова|+79998887766|3"; // корректный
+ Files.write(testFilePath, content.getBytes());
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: прочитан только последний клиент
+ assertEquals(1, clients.size());
+ assertEquals("Анна Сидорова", clients.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getData должен выбрасывать RuntimeException, если файл не найден")
+ void testGetDataThrowsExceptionWhenFileNotFound() {
+ ClientInputStrategy strategy = new FileReaderStrategy("nonexistent.txt");
+
+ assertThrows(RuntimeException.class,
+ strategy::getData,
+ "getData должен выбрасывать RuntimeException для несуществующего файла"
+ );
+ }
+
+ @Test
+ @DisplayName("getData должен выбрасывать RuntimeException при ошибке вводавывода")
+ void testGetDataThrowsExceptionOnIOError() throws IOException {
+ // Arrange: создаём файл и удаляем его перед чтением
+ Files.createFile(testFilePath);
+ Files.delete(testFilePath); // файл удаляется, чтобы вызвать ошибку
+
+ ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ assertThrows(RuntimeException.class,
+ strategy::getData,
+ "getData должен выбрасывать RuntimeException при IO ошибке"
+ );
+ }
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для строки с неверным форматом")
+ void testParseToClientReturnsEmptyForInvalidFormat() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+ Optional result = strategy.parseToClient("Иван Иванов|+79991234567");
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для строки с невалидным ID")
+ void testParseToClientReturnsEmptyForInvalidId() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+ Optional result = strategy.parseToClient("Иван Иванов|+79991234567|abc");
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для строки с пустым именем")
+ void testParseToClientReturnsEmptyForEmptyName() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+ Optional result = strategy.parseToClient("|+79991234567|1");
+ assertFalse(result.isPresent());
+ }
+
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для строки с некорректным форматом телефона")
+ void testParseToClientReturnsEmptyForInvalidPhoneFormat() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Тестируем разные варианты некорректного телефона
+ Optional result1 = strategy.parseToClient("Иван Иванов|79991234567|1"); // нет +7
+ Optional result2 = strategy.parseToClient("Иван Иванов|+7123|1"); // недостаточно цифр
+ Optional result3 = strategy.parseToClient("Иван Иванов|+89991234567|1"); // не +7
+
+ assertFalse(result1.isPresent(), "Должен возвращать Optional.empty, если отсутствует +7 в номере");
+ assertFalse(result2.isPresent(), "Должен возвращать Optional.empty, если недостаточно цифр в номере");
+ assertFalse(result3.isPresent(), "Должен возвращать Optional.empty, если код страны не +7");
+ }
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для строки с неверным количеством полей")
+ void testParseToClientReturnsEmptyForWrongFieldCount() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ // Слишком мало полей
+ Optional result1 = strategy.parseToClient("Иван Иванов|+79991234567");
+ // Слишком много полей
+ Optional result2 = strategy.parseToClient("Иван Иванов|+79991234567|1|extra");
+
+ assertFalse(result1.isPresent(), "Должен возвращать Optional.empty, если полей меньше трёх");
+ assertFalse(result2.isPresent(), "Должен возвращать Optional.empty, если полей больше трёх");
+ }
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для пустой строки")
+ void testParseToClientReturnsEmptyForEmptyString() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ Optional result = strategy.parseToClient("");
+
+ assertFalse(result.isPresent(), "Должен возвращать Optional.empty для пустой строки");
+ }
+
+ @Test
+ @DisplayName("parseToClient должен возвращать Optional.empty для строки, состоящей только из пробелов")
+ void testParseToClientReturnsEmptyForWhitespaceString() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ Optional result = strategy.parseToClient(" ");
+
+ assertFalse(result.isPresent(), "Должен возвращать Optional.empty для строки из пробелов");
+ }
+
+ @Test
+ @DisplayName("parseToClient должен корректно парсить валидную строку")
+ void testParseToClientParsesValidLineCorrectly() {
+ FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH);
+
+ Optional result = strategy.parseToClient("Иван Иванов|+79991234567|1");
+
+ assertTrue(result.isPresent(), "Должен возвращать непустой Optional для валидной строки");
+
+ Client.ClientBuilder builder = result.get();
+ assertEquals("Иван Иванов", builder.getName(), "Имя должно быть корректно извлечено из строки");
+ assertEquals("+79991234567", builder.getPhoneNumber(), "Номер телефона должен быть корректно извлечён из строки");
+ assertEquals(1, builder.getIdNumber(), "ID должен быть корректно преобразован в число");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/input/strategy/ManualInputReaderStrategyTest.java b/src/test/java/input/strategy/ManualInputReaderStrategyTest.java
new file mode 100644
index 0000000..0ebea04
--- /dev/null
+++ b/src/test/java/input/strategy/ManualInputReaderStrategyTest.java
@@ -0,0 +1,197 @@
+package input.strategy;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ManualInputReaderStrategyTest {
+
+ private InputStream originalSystemIn;
+ private final ByteArrayInputStream testInput = new ByteArrayInputStream(new byte[0]);
+
+ @BeforeEach
+ void setUp() {
+ // Сохраняем оригинальный System.in
+ originalSystemIn = System.in;
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Восстанавливаем оригинальный System.in после теста
+ System.setIn(originalSystemIn);
+ }
+
+ @Test
+ @DisplayName("getData должен вернуть пустую коллекцию при отказе от добавления первого клиента")
+ void testGetDataReturnsEmptyCollectionWhenUserDeclinesFirstClient() throws IOException {
+ // Arrange: пользователь отказывается добавлять первого клиента
+ String input = "н\n"; // 'нет' на вопрос «Добавляем клиента?»
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert
+ assertEquals(0, clients.size());
+ }
+
+ @Test
+ @DisplayName("getData должен корректно добавить одного клиента с валидными данными")
+ void testGetDataAddsOneValidClient() throws IOException {
+ // Arrange: последовательность ввода для одного клиента
+ String input = "да\n" + // добавляем клиента
+ "Иван Иванов\n" + // имя
+ "+79991234567\n" + // телефон
+ "1\n" + // ID
+ "да\n" + // подтверждение данных
+ "нет\n"; // отказ от добавления следующего клиента
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert
+ assertEquals(1, clients.size());
+ Client client = clients.get(0);
+ assertEquals("Иван Иванов", client.getName());
+ assertEquals("+79991234567", client.getPhoneNumber());
+ assertEquals(1, client.getIdNumber());
+ }
+
+ @Test
+ @DisplayName("getData должен пропускать клиента при отмене подтверждения данных")
+ void testGetDataSkipsClientWhenUserCancelsConfirmation() throws IOException {
+ // Arrange: добавляем клиента, но отменяем подтверждение данных
+ String input = "да\n" +
+ "Мария Петрова\n" +
+ "+79997654321\n" +
+ "2\n" +
+ "нет\n" + // отмена подтверждения
+ "нет\n"; // отказ от следующего клиента
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: клиент не добавлен
+ assertEquals(0, clients.size());
+ }
+
+ @Test
+ @DisplayName("promptForNumber должен запрашивать повторный ввод при неверном формате телефона")
+ void testPromptForNumberRepeatsOnInvalidPhoneFormat() throws IOException {
+ // Arrange: неверный формат телефона, затем корректный
+ String input = "да\n" +
+ "Анна Сидорова\n" +
+ "79998887766\n" + // без +7
+ "+79998887766\n" + // корректный формат
+ "3\n" +
+ "да\n" +
+ "нет\n";
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: клиент добавлен с корректным номером
+ assertEquals(1, clients.size());
+ assertEquals("+79998887766", clients.get(0).getPhoneNumber());
+ }
+
+ @Test
+ @DisplayName("promptForId должен отклонять занятый ID и запрашивать новый")
+ void testPromptForIdRejectsDuplicateId() throws IOException {
+ // Arrange: попытка ввести дублирующий ID
+ String input = "да\n" +
+ "Иван Иванов\n" +
+ "+79991234567\n" +
+ "1\n" +
+ "да\n" + // подтверждение первого клиента
+ "да\n" + // добавляем второго клиента
+ "Мария Петрова\n" +
+ "+79997654321\n" +
+ "1\n" + // дублирующий ID
+ "2\n" + // новый ID
+ "да\n" +
+ "нет\n";
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: два клиента с разными ID
+ assertEquals(2, clients.size());
+ assertEquals(1, clients.get(0).getIdNumber());
+ assertEquals(2, clients.get(1).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("promptForId должен отклонять ID вне диапазона 0–1000")
+ void testPromptForIdRejectsOutOfRangeId() throws IOException {
+ // Arrange: ID вне диапазона, затем корректный
+ String input = "да\n" +
+ "Анна Сидорова\n" +
+ "+79998887766\n" +
+ "-5\n" + // меньше 0
+ "1500\n" + // больше 1000
+ "5\n" + // корректный ID
+ "да\n" +
+ "нет\n";
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: клиент добавлен с ID=5
+ assertEquals(1, clients.size());
+ assertEquals(5, clients.get(0).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("getData должен остановить ввод при достижении лимита в 1000 клиентов")
+ void testGetDataStopsAtLimitOf1000Clients() throws IOException {
+ // Arrange: имитируем достижение лимита
+ StringBuilder inputBuilder = new StringBuilder();
+ for (int i = 0; i < 1000; i++) {
+ inputBuilder.append("да\n");
+ inputBuilder.append("Клиент ").append(i).append("\n");
+ inputBuilder.append("+7").append(String.format("%010d", i)).append("\n");
+ inputBuilder.append(i).append("\n");
+ inputBuilder.append("да\n");
+ }
+ inputBuilder.append("да\n"); // попытка добавить 1001-го клиента
+
+ System.setIn(new ByteArrayInputStream(inputBuilder.toString().getBytes()));
+
+ ClientInputStrategy strategy = new ManualInputReaderStrategy();
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: добавлено ровно 1000 клиентов
+ assertEquals(1000, clients.size());
+ }
+
+
+ }
\ No newline at end of file
diff --git a/src/test/java/input/strategy/RandomDataGeneratorStrategyTest.java b/src/test/java/input/strategy/RandomDataGeneratorStrategyTest.java
new file mode 100644
index 0000000..b73de13
--- /dev/null
+++ b/src/test/java/input/strategy/RandomDataGeneratorStrategyTest.java
@@ -0,0 +1,170 @@
+package input.strategy;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class RandomDataGeneratorStrategyTest {
+
+ @Test
+ @DisplayName("Конструктор с положительным количеством записей должен создавать стратегию")
+ void testConstructorWithPositiveCount() {
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(5);
+ assertNotNull(strategy);
+ assertEquals(5, ((RandomDataGeneratorStrategy) strategy).getCount());
+ }
+
+ @Test
+ @DisplayName("Конструктор с нулевым количеством записей должен выбрасывать IllegalArgumentException")
+ void testConstructorWithZeroCountThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new RandomDataGeneratorStrategy(0),
+ "Конструктор должен выбрасывать исключение при нулевом количестве записей"
+ );
+ }
+
+ @Test
+ @DisplayName("Конструктор с отрицательным количеством записей должен выбрасывать IllegalArgumentException")
+ void testConstructorWithNegativeCountThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new RandomDataGeneratorStrategy(-5),
+ "Конструктор должен выбрасывать исключение при отрицательном количестве записей"
+ );
+ }
+
+ @Test
+ @DisplayName("getData должен генерировать указанное количество клиентов")
+ void testGetDataGeneratesCorrectNumberOfClients() throws IOException {
+ // Arrange
+ int expectedCount = 3;
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(expectedCount);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert
+ assertEquals(expectedCount, clients.size());
+ }
+
+ @Test
+ @DisplayName("Все сгенерированные клиенты должны иметь уникальные ID")
+ void testGeneratedClientsHaveUniqueIds() throws IOException {
+ // Arrange
+ int count = 10;
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+ Set ids = new HashSet<>();
+
+ // Assert: проверяем уникальность ID
+ for (Client client : clients) {
+ assertTrue(ids.add(client.getIdNumber()),
+ "ID клиента не уникален: " + client.getIdNumber());
+ }
+ assertEquals(count, ids.size());
+ }
+
+ @Test
+ @DisplayName("ID клиентов должны быть в диапазоне 0–1000")
+ void testIdsAreInRange() throws IOException {
+ // Arrange
+ int count = 50;
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: каждый ID должен быть в диапазоне [0, 1000]
+ for (Client client : clients) {
+ int id = client.getIdNumber();
+ assertTrue(id >= 0 && id <= 1000,
+ "ID " + id + " выходит за пределы диапазона 0–1000");
+ }
+ }
+
+ @Test
+ @DisplayName("Номера телефонов должны соответствовать формату +79XXXXXXXXX")
+ void testPhoneNumbersMatchFormat() throws IOException {
+ // Arrange
+ int count = 20;
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: проверяем формат номера телефона
+ for (Client client : clients) {
+ String phone = client.getPhoneNumber();
+ assertTrue(phone.matches("^\\+79\\d{9}$"),
+ "Номер телефона '" + phone + "' не соответствует формату +79XXXXXXXXX");
+ }
+ }
+
+ @Test
+ @DisplayName("Имена клиентов не должны быть пустыми")
+ void testNamesAreNotEmpty() throws IOException {
+ // Arrange
+ int count = 15;
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count);
+
+ // Act
+ CustomCollection clients = strategy.getData();
+
+ // Assert: имя не должно быть пустым или null
+ for (Client client : clients) {
+ assertNotNull(client.getName(), "Имя клиента не должно быть null");
+ assertFalse(client.getName().trim().isEmpty(),
+ "Имя клиента не должно быть пустым");
+ }
+ }
+
+ @Test
+ @DisplayName("getData должен выбрасывать IllegalStateException при попытке сгенерировать >1000 уникальных ID")
+ void testGetDataThrowsIllegalStateExceptionWhenExceedingUniqueIdLimit() throws IOException {
+ // Arrange: пытаемся сгенерировать больше уникальных ID, чем доступно в диапазоне 0–1000
+ ClientInputStrategy strategy = new RandomDataGeneratorStrategy(1001);
+
+ // Act & Assert
+ assertThrows(IllegalStateException.class,
+ strategy::getData,
+ "getData должен выбрасывать IllegalStateException, когда все ID в диапазоне 0–1000 использованы"
+ );
+ }
+
+ @Test
+ @DisplayName("Повторный вызов getData должен генерировать новые случайные данные")
+ void testRepeatedGetDataCallsGenerateDifferentData() throws IOException {
+ // Arrange
+ int count = 2;
+ RandomDataGeneratorStrategy strategy = new RandomDataGeneratorStrategy(count);
+
+ // Act: первый вызов
+ CustomCollection firstResult = strategy.getData();
+ // Второй вызов
+ CustomCollection secondResult = strategy.getData();
+
+ // Assert: сравниваем ID клиентов из двух вызовов
+ Set firstIds = new HashSet<>();
+ for (Client client : firstResult) {
+ firstIds.add(client.getIdNumber());
+ }
+
+ Set secondIds = new HashSet<>();
+ for (Client client : secondResult) {
+ secondIds.add(client.getIdNumber());
+ }
+
+ // Ожидаем, что хотя бы один ID отличается (высокая вероятность при случайной генерации)
+ boolean hasDifferentIds = !firstIds.equals(secondIds);
+ assertTrue(hasDifferentIds,
+ "Повторные вызовы getData должны генерировать разные наборы данных");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/output/FileDataWriterTest.java b/src/test/java/output/FileDataWriterTest.java
new file mode 100644
index 0000000..3c95e60
--- /dev/null
+++ b/src/test/java/output/FileDataWriterTest.java
@@ -0,0 +1,137 @@
+package output;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FileDataWriterTest {
+
+ private FileDataWriter fileDataWriter;
+ private final String testFilePath = "savedData.txt";
+ private File testFile;
+
+ @BeforeEach
+ void setUp() {
+ fileDataWriter = new FileDataWriter();
+ testFile = new File(testFilePath);
+ // Удаляем файл перед каждым тестом, если он существует
+ if (testFile.exists()) {
+ testFile.delete();
+ }
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Удаляем тестовый файл после каждого теста
+ if (testFile.exists()) {
+ testFile.delete();
+ }
+ }
+
+ @Test
+ @DisplayName("writeDataToFile должен создавать файл, если он не существует")
+ void testWriteDataToFileCreatesFileIfNotExists() {
+ // Act
+ fileDataWriter.writeDataToFile("Тестовые данные");
+
+ // Assert
+ assertTrue(testFile.exists(), "Файл должен быть создан");
+ assertTrue(testFile.isFile(), "Созданный объект должен быть файлом");
+ }
+
+ @Test
+ @DisplayName("writeDataToFile должен записывать данные в файл")
+ void testWriteDataToFileWritesDataToFile() throws IOException {
+ // Arrange
+ String testData = "Тестовые данные для записи";
+
+ // Act
+ fileDataWriter.writeDataToFile(testData);
+
+ // Assert
+ assertTrue(testFile.exists());
+ String fileContent = new String(Files.readAllBytes(testFile.toPath()));
+ assertEquals(testData, fileContent, "Содержимое файла должно совпадать с записанными данными");
+ }
+
+ @Test
+ @DisplayName("writeDataToFile должен дописывать данные в конец файла (append)")
+ void testWriteDataToFileAppendsDataToExistingFile() throws IOException {
+ // Arrange
+ String firstData = "Первая строка\n";
+ String secondData = "Вторая строка\n";
+
+ // Act — записываем данные дважды
+ fileDataWriter.writeDataToFile(firstData);
+ fileDataWriter.writeDataToFile(secondData);
+
+ // Assert
+ assertTrue(testFile.exists());
+ String fileContent = new String(Files.readAllBytes(testFile.toPath()));
+ assertTrue(fileContent.contains(firstData), "Файл должен содержать первую строку");
+ assertTrue(fileContent.contains(secondData), "Файл должен содержать вторую строку");
+ assertEquals(firstData + secondData, fileContent, "Полное содержимое файла должно быть корректным");
+ }
+
+ @Test
+ @DisplayName("writeDataToFile с пустой строкой должен создавать файл без содержимого")
+ void testWriteDataToFileWithEmptyStringCreatesEmptyFile() throws IOException {
+ // Act
+ fileDataWriter.writeDataToFile("");
+
+ // Assert
+ assertTrue(testFile.exists());
+ long fileSize = testFile.length();
+ assertEquals(0, fileSize, "Файл с пустой строкой должен иметь нулевой размер");
+ }
+
+ @Test
+ @DisplayName("writeDataToFile с null должен создавать файл и не записывать ничего (изза обработки исключения)")
+ void testWriteDataToFileWithNullCreatesFile() {
+ // Act
+ fileDataWriter.writeDataToFile(null);
+
+ // Assert
+ assertTrue(testFile.exists(), "Файл должен быть создан даже при записи null");
+ // В текущем коде при записи null в файл запишется строка "null"
+ // Если ожидается другое поведение, нужно изменить реализацию метода
+ }
+
+ @Test
+ @DisplayName("writeDataToFile должен корректно обрабатывать специальные символы")
+ void testWriteDataToFileHandlesSpecialCharacters() throws IOException {
+ // Arrange
+ String specialData = "Текст с символами: !@#$%^&*()_+-=[]{}|;':\",./<>?\n\t\n";
+
+ // Act
+ fileDataWriter.writeDataToFile(specialData);
+
+ // Assert
+ assertTrue(testFile.exists());
+ String fileContent = new String(Files.readAllBytes(testFile.toPath()));
+ assertEquals(specialData, fileContent, "Файл должен корректно сохранять специальные символы");
+ }
+
+ @Test
+ @DisplayName("writeDataToFile при ошибке создания файла должен выбрасывать RuntimeException")
+ void testWriteDataToFileThrowsRuntimeExceptionOnCreateNewFileFailure() {
+ // Arrange — создаём директорию с тем же именем, что и файл, чтобы вызвать ошибку при createNewFile()
+ File conflictingDir = new File("savedData.txt");
+ conflictingDir.mkdir();
+
+ try {
+ // Act & Assert
+ assertThrows(RuntimeException.class, () -> fileDataWriter.writeDataToFile("Данные"));
+ } finally {
+ // Очищаем — удаляем директорию после теста
+ conflictingDir.delete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/sorting/AbstractMergeSortStrategyTest.java b/src/test/java/sorting/AbstractMergeSortStrategyTest.java
new file mode 100644
index 0000000..96defe1
--- /dev/null
+++ b/src/test/java/sorting/AbstractMergeSortStrategyTest.java
@@ -0,0 +1,164 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Comparator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AbstractMergeSortStrategyTest {
+
+ // Конкретная реализация абстрактного класса для тестирования
+ static class TestMergeSortStrategy extends AbstractMergeSortStrategy {
+ @Override
+ protected Comparator getComparator() {
+ return Comparator.comparing(Client::getName);
+ }
+
+ @Override
+ public void sort(CustomCollection clients) {
+ }
+ }
+
+ @Test
+ @DisplayName("sortWithComparator с null-коллекцией не должен вызывать исключений")
+ void testSortWithComparatorWithNullCollection() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ strategy.sortWithComparator(null, Comparator.comparing(Client::getIdNumber));
+ // Тест проходит, если не выброшено исключение
+ }
+
+ @Test
+ @DisplayName("sortWithComparator с пустой коллекцией не должен изменять коллекцию")
+ void testSortWithComparatorWithEmptyCollection() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+ strategy.sortWithComparator(clients, Comparator.comparing(Client::getIdNumber));
+ assertEquals(0, clients.size());
+ }
+
+ @Test
+ @DisplayName("sortWithComparator с коллекцией из одного элемента не должен изменять коллекцию")
+ void testSortWithComparatorWithSingleElement() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+ Client client = new Client.ClientBuilder().name("Иван").phoneNumber("+79991234567").idNumber(1).build();
+ clients.add(client);
+
+ strategy.sortWithComparator(clients, Comparator.comparing(Client::getIdNumber));
+
+ assertEquals(1, clients.size());
+ assertEquals("Иван", clients.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("sortWithComparator должен корректно сортировать коллекцию по idNumber")
+ void testSortWithComparatorSortsByIdNumber() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ // Добавляем клиентов в неупорядоченном порядке
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(2).build());
+
+ Comparator idComparator = Comparator.comparing(Client::getIdNumber);
+ strategy.sortWithComparator(clients, idComparator);
+
+ // Проверяем, что клиенты отсортированы по idNumber: 1, 2, 3
+ assertEquals(1, clients.get(0).getIdNumber());
+ assertEquals(2, clients.get(1).getIdNumber());
+ assertEquals(3, clients.get(2).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("getStrategyName должен возвращать ожидаемую строку")
+ void testGetStrategyName() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ String expectedName = "Abstract Merge Sort (сортировка слиянием)";
+ assertEquals(expectedName, strategy.getStrategyName());
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly с null-коллекцией не должен вызывать исключений")
+ void testSortEvenValuesOnlyWithNullCollection() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ strategy.sortEvenValuesOnly(null);
+ // Тест проходит, если не выброшено исключение
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly с пустой коллекцией не должен изменять коллекцию")
+ void testSortEvenValuesOnlyWithEmptyCollection() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+ strategy.sortEvenValuesOnly(clients);
+ assertEquals(0, clients.size());
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly должен сортировать только элементы с чётными idNumber")
+ void testSortEvenValuesOnlySortsEvenIdNumbers() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ // Порядок: нечётный, чётный, нечётный, чётный, чётный
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); // нечётный
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(4).build()); // чётный
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(3).build()); // нечётный
+ clients.add(new Client.ClientBuilder().name("Галина").phoneNumber("+7444").idNumber(8).build()); // чётный
+ clients.add(new Client.ClientBuilder().name("Дмитрий").phoneNumber("+7555").idNumber(6).build()); // чётный
+
+ strategy.sortEvenValuesOnly(clients);
+
+ // Нечётные элементы остаются на своих местах
+ assertEquals(1, clients.get(0).getIdNumber()); // Анна на позиции 0
+ assertEquals(3, clients.get(2).getIdNumber()); // Виктор на позиции 2
+
+ // Чётные элементы отсортированы по возрастанию: 4, 6, 8
+ assertEquals(4, clients.get(1).getIdNumber()); // Борис на позиции 1
+ assertEquals(6, clients.get(3).getIdNumber()); // Дмитрий на позиции 3
+ assertEquals(8, clients.get(4).getIdNumber()); // Галина на позиции 4
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly не должен ничего делать, если нет чётных idNumber")
+ void testSortEvenValuesOnlyDoesNothingWhenNoEvenIdNumbers() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(5).build());
+
+ strategy.sortEvenValuesOnly(clients);
+
+ // Все элементы остались на своих местах в исходном порядке
+ assertEquals(1, clients.get(0).getIdNumber());
+ assertEquals(3, clients.get(1).getIdNumber());
+ assertEquals(5, clients.get(2).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly с пользовательским компаратором должен сортировать по заданному критерию")
+ void testSortEvenValuesOnlyWithCustomComparator() {
+ AbstractMergeSortStrategy strategy = new TestMergeSortStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(4).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(8).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(6).build());
+
+ // Компаратор по убыванию idNumber
+ Comparator descendingIdComparator = Comparator.comparing(Client::getIdNumber).reversed();
+ strategy.sortEvenValuesOnly(clients, descendingIdComparator);
+
+ // Чётные idNumber отсортированы по убыванию: 8, 6, 4
+ assertEquals(8, clients.get(0).getIdNumber());
+ assertEquals(6, clients.get(1).getIdNumber());
+ assertEquals(4, clients.get(2).getIdNumber());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/sorting/MergeSortDefaultStrategyTest.java b/src/test/java/sorting/MergeSortDefaultStrategyTest.java
new file mode 100644
index 0000000..a3b25ae
--- /dev/null
+++ b/src/test/java/sorting/MergeSortDefaultStrategyTest.java
@@ -0,0 +1,159 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MergeSortDefaultStrategyTest {
+
+ @Test
+ @DisplayName("sort с null-коллекцией не должен вызывать исключений")
+ void testSortWithNullCollection() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ strategy.sort(null);
+ // Тест проходит, если не выброшено исключение
+ }
+
+ @Test
+ @DisplayName("sort с пустой коллекцией не должен изменять коллекцию")
+ void testSortWithEmptyCollection() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+ strategy.sort(clients);
+ assertEquals(0, clients.size());
+ }
+
+ @Test
+ @DisplayName("sort с коллекцией из одного элемента не должен изменять коллекцию")
+ void testSortWithSingleElement() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+ Client client = new Client.ClientBuilder()
+ .name("Иван")
+ .phoneNumber("+79991234567")
+ .idNumber(1)
+ .build();
+ clients.add(client);
+
+ strategy.sort(clients);
+
+ assertEquals(1, clients.size());
+ assertEquals("Иван", clients.get(0).getName());
+ assertEquals("+79991234567", clients.get(0).getPhoneNumber());
+ assertEquals(1, clients.get(0).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("sort должен сортировать по имени (основной критерий)")
+ void testSortByName() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Сергей").phoneNumber("+7111").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(2).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7333").idNumber(1).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: Анна, Борис, Сергей
+ assertEquals("Анна", clients.get(0).getName());
+ assertEquals("Борис", clients.get(1).getName());
+ assertEquals("Сергей", clients.get(2).getName());
+ }
+
+ @Test
+ @DisplayName("sort должен сортировать по ID, если имена одинаковые")
+ void testSortByIdWhenNamesAreEqual() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7333").idNumber(2).build());
+
+ strategy.sort(clients);
+
+ // Все имена одинаковые, сортируем по ID: 1, 2, 3
+ assertEquals(1, clients.get(0).getIdNumber());
+ assertEquals(2, clients.get(1).getIdNumber());
+ assertEquals(3, clients.get(2).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("sort должен сортировать по телефону, если имена и ID одинаковые")
+ void testSortByPhoneWhenNamesAndIdsAreEqual() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7333").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(1).build());
+
+ strategy.sort(clients);
+
+ // Имена и ID одинаковые, сортируем по телефону: +7111, +7222, +7333
+ assertEquals("+7111", clients.get(0).getPhoneNumber());
+ assertEquals("+7222", clients.get(1).getPhoneNumber());
+ assertEquals("+7333", clients.get(2).getPhoneNumber());
+ }
+
+ @Test
+ @DisplayName("sort должен корректно обрабатывать дубликаты")
+ void testSortHandlesDuplicates() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ Client duplicate1 = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build();
+ Client duplicate2 = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build();
+
+ clients.add(duplicate1);
+ clients.add(duplicate2);
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(2).build());
+
+ strategy.sort(clients);
+
+ // Дубликаты должны остаться в коллекции
+ assertEquals("Анна", clients.get(0).getName());
+ assertEquals("Анна", clients.get(1).getName());
+ assertEquals("Борис", clients.get(2).getName());
+ }
+
+ @Test
+ @DisplayName("getStrategyName должен возвращать ожидаемую строку")
+ void testGetStrategyName() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ String expectedName = "Merge Sort Default Strategy (сортировка по имени -> ID -> телефону)";
+ assertEquals(expectedName, strategy.getStrategyName());
+ }
+
+ @Test
+ @DisplayName("sort должен сохранять стабильность сортировки для равных элементов")
+ void testSortStabilityForEqualElements() {
+ MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy();
+ CustomCollection clients = new CustomCollection<>();
+
+ // Добавляем два клиента с одинаковыми данными в разном порядке
+ Client first = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build();
+ Client second = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build();
+
+ clients.add(second);
+ clients.add(first);
+
+ strategy.sort(clients);
+
+ // Для стабильной сортировки порядок равных элементов должен сохраняться
+ // В данном случае оба элемента равны, поэтому порядок может быть любым,
+ // но оба элемента должны присутствовать
+ assertEquals(2, clients.size());
+ assertTrue(clients.get(0).getName().equals("Анна") &&
+ clients.get(0).getPhoneNumber().equals("+7111") &&
+ clients.get(0).getIdNumber() == 1);
+ assertTrue(clients.get(1).getName().equals("Анна") &&
+ clients.get(1).getPhoneNumber().equals("+7111") &&
+ clients.get(1).getIdNumber() == 1);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/sorting/MergeSortDynamicStrategyTest.java b/src/test/java/sorting/MergeSortDynamicStrategyTest.java
new file mode 100644
index 0000000..3d73303
--- /dev/null
+++ b/src/test/java/sorting/MergeSortDynamicStrategyTest.java
@@ -0,0 +1,183 @@
+package sorting;
+
+import dto.Client;
+import enums.Field;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class MergeSortDynamicStrategyTest {
+
+ @Test
+ @DisplayName("Конструктор с валидными параметрами должен создавать стратегию")
+ void testConstructorWithValidParameters() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, true);
+ assertNotNull(strategy);
+ }
+
+ @Test
+ @DisplayName("sort с null-коллекцией не должен вызывать исключений")
+ void testSortWithNullCollection() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, true);
+ strategy.sort(null);
+ // Тест проходит, если не выброшено исключение
+ }
+
+ @Test
+ @DisplayName("sort с пустой коллекцией не должен изменять коллекцию")
+ void testSortWithEmptyCollection() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, false);
+ CustomCollection clients = new CustomCollection<>();
+ strategy.sort(clients);
+ assertEquals(0, clients.size());
+ }
+
+ @Test
+ @DisplayName("sort с коллекцией из одного элемента не должен изменять коллекцию")
+ void testSortWithSingleElement() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.PHONE_NUMBER, true);
+ CustomCollection clients = new CustomCollection<>();
+ Client client = new Client.ClientBuilder()
+ .name("Иван")
+ .phoneNumber("+79991234567")
+ .idNumber(1)
+ .build();
+ clients.add(client);
+
+ strategy.sort(clients);
+
+ assertEquals(1, clients.size());
+ assertEquals("Иван", clients.get(0).getName());
+ assertEquals("+79991234567", clients.get(0).getPhoneNumber());
+ assertEquals(1, clients.get(0).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("sort по имени в порядке возрастания должен корректно сортировать")
+ void testSortByNameAscending() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, true);
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Сергей").phoneNumber("+7111").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(2).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7333").idNumber(1).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: Анна, Борис, Сергей
+ assertEquals("Анна", clients.get(0).getName());
+ assertEquals("Борис", clients.get(1).getName());
+ assertEquals("Сергей", clients.get(2).getName());
+ }
+
+ @Test
+ @DisplayName("sort по имени в порядке убывания должен корректно сортировать")
+ void testSortByNameDescending() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, false);
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(2).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(3).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: Виктор, Борис, Анна
+ assertEquals("Виктор", clients.get(0).getName());
+ assertEquals("Борис", clients.get(1).getName());
+ assertEquals("Анна", clients.get(2).getName());
+ }
+
+ @Test
+ @DisplayName("sort по ID в порядке возрастания должен корректно сортировать")
+ void testSortByIdAscending() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, true);
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(2).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: 1, 2, 3
+ assertEquals(1, clients.get(0).getIdNumber());
+ assertEquals(2, clients.get(1).getIdNumber());
+ assertEquals(3, clients.get(2).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("sort по ID в порядке убывания должен корректно сортировать")
+ void testSortByIdDescending() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, false);
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(3).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(2).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: 3, 2, 1
+ assertEquals(3, clients.get(0).getIdNumber());
+ assertEquals(2, clients.get(1).getIdNumber());
+ assertEquals(1, clients.get(2).getIdNumber());
+ }
+
+ @Test
+ @DisplayName("sort по номеру телефона в порядке возрастания должен корректно сортировать")
+ void testSortByPhoneAscending() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.PHONE_NUMBER, true);
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7333").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7111").idNumber(2).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7222").idNumber(3).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: +7111, +7222, +7333
+ assertEquals("+7111", clients.get(0).getPhoneNumber());
+ assertEquals("+7222", clients.get(1).getPhoneNumber());
+ assertEquals("+7333", clients.get(2).getPhoneNumber());
+ }
+
+ @Test
+ @DisplayName("sort по номеру телефона в порядке убывания должен корректно сортировать")
+ void testSortByPhoneDescending() {
+ MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.PHONE_NUMBER, false);
+ CustomCollection clients = new CustomCollection<>();
+
+ clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build());
+ clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7333").idNumber(2).build());
+ clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7222").idNumber(3).build());
+
+ strategy.sort(clients);
+
+ // Ожидаемый порядок: +7333, +7222, +7111
+ assertEquals("+7333", clients.get(0).getPhoneNumber());
+ assertEquals("+7222", clients.get(1).getPhoneNumber());
+ assertEquals("+7111", clients.get(2).getPhoneNumber());
+ }
+
+
+ @Test
+ @DisplayName("getStrategyName должен возвращать ожидаемую строку с указанием поля сортировки")
+ void testGetStrategyName() {
+ MergeSortDynamicStrategy ascendingStrategy = new MergeSortDynamicStrategy(Field.NAME, true);
+ MergeSortDynamicStrategy descendingStrategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, false);
+
+ // Act & Assert
+ String expectedAscending = "Dynamic Merge Sort (Динамическая сортировка по: NAME)";
+ String expectedDescending = "Dynamic Merge Sort (Динамическая сортировка по: ID_NUMBER)";
+
+ assertEquals(expectedAscending, ascendingStrategy.getStrategyName(),
+ "Для стратегии с сортировкой по NAME (возрастание) должна возвращаться корректная строка");
+ assertEquals(expectedDescending, descendingStrategy.getStrategyName(),
+ "Для стратегии с сортировкой по ID_NUMBER (убывание) должна возвращаться корректная строка");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/sorting/SortingManagerTest.java b/src/test/java/sorting/SortingManagerTest.java
new file mode 100644
index 0000000..5ff9342
--- /dev/null
+++ b/src/test/java/sorting/SortingManagerTest.java
@@ -0,0 +1,148 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SortingManagerTest {
+
+
+ @Test
+ @DisplayName("Конструктор без параметров должен создавать SortingManager с null-стратегией")
+ void testConstructorWithoutParameters() {
+ SortingManager manager = new SortingManager();
+ assertNull(manager.getCurrentStrategy());
+ }
+
+ @Test
+ @DisplayName("Конструктор с nullстратегией должен выбрасывать IllegalArgumentException")
+ void testConstructorWithNullStrategyThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new SortingManager(null),
+ "Конструктор должен выбрасывать исключение при nullстратегии"
+ );
+ }
+
+ @Test
+ @DisplayName("Конструктор с валидной стратегией должен устанавливать её как текущую")
+ void testConstructorWithValidStrategy() {
+ // Создаём тестовую реализацию SortingStrategy
+ SortingStrategy testStrategy = new SortingStrategy() {
+ @Override
+ public void sort(CustomCollection clients) {
+ // Пустая реализация для теста
+ }
+
+ @Override
+ public String getStrategyName() {
+ return "Test Strategy";
+ }
+
+ @Override
+ public void sortEvenValuesOnly(CustomCollection clients) {
+ }
+ };
+
+ SortingManager manager = new SortingManager(testStrategy);
+ assertEquals(testStrategy, manager.getCurrentStrategy(),
+ "Текущая стратегия должна совпадать с переданной в конструкторе"
+ );
+ }
+
+ @Test
+ @DisplayName("setStrategy с null должен выбрасывать IllegalArgumentException")
+ void testSetStrategyWithNullThrowsException() {
+ SortingManager manager = new SortingManager();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> manager.setStrategy(null),
+ "setStrategy должен выбрасывать исключение при nullстратегии"
+ );
+ }
+
+ @Test
+ @DisplayName("setStrategy должен корректно устанавливать новую стратегию")
+ void testSetStrategy() {
+ SortingManager manager = new SortingManager();
+
+ // Первая стратегия
+ SortingStrategy strategy1 = new SortingStrategy() {
+ @Override
+ public void sort(CustomCollection clients) {}
+
+ @Override
+ public String getStrategyName() { return "Strategy 1"; }
+
+ @Override
+ public void sortEvenValuesOnly(CustomCollection clients) {
+ }
+ };
+
+ // Вторая стратегия
+ SortingStrategy strategy2 = new SortingStrategy() {
+ @Override
+ public void sort(CustomCollection clients) {}
+
+ @Override
+ public String getStrategyName() { return "Strategy 2"; }
+
+ @Override
+ public void sortEvenValuesOnly(CustomCollection clients) {
+
+ }
+ };
+
+ // Устанавливаем первую стратегию
+ manager.setStrategy(strategy1);
+ assertEquals(strategy1, manager.getCurrentStrategy(),
+ "После setStrategy текущая стратегия должна быть strategy1"
+ );
+
+ // Заменяем на вторую стратегию
+ manager.setStrategy(strategy2);
+ assertEquals(strategy2, manager.getCurrentStrategy(),
+ "После повторного setStrategy текущая стратегия должна быть strategy2"
+ );
+ }
+
+ @Test
+ @DisplayName("getStrategy после конструктора без параметров должен возвращать null")
+ void testGetStrategyAfterDefaultConstructor() {
+ SortingManager manager = new SortingManager();
+ assertNull(manager.getCurrentStrategy(),
+ "getStrategy должен возвращать null после конструктора без параметров"
+ );
+ }
+
+ @Test
+ @DisplayName("Последовательность операций: конструктор без параметров → setStrategy → getStrategy")
+ void testSequenceOfOperations() {
+ SortingManager manager = new SortingManager();
+
+ // Изначально стратегия null
+ assertNull(manager.getCurrentStrategy());
+
+ // Устанавливаем стратегию
+ SortingStrategy testStrategy = new SortingStrategy() {
+ @Override
+ public void sort(CustomCollection clients) {}
+
+ @Override
+ public String getStrategyName() { return "Test Strategy"; }
+
+ @Override
+ public void sortEvenValuesOnly(CustomCollection clients) {
+
+ }
+ };
+ manager.setStrategy(testStrategy);
+
+ // Проверяем, что стратегия установилась
+ assertEquals(testStrategy, manager.getCurrentStrategy(),
+ "После setStrategy getStrategy должен возвращать установленную стратегию"
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/sorting/SortingStrategyTest.java b/src/test/java/sorting/SortingStrategyTest.java
new file mode 100644
index 0000000..6c95f87
--- /dev/null
+++ b/src/test/java/sorting/SortingStrategyTest.java
@@ -0,0 +1,148 @@
+package sorting;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SortingStrategyTest {
+
+ // Тестовая реализация интерфейса SortingStrategy для использования в тестах
+ private static class TestSortingStrategy implements SortingStrategy {
+ private boolean sortCalled = false;
+ private boolean sortEvenValuesOnlyCalled = false;
+ private String strategyName;
+
+ public TestSortingStrategy(String strategyName) {
+ this.strategyName = strategyName;
+ }
+
+ @Override
+ public void sort(CustomCollection clients) {
+ sortCalled = true;
+ }
+
+ @Override
+ public String getStrategyName() {
+ return strategyName;
+ }
+
+ @Override
+ public void sortEvenValuesOnly(CustomCollection clients) {
+ sortEvenValuesOnlyCalled = true;
+ }
+
+ // Вспомогательные методы для проверки вызовов
+ public boolean isSortCalled() {
+ return sortCalled;
+ }
+
+ public boolean isSortEvenValuesOnlyCalled() {
+ return sortEvenValuesOnlyCalled;
+ }
+ }
+
+ @Test
+ @DisplayName("sort должен быть вызываемым без исключений")
+ void testSortIsCallable() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+ CustomCollection clients = new CustomCollection<>();
+
+ strategy.sort(clients);
+
+ assertTrue(((TestSortingStrategy) strategy).isSortCalled(),
+ "Метод sort должен быть вызван без исключений");
+ }
+
+ @Test
+ @DisplayName("getStrategyName должен возвращать корректное имя стратегии")
+ void testGetStrategyNameReturnsCorrectName() {
+ String expectedName = "Test Strategy";
+ SortingStrategy strategy = new TestSortingStrategy(expectedName);
+
+ String actualName = strategy.getStrategyName();
+
+ assertEquals(expectedName, actualName,
+ "getStrategyName должен возвращать имя, установленное при создании стратегии");
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly должен быть вызываемым без исключений")
+ void testSortEvenValuesOnlyIsCallable() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+ CustomCollection clients = new CustomCollection<>();
+
+ strategy.sortEvenValuesOnly(clients);
+
+ assertTrue(((TestSortingStrategy) strategy).isSortEvenValuesOnlyCalled(),
+ "Метод sortEvenValuesOnly должен быть вызван без исключений");
+ }
+
+ @Test
+ @DisplayName("sort с null-коллекцией не должен вызывать исключений (по контракту интерфейса)")
+ void testSortWithNullCollection() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+
+ assertDoesNotThrow(() -> strategy.sort(null),
+ "sort не должен выбрасывать исключений при null-коллекции");
+ }
+
+ @Test
+ @DisplayName("sortEvenValuesOnly с null-коллекцией не должен вызывать исключений (по контракту интерфейса)")
+ void testSortEvenValuesOnlyWithNullCollection() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+
+ assertDoesNotThrow(() -> strategy.sortEvenValuesOnly(null),
+ "sortEvenValuesOnly не должен выбрасывать исключений при null-коллекции");
+ }
+
+ @Test
+ @DisplayName("Методы sort и sortEvenValuesOnly могут работать с пустой коллекцией")
+ void testMethodsWorkWithEmptyCollection() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+ CustomCollection emptyClients = new CustomCollection<>();
+
+ // Вызываем sort с пустой коллекцией
+ strategy.sort(emptyClients);
+ assertTrue(((TestSortingStrategy) strategy).isSortCalled(),
+ "sort должен корректно обрабатывать пустую коллекцию");
+
+ // Сбрасываем флаг для следующего теста
+ ((TestSortingStrategy) strategy).sortCalled = false;
+
+ // Вызываем sortEvenValuesOnly с пустой коллекцией
+ strategy.sortEvenValuesOnly(emptyClients);
+ assertTrue(((TestSortingStrategy) strategy).isSortEvenValuesOnlyCalled(),
+ "sortEvenValuesOnly должен корректно обрабатывать пустую коллекцию");
+ }
+
+ @Test
+ @DisplayName("Несколько вызовов sort должны обрабатываться корректно")
+ void testMultipleSortCalls() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+ CustomCollection clients1 = new CustomCollection<>();
+ CustomCollection clients2 = new CustomCollection<>();
+
+ strategy.sort(clients1);
+ strategy.sort(clients2);
+
+ assertTrue(((TestSortingStrategy) strategy).isSortCalled(),
+ "После нескольких вызовов sort флаг вызова должен оставаться установленным");
+ }
+
+ @Test
+ @DisplayName("Несколько вызовов sortEvenValuesOnly должны обрабатываться корректно")
+ void testMultipleSortEvenValuesOnlyCalls() {
+ SortingStrategy strategy = new TestSortingStrategy("Test Strategy");
+ CustomCollection clients1 = new CustomCollection<>();
+ CustomCollection clients2 = new CustomCollection<>();
+
+ strategy.sortEvenValuesOnly(clients1);
+ strategy.sortEvenValuesOnly(clients2);
+
+ assertTrue(((TestSortingStrategy) strategy).isSortEvenValuesOnlyCalled(),
+ "После нескольких вызовов sortEvenValuesOnly флаг вызова должен оставаться установленным");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/userInterface/AppControllerTest.java b/src/test/java/userInterface/AppControllerTest.java
new file mode 100644
index 0000000..3bcc855
--- /dev/null
+++ b/src/test/java/userInterface/AppControllerTest.java
@@ -0,0 +1,152 @@
+package userInterface;
+
+import dto.Client;
+import enums.Field;
+import input.CustomCollection;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class AppControllerTest {
+
+ private AppController controller;
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ @BeforeEach
+ void setUp() {
+ controller = new AppController();
+ // Перенаправляем System.out в ByteArrayOutputStream для перехвата вывода
+ System.setOut(new PrintStream(outputStream));
+ }
+
+ @Test
+ @DisplayName("startDefaultSorting должен отсортировать список и вывести всех клиентов")
+ void testStartDefaultSorting() {
+ // Arrange: добавляем тестовых клиентов
+ Client client1 = new Client.ClientBuilder().name("Иван").phoneNumber("+79991234567").idNumber(2).build();
+ Client client2 = new Client.ClientBuilder().name("Пётр").phoneNumber("+79997654321").idNumber(1).build();
+ controller.getFullList().add(client1);
+ controller.getFullList().add(client2);
+
+ // Act
+ controller.startDefaultSorting();
+
+ // Assert: проверяем, что вывод содержит обоих клиентов (порядок после сортировки: client2, client1)
+ String output = outputStream.toString();
+ assertTrue(output.contains("Иван") && output.contains("Пётр"),
+ "Вывод должен содержать всех клиентов после сортировки");
+ }
+
+ @Test
+ @DisplayName("startDynamicSorting должен отсортировать по указанному полю")
+ void testStartDynamicSorting() {
+ // Arrange: сортировка по имени (Field.NAME)
+ Client client1 = new Client.ClientBuilder().name("Сергей").phoneNumber("+79993333333").idNumber(5).build();
+ Client client2 = new Client.ClientBuilder().name("Алексей").phoneNumber("+79994444444").idNumber(6).build();
+ controller.getFullList().add(client1);
+ controller.getFullList().add(client2);
+
+ // Act: сортировка по полю имени
+ controller.startDynamicSorting(Field.NAME);
+
+ // Assert: после сортировки первым должен идти «Алексей»
+ String output = outputStream.toString();
+ int alexeyIndex = output.indexOf("Алексей");
+ int sergeyIndex = output.indexOf("Сергей");
+ assertTrue(alexeyIndex < sergeyIndex,
+ "После сортировки по имени 'Алексей' должен идти перед 'Сергеем'");
+ }
+
+ @Test
+ @DisplayName("getFullList должен возвращать актуальную коллекцию клиентов")
+ void testGetFullList() {
+ // Arrange: заполняем коллекцию
+ Client client = new Client.ClientBuilder().name("Тест").phoneNumber("+70000000000").idNumber(99).build();
+ controller.getFullList().add(client);
+
+ // Act
+ CustomCollection list = controller.getFullList();
+
+ // Assert
+ assertEquals(1, list.size());
+ assertEquals("Тест", list.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("showAndWriteAllClients должен выводить и записывать всех клиентов в файл")
+ void testShowAndWriteAllClients() {
+ // Arrange
+ Client client = new Client.ClientBuilder().name("ВыводТест").phoneNumber("+71111111111").idNumber(77).build();
+ controller.getFullList().add(client);
+
+ // Act
+ controller.showAndWriteAllClients();
+
+ // Assert: вывод должен содержать имя клиента
+ String output = outputStream.toString();
+ assertTrue(output.contains("ВыводТест"),
+ "showAndWriteAllClients должен вывести всех клиентов из fullList");
+ }
+
+ @Test
+ @DisplayName("startFileReaderStrategy должен загрузить клиентов из файла и посчитать Алексеев")
+ void testStartFileReaderStrategy() {
+
+ File testFile = new File("test_clients.txt");
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(testFile))) {
+ writer.write("Алексей|+79991112233|1\n");
+ writer.write("Мария|+79992223344|2\n");
+ } catch (Exception e){
+ throw new RuntimeException(e);
+ }
+
+ // Arrange: предполагаем, что createFileStrategy корректно создаёт стратегию для тестового файла
+ // В реальных тестах нужно подготовить файл с данными, содержащими хотя бы одного «Алексея»
+
+ String testFilePath = "test_clients.txt";
+
+ // Act: запускаем загрузку из файла
+ controller.startFileReaderStrategy(testFilePath);
+
+ // Assert: проверяем, что fullList не пуста (данные загружены)
+ assertFalse(controller.getFullList().isEmpty(),
+ "fullList должен быть заполнен после загрузки из файла");
+
+ // Проверяем, что в выводе есть строка с количеством Алексеев
+ String output = outputStream.toString();
+ assertTrue(output.contains("Добавлено Алексеев:"),
+ "Вывод должен содержать количество добавленных Алексеев");
+ }
+
+ @Test
+ @DisplayName("startRandomDataStrategy должен сгенерировать случайных клиентов и посчитать Алексеев")
+ void testStartRandomDataStrategy() {
+ // Arrange: генерируем 3 случайных клиента
+
+ int count = 3;
+
+ // Act
+ controller.startRandomDataStrategy(count);
+
+ // Assert: fullList должен содержать 3 клиента
+ assertEquals(count, controller.getFullList().size(),
+ "fullList должен содержать указанное количество сгенерированных клиентов");
+
+ // Проверяем вывод количества Алексеев
+ String output = outputStream.toString();
+ assertTrue(output.contains("Добавлено Алексеев:"),
+ "Вывод должен содержать количество добавленных Алексеев");
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Восстанавливаем оригинальный System.out после теста
+ System.setOut(originalOut);
+ }
+}
diff --git a/src/test/java/userInterface/ConcurrentCounterTest.java b/src/test/java/userInterface/ConcurrentCounterTest.java
new file mode 100644
index 0000000..a9e047c
--- /dev/null
+++ b/src/test/java/userInterface/ConcurrentCounterTest.java
@@ -0,0 +1,225 @@
+package userInterface;
+
+import dto.Client;
+import input.CustomCollection;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.*;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ConcurrentCounterTest {
+
+
+ private final ConcurrentCounter counter = new ConcurrentCounter();
+
+ @Test
+ @DisplayName("countAlexes должен вернуть 0 для пустой коллекции")
+ void testCountAlexesEmptyCollection() {
+ // Arrange
+ CustomCollection emptyClients = new CustomCollection<>();
+
+ // Act
+ int result = counter.countAlexes(emptyClients);
+
+ // Assert
+ assertEquals(0, result, "Для пустой коллекции должно быть 0 Алексеев");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен вернуть 0 если нет клиентов с именем Алексей")
+ void testCountAlexesNoAlexes() {
+ // Arrange
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Иван Иванов").build());
+ clients.add(new Client.ClientBuilder().name("Мария Петрова").build());
+ clients.add(new Client.ClientBuilder().name("Пётр Сидоров").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert
+ assertEquals(0, result, "Если нет Алексеев, результат должен быть 0");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен корректно посчитать одного Алексея")
+ void testCountAlexesOneAlex() {
+ // Arrange
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей Иванов").build());
+ clients.add(new Client.ClientBuilder().name("Мария Петрова").build());
+ clients.add(new Client.ClientBuilder().name("Пётр Сидоров").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert
+ assertEquals(1, result, "Должен быть найден 1 Алексей");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен корректно посчитать нескольких Алексеев")
+ void testCountAlexesMultipleAlexes() {
+ // Arrange
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей Иванов").build());
+ clients.add(new Client.ClientBuilder().name("Мария Петрова").build());
+ clients.add(new Client.ClientBuilder().name("Алексей Сидоров").build());
+ clients.add(new Client.ClientBuilder().name("Анна Козлова").build());
+ clients.add(new Client.ClientBuilder().name("Алексей Николаев").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert
+ assertEquals(3, result, "Должно быть найдено 3 Алексея");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен найти Алексея даже если имя содержит дополнительные слова")
+ void testCountAlexesNameWithAdditionalWords() {
+ // Arrange
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей Владимирович Иванов").build());
+ clients.add(new Client.ClientBuilder().name("Сергей Алексеевич Петров").build()); // не должен учитываться
+ clients.add(new Client.ClientBuilder().name("Алексей").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert
+ assertEquals(2, result, "Должны быть найдены оба Алексея, даже с дополнительными словами в имени");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен корректно работать с нечётным количеством клиентов")
+ void testCountAlexesOddNumberOfClients() {
+ // Arrange: 5 клиентов, 2 из которых — Алексей
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей Иванов").build());
+ clients.add(new Client.ClientBuilder().name("Мария Петрова").build());
+ clients.add(new Client.ClientBuilder().name("Алексей Сидоров").build());
+ clients.add(new Client.ClientBuilder().name("Анна Козлова").build());
+ clients.add(new Client.ClientBuilder().name("Пётр Николаев").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert: коллекция делится на 2 и 3 элемента, но результат должен быть корректным
+ assertEquals(2, result, "Для нечётного количества клиентов должно быть найдено 2 Алексея");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен корректно работать с чётным количеством клиентов")
+ void testCountAlexesEvenNumberOfClients() {
+ // Arrange: 4 клиента, 2 из которых — Алексей
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей Иванов").build());
+ clients.add(new Client.ClientBuilder().name("Мария Петрова").build());
+ clients.add(new Client.ClientBuilder().name("Алексей Сидоров").build());
+ clients.add(new Client.ClientBuilder().name("Анна Козлова").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert: коллекция делится на 2 части по 2 элемента
+ assertEquals(2, result, "Для чётного количества клиентов должно быть найдено 2 Алексея");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен обработать коллекцию с одним клиентом — Алексеем")
+ void testCountAlexesSingleClientAlex() {
+ // Arrange
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert
+ assertEquals(1, result, "Для одного клиента-Алексея результат должен быть 1");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен обработать коллекцию с одним клиентом не-Алексеем")
+ void testCountAlexesSingleClientNotAlex() {
+ // Arrange
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Иван").build());
+
+ // Act
+ int result = counter.countAlexes(clients);
+
+ // Assert
+ assertEquals(0, result, "Для одного клиента не-Алексея результат должен быть 0");
+ }
+
+ @Test
+ @DisplayName("countAlexes должен выбрасывать RuntimeException при InterruptedException")
+ void testCountAlexesThrowsOnInterruptedException() throws Exception {
+ // Arrange: создаём кастомный ConcurrentCounter, который имитирует InterruptedException
+ ConcurrentCounter faultyCounter = new ConcurrentCounter() {
+ @Override
+ public int countAlexes(CustomCollection clients) {
+ ExecutorService executorService = Executors.newFixedThreadPool(2);
+ Future count1 = executorService.submit(() -> 0L);
+ executorService.shutdown();
+ try {
+ Thread.currentThread().interrupt();
+ count1.get(); // вызовет InterruptedException
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ return 0;
+ }
+ };
+
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей").build());
+
+ // Act & Assert
+ assertThrows(RuntimeException.class,
+ () -> faultyCounter.countAlexes(clients),
+ "При InterruptedException должен выбрасываться RuntimeException"
+ );
+ }
+
+ @Test
+ @DisplayName("countAlexes должен выбрасывать RuntimeException при ExecutionException")
+ void testCountAlexesThrowsOnExecutionException() throws Exception {
+ // Arrange: имитируем ExecutionException
+ ConcurrentCounter faultyCounter = new ConcurrentCounter() {
+ @Override
+ public int countAlexes(CustomCollection clients) {
+ ExecutorService executorService = Executors.newFixedThreadPool(2);
+ Future count1 = executorService.submit(new Callable() {
+ @Override
+ public Long call() throws Exception {
+ throw new Exception("Simulated execution error");
+ }
+ });
+ executorService.shutdown();
+ try {
+ count1.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ return 0;
+ }
+ };
+
+ CustomCollection clients = new CustomCollection<>();
+ clients.add(new Client.ClientBuilder().name("Алексей").build());
+
+// Act & Assert
+ assertThrows(RuntimeException.class,
+ () -> faultyCounter.countAlexes(clients),
+ "При ExecutionException должен выбрасываться RuntimeException"
+ );
+ }
+}
\ No newline at end of file
diff --git a/test_clients.txt b/test_clients.txt
new file mode 100644
index 0000000..ff44f20
--- /dev/null
+++ b/test_clients.txt
@@ -0,0 +1,2 @@
+Алексей|+79991112233|1
+Мария|+79992223344|2
diff --git a/testfile_clients.txt b/testfile_clients.txt
new file mode 100644
index 0000000..de72660
--- /dev/null
+++ b/testfile_clients.txt
@@ -0,0 +1,30 @@
+Шаров Юрий|+79408809462|699
+Кулагин Дмитрий|+79019463584|93
+Мухина Раиса Валентиновна|+79204742853|512
+Мухина Алина Сергеевна|+79841015291|296
+Аркадий Игнатьевич Тихонов|+79337132205|23
+Большаков Матвей Эдуардович|+79051246042|591
+Игнатьева Зинаида Вячеславовна|+79468715374|813
+Оксана Кулагина|+79538510155|860
+Бобылев Вячеслав|+79549089324|573
+Андреева Дарья|+79384195474|999
+Игнатий Ильич Антонов|+79946035137|829
+Фокин Матвей Витальевич|+79274394409|933
+Анжела Викторовна Блохина|+79296568824|661
+Ефремова Ольга Вячеславовна|+79201039302|778
+Русакова Валентина|+79499915311|3
+Антон Воронов|+79938685129|262
+Субботина Евгения|+79883134731|275
+Фадеев Игорь Даниилович|+79907800005|194
+Панфилова Арина|+79678575110|927
+Попов Роман Евгеньевич|+79392077705|996
+Гаврилова Алла Владимировна|+79303034501|387
+Екатерина Филатова|+79524244998|669
+Виктор Леонидович Зиновьев|+79272614762|627
+Анжелика Кошелева|+79804707713|78
+Медведева Валентина|+79282932301|594
+Василиса Антоновна Лапина|+79971387677|853
+Вера Артёмовна Капустина|+79591168118|461
+Лаврентьев Вячеслав Сергеевич|+79639495445|459
+Маргарита Викторовна Кузьмина|+79571424972|549
+Вероника Моисеева|+79058575995|889
diff --git a/wrongTestfile_clients.txt b/wrongTestfile_clients.txt
new file mode 100644
index 0000000..43c97a0
--- /dev/null
+++ b/wrongTestfile_clients.txt
@@ -0,0 +1,30 @@
+Шаров Юрий;+79408809462;699
+Кулагин Дмитрий;+79019463584;93
+Мухина Раиса Валентиновна;+79204742853;512
+Мухина Алина Сергеевна;+79841015291;296
+Аркадий Игнатьевич Тихонов;+79337132205;23
+Большаков Матвей Эдуардович;+79051246042;591
+Игнатьева Зинаида Вячеславовна;+79468715374;813
+Оксана Кулагина;+79538510155;860
+Бобылев Вячеслав;+79549089324;573
+Андреева Дарья;+79384195474;999
+Игнатий Ильич Антонов;+79946035137;829
+Фокин Матвей Витальевич;+79274394409;933
+Анжела Викторовна Блохина;+79296568824;661
+Ефремова Ольга Вячеславовна;+79201039302;778
+Русакова Валентина;+79499915311;3
+Антон Воронов;+79938685129;262
+Субботина Евгения;+79883134731;275
+Фадеев Игорь Даниилович;+79907800005;194
+Панфилова Арина;+79678575110;927
+Попов Роман Евгеньевич;+79392077705;996
+Гаврилова Алла Владимировна;+79303034501;387
+Екатерина Филатова;+79524244998;669
+Виктор Леонидович Зиновьев;+79272614762;627
+Анжелика Кошелева;+79804707713;78
+Медведева Валентина;+79282932301;594
+Василиса Антоновна Лапина;+79971387677;853
+Вера Артёмовна Капустина;+79591168118;461
+Лаврентьев Вячеслав Сергеевич;+79639495445;459
+Маргарита Викторовна Кузьмина;+79571424972;549
+Вероника Моисеева;+79058575995;889