From 06767c91961cef473a352ed8397224af39b04829 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Tue, 14 Apr 2026 17:43:07 +0300 Subject: [PATCH 01/45] feat: add duration and start time to Task --- src/model/Task.java | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/model/Task.java b/src/model/Task.java index 8ed25ad..71b3bd5 100644 --- a/src/model/Task.java +++ b/src/model/Task.java @@ -1,5 +1,7 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.Objects; public class Task { @@ -7,11 +9,19 @@ public class Task { private String name; private String description; private Status status; + private Duration duration; + private LocalDateTime startTime; public Task(String name, String description, Status status) { + this(name, description, status, null, null); + } + + public Task(String name, String description, Status status, Duration duration, LocalDateTime startTime) { this.name = name; this.description = description; this.status = status; + this.duration = duration; + this.startTime = startTime; } public int getId() { @@ -46,6 +56,29 @@ public void setStatus(Status status) { this.status = status; } + public Duration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + if (startTime == null || duration == null) { + return null; + } + return startTime.plus(duration); + } + // Переопределение методов equals и hashCode для ID @Override public boolean equals(Object obj) { @@ -67,6 +100,9 @@ public String toString() { + ", name='" + name + '\'' + ", description='" + description + '\'' + ", status=" + status + + ", duration=" + duration + + ", startTime=" + startTime + + ", endTime=" + getEndTime() + '}'; } } From 7853dc76eb1ff7c22ba28cdae14b0f20417c1d90 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Tue, 14 Apr 2026 17:46:49 +0300 Subject: [PATCH 02/45] feat: add duration and start time to Subtask --- src/model/Subtask.java | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/model/Subtask.java b/src/model/Subtask.java index 50b0071..8abe94a 100644 --- a/src/model/Subtask.java +++ b/src/model/Subtask.java @@ -1,12 +1,19 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.Objects; public class Subtask extends Task { private int epicId; public Subtask(String name, String description, Status status, int epicId) { - super(name, description, status); + this(name, description, status, null, null, epicId); + } + + public Subtask(String name, String description, Status status, + Duration duration, LocalDateTime startTime, int epicId) { + super(name, description, status, duration, startTime); this.epicId = epicId; } @@ -44,12 +51,15 @@ public int hashCode() { @Override public String toString() { - return "Subtask{" + - "id=" + getId() + - ", name='" + getName() + '\'' + - ", description='" + getDescription() + '\'' + - ", status=" + getStatus() + - ", epicId=" + epicId + - '}'; + return "Subtask{" + + "id=" + getId() + + ", name='" + getName() + '\'' + + ", description='" + getDescription() + '\'' + + ", status=" + getStatus() + + ", duration=" + getDuration() + + ", startTime=" + getStartTime() + + ", endTime=" + getEndTime() + + ", epicId=" + epicId + + '}'; } } From d1eda1c0c94dbeaeb43c2b0c8d6d32baa755c6a4 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Tue, 14 Apr 2026 17:49:37 +0300 Subject: [PATCH 03/45] feat: add calculated time fields to Epic --- src/model/Epic.java | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/model/Epic.java b/src/model/Epic.java index 00edfd5..0e08e37 100644 --- a/src/model/Epic.java +++ b/src/model/Epic.java @@ -1,9 +1,12 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; public class Epic extends Task { private ArrayList subtaskIds = new ArrayList<>(); + private LocalDateTime endTime; public Epic(String name, String description) { super(name, description, Status.NEW); @@ -34,14 +37,34 @@ public void removeSubtaskId(Integer subtaskId) { subtaskIds.remove(subtaskId); } + public void setEpicDuration(Duration duration) { + setDuration(duration); + } + + public void setEpicStartTime(LocalDateTime startTime) { + setStartTime(startTime); + } + + public void setEpicEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + @Override + public LocalDateTime getEndTime() { + return endTime; + } + @Override public String toString() { - return "Epic{" + - "id=" + getId() + - ", name='" + getName() + '\'' + - ", description='" + getDescription() + '\'' + - ", status=" + getStatus() + - ", subtaskIds=" + subtaskIds + - '}'; + return "Epic{" + + "id=" + getId() + + ", name='" + getName() + '\'' + + ", description='" + getDescription() + '\'' + + ", status=" + getStatus() + + ", duration=" + getDuration() + + ", startTime=" + getStartTime() + + ", endTime=" + getEndTime() + + ", subtaskIds=" + subtaskIds + + '}'; } } From a4aaf79621eab5085e253ef17a758752d8b288dd Mon Sep 17 00:00:00 2001 From: Ksenia Date: Tue, 14 Apr 2026 17:53:05 +0300 Subject: [PATCH 04/45] fix: use defensive copy in Epic setSubtaskIds --- src/model/Epic.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/Epic.java b/src/model/Epic.java index 0e08e37..1c52eb1 100644 --- a/src/model/Epic.java +++ b/src/model/Epic.java @@ -17,7 +17,7 @@ public ArrayList getSubtaskIds() { } public void setSubtaskIds(ArrayList subtaskIds) { - this.subtaskIds = subtaskIds; + this.subtaskIds = new ArrayList<>(subtaskIds); } // Очищение всех ID подзадач @@ -67,4 +67,4 @@ public String toString() { + ", subtaskIds=" + subtaskIds + '}'; } -} +} \ No newline at end of file From c7c5c4681082653857bb85578e884e51d531edb3 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 13:36:29 +0300 Subject: [PATCH 05/45] fix: return full copies of tasks with time fields --- src/service/InMemoryTaskManager.java | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index b2d6126..811bf07 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -87,11 +87,19 @@ public void clearSubtasks() { @Override public Task getTask(int id) { Task task = tasks.get(id); - if (task == null) return null; + if (task == null) { + return null; + } historyManager.add(task); - Task copy = new Task(task.getName(), task.getDescription(), task.getStatus()); + Task copy = new Task( + task.getName(), + task.getDescription(), + task.getStatus(), + task.getDuration(), + task.getStartTime() + ); copy.setId(task.getId()); return copy; @@ -100,7 +108,9 @@ public Task getTask(int id) { @Override public Epic getEpic(int id) { Epic epic = epics.get(id); - if (epic == null) return null; + if (epic == null) { + return null; + } historyManager.add(epic); @@ -108,6 +118,9 @@ public Epic getEpic(int id) { copy.setId(epic.getId()); copy.setStatus(epic.getStatus()); copy.setSubtaskIds(epic.getSubtaskIds()); + copy.setEpicDuration(epic.getDuration()); + copy.setEpicStartTime(epic.getStartTime()); + copy.setEpicEndTime(epic.getEndTime()); return copy; } @@ -115,7 +128,9 @@ public Epic getEpic(int id) { @Override public Subtask getSubtask(int id) { Subtask subtask = subtasks.get(id); - if (subtask == null) return null; + if (subtask == null) { + return null; + } historyManager.add(subtask); @@ -123,6 +138,8 @@ public Subtask getSubtask(int id) { subtask.getName(), subtask.getDescription(), subtask.getStatus(), + subtask.getDuration(), + subtask.getStartTime(), subtask.getEpicId() ); copy.setId(subtask.getId()); From 7e81a478e0751c807c6a4ab82e3e92179011cc6f Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 15:05:07 +0300 Subject: [PATCH 06/45] feat: add prioritized tasks storage with TreeSet --- src/service/InMemoryTaskManager.java | 91 +++++++++++++++++++++------- src/service/TaskManager.java | 7 ++- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 811bf07..d78942e 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -6,8 +6,10 @@ import model.Epic; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.TreeSet; public class InMemoryTaskManager implements service.TaskManager { // Хеш-таблицы для хранения задач для классов Task, Epic, Subtask @@ -15,18 +17,24 @@ public class InMemoryTaskManager implements service.TaskManager { private HashMap epics = new HashMap<>(); private HashMap subtasks = new HashMap<>(); + // Хранение задач и подзадач в отсортированном порядке по времени начала + private final TreeSet prioritizedTasks = new TreeSet<>( + Comparator.comparing(Task::getStartTime) + .thenComparing(Task::getId) + ); + // Менеджер истории private final HistoryManager historyManager = Managers.getDefaultHistory(); // Счетчик для генерации ID private int nextId = 1; - // Метод для увеличения ID + // Получение следующего идентификатора задачи private int getNextId() { return nextId++; } - // Получение списка всех задач для каждого из типов задач(Задача/Эпик/Подзадача) + // Получение списков задач, эпиков и подзадач @Override public ArrayList getTasks() { return new ArrayList<>(tasks.values()); @@ -42,22 +50,25 @@ public ArrayList getSubtasks() { return new ArrayList<>(subtasks.values()); } - // Удаление всех задач для каждого из типов задач(Задача/Эпик/Подзадача) + // Удаление всех задач @Override public void clearTasks() { - // Удаление каждой задачи из истории перед очисткой хранилища - for (int id : tasks.keySet()) { - historyManager.remove(id); + // Удаление каждой задачи из истории и списка приоритетов перед очисткой хранилища + for (Task task : tasks.values()) { + prioritizedTasks.remove(task); + historyManager.remove(task.getId()); } tasks.clear(); } @Override public void clearEpics() { - // Сначала удаление всех подзадач из истории - for (int id : subtasks.keySet()) { - historyManager.remove(id); + // Сначала удаление всех подзадач из истории и списка приоритетов + for (Subtask subtask : subtasks.values()) { + prioritizedTasks.remove(subtask); + historyManager.remove(subtask.getId()); } + // Затем самих эпиков for (int id : epics.keySet()) { historyManager.remove(id); @@ -69,21 +80,22 @@ public void clearEpics() { @Override public void clearSubtasks() { - // Удаление каждой подзадачи из истории - for (int id : subtasks.keySet()) { - historyManager.remove(id); + // Удаление каждой подзадачи из истории и списка приоритетов + for (Subtask subtask : subtasks.values()) { + prioritizedTasks.remove(subtask); + historyManager.remove(subtask.getId()); } subtasks.clear(); - // Чистка Эпиков, если подзадач больше нет + // Очистка эпиков, если подзадач больше нет for (Epic epic : epics.values()) { epic.clearSubtaskIds(); epic.setStatus(Status.NEW); } } - // Получение по идентификатору для каждого из типов задач(Задача/Эпик/Подзадача) + // Получение задачи, эпика или подзадачи по идентификатору @Override public Task getTask(int id) { Task task = tasks.get(id); @@ -147,18 +159,33 @@ public Subtask getSubtask(int id) { return copy; } - // Возвращение списка истории + // Получение списка истории @Override public List getHistory() { return historyManager.getHistory(); } - // Создание для каждого из типов задач(Задача/Эпик/Подзадача) + // Получение задач и подзадач в порядке приоритета по времени начала + @Override + public List getPrioritizedTasks() { + return new ArrayList<>(prioritizedTasks); + } + + // Добавление задачи или подзадачи в список приоритетов, если задано время начала + protected void addTaskToPrioritizedTasks(Task task) { + if (task.getStartTime() != null) { + prioritizedTasks.add(task); + } + } + + // Создание задач, эпиков и подзадач @Override public Task createTask(Task task) { task.setId(getNextId()); task.setStatus(Status.NEW); tasks.put(task.getId(), task); + addTaskToPrioritizedTasks(task); + return task; } @@ -175,6 +202,7 @@ public Subtask createSubtask(Subtask subtask) { subtask.setId(getNextId()); subtask.setStatus(Status.NEW); subtasks.put(subtask.getId(), subtask); + addTaskToPrioritizedTasks(subtask); // Добавляем в Эпик ID новой Подзадачи Epic epic = epics.get(subtask.getEpicId()); @@ -186,12 +214,15 @@ public Subtask createSubtask(Subtask subtask) { return subtask; } - // Обновление задач для каждого из типов задач(Задача/Эпик/Подзадача) + // Обновление задачи с синхронизацией списка приоритетов @Override public void updateTask(Task task) { - // Проверка на наличие задачи if (tasks.containsKey(task.getId())) { + Task oldTask = tasks.get(task.getId()); + + prioritizedTasks.remove(oldTask); tasks.put(task.getId(), task); + addTaskToPrioritizedTasks(task); } } @@ -238,9 +269,13 @@ protected void updateEpicStatus(Epic epic) { @Override public void updateSubtask(Subtask subtask) { if (subtasks.containsKey(subtask.getId())) { + Subtask oldSubtask = subtasks.get(subtask.getId()); + + prioritizedTasks.remove(oldSubtask); subtasks.put(subtask.getId(), subtask); + addTaskToPrioritizedTasks(subtask); - // Пересчёт статуса Эпик после обновления Подзадач + // Пересчёт статуса эпика после обновления подзадачи Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { updateEpicStatus(epic); @@ -251,7 +286,10 @@ public void updateSubtask(Subtask subtask) { // Удаление по идентификатору для каждого из типов задач(Задача/Эпик/Подзадача) @Override public void deleteTask(int id) { - tasks.remove(id); + Task removedTask = tasks.remove(id); + if (removedTask != null) { + prioritizedTasks.remove(removedTask); + } historyManager.remove(id); } @@ -261,7 +299,10 @@ public void deleteEpic(int id) { if (epic != null) { // Удаление подзадач, связанных с этим Эпиком for (Integer subtaskId : epic.getSubtaskIds()) { - subtasks.remove(subtaskId); + Subtask removedSubtask = subtasks.remove(subtaskId); + if (removedSubtask != null) { + prioritizedTasks.remove(removedSubtask); + } historyManager.remove(subtaskId); } } @@ -272,6 +313,8 @@ public void deleteEpic(int id) { public void deleteSubtask(int id) { Subtask subtask = subtasks.remove(id); if (subtask != null) { + prioritizedTasks.remove(subtask); + // Удаление ID Подзадачи из Эпика Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { @@ -282,8 +325,8 @@ public void deleteSubtask(int id) { historyManager.remove(id); } - // Получение списка всех Подзадач для определённого Эпика - public ArrayList getEpicSubtasks(int epicId) { + // Получение списка подзадач определённого эпика + public List getEpicSubtasks(int epicId) { ArrayList result = new ArrayList<>(); Epic epic = epics.get(epicId); if (epic != null) { @@ -297,6 +340,7 @@ public ArrayList getEpicSubtasks(int epicId) { // Служебные методы для восстановления менеджера из файла protected void putLoadedTask(Task task) { tasks.put(task.getId(), task); + addTaskToPrioritizedTasks(task); } protected void putLoadedEpic(Epic epic) { @@ -305,6 +349,7 @@ protected void putLoadedEpic(Epic epic) { protected void putLoadedSubtask(Subtask subtask) { subtasks.put(subtask.getId(), subtask); + addTaskToPrioritizedTasks(subtask); Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { diff --git a/src/service/TaskManager.java b/src/service/TaskManager.java index b70e41d..85c6c17 100644 --- a/src/service/TaskManager.java +++ b/src/service/TaskManager.java @@ -53,6 +53,9 @@ public interface TaskManager { void deleteSubtask(int id); - // Получение списка всех Подзадач для определённого Эпика - ArrayList getEpicSubtasks(int epicId); + // Получение подзадач эпика по id + List getEpicSubtasks(int epicId); + + // Получение задач и подзадач в порядке приоритета (по startTime) + List getPrioritizedTasks(); } From 3b4b597f5b1e133dbe12dd5f9572eee53045cb27 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 15:57:24 +0300 Subject: [PATCH 07/45] feat: add task time overlap validation --- src/service/InMemoryTaskManager.java | 45 ++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index d78942e..f784ffb 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -171,6 +171,27 @@ public List getPrioritizedTasks() { return new ArrayList<>(prioritizedTasks); } + // Проверка пересечения двух задач по времени выполнения + protected boolean isTaskOverlapping(Task firstTask, Task secondTask) { + if (firstTask.getStartTime() == null || secondTask.getStartTime() == null) { + return false; + } + + if (firstTask.getEndTime() == null || secondTask.getEndTime() == null) { + return false; + } + + return firstTask.getStartTime().isBefore(secondTask.getEndTime()) + && secondTask.getStartTime().isBefore(firstTask.getEndTime()); + } + + // Проверка пересечения задачи с уже существующими задачами и подзадачами + protected boolean hasTimeOverlap(Task task) { + return prioritizedTasks.stream() + .anyMatch(prioritizedTask -> prioritizedTask.getId() != task.getId() + && isTaskOverlapping(task, prioritizedTask)); + } + // Добавление задачи или подзадачи в список приоритетов, если задано время начала protected void addTaskToPrioritizedTasks(Task task) { if (task.getStartTime() != null) { @@ -181,6 +202,10 @@ protected void addTaskToPrioritizedTasks(Task task) { // Создание задач, эпиков и подзадач @Override public Task createTask(Task task) { + if (hasTimeOverlap(task)) { + throw new IllegalArgumentException("Задача пересекается по времени с другой задачей."); + } + task.setId(getNextId()); task.setStatus(Status.NEW); tasks.put(task.getId(), task); @@ -199,12 +224,16 @@ public Epic createEpic(Epic epic) { @Override public Subtask createSubtask(Subtask subtask) { + if (hasTimeOverlap(subtask)) { + throw new IllegalArgumentException("Подзадача пересекается по времени с другой задачей."); + } + subtask.setId(getNextId()); subtask.setStatus(Status.NEW); subtasks.put(subtask.getId(), subtask); addTaskToPrioritizedTasks(subtask); - // Добавляем в Эпик ID новой Подзадачи + // Добавляем в эпик id новой подзадачи Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { epic.addSubtaskId(subtask.getId()); @@ -221,6 +250,12 @@ public void updateTask(Task task) { Task oldTask = tasks.get(task.getId()); prioritizedTasks.remove(oldTask); + + if (hasTimeOverlap(task)) { + addTaskToPrioritizedTasks(oldTask); + throw new IllegalArgumentException("Обновлённая задача пересекается по времени с другой задачей."); + } + tasks.put(task.getId(), task); addTaskToPrioritizedTasks(task); } @@ -272,6 +307,12 @@ public void updateSubtask(Subtask subtask) { Subtask oldSubtask = subtasks.get(subtask.getId()); prioritizedTasks.remove(oldSubtask); + + if (hasTimeOverlap(subtask)) { + addTaskToPrioritizedTasks(oldSubtask); + throw new IllegalArgumentException("Обновлённая подзадача пересекается по времени с другой задачей."); + } + subtasks.put(subtask.getId(), subtask); addTaskToPrioritizedTasks(subtask); @@ -283,7 +324,7 @@ public void updateSubtask(Subtask subtask) { } } - // Удаление по идентификатору для каждого из типов задач(Задача/Эпик/Подзадача) + // Удаление задачи, эпика или подзадачи по идентификатору @Override public void deleteTask(int id) { Task removedTask = tasks.remove(id); From 07ab5ea67943e03c86e2bf9ea65396b6a910fddf Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 16:26:57 +0300 Subject: [PATCH 08/45] feat: calculate epic duration and time based on subtasks --- src/service/InMemoryTaskManager.java | 57 ++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index f784ffb..4e111af 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -5,6 +5,8 @@ import model.Subtask; import model.Epic; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -13,9 +15,9 @@ public class InMemoryTaskManager implements service.TaskManager { // Хеш-таблицы для хранения задач для классов Task, Epic, Subtask - private HashMap tasks = new HashMap<>(); - private HashMap epics = new HashMap<>(); - private HashMap subtasks = new HashMap<>(); + private final HashMap tasks = new HashMap<>(); + private final HashMap epics = new HashMap<>(); + private final HashMap subtasks = new HashMap<>(); // Хранение задач и подзадач в отсортированном порядке по времени начала private final TreeSet prioritizedTasks = new TreeSet<>( @@ -92,6 +94,7 @@ public void clearSubtasks() { for (Epic epic : epics.values()) { epic.clearSubtaskIds(); epic.setStatus(Status.NEW); + updateEpicTime(epic); } } @@ -238,6 +241,7 @@ public Subtask createSubtask(Subtask subtask) { if (epic != null) { epic.addSubtaskId(subtask.getId()); updateEpicStatus(epic); + updateEpicTime(epic); } return subtask; @@ -273,7 +277,7 @@ public void updateEpic(Epic epic) { } } - // Метод для обновления статуса Эпик при обновлении Подзадач + // Пересчёт статуса эпика на основе его подзадач protected void updateEpicStatus(Epic epic) { if (epic.getSubtaskIds().isEmpty()) { epic.setStatus(Status.NEW); @@ -301,6 +305,47 @@ protected void updateEpicStatus(Epic epic) { } } + // Пересчёт времени эпика на основе его подзадач + protected void updateEpicTime(Epic epic) { + if (epic.getSubtaskIds().isEmpty()) { + epic.setEpicDuration(null); + epic.setEpicStartTime(null); + epic.setEpicEndTime(null); + return; + } + + long totalDurationInMinutes = 0; + LocalDateTime earliestStartTime = null; + LocalDateTime latestEndTime = null; + + for (int subtaskId : epic.getSubtaskIds()) { + Subtask subtask = subtasks.get(subtaskId); + if (subtask == null) { + continue; + } + + if (subtask.getDuration() != null) { + totalDurationInMinutes += subtask.getDuration().toMinutes(); + } + + if (subtask.getStartTime() != null) { + if (earliestStartTime == null || subtask.getStartTime().isBefore(earliestStartTime)) { + earliestStartTime = subtask.getStartTime(); + } + } + + if (subtask.getEndTime() != null) { + if (latestEndTime == null || subtask.getEndTime().isAfter(latestEndTime)) { + latestEndTime = subtask.getEndTime(); + } + } + } + + epic.setEpicDuration(Duration.ofMinutes(totalDurationInMinutes)); + epic.setEpicStartTime(earliestStartTime); + epic.setEpicEndTime(latestEndTime); + } + @Override public void updateSubtask(Subtask subtask) { if (subtasks.containsKey(subtask.getId())) { @@ -320,6 +365,7 @@ public void updateSubtask(Subtask subtask) { Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { updateEpicStatus(epic); + updateEpicTime(epic); } } } @@ -361,6 +407,7 @@ public void deleteSubtask(int id) { if (epic != null) { epic.removeSubtaskId(id); updateEpicStatus(epic); + updateEpicTime(epic); } } historyManager.remove(id); @@ -395,6 +442,8 @@ protected void putLoadedSubtask(Subtask subtask) { Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { epic.addSubtaskId(subtask.getId()); + updateEpicStatus(epic); + updateEpicTime(epic); } } From 7ae11d242489c40176b183ee605d0352e722e270 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 16:58:47 +0300 Subject: [PATCH 09/45] feat: save and load task time fields in csv --- src/service/FileBackedTaskManager.java | 37 ++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/service/FileBackedTaskManager.java b/src/service/FileBackedTaskManager.java index 44e50ce..63328b2 100644 --- a/src/service/FileBackedTaskManager.java +++ b/src/service/FileBackedTaskManager.java @@ -12,6 +12,8 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.TreeMap; public class FileBackedTaskManager extends InMemoryTaskManager { @@ -24,7 +26,7 @@ public FileBackedTaskManager(File file) { // Сохраняет текущее состояние менеджера в CSV-файл private void save() { try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { - writer.write("id,type,name,status,description,epic"); + writer.write("id,type,name,status,description,duration,startTime,epic"); writer.newLine(); TreeMap allTasks = new TreeMap<>(); @@ -50,18 +52,27 @@ private void save() { } } - // Преобразует задачу в строку формата CSV + // Преобразует задачу в строку формата CSV с учетом времени и продолжительности private String toString(Task task) { StringBuilder builder = new StringBuilder(); builder.append(task.getId()).append(","); builder.append(task.getType()).append(","); - builder.append(task.getName()).append(","); builder.append(task.getStatus()).append(","); builder.append(task.getDescription()).append(","); - if (task instanceof Subtask) { + if (task.getDuration() != null) { + builder.append(task.getDuration().toMinutes()); + } + builder.append(","); + + if (task.getStartTime() != null) { + builder.append(task.getStartTime()); + } + builder.append(","); + + if (task.getType() == TaskType.SUBTASK) { Subtask subtask = (Subtask) task; builder.append(subtask.getEpicId()); } @@ -69,7 +80,7 @@ private String toString(Task task) { return builder.toString(); } - // Преобразует строку CSV в объект задачи + // Преобразует строку CSV в объект задачи с учетом времени и продолжительности private static Task fromString(String value) { String[] fields = value.split(",", -1); @@ -79,16 +90,26 @@ private static Task fromString(String value) { Status status = Status.valueOf(fields[3]); String description = fields[4]; + Duration duration = null; + if (!fields[5].isBlank()) { + duration = Duration.ofMinutes(Long.parseLong(fields[5])); + } + + LocalDateTime startTime = null; + if (!fields[6].isBlank()) { + startTime = LocalDateTime.parse(fields[6]); + } + Task task; if (taskType == TaskType.TASK) { - task = new Task(name, description, status); + task = new Task(name, description, status, duration, startTime); } else if (taskType == TaskType.EPIC) { task = new Epic(name, description); task.setStatus(status); } else { - int epicId = Integer.parseInt(fields[5]); - task = new Subtask(name, description, status, epicId); + int epicId = Integer.parseInt(fields[7]); + task = new Subtask(name, description, status, duration, startTime, epicId); } task.setId(id); From 5374a8e434ee7694d621f03c327e641e6d25cb3a Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 18:18:44 +0300 Subject: [PATCH 10/45] refactor: replace loops with stream api in task processing --- src/service/InMemoryTaskManager.java | 82 +++++++++++++--------------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 4e111af..0f5772a 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -284,17 +284,15 @@ protected void updateEpicStatus(Epic epic) { return; } - int countNew = 0; - int countDone = 0; - - for (int subtaskId : epic.getSubtaskIds()) { - Subtask subtask = subtasks.get(subtaskId); - if (subtask.getStatus() == Status.NEW) { - countNew++; - } else if (subtask.getStatus() == Status.DONE) { - countDone++; - } - } + long countNew = epic.getSubtaskIds().stream() + .map(subtasks::get) + .filter(subtask -> subtask.getStatus() == Status.NEW) + .count(); + + long countDone = epic.getSubtaskIds().stream() + .map(subtasks::get) + .filter(subtask -> subtask.getStatus() == Status.DONE) + .count(); if (countNew == epic.getSubtaskIds().size()) { epic.setStatus(Status.NEW); @@ -305,6 +303,7 @@ protected void updateEpicStatus(Epic epic) { } } + // Пересчёт времени эпика на основе его подзадач // Пересчёт времени эпика на основе его подзадач protected void updateEpicTime(Epic epic) { if (epic.getSubtaskIds().isEmpty()) { @@ -314,32 +313,25 @@ protected void updateEpicTime(Epic epic) { return; } - long totalDurationInMinutes = 0; - LocalDateTime earliestStartTime = null; - LocalDateTime latestEndTime = null; - - for (int subtaskId : epic.getSubtaskIds()) { - Subtask subtask = subtasks.get(subtaskId); - if (subtask == null) { - continue; - } - - if (subtask.getDuration() != null) { - totalDurationInMinutes += subtask.getDuration().toMinutes(); - } - - if (subtask.getStartTime() != null) { - if (earliestStartTime == null || subtask.getStartTime().isBefore(earliestStartTime)) { - earliestStartTime = subtask.getStartTime(); - } - } - - if (subtask.getEndTime() != null) { - if (latestEndTime == null || subtask.getEndTime().isAfter(latestEndTime)) { - latestEndTime = subtask.getEndTime(); - } - } - } + long totalDurationInMinutes = epic.getSubtaskIds().stream() + .map(subtasks::get) + .filter(subtask -> subtask.getDuration() != null) + .mapToLong(subtask -> subtask.getDuration().toMinutes()) + .sum(); + + LocalDateTime earliestStartTime = epic.getSubtaskIds().stream() + .map(subtasks::get) + .map(Subtask::getStartTime) + .filter(start -> start != null) + .min(LocalDateTime::compareTo) + .orElse(null); + + LocalDateTime latestEndTime = epic.getSubtaskIds().stream() + .map(subtasks::get) + .map(Subtask::getEndTime) + .filter(end -> end != null) + .max(LocalDateTime::compareTo) + .orElse(null); epic.setEpicDuration(Duration.ofMinutes(totalDurationInMinutes)); epic.setEpicStartTime(earliestStartTime); @@ -413,16 +405,18 @@ public void deleteSubtask(int id) { historyManager.remove(id); } - // Получение списка подзадач определённого эпика + // Получение списка подзадач эпика с использованием Stream API + @Override public List getEpicSubtasks(int epicId) { - ArrayList result = new ArrayList<>(); Epic epic = epics.get(epicId); - if (epic != null) { - for (int subtaskId : epic.getSubtaskIds()) { - result.add(subtasks.get(subtaskId)); - } + + if (epic == null) { + return new ArrayList<>(); } - return result; + + return epic.getSubtaskIds().stream() + .map(subtasks::get) + .toList(); } // Служебные методы для восстановления менеджера из файла From 9b730bd3b298b5f10dcc7f5e333035f5ce817351 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 18:21:49 +0300 Subject: [PATCH 11/45] fix: save time fields in history snapshots --- src/service/InMemoryHistoryManager.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/service/InMemoryHistoryManager.java b/src/service/InMemoryHistoryManager.java index 1a4ad28..7a60012 100644 --- a/src/service/InMemoryHistoryManager.java +++ b/src/service/InMemoryHistoryManager.java @@ -25,6 +25,8 @@ private Task makeSnapshot(Task task) { original.getName(), original.getDescription(), original.getStatus(), + original.getDuration(), + original.getStartTime(), original.getEpicId() ); copy.setId(original.getId()); @@ -35,9 +37,18 @@ private Task makeSnapshot(Task task) { copy.setId(original.getId()); copy.setStatus(original.getStatus()); copy.setSubtaskIds(original.getSubtaskIds()); + copy.setEpicDuration(original.getDuration()); + copy.setEpicStartTime(original.getStartTime()); + copy.setEpicEndTime(original.getEndTime()); return copy; } else { - Task copy = new Task(task.getName(), task.getDescription(), task.getStatus()); + Task copy = new Task( + task.getName(), + task.getDescription(), + task.getStatus(), + task.getDuration(), + task.getStartTime() + ); copy.setId(task.getId()); return copy; } From 033fdefb99cb37f9c098c244ceea28082086a540 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 22:32:25 +0300 Subject: [PATCH 12/45] test: add base task manager test class --- test/service/TaskManagerTest.java | 308 ++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 test/service/TaskManagerTest.java diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java new file mode 100644 index 0000000..7fdde9d --- /dev/null +++ b/test/service/TaskManagerTest.java @@ -0,0 +1,308 @@ +package service; + +import model.Epic; +import model.Status; +import model.Subtask; +import model.Task; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +abstract class TaskManagerTest { + + protected T taskManager; + + // Каждый наследник сам создаёт свою реализацию менеджера + protected abstract T createTaskManager(); + + @BeforeEach + void setUp() { + taskManager = createTaskManager(); + } + + // Проверка создания обычной задачи + @Test + void shouldCreateTask() { + Task task = new Task( + "Task name", + "Task description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0) + ); + + Task createdTask = taskManager.createTask(task); + + assertNotNull(createdTask, "Задача должна создаться."); + assertNotNull(taskManager.getTask(createdTask.getId()), "Задача должна находиться по id."); + assertEquals("Task name", createdTask.getName(), "Имя задачи должно сохраниться."); + assertEquals(Duration.ofMinutes(30), createdTask.getDuration(), + "Продолжительность задачи должна сохраниться."); + assertEquals(LocalDateTime.of(2026, 4, 16, 10, 0), createdTask.getStartTime(), + "Время начала задачи должно сохраниться."); + } + + // Проверка создания эпика + @Test + void shouldCreateEpic() { + Epic epic = new Epic("Epic name", "Epic description"); + + Epic createdEpic = taskManager.createEpic(epic); + + assertNotNull(createdEpic, "Эпик должен создаться."); + assertNotNull(taskManager.getEpic(createdEpic.getId()), "Эпик должен находиться по id."); + assertEquals("Epic name", createdEpic.getName(), "Имя эпика должно сохраниться."); + } + + // Проверка создания подзадачи и связи с эпиком + @Test + void shouldCreateSubtaskWithEpic() { + Epic epic = new Epic("Epic", "Epic description"); + Epic createdEpic = taskManager.createEpic(epic); + + Subtask subtask = new Subtask( + "Subtask", + "Subtask description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 12, 0), + createdEpic.getId() + ); + + Subtask createdSubtask = taskManager.createSubtask(subtask); + + assertNotNull(createdSubtask, "Подзадача должна создаться."); + assertEquals(createdEpic.getId(), createdSubtask.getEpicId(), + "Подзадача должна хранить id связанного эпика."); + + List epicSubtasks = taskManager.getEpicSubtasks(createdEpic.getId()); + assertEquals(1, epicSubtasks.size(), "У эпика должна быть одна подзадача."); + assertEquals(createdSubtask.getId(), epicSubtasks.get(0).getId(), + "Подзадача должна входить в список подзадач эпика."); + } + + // Проверка расчёта статуса эпика: все подзадачи NEW + @Test + void shouldSetEpicStatusNewWhenAllSubtasksAreNew() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + taskManager.createSubtask(new Subtask( + "Subtask 1", + "Description 1", + Status.NEW, + Duration.ofMinutes(10), + LocalDateTime.of(2026, 4, 16, 9, 0), + epic.getId() + )); + + taskManager.createSubtask(new Subtask( + "Subtask 2", + "Description 2", + Status.NEW, + Duration.ofMinutes(15), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + assertEquals(Status.NEW, savedEpic.getStatus(), + "Статус эпика должен быть NEW, если все подзадачи NEW."); + } + + // Проверка расчёта статуса эпика: все подзадачи DONE + @Test + void shouldSetEpicStatusDoneWhenAllSubtasksAreDone() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask subtask1 = taskManager.createSubtask(new Subtask( + "Subtask 1", + "Description 1", + Status.NEW, + Duration.ofMinutes(10), + LocalDateTime.of(2026, 4, 16, 9, 0), + epic.getId() + )); + + Subtask subtask2 = taskManager.createSubtask(new Subtask( + "Subtask 2", + "Description 2", + Status.NEW, + Duration.ofMinutes(15), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + subtask1.setStatus(Status.DONE); + subtask2.setStatus(Status.DONE); + + taskManager.updateSubtask(subtask1); + taskManager.updateSubtask(subtask2); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + assertEquals(Status.DONE, savedEpic.getStatus(), + "Статус эпика должен быть DONE, если все подзадачи DONE."); + } + + // Проверка расчёта статуса эпика: подзадачи NEW и DONE + @Test + void shouldSetEpicStatusInProgressWhenSubtasksAreNewAndDone() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask subtask1 = taskManager.createSubtask(new Subtask( + "Subtask 1", + "Description 1", + Status.NEW, + Duration.ofMinutes(10), + LocalDateTime.of(2026, 4, 16, 9, 0), + epic.getId() + )); + + Subtask subtask2 = taskManager.createSubtask(new Subtask( + "Subtask 2", + "Description 2", + Status.NEW, + Duration.ofMinutes(15), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + subtask2.setStatus(Status.DONE); + + taskManager.updateSubtask(subtask1); + taskManager.updateSubtask(subtask2); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + assertEquals(Status.IN_PROGRESS, savedEpic.getStatus(), + "Статус эпика должен быть IN_PROGRESS, если подзадачи NEW и DONE."); + } + + // Проверка расчёта статуса эпика: есть подзадача IN_PROGRESS + @Test + void shouldSetEpicStatusInProgressWhenSubtaskIsInProgress() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask subtask = taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 9, 0), + epic.getId() + )); + + subtask.setStatus(Status.IN_PROGRESS); + taskManager.updateSubtask(subtask); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + assertEquals(Status.IN_PROGRESS, savedEpic.getStatus(), + "Статус эпика должен быть IN_PROGRESS, если есть подзадача IN_PROGRESS."); + } + + // Проверка приоритетного списка задач + @Test + void shouldReturnPrioritizedTasksSortedByStartTime() { + Task laterTask = taskManager.createTask(new Task( + "Later task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 12, 0) + )); + + Task earlierTask = taskManager.createTask(new Task( + "Earlier task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 9, 0) + )); + + List prioritizedTasks = taskManager.getPrioritizedTasks(); + + assertEquals(2, prioritizedTasks.size(), "Должно быть две задачи в списке приоритетов."); + assertEquals(earlierTask.getId(), prioritizedTasks.get(0).getId(), + "Первая задача должна быть с более ранним startTime."); + assertEquals(laterTask.getId(), prioritizedTasks.get(1).getId(), + "Вторая задача должна быть с более поздним startTime."); + } + + // Проверка, что задача без startTime не попадает в приоритетный список + @Test + void shouldNotAddTaskWithoutStartTimeToPrioritizedTasks() { + Task taskWithoutTime = new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + null + ); + + taskManager.createTask(taskWithoutTime); + + List prioritizedTasks = taskManager.getPrioritizedTasks(); + assertTrue(prioritizedTasks.isEmpty(), + "Задача без startTime не должна попадать в список приоритетов."); + } + + // Проверка пересечения интервалов при создании задачи + @Test + void shouldThrowExceptionWhenCreatingOverlappingTask() { + taskManager.createTask(new Task( + "Task 1", + "Description", + Status.NEW, + Duration.ofMinutes(60), + LocalDateTime.of(2026, 4, 16, 10, 0) + )); + + assertThrows( + IllegalArgumentException.class, + () -> taskManager.createTask(new Task( + "Task 2", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 30) + )), + "Создание пересекающейся задачи должно приводить к исключению." + ); + } + + // Проверка времени эпика по подзадачам + @Test + void shouldCalculateEpicTimeFromSubtasks() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + taskManager.createSubtask(new Subtask( + "Subtask 1", + "Description 1", + Status.NEW, + Duration.ofMinutes(60), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + taskManager.createSubtask(new Subtask( + "Subtask 2", + "Description 2", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 12, 0), + epic.getId() + )); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + + assertEquals(Duration.ofMinutes(90), savedEpic.getDuration(), + "Продолжительность эпика должна быть суммой продолжительностей подзадач."); + assertEquals(LocalDateTime.of(2026, 4, 16, 10, 0), savedEpic.getStartTime(), + "Время начала эпика должно быть временем начала самой ранней подзадачи."); + assertEquals(LocalDateTime.of(2026, 4, 16, 12, 30), savedEpic.getEndTime(), + "Время окончания эпика должно быть временем окончания самой поздней подзадачи."); + } +} \ No newline at end of file From d41c8c4167277281ef795241d05d641bac3b4631 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 22:33:06 +0300 Subject: [PATCH 13/45] test: simplify in-memory task manager test setup --- src/service/InMemoryTaskManager.java | 1 - test/service/InMemoryTaskManagerTest.java | 270 +--------------------- 2 files changed, 4 insertions(+), 267 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 0f5772a..00f6d45 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -303,7 +303,6 @@ protected void updateEpicStatus(Epic epic) { } } - // Пересчёт времени эпика на основе его подзадач // Пересчёт времени эпика на основе его подзадач protected void updateEpicTime(Epic epic) { if (epic.getSubtaskIds().isEmpty()) { diff --git a/test/service/InMemoryTaskManagerTest.java b/test/service/InMemoryTaskManagerTest.java index 8a43bf2..bc0f78c 100644 --- a/test/service/InMemoryTaskManagerTest.java +++ b/test/service/InMemoryTaskManagerTest.java @@ -1,271 +1,9 @@ package service; -import model.Status; -import model.Task; -import model.Epic; -import model.Subtask; +class InMemoryTaskManagerTest extends TaskManagerTest { -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -class InMemoryTaskManagerTest { - - private TaskManager taskManager; - - @BeforeEach - void setUp() { - taskManager = new InMemoryTaskManager(); - } - - //InMemoryTaskManager действительно добавляет задачи разного типа и может найти их по id - @Test - void addNewTask() { - Task task = new Task("Task", "Task Description", Status.NEW); - taskManager.createTask(task); - - final Task savedTask = taskManager.getTask(task.getId()); - - assertNotNull(savedTask, "Задача не найдена."); - assertEquals(task, savedTask, "Задачи не совпадают."); - - final List tasks = taskManager.getTasks(); - assertNotNull(tasks, "Задачи не возвращаются."); - assertEquals(1, tasks.size(), "Неверное количество задач."); - assertEquals(task, tasks.get(0), "Задачи не совпадают"); - } - - @Test - void addNewEpic() { - Epic epic = new Epic("Epic", "Epic Description"); - taskManager.createEpic(epic); - - final Epic savedEpic = taskManager.getEpic(epic.getId()); - - assertNotNull(savedEpic, "Эпик не найден."); - assertEquals(epic, savedEpic, "Эпики не совпадают."); - - final List epics = taskManager.getEpics(); - assertEquals(1, epics.size(), "Неверное количество эпиков."); - } - - @Test - void addNewSubtask() { - Epic epic = new Epic("Epic", "Epic Description"); - taskManager.createEpic(epic); - - Subtask subtask = new Subtask("Subtask", "Subtask Description", Status.NEW, epic.getId()); - taskManager.createSubtask(subtask); - - final Subtask savedSubtask = taskManager.getSubtask(subtask.getId()); - - assertNotNull(savedSubtask, "Подзадача не найдена."); - assertEquals(subtask, savedSubtask, "Подзадачи не совпадают."); - assertEquals(epic.getId(), savedSubtask.getEpicId(), "У Подзадачи неверный EpicID"); - - final List subtasks = taskManager.getSubtasks(); - assertEquals(1, subtasks.size(), "Неверное количество подзадач."); - } - - // Задачи с заданным id и сгенерированным id не конфликтуют внутри менеджера - @Test - void taskWithGeneratedIdShouldNotConflict() { - Task task = new Task("Task", "Task Description", Status.NEW); - task.setId(999); - - taskManager.createTask(task); - - assertNotEquals(999, task.getId(), - "Менеджер должен игнорировать заданный вручную ID и генерировать уникальный"); - - Task savedTask = taskManager.getTask(task.getId()); - assertNotNull(savedTask, "Задача должна быть найдена по сгенерированному ID"); - assertEquals(task, savedTask); - } - - // Tест, в котором проверяется неизменность задачи (по всем полям) при добавлении задачи в менеджер - @Test - void taskShouldBeUnchangedAfterAddingToManager() { - // Эталонные данные - String expectedName = "Orig Name"; - String expectedDescription = "Orig Description"; - Status expectedStatus = Status.NEW; - - Task task = new Task(expectedName, expectedDescription, expectedStatus); - - // Добавляем её - taskManager.createTask(task); - - // Забираем - Task savedTask = taskManager.getTask(task.getId()); - - // Проверяем - assertEquals(expectedName, savedTask.getName(), "Имя задачи изменилось при сохранении"); - assertEquals(expectedDescription, savedTask.getDescription(), - "Описание задачи изменилось при сохранении"); - assertEquals(expectedStatus, savedTask.getStatus(), "Статус задачи изменился при сохранении"); - } - - // Если удалена задача, то она должна исчезнуть из истории - @Test - void deletedTaskShouldBeRemovedFromHistory() { - Task task = new Task("Task", "Description", Status.NEW); - taskManager.createTask(task); - - taskManager.getTask(task.getId()); - assertEquals(1, taskManager.getHistory().size(), "Задача должна быть в истории"); - - taskManager.deleteTask(task.getId()); // удаляем - assertEquals(0, taskManager.getHistory().size(), - "Удалённая задача не должна оставаться в истории"); - } - - // При удалении эпика из истории удаляется и сам эпик, и все его подзадачи - @Test - void deletedEpicShouldBeRemovedFromHistoryWithSubtasks() { - Epic epic = new Epic("Epic", "Description"); - taskManager.createEpic(epic); - - Subtask subtask1 = new Subtask("Sub1", "Desc", Status.NEW, epic.getId()); - Subtask subtask2 = new Subtask("Sub2", "Desc", Status.NEW, epic.getId()); - taskManager.createSubtask(subtask1); - taskManager.createSubtask(subtask2); - - // Просматриваем всё — всё попадает в историю - taskManager.getEpic(epic.getId()); - taskManager.getSubtask(subtask1.getId()); - taskManager.getSubtask(subtask2.getId()); - assertEquals(3, taskManager.getHistory().size(), "В истории должно быть 3 элемента"); - - taskManager.deleteEpic(epic.getId()); // удаляем эпик - assertEquals(0, taskManager.getHistory().size(), - "После удаления эпика история должна быть пустой"); - } - - // Если удалена подзадача, то её id не должен оставаться внутри эпика - @Test - void deletedSubtaskShouldBeRemovedFromEpic() { - Epic epic = new Epic("Epic", "Description"); - taskManager.createEpic(epic); - - Subtask subtask = new Subtask("Sub", "Desc", Status.NEW, epic.getId()); - taskManager.createSubtask(subtask); - - int subtaskId = subtask.getId(); - taskManager.deleteSubtask(subtaskId); - - assertEquals(0, taskManager.getEpicSubtasks(epic.getId()).size(), - "После удаления подзадачи эпик не должен содержать её id"); - } - - // Изменение задачи через сеттер не должно влиять на данные внутри менеджера - @Test - void taskShouldNotChangeInManagerAfterSetterCall() { - Task task = new Task("Оригинальное имя", "Описание", Status.NEW); - taskManager.createTask(task); - - Task savedTask = taskManager.getTask(task.getId()); - savedTask.setName("Изменённое имя"); - - assertEquals("Оригинальное имя", - taskManager.getTask(task.getId()).getName(), - "Сеттер не должен менять данные задачи внутри менеджера"); - } - - // clearTasks() должен удалять задачи из истории - @Test - void clearTasksShouldRemoveTasksFromHistory() { - Task task1 = new Task("Task1", "Desc", Status.NEW); - Task task2 = new Task("Task2", "Desc", Status.NEW); - taskManager.createTask(task1); - taskManager.createTask(task2); - - taskManager.getTask(task1.getId()); - taskManager.getTask(task2.getId()); - assertEquals(2, taskManager.getHistory().size(), - "Перед очисткой в истории должно быть 2 задачи"); - - taskManager.clearTasks(); - - assertEquals(0, taskManager.getHistory().size(), - "После clearTasks история должна быть пустой"); - } - - // clearEpics() должен удалять эпики и их подзадачи из истории - @Test - void clearEpicsShouldRemoveEpicsAndSubtasksFromHistory() { - Epic epic = new Epic("Epic", "Desc"); - taskManager.createEpic(epic); - - Subtask subtask = new Subtask("Sub", "Desc", Status.NEW, epic.getId()); - taskManager.createSubtask(subtask); - - taskManager.getEpic(epic.getId()); - taskManager.getSubtask(subtask.getId()); - assertEquals(2, taskManager.getHistory().size(), - "Перед очисткой в истории должно быть 2 элемента"); - - taskManager.clearEpics(); - - assertEquals(0, taskManager.getHistory().size(), - "После clearEpics история должна быть пустой"); - } - - // Изменение подзадачи через сеттер не должно влиять на данные внутри менеджера - @Test - void subtaskShouldNotChangeInManagerAfterSetterCall() { - Epic epic = new Epic("Epic", "Desc"); - taskManager.createEpic(epic); - - Subtask subtask = new Subtask("Оригинальное имя", "Описание", Status.NEW, epic.getId()); - taskManager.createSubtask(subtask); - - Subtask savedSubtask = taskManager.getSubtask(subtask.getId()); - savedSubtask.setName("Изменённое имя"); - - assertEquals("Оригинальное имя", - taskManager.getSubtask(subtask.getId()).getName(), - "Сеттер не должен менять данные подзадачи внутри менеджера"); - } - - // clearSubtasks() должен удалять подзадачи из истории - @Test - void clearSubtasksShouldRemoveSubtasksFromHistory() { - Epic epic = new Epic("Epic", "Desc"); - taskManager.createEpic(epic); - - Subtask subtask = new Subtask("Sub", "Desc", Status.NEW, epic.getId()); - taskManager.createSubtask(subtask); - - taskManager.getSubtask(subtask.getId()); // добавление в историю - assertEquals(1, taskManager.getHistory().size(), - "Перед очисткой в истории должна быть 1 подзадача"); - - taskManager.clearSubtasks(); - - assertEquals(0, taskManager.getHistory().size(), - "После clearSubtasks история должна быть пустой"); - } - - // Проверка неизменности эпика по всем полям при добавлении в менеджер - @Test - void epicShouldBeUnchangedAfterAddingToManager() { - String expectedName = "Оригинальный эпик"; - String expectedDescription = "Описание эпика"; - - Epic epic = new Epic(expectedName, expectedDescription); - taskManager.createEpic(epic); - - Epic savedEpic = taskManager.getEpic(epic.getId()); - - assertEquals(expectedName, savedEpic.getName(), - "Имя эпика изменилось при сохранении"); - assertEquals(expectedDescription, savedEpic.getDescription(), - "Описание эпика изменилось при сохранении"); + @Override + protected InMemoryTaskManager createTaskManager() { + return new InMemoryTaskManager(); } } \ No newline at end of file From 3c9ecb5ac8cbcc8757241ea5b888f8f0b5e4a24e Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 22:36:36 +0300 Subject: [PATCH 14/45] test: add file-backed task manager persistence tests --- test/service/FileBackedTaskManagerTest.java | 166 +++++++++++--------- 1 file changed, 91 insertions(+), 75 deletions(-) diff --git a/test/service/FileBackedTaskManagerTest.java b/test/service/FileBackedTaskManagerTest.java index 2e2bbe1..e6cd829 100644 --- a/test/service/FileBackedTaskManagerTest.java +++ b/test/service/FileBackedTaskManagerTest.java @@ -6,95 +6,111 @@ import model.Task; import org.junit.jupiter.api.Test; -import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.*; -class FileBackedTaskManagerTest { +class FileBackedTaskManagerTest extends TaskManagerTest { - // Тест на сохранение и загрузку пустого менеджера - @Test - void shouldSaveAndLoadEmptyManager() throws IOException { - File tempFile = File.createTempFile("tasks", ".csv"); + private File file; - try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) { - writer.write("id,type,name,status,description,epic"); - writer.newLine(); + @Override + protected FileBackedTaskManager createTaskManager() { + try { + file = File.createTempFile("tasks", ".csv"); + return new FileBackedTaskManager(file); + } catch (IOException e) { + throw new RuntimeException("Не удалось создать временный файл для теста.", e); } - - FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(tempFile); - - assertTrue(loadedManager.getTasks().isEmpty(), "Список задач должен быть пустым"); - assertTrue(loadedManager.getEpics().isEmpty(), "Список эпиков должен быть пустым"); - assertTrue(loadedManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым"); } - // Тест на сохранение нескольких задач + // Проверка сохранения и загрузки пустого менеджера @Test - void shouldSaveMultipleTasks() throws IOException { - File tempFile = File.createTempFile("tasks", ".csv"); - - FileBackedTaskManager manager = new FileBackedTaskManager(tempFile); - - Task task = new Task("Task1", "Description1", Status.NEW); - manager.createTask(task); - - Epic epic = new Epic("Epic1", "Description epic"); - manager.createEpic(epic); - - Subtask subtask = new Subtask("Subtask1", "Description sub", Status.NEW, epic.getId()); - manager.createSubtask(subtask); - - FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(tempFile); - - assertEquals(1, loadedManager.getTasks().size(), "Должна быть 1 задача"); - assertEquals(1, loadedManager.getEpics().size(), "Должен быть 1 эпик"); - assertEquals(1, loadedManager.getSubtasks().size(), "Должна быть 1 подзадача"); + void shouldSaveAndLoadEmptyManager() { + assertDoesNotThrow(() -> { + FileBackedTaskManager manager = createTaskManager(); + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + + assertTrue(loadedManager.getTasks().isEmpty(), "Список задач должен быть пустым."); + assertTrue(loadedManager.getEpics().isEmpty(), "Список эпиков должен быть пустым."); + assertTrue(loadedManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); + }, "Загрузка пустого менеджера не должна выбрасывать исключение."); } - // Тест на загрузку нескольких задач из файла + // Проверка сохранения и загрузки задачи с полями времени @Test - void shouldLoadMultipleTasks() throws IOException { - File tempFile = File.createTempFile("tasks", ".csv"); - - FileBackedTaskManager manager = new FileBackedTaskManager(tempFile); - - Task task = new Task("Task1", "Description1", Status.NEW); - manager.createTask(task); - - Epic epic = new Epic("Epic1", "Description epic"); - manager.createEpic(epic); - - Subtask subtask = new Subtask("Subtask1", "Description sub", Status.NEW, epic.getId()); - manager.createSubtask(subtask); - - subtask.setStatus(Status.DONE); - manager.updateSubtask(subtask); - - FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(tempFile); - - Task loadedTask = loadedManager.getTasks().get(0); - Epic loadedEpic = loadedManager.getEpics().get(0); - Subtask loadedSubtask = loadedManager.getSubtasks().get(0); - - assertEquals("Task1", loadedTask.getName(), "Имя задачи должно совпадать"); - assertEquals("Description1", loadedTask.getDescription(), "Описание задачи должно совпадать"); - assertEquals(Status.NEW, loadedTask.getStatus(), "Статус задачи должен совпадать"); - - assertEquals("Epic1", loadedEpic.getName(), "Имя эпика должно совпадать"); - assertEquals("Description epic", loadedEpic.getDescription(), "Описание эпика должно совпадать"); - assertEquals(Status.DONE, loadedEpic.getStatus(), "Статус эпика должен пересчитаться по подзадаче"); + void shouldSaveAndLoadTaskWithTimeFields() { + FileBackedTaskManager manager = createTaskManager(); + + Task task = new Task( + "Task", + "Task description", + Status.NEW, + Duration.ofMinutes(45), + LocalDateTime.of(2026, 4, 16, 10, 0) + ); + + Task createdTask = manager.createTask(task); + + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + Task loadedTask = loadedManager.getTask(createdTask.getId()); + + assertNotNull(loadedTask, "Задача должна загрузиться из файла."); + assertEquals(Duration.ofMinutes(45), loadedTask.getDuration(), + "Продолжительность задачи должна сохраниться."); + assertEquals(LocalDateTime.of(2026, 4, 16, 10, 0), loadedTask.getStartTime(), + "Время начала задачи должно сохраниться."); + assertEquals(LocalDateTime.of(2026, 4, 16, 10, 45), loadedTask.getEndTime(), + "Время окончания задачи должно корректно рассчитываться после загрузки."); + } - assertEquals("Subtask1", loadedSubtask.getName(), "Имя подзадачи должно совпадать"); - assertEquals("Description sub", loadedSubtask.getDescription(), "Описание подзадачи должно совпадать"); - assertEquals(Status.DONE, loadedSubtask.getStatus(), "Статус подзадачи должен совпадать"); - assertEquals(epic.getId(), loadedSubtask.getEpicId(), "Epic ID подзадачи должен совпадать"); + // Проверка сохранения и загрузки времени эпика через подзадачи + @Test + void shouldSaveAndLoadEpicTimeCalculatedFromSubtasks() { + FileBackedTaskManager manager = createTaskManager(); + + Epic epic = manager.createEpic(new Epic("Epic", "Epic description")); + + manager.createSubtask(new Subtask( + "Subtask 1", + "Description 1", + Status.NEW, + Duration.ofMinutes(60), + LocalDateTime.of(2026, 4, 16, 9, 0), + epic.getId() + )); + + manager.createSubtask(new Subtask( + "Subtask 2", + "Description 2", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 12, 0), + epic.getId() + )); + + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + Epic loadedEpic = loadedManager.getEpic(epic.getId()); + + assertNotNull(loadedEpic, "Эпик должен загрузиться из файла."); + assertEquals(Duration.ofMinutes(90), loadedEpic.getDuration(), + "Продолжительность эпика должна восстановиться по подзадачам."); + assertEquals(LocalDateTime.of(2026, 4, 16, 9, 0), loadedEpic.getStartTime(), + "Время начала эпика должно восстановиться по самой ранней подзадаче."); + assertEquals(LocalDateTime.of(2026, 4, 16, 12, 30), loadedEpic.getEndTime(), + "Время окончания эпика должно восстановиться по самой поздней подзадаче."); + } - assertEquals(task.getId(), loadedTask.getId(), "ID задачи должен совпадать"); - assertEquals(epic.getId(), loadedEpic.getId(), "ID эпика должен совпадать"); - assertEquals(subtask.getId(), loadedSubtask.getId(), "ID подзадачи должен совпадать"); + // Проверка исключения при загрузке несуществующего файла + @Test + void shouldThrowExceptionWhenLoadingFromInvalidFile() { + assertThrows( + ManagerSaveException.class, + () -> FileBackedTaskManager.loadFromFile(new File("file_does_not_exist.csv")), + "Загрузка несуществующего файла должна приводить к ManagerSaveException." + ); } -} +} \ No newline at end of file From e430a7a948575cff22c6a51c9b73adf558e3371d Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 22:36:46 +0300 Subject: [PATCH 15/45] test: add history manager edge case tests --- test/service/InMemoryHistoryManagerTest.java | 203 +++++++++---------- 1 file changed, 98 insertions(+), 105 deletions(-) diff --git a/test/service/InMemoryHistoryManagerTest.java b/test/service/InMemoryHistoryManagerTest.java index 35de938..be0c84d 100644 --- a/test/service/InMemoryHistoryManagerTest.java +++ b/test/service/InMemoryHistoryManagerTest.java @@ -1,180 +1,173 @@ package service; import model.Status; +import model.Subtask; import model.Task; - -import java.util.List; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; class InMemoryHistoryManagerTest { - private HistoryManager historyManager; + private InMemoryHistoryManager historyManager; @BeforeEach void setUp() { historyManager = new InMemoryHistoryManager(); } - // Задачи, добавляемые в HistoryManager, сохраняют предыдущую версию задачи и её данных + // Проверка пустой истории @Test - void add() { - Task task = new Task("Task", "Task Description", Status.NEW); - task.setId(1); - - historyManager.add(task); - - final List history = historyManager.getHistory(); - - assertNotNull(history, "После добавления задачи, история не должна быть пустой."); - assertEquals(1, history.size(), "После добавления задачи, история не должна быть пустой."); + void shouldReturnEmptyHistoryWhenNoTasksWereAdded() { + List history = historyManager.getHistory(); - assertEquals(task.getName(), history.get(0).getName(), "Имя задачи в истории не совпадает"); - assertEquals(task.getDescription(), history.get(0).getDescription(), "Описание задачи в истории не совпадает"); - assertEquals(task.getStatus(), history.get(0).getStatus(), "Статус задачи в истории не совпадает"); - assertEquals(task.getId(), history.get(0).getId(), "ID задачи в истории не совпадает"); + assertNotNull(history, "История не должна быть null."); + assertTrue(history.isEmpty(), "История должна быть пустой."); } - // При повторном добавлении задачи в историю дубликат не создастся + // Проверка добавления задачи в историю @Test - void addShouldNotCreateDuplicates() { - Task task = new Task("Task", "Description", Status.NEW); + void shouldAddTaskToHistory() { + Task task = new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0) + ); task.setId(1); historyManager.add(task); - historyManager.add(task); // добавляем второй раз - historyManager.add(task); // и третий - assertEquals(1, historyManager.getHistory().size(), "История не должна содержать дубликаты"); - } + List history = historyManager.getHistory(); - // История не должна ограничиваться 10 элементами - @Test - void historyShouldBeUnlimited() { - for (int i = 1; i <= 15; i++) { - Task task = new Task("Task " + i, "Description", Status.NEW); - task.setId(i); - historyManager.add(task); - } - - assertEquals(15, historyManager.getHistory().size(), "История должна хранить более 10 элементов"); + assertEquals(1, history.size(), "История должна содержать одну задачу."); + assertEquals(task.getId(), history.get(0).getId(), "В истории должна быть добавленная задача."); } - // Удаление из начала истории + // Проверка удаления дубликатов в истории @Test - void removeShouldDeleteFromBeginning() { - Task task1 = new Task("Task1", "Desc", Status.NEW); - task1.setId(1); - Task task2 = new Task("Task2", "Desc", Status.NEW); - task2.setId(2); - Task task3 = new Task("Task3", "Desc", Status.NEW); - task3.setId(3); - - historyManager.add(task1); - historyManager.add(task2); - historyManager.add(task3); + void shouldKeepOnlyOneTaskWhenTaskAddedTwice() { + Task task = new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0) + ); + task.setId(1); - historyManager.remove(1); // удаление первой - головы + historyManager.add(task); + historyManager.add(task); List history = historyManager.getHistory(); - assertEquals(2, history.size(), "В истории должно остаться 2 задачи"); - assertEquals(2, history.get(0).getId(), "Первой должна быть task2"); + + assertEquals(1, history.size(), "Дубликаты не должны сохраняться в истории."); + assertEquals(task.getId(), history.get(0).getId(), "В истории должна остаться одна задача."); } - // Удаление из середины истории + // Проверка удаления задачи из начала истории @Test - void removeShouldDeleteFromMiddle() { - Task task1 = new Task("Task1", "Desc", Status.NEW); + void shouldRemoveTaskFromBeginningOfHistory() { + Task task1 = new Task("Task 1", "Description 1", Status.NEW, + Duration.ofMinutes(10), LocalDateTime.of(2026, 4, 16, 9, 0)); task1.setId(1); - Task task2 = new Task("Task2", "Desc", Status.NEW); + + Task task2 = new Task("Task 2", "Description 2", Status.NEW, + Duration.ofMinutes(20), LocalDateTime.of(2026, 4, 16, 10, 0)); task2.setId(2); - Task task3 = new Task("Task3", "Desc", Status.NEW); - task3.setId(3); historyManager.add(task1); historyManager.add(task2); - historyManager.add(task3); - historyManager.remove(2); // удаление из середины + historyManager.remove(1); List history = historyManager.getHistory(); - assertEquals(2, history.size(), "В истории должно остаться 2 задачи"); - assertEquals(1, history.get(0).getId(), "Первой должна быть task1"); - assertEquals(3, history.get(1).getId(), "Второй должна быть task3"); + + assertEquals(1, history.size(), "После удаления в истории должна остаться одна задача."); + assertEquals(2, history.get(0).getId(), "В истории должна остаться вторая задача."); } - // Удаление из конца истории + // Проверка удаления задачи из середины истории @Test - void removeShouldDeleteFromEnd() { - Task task1 = new Task("Task1", "Desc", Status.NEW); + void shouldRemoveTaskFromMiddleOfHistory() { + Task task1 = new Task("Task 1", "Description 1", Status.NEW, + Duration.ofMinutes(10), LocalDateTime.of(2026, 4, 16, 9, 0)); task1.setId(1); - Task task2 = new Task("Task2", "Desc", Status.NEW); + + Task task2 = new Task("Task 2", "Description 2", Status.NEW, + Duration.ofMinutes(20), LocalDateTime.of(2026, 4, 16, 10, 0)); task2.setId(2); - Task task3 = new Task("Task3", "Desc", Status.NEW); + + Task task3 = new Task("Task 3", "Description 3", Status.NEW, + Duration.ofMinutes(30), LocalDateTime.of(2026, 4, 16, 11, 0)); task3.setId(3); historyManager.add(task1); historyManager.add(task2); historyManager.add(task3); - historyManager.remove(3); // удаление последней - хвоста + historyManager.remove(2); List history = historyManager.getHistory(); - assertEquals(2, history.size(), "В истории должно остаться 2 задачи"); - assertEquals(2, history.get(1).getId(), "Последней должна быть task2"); + + assertEquals(2, history.size(), "После удаления в истории должно остаться две задачи."); + assertEquals(1, history.get(0).getId(), "Первая задача должна остаться."); + assertEquals(3, history.get(1).getId(), "Третья задача должна остаться."); } - // Повторный просмотр задачи должен переместить её в конец истории + // Проверка удаления задачи из конца истории @Test - void repeatedViewShouldMoveTaskToEnd() { - Task task1 = new Task("Task1", "Desc", Status.NEW); + void shouldRemoveTaskFromEndOfHistory() { + Task task1 = new Task("Task 1", "Description 1", Status.NEW, + Duration.ofMinutes(10), LocalDateTime.of(2026, 4, 16, 9, 0)); task1.setId(1); - Task task2 = new Task("Task2", "Desc", Status.NEW); + + Task task2 = new Task("Task 2", "Description 2", Status.NEW, + Duration.ofMinutes(20), LocalDateTime.of(2026, 4, 16, 10, 0)); task2.setId(2); - Task task3 = new Task("Task3", "Desc", Status.NEW); - task3.setId(3); historyManager.add(task1); historyManager.add(task2); - historyManager.add(task3); - historyManager.add(task1); // task1 повторно — должна уйти в конец - List history = historyManager.getHistory(); - - assertEquals(3, history.size(), "Дубликатов быть не должно"); - assertEquals(2, history.get(0).getId(), "Первой должна быть task2"); - assertEquals(3, history.get(1).getId(), "Второй должна быть task3"); - assertEquals(1, history.get(2).getId(), "Последней должна быть task1"); - } + historyManager.remove(2); - // Пустая история должна возвращать пустой список - @Test - void emptyHistoryShouldReturnEmptyList() { List history = historyManager.getHistory(); - assertNotNull(history, "getHistory() не должен возвращать null"); - assertEquals(0, history.size(), "Пустая история должна содержать 0 элементов"); + + assertEquals(1, history.size(), "После удаления в истории должна остаться одна задача."); + assertEquals(1, history.get(0).getId(), "В истории должна остаться первая задача."); } - // Статус задачи в истории не должен меняться при изменении оригинала через сеттер + // Проверка сохранения новых полей во snapshot истории @Test - void historyShouldStoreSnapshotNotReference() { - Task task = new Task("Task", "Desc", Status.NEW); - task.setId(1); + void shouldSaveTimeFieldsInHistorySnapshot() { + Subtask subtask = new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(40), + LocalDateTime.of(2026, 4, 16, 13, 0), + 100 + ); + subtask.setId(1); + + historyManager.add(subtask); - historyManager.add(task); + List history = historyManager.getHistory(); - // Меняем оригинал после добавления в историю - task.setName("Изменённое имя"); - task.setStatus(Status.DONE); + assertEquals(1, history.size(), "История должна содержать одну подзадачу."); + Task savedTask = history.get(0); - Task inHistory = historyManager.getHistory().get(0); - assertEquals("Task", inHistory.getName(), - "История должна хранить снимок: имя не должно измениться"); - assertEquals(Status.NEW, inHistory.getStatus(), - "История должна хранить снимок: статус не должен измениться"); + assertEquals(Duration.ofMinutes(40), savedTask.getDuration(), + "Продолжительность должна сохраниться в snapshot истории."); + assertEquals(LocalDateTime.of(2026, 4, 16, 13, 0), savedTask.getStartTime(), + "Время начала должно сохраниться в snapshot истории."); + assertEquals(LocalDateTime.of(2026, 4, 16, 13, 40), savedTask.getEndTime(), + "Время окончания должно корректно рассчитываться в snapshot истории."); } } \ No newline at end of file From 7cc4bc5ec697c96326009a3277e8b1c7e11c534d Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 23:43:57 +0300 Subject: [PATCH 16/45] fix: preserve calculated fields in Epic during update --- src/service/InMemoryTaskManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 00f6d45..ab87a66 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -272,6 +272,9 @@ public void updateEpic(Epic epic) { Epic oldEpic = epics.get(epic.getId()); epic.setSubtaskIds(oldEpic.getSubtaskIds()); epic.setStatus(oldEpic.getStatus()); + epic.setEpicDuration(oldEpic.getDuration()); + epic.setEpicStartTime(oldEpic.getStartTime()); + epic.setEpicEndTime(oldEpic.getEndTime()); epics.put(epic.getId(), epic); } From 727eee05942e6d5833d302351cbeda19dacd3d22 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 23:55:41 +0300 Subject: [PATCH 17/45] fix: prevent creating subtask without existing epic --- src/service/InMemoryTaskManager.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index ab87a66..cf8b99d 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -227,6 +227,11 @@ public Epic createEpic(Epic epic) { @Override public Subtask createSubtask(Subtask subtask) { + Epic epic = epics.get(subtask.getEpicId()); + if (epic == null) { + return null; + } + if (hasTimeOverlap(subtask)) { throw new IllegalArgumentException("Подзадача пересекается по времени с другой задачей."); } @@ -236,13 +241,9 @@ public Subtask createSubtask(Subtask subtask) { subtasks.put(subtask.getId(), subtask); addTaskToPrioritizedTasks(subtask); - // Добавляем в эпик id новой подзадачи - Epic epic = epics.get(subtask.getEpicId()); - if (epic != null) { - epic.addSubtaskId(subtask.getId()); - updateEpicStatus(epic); - updateEpicTime(epic); - } + epic.addSubtaskId(subtask.getId()); + updateEpicStatus(epic); + updateEpicTime(epic); return subtask; } From 9f37d136ccaf63edf3f08affcd27fedf422f72c2 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 23:56:14 +0300 Subject: [PATCH 18/45] fix: validate epic existence in updateSubtask() --- src/service/InMemoryTaskManager.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index cf8b99d..8090c90 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -344,6 +344,11 @@ protected void updateEpicTime(Epic epic) { @Override public void updateSubtask(Subtask subtask) { if (subtasks.containsKey(subtask.getId())) { + Epic epic = epics.get(subtask.getEpicId()); + if (epic == null) { + return; + } + Subtask oldSubtask = subtasks.get(subtask.getId()); prioritizedTasks.remove(oldSubtask); @@ -356,12 +361,8 @@ public void updateSubtask(Subtask subtask) { subtasks.put(subtask.getId(), subtask); addTaskToPrioritizedTasks(subtask); - // Пересчёт статуса эпика после обновления подзадачи - Epic epic = epics.get(subtask.getEpicId()); - if (epic != null) { - updateEpicStatus(epic); - updateEpicTime(epic); - } + updateEpicStatus(epic); + updateEpicTime(epic); } } From d03d01759539d18f681508a6050a6ea71142fb93 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Thu, 16 Apr 2026 23:59:33 +0300 Subject: [PATCH 19/45] refactor: preserve task and subtask status on creation --- src/service/InMemoryTaskManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 8090c90..ab0b04a 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -210,7 +210,6 @@ public Task createTask(Task task) { } task.setId(getNextId()); - task.setStatus(Status.NEW); tasks.put(task.getId(), task); addTaskToPrioritizedTasks(task); From 3c5bf447f98544af749706ede81d1fcdca47b968 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:02:12 +0300 Subject: [PATCH 20/45] refactor: improve safety in getEpicSubtasks() --- src/service/InMemoryTaskManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index ab0b04a..18f4bd9 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -419,6 +419,7 @@ public List getEpicSubtasks(int epicId) { return epic.getSubtaskIds().stream() .map(subtasks::get) + .filter(subtask -> subtask != null) .toList(); } From f0c8de0767eb34dff4c357a46b3e37a044fe557d Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:08:50 +0300 Subject: [PATCH 21/45] test: add tests for clear operations in TaskManager --- test/service/TaskManagerTest.java | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index 7fdde9d..6271db1 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -305,4 +305,75 @@ void shouldCalculateEpicTimeFromSubtasks() { assertEquals(LocalDateTime.of(2026, 4, 16, 12, 30), savedEpic.getEndTime(), "Время окончания эпика должно быть временем окончания самой поздней подзадачи."); } + + // Проверка очистки всех задач + @Test + void shouldClearAllTasks() { + Task task = taskManager.createTask(new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0) + )); + + taskManager.getTask(task.getId()); + taskManager.clearTasks(); + + assertTrue(taskManager.getTasks().isEmpty(), "Список задач должен быть пустым."); + assertNull(taskManager.getTask(task.getId()), "Задача не должна находиться после очистки."); + assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться от удалённых задач."); + assertTrue(taskManager.getPrioritizedTasks().isEmpty(), "Приоритетный список должен очищаться."); + } + + // Проверка очистки всех подзадач и сброса полей эпика + @Test + void shouldClearAllSubtasksAndResetEpicFields() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + taskManager.clearSubtasks(); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + + assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); + assertTrue(savedEpic.getSubtaskIds().isEmpty(), "У эпика не должно остаться подзадач."); + assertEquals(Status.NEW, savedEpic.getStatus(), "Статус эпика должен стать NEW."); + assertNull(savedEpic.getStartTime(), "Время начала эпика должно стать null."); + assertNull(savedEpic.getEndTime(), "Время окончания эпика должно стать null."); + assertNull(savedEpic.getDuration(), "Продолжительность эпика должна стать null."); + } + + // Проверка очистки всех эпиков и связанных подзадач + @Test + void shouldClearAllEpicsAndSubtasks() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + Subtask subtask = taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 11, 0), + epic.getId() + )); + + taskManager.getEpic(epic.getId()); + taskManager.getSubtask(subtask.getId()); + + taskManager.clearEpics(); + + assertTrue(taskManager.getEpics().isEmpty(), "Список эпиков должен быть пустым."); + assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); + assertNull(taskManager.getEpic(epic.getId()), "Эпик не должен находиться после очистки."); + assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача не должна находиться после очистки."); + assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться."); + } } \ No newline at end of file From ea4d86150f9ac336de3a6e10b1133b11020c2307 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:12:10 +0300 Subject: [PATCH 22/45] test: add tests for update operations in TaskManager --- test/service/TaskManagerTest.java | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index 6271db1..5a48f76 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -376,4 +376,101 @@ void shouldClearAllEpicsAndSubtasks() { assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача не должна находиться после очистки."); assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться."); } + + // Проверка обновления задач, подзадач и эпиков + @Test + void shouldUpdateTask() { + Task task = taskManager.createTask(new Task( + "Old name", + "Old description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0) + )); + + Task updatedTask = new Task( + "New name", + "New description", + Status.IN_PROGRESS, + Duration.ofMinutes(45), + LocalDateTime.of(2026, 4, 16, 12, 0) + ); + updatedTask.setId(task.getId()); + + taskManager.updateTask(updatedTask); + + Task savedTask = taskManager.getTask(task.getId()); + + assertEquals("New name", savedTask.getName()); + assertEquals("New description", savedTask.getDescription()); + assertEquals(Status.IN_PROGRESS, savedTask.getStatus()); + assertEquals(Duration.ofMinutes(45), savedTask.getDuration()); + assertEquals(LocalDateTime.of(2026, 4, 16, 12, 0), savedTask.getStartTime()); + } + + @Test + void shouldUpdateEpicWithoutLosingCalculatedFields() { + Epic epic = taskManager.createEpic(new Epic("Old epic", "Old description")); + + taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + Epic updatedEpic = new Epic("New epic", "New description"); + updatedEpic.setId(epic.getId()); + + taskManager.updateEpic(updatedEpic); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + + assertEquals("New epic", savedEpic.getName()); + assertEquals("New description", savedEpic.getDescription()); + assertEquals(1, savedEpic.getSubtaskIds().size(), "Связь с подзадачами должна сохраниться."); + assertEquals(Status.NEW, savedEpic.getStatus(), "Статус не должен теряться."); + assertEquals(Duration.ofMinutes(30), savedEpic.getDuration(), "Продолжительность не должна теряться."); + assertEquals(LocalDateTime.of(2026, 4, 16, 10, 0), savedEpic.getStartTime(), "StartTime не должен теряться."); + assertEquals(LocalDateTime.of(2026, 4, 16, 10, 30), savedEpic.getEndTime(), "EndTime не должен теряться."); + } + + @Test + void shouldUpdateSubtaskAndRecalculateEpic() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask subtask = taskManager.createSubtask(new Subtask( + "Old subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 9, 0), + epic.getId() + )); + + Subtask updatedSubtask = new Subtask( + "New subtask", + "New description", + Status.DONE, + Duration.ofMinutes(40), + LocalDateTime.of(2026, 4, 16, 11, 0), + epic.getId() + ); + updatedSubtask.setId(subtask.getId()); + + taskManager.updateSubtask(updatedSubtask); + + Subtask savedSubtask = taskManager.getSubtask(subtask.getId()); + Epic savedEpic = taskManager.getEpic(epic.getId()); + + assertEquals("New subtask", savedSubtask.getName()); + assertEquals(Status.DONE, savedSubtask.getStatus()); + assertEquals(Duration.ofMinutes(40), savedSubtask.getDuration()); + assertEquals(LocalDateTime.of(2026, 4, 16, 11, 0), savedSubtask.getStartTime()); + + assertEquals(Status.DONE, savedEpic.getStatus(), "Статус эпика должен пересчитаться."); + assertEquals(Duration.ofMinutes(40), savedEpic.getDuration(), "Продолжительность эпика должна пересчитаться."); + } } \ No newline at end of file From 1f552a06acb070291b0c7be251173dba47327287 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:14:09 +0300 Subject: [PATCH 23/45] test: add tests for delete operations --- test/service/TaskManagerTest.java | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index 5a48f76..a2484fd 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -473,4 +473,67 @@ void shouldUpdateSubtaskAndRecalculateEpic() { assertEquals(Status.DONE, savedEpic.getStatus(), "Статус эпика должен пересчитаться."); assertEquals(Duration.ofMinutes(40), savedEpic.getDuration(), "Продолжительность эпика должна пересчитаться."); } + + // // Проверка удаления задач, подзадач и эпиков + @Test + void shouldDeleteTaskById() { + Task task = taskManager.createTask(new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 10, 0) + )); + + taskManager.getTask(task.getId()); + taskManager.deleteTask(task.getId()); + + assertNull(taskManager.getTask(task.getId()), "Задача должна быть удалена."); + assertTrue(taskManager.getTasks().isEmpty(), "Список задач должен быть пустым."); + assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться от удалённой задачи."); + } + + @Test + void shouldDeleteSubtaskAndUpdateEpic() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask subtask = taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + taskManager.deleteSubtask(subtask.getId()); + + Epic savedEpic = taskManager.getEpic(epic.getId()); + + assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача должна быть удалена."); + assertTrue(savedEpic.getSubtaskIds().isEmpty(), "У эпика не должно остаться подзадач."); + assertEquals(Status.NEW, savedEpic.getStatus(), "Статус эпика должен пересчитаться."); + assertNull(savedEpic.getDuration(), "Продолжительность эпика должна стать null."); + } + + @Test + void shouldDeleteEpicWithItsSubtasks() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask subtask = taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + taskManager.deleteEpic(epic.getId()); + + assertNull(taskManager.getEpic(epic.getId()), "Эпик должен быть удалён."); + assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача эпика тоже должна быть удалена."); + assertTrue(taskManager.getEpics().isEmpty(), "Список эпиков должен быть пустым."); + assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); + } } \ No newline at end of file From 7b131348aa49ed841134d9bdd535119e709aecef Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:17:31 +0300 Subject: [PATCH 24/45] test: add edge case tests for entity lookup in TaskManager --- test/service/TaskManagerTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index a2484fd..c6cf150 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -536,4 +536,20 @@ void shouldDeleteEpicWithItsSubtasks() { assertTrue(taskManager.getEpics().isEmpty(), "Список эпиков должен быть пустым."); assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); } + + // Проверка граничных случаев поиска задач, эпиков и подзадач + @Test + void shouldReturnNullWhenTaskNotFound() { + assertNull(taskManager.getTask(999), "Несуществующая задача должна возвращать null."); + } + + @Test + void shouldReturnNullWhenEpicNotFound() { + assertNull(taskManager.getEpic(999), "Несуществующий эпик должен возвращать null."); + } + + @Test + void shouldReturnNullWhenSubtaskNotFound() { + assertNull(taskManager.getSubtask(999), "Несуществующая подзадача должна возвращать null."); + } } \ No newline at end of file From 74db52789a8eed83f44836f6c930b57a662e0cdc Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:21:29 +0300 Subject: [PATCH 25/45] test: add history and subtask validation tests in TaskManager --- test/service/TaskManagerTest.java | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index c6cf150..c6787b4 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -552,4 +552,42 @@ void shouldReturnNullWhenEpicNotFound() { void shouldReturnNullWhenSubtaskNotFound() { assertNull(taskManager.getSubtask(999), "Несуществующая подзадача должна возвращать null."); } + + // Проверка добавления задач в историю просмотров + @Test + void shouldAddViewedTaskToHistory() { + Task task = taskManager.createTask(new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(15), + LocalDateTime.of(2026, 4, 16, 8, 0) + )); + + taskManager.getTask(task.getId()); + + List history = taskManager.getHistory(); + + assertEquals(1, history.size(), "После просмотра задача должна попасть в историю."); + assertEquals(task.getId(), history.get(0).getId(), "В истории должна быть просмотренная задача."); + } + + // Проверка создания подзадачи без существующего эпика + @Test + void shouldNotCreateSubtaskWithoutExistingEpic() { + Subtask subtask = new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 10, 0), + 999 + ); + + Subtask createdSubtask = taskManager.createSubtask(subtask); + + assertNull(createdSubtask, "Подзадача не должна создаваться без существующего эпика."); + assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен остаться пустым."); + } + } \ No newline at end of file From fac189f8eb58797958be11ef61cedfc1030f5544 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:22:58 +0300 Subject: [PATCH 26/45] test: add overlap validation tests for tasks and subtasks --- test/service/TaskManagerTest.java | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index c6787b4..dbf2b9a 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -590,4 +590,45 @@ void shouldNotCreateSubtaskWithoutExistingEpic() { assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен остаться пустым."); } + // Проверка пересечения временных интервалов задач и подзадач + @Test + void shouldAllowTasksThatTouchBordersButDoNotOverlap() { + taskManager.createTask(new Task( + "Task 1", + "Description", + Status.NEW, + Duration.ofMinutes(60), + LocalDateTime.of(2026, 4, 16, 10, 0) + )); + + assertDoesNotThrow(() -> taskManager.createTask(new Task( + "Task 2", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 11, 0) + )), "Задачи, соприкасающиеся границами, не должны считаться пересекающимися."); + } + + @Test + void shouldThrowExceptionWhenCreatingOverlappingSubtask() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + taskManager.createTask(new Task( + "Task 1", + "Description", + Status.NEW, + Duration.ofMinutes(60), + LocalDateTime.of(2026, 4, 16, 10, 0) + )); + + assertThrows(IllegalArgumentException.class, () -> taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 10, 30), + epic.getId() + )), "Подзадача не должна создаваться, если пересекается по времени с другой задачей."); + } } \ No newline at end of file From c059c8554af15066c42a3a22c94009877fed9790 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:28:28 +0300 Subject: [PATCH 27/45] fix: preserve epic connection when updating subtask --- src/service/InMemoryTaskManager.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 18f4bd9..81497e9 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -343,13 +343,15 @@ protected void updateEpicTime(Epic epic) { @Override public void updateSubtask(Subtask subtask) { if (subtasks.containsKey(subtask.getId())) { + Subtask oldSubtask = subtasks.get(subtask.getId()); + + subtask.setEpicId(oldSubtask.getEpicId()); + Epic epic = epics.get(subtask.getEpicId()); if (epic == null) { return; } - Subtask oldSubtask = subtasks.get(subtask.getId()); - prioritizedTasks.remove(oldSubtask); if (hasTimeOverlap(subtask)) { From 1e5ee6f5559ac34940f31e2125567cd4889e8a0c Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 00:35:56 +0300 Subject: [PATCH 28/45] test: add tests for epic subtasks and prioritized tasks --- test/service/TaskManagerTest.java | 48 ++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index dbf2b9a..2452b28 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -474,7 +474,53 @@ void shouldUpdateSubtaskAndRecalculateEpic() { assertEquals(Duration.ofMinutes(40), savedEpic.getDuration(), "Продолжительность эпика должна пересчитаться."); } - // // Проверка удаления задач, подзадач и эпиков + // Проверка получения списка подзадач эпика + @Test + void shouldReturnEmptyListForEpicWithoutSubtasks() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + List epicSubtasks = taskManager.getEpicSubtasks(epic.getId()); + + assertTrue(epicSubtasks.isEmpty(), "У нового эпика список подзадач должен быть пустым."); + } + + @Test + void shouldReturnEmptyListForNonExistingEpicSubtasks() { + List epicSubtasks = taskManager.getEpicSubtasks(999); + + assertTrue(epicSubtasks.isEmpty(), "Для несуществующего эпика должен возвращаться пустой список."); + } + + // Проверка сортировки задач и подзадач в приоритетном списке + @Test + void shouldReturnTasksAndSubtasksSortedByStartTime() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Task task = taskManager.createTask(new Task( + "Task", + "Description", + Status.NEW, + Duration.ofMinutes(30), + LocalDateTime.of(2026, 4, 16, 12, 0) + )); + + Subtask subtask = taskManager.createSubtask(new Subtask( + "Subtask", + "Description", + Status.NEW, + Duration.ofMinutes(20), + LocalDateTime.of(2026, 4, 16, 10, 0), + epic.getId() + )); + + List prioritizedTasks = taskManager.getPrioritizedTasks(); + + assertEquals(2, prioritizedTasks.size(), "В приоритетном списке должны быть задача и подзадача."); + assertEquals(subtask.getId(), prioritizedTasks.get(0).getId(), "Подзадача с более ранним startTime должна быть первой."); + assertEquals(task.getId(), prioritizedTasks.get(1).getId(), "Задача с более поздним startTime должна быть второй."); + } + + // Проверка удаления задач, подзадач и эпиков @Test void shouldDeleteTaskById() { Task task = taskManager.createTask(new Task( From a4bef1207eb72f1415a63642f35d320de54e1e84 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 13:06:07 +0300 Subject: [PATCH 29/45] feat: add base http handler and not found exception --- src/http/handler/BaseHttpHandler.java | 39 ++++++++++++++++++++ src/service/exception/NotFoundException.java | 10 +++++ 2 files changed, 49 insertions(+) create mode 100644 src/http/handler/BaseHttpHandler.java create mode 100644 src/service/exception/NotFoundException.java diff --git a/src/http/handler/BaseHttpHandler.java b/src/http/handler/BaseHttpHandler.java new file mode 100644 index 0000000..a2abae0 --- /dev/null +++ b/src/http/handler/BaseHttpHandler.java @@ -0,0 +1,39 @@ +package http.handler; + +import com.sun.net.httpserver.HttpExchange; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +// Базовый класс для всех HTTP-обработчиков. +// Содержит общие методы для отправки ответов клиенту. +public class BaseHttpHandler { + + // Отправка успешного ответа с текстом (JSON) + protected void sendText(HttpExchange exchange, String text, int statusCode) throws IOException { + byte[] response = text.getBytes(StandardCharsets.UTF_8); + + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(statusCode, response.length); + exchange.getResponseBody().write(response); + exchange.close(); + } + + // Ответ 404 — объект не найден + protected void sendNotFound(HttpExchange exchange) throws IOException { + exchange.sendResponseHeaders(404, -1); + exchange.close(); + } + + // Ответ 406 — пересечение задач по времени + protected void sendHasInteractions(HttpExchange exchange) throws IOException { + exchange.sendResponseHeaders(406, -1); + exchange.close(); + } + + // Ответ 500 — внутренняя ошибка сервера + protected void sendInternalError(HttpExchange exchange) throws IOException { + exchange.sendResponseHeaders(500, -1); + exchange.close(); + } +} \ No newline at end of file diff --git a/src/service/exception/NotFoundException.java b/src/service/exception/NotFoundException.java new file mode 100644 index 0000000..513f525 --- /dev/null +++ b/src/service/exception/NotFoundException.java @@ -0,0 +1,10 @@ +package service.exception; + +// Исключение, которое выбрасывается, если объект не найден. +// Используется в HTTP-обработчиках, чтобы вернуть статус 404. +public class NotFoundException extends RuntimeException { + + public NotFoundException(String message) { + super(message); + } +} From f1c4a0888acedec2704cb0f214ae0eef070715e5 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 13:46:47 +0300 Subject: [PATCH 30/45] feat: add HttpTaskServer skeleton with endpoint handlers --- src/http/HttpTaskServer.java | 67 ++++++++++++++++++++++++ src/http/handler/EpicsHandler.java | 23 ++++++++ src/http/handler/HistoryHandler.java | 23 ++++++++ src/http/handler/PrioritizedHandler.java | 23 ++++++++ src/http/handler/SubtasksHandler.java | 23 ++++++++ src/http/handler/TasksHandler.java | 23 ++++++++ 6 files changed, 182 insertions(+) create mode 100644 src/http/HttpTaskServer.java create mode 100644 src/http/handler/EpicsHandler.java create mode 100644 src/http/handler/HistoryHandler.java create mode 100644 src/http/handler/PrioritizedHandler.java create mode 100644 src/http/handler/SubtasksHandler.java create mode 100644 src/http/handler/TasksHandler.java diff --git a/src/http/HttpTaskServer.java b/src/http/HttpTaskServer.java new file mode 100644 index 0000000..6020921 --- /dev/null +++ b/src/http/HttpTaskServer.java @@ -0,0 +1,67 @@ +package http; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.sun.net.httpserver.HttpServer; +import http.handler.EpicsHandler; +import http.handler.HistoryHandler; +import http.handler.PrioritizedHandler; +import http.handler.SubtasksHandler; +import http.handler.TasksHandler; +import service.Managers; +import service.TaskManager; + +import java.io.IOException; +import java.net.InetSocketAddress; + +public class HttpTaskServer { + + // Порт, на котором будет работать сервер + private static final int PORT = 8080; + + // Экземпляр HTTP-сервера из стандартной библиотеки Java + private final HttpServer httpServer; + + // Менеджер задач, с которым будут работать обработчики запросов + private final TaskManager taskManager; + + // Gson нужен для преобразования объектов Java в JSON и обратно + private static final Gson GSON = new GsonBuilder().create(); + + // Конструктор для обычного запуска приложения и для тестов + public HttpTaskServer(TaskManager taskManager) throws IOException { + this.taskManager = taskManager; + this.httpServer = HttpServer.create(new InetSocketAddress(PORT), 0); + + // Регистрируем обработчики для каждого базового пути API + httpServer.createContext("/tasks", new TasksHandler(this.taskManager)); + httpServer.createContext("/subtasks", new SubtasksHandler(this.taskManager)); + httpServer.createContext("/epics", new EpicsHandler(this.taskManager)); + httpServer.createContext("/history", new HistoryHandler(this.taskManager)); + httpServer.createContext("/prioritized", new PrioritizedHandler(this.taskManager)); + } + + // Метод запуска сервера + public void start() { + httpServer.start(); + System.out.println("HTTP task server started on port " + PORT); + } + + // Метод остановки сервера + public void stop() { + httpServer.stop(0); + System.out.println("HTTP task server stopped on port " + PORT); + } + + // Возвращает экземпляр Gson для работы с JSON + public static Gson getGson() { + return GSON; + } + + // Точка входа в приложение + public static void main(String[] args) throws IOException { + TaskManager manager = Managers.getDefault(); + HttpTaskServer httpTaskServer = new HttpTaskServer(manager); + httpTaskServer.start(); + } +} \ No newline at end of file diff --git a/src/http/handler/EpicsHandler.java b/src/http/handler/EpicsHandler.java new file mode 100644 index 0000000..8422c1f --- /dev/null +++ b/src/http/handler/EpicsHandler.java @@ -0,0 +1,23 @@ +package http.handler; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import service.TaskManager; + +import java.io.IOException; + +// Обработчик запросов по пути /epics. +// Пока содержит только базовую заготовку, логика будет добавлена позже. +public class EpicsHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager taskManager; + + public EpicsHandler(TaskManager taskManager) { + this.taskManager = taskManager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendText(exchange, "\"Epics endpoint is not implemented yet\"", 200); + } +} \ No newline at end of file diff --git a/src/http/handler/HistoryHandler.java b/src/http/handler/HistoryHandler.java new file mode 100644 index 0000000..6476f69 --- /dev/null +++ b/src/http/handler/HistoryHandler.java @@ -0,0 +1,23 @@ +package http.handler; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import service.TaskManager; + +import java.io.IOException; + +// Обработчик запросов по пути /history. +// Пока содержит только базовую заготовку, логика будет добавлена позже. +public class HistoryHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager taskManager; + + public HistoryHandler(TaskManager taskManager) { + this.taskManager = taskManager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendText(exchange, "\"History endpoint is not implemented yet\"", 200); + } +} \ No newline at end of file diff --git a/src/http/handler/PrioritizedHandler.java b/src/http/handler/PrioritizedHandler.java new file mode 100644 index 0000000..74309ab --- /dev/null +++ b/src/http/handler/PrioritizedHandler.java @@ -0,0 +1,23 @@ +package http.handler; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import service.TaskManager; + +import java.io.IOException; + +// Обработчик запросов по пути /prioritized. +// Пока содержит только базовую заготовку, логика будет добавлена позже. +public class PrioritizedHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager taskManager; + + public PrioritizedHandler(TaskManager taskManager) { + this.taskManager = taskManager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendText(exchange, "\"Prioritized endpoint is not implemented yet\"", 200); + } +} \ No newline at end of file diff --git a/src/http/handler/SubtasksHandler.java b/src/http/handler/SubtasksHandler.java new file mode 100644 index 0000000..99f4c67 --- /dev/null +++ b/src/http/handler/SubtasksHandler.java @@ -0,0 +1,23 @@ +package http.handler; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import service.TaskManager; + +import java.io.IOException; + +// Обработчик запросов по пути /subtasks. +// Пока содержит только базовую заготовку, логика будет добавлена позже. +public class SubtasksHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager taskManager; + + public SubtasksHandler(TaskManager taskManager) { + this.taskManager = taskManager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendText(exchange, "\"Subtasks endpoint is not implemented yet\"", 200); + } +} \ No newline at end of file diff --git a/src/http/handler/TasksHandler.java b/src/http/handler/TasksHandler.java new file mode 100644 index 0000000..5251411 --- /dev/null +++ b/src/http/handler/TasksHandler.java @@ -0,0 +1,23 @@ +package http.handler; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import service.TaskManager; + +import java.io.IOException; + +// Обработчик запросов по пути /tasks. +// Пока содержит только базовую заготовку, логика будет добавлена позже. +public class TasksHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager taskManager; + + public TasksHandler(TaskManager taskManager) { + this.taskManager = taskManager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendText(exchange, "\"Tasks endpoint is not implemented yet\"", 200); + } +} \ No newline at end of file From 4c91767823fe6e26ebb3460bb8971d3177dcdc7f Mon Sep 17 00:00:00 2001 From: Ksenia Date: Fri, 17 Apr 2026 13:58:03 +0300 Subject: [PATCH 31/45] chore: add gson library jar to project --- lib/gson-2.9.0.jar | Bin 0 -> 249277 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/gson-2.9.0.jar diff --git a/lib/gson-2.9.0.jar b/lib/gson-2.9.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..fb62e05657d2004475bacb81191765012df6b3cb GIT binary patch literal 249277 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

<^K9jEkp^kL?uwD7rfW; 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 Date: Sat, 18 Apr 2026 00:06:21 +0300 Subject: [PATCH 32/45] refactor: move null time validation to hasTimeOverlap --- src/service/InMemoryTaskManager.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 81497e9..0a955cb 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -176,23 +176,21 @@ public List getPrioritizedTasks() { // Проверка пересечения двух задач по времени выполнения protected boolean isTaskOverlapping(Task firstTask, Task secondTask) { - if (firstTask.getStartTime() == null || secondTask.getStartTime() == null) { - return false; - } - - if (firstTask.getEndTime() == null || secondTask.getEndTime() == null) { - return false; - } - return firstTask.getStartTime().isBefore(secondTask.getEndTime()) && secondTask.getStartTime().isBefore(firstTask.getEndTime()); } // Проверка пересечения задачи с уже существующими задачами и подзадачами protected boolean hasTimeOverlap(Task task) { + if (task.getStartTime() == null || task.getEndTime() == null) { + return false; + } + return prioritizedTasks.stream() - .anyMatch(prioritizedTask -> prioritizedTask.getId() != task.getId() - && isTaskOverlapping(task, prioritizedTask)); + .anyMatch(prioritizedTask -> + prioritizedTask.getId() != task.getId() + && isTaskOverlapping(task, prioritizedTask) + ); } // Добавление задачи или подзадачи в список приоритетов, если задано время начала From fdaf4d6878ee27c44563d258abe0f268441bdfda Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 16:13:06 +0300 Subject: [PATCH 33/45] refactor: throw NotFoundException in InMemoryTaskManager --- src/service/InMemoryTaskManager.java | 170 ++++++++++++++------------- 1 file changed, 87 insertions(+), 83 deletions(-) diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 0a955cb..8fac7c7 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -4,6 +4,7 @@ import model.Task; import model.Subtask; import model.Epic; +import service.exception.NotFoundException; import java.time.Duration; import java.time.LocalDateTime; @@ -36,6 +37,33 @@ private int getNextId() { return nextId++; } + // Поиск обычной задачи по id + private Task findTaskById(int id) { + Task task = tasks.get(id); + if (task == null) { + throw new NotFoundException("Задача с id=" + id + " не найдена."); + } + return task; + } + + // Поиск эпика по id + private Epic findEpicById(int id) { + Epic epic = epics.get(id); + if (epic == null) { + throw new NotFoundException("Эпик с id=" + id + " не найден."); + } + return epic; + } + + // Поиск подзадачи по id + private Subtask findSubtaskById(int id) { + Subtask subtask = subtasks.get(id); + if (subtask == null) { + throw new NotFoundException("Подзадача с id=" + id + " не найдена."); + } + return subtask; + } + // Получение списков задач, эпиков и подзадач @Override public ArrayList getTasks() { @@ -101,10 +129,7 @@ public void clearSubtasks() { // Получение задачи, эпика или подзадачи по идентификатору @Override public Task getTask(int id) { - Task task = tasks.get(id); - if (task == null) { - return null; - } + Task task = findTaskById(id); historyManager.add(task); @@ -122,10 +147,7 @@ public Task getTask(int id) { @Override public Epic getEpic(int id) { - Epic epic = epics.get(id); - if (epic == null) { - return null; - } + Epic epic = findEpicById(id); historyManager.add(epic); @@ -142,10 +164,7 @@ public Epic getEpic(int id) { @Override public Subtask getSubtask(int id) { - Subtask subtask = subtasks.get(id); - if (subtask == null) { - return null; - } + Subtask subtask = findSubtaskById(id); historyManager.add(subtask); @@ -224,10 +243,7 @@ public Epic createEpic(Epic epic) { @Override public Subtask createSubtask(Subtask subtask) { - Epic epic = epics.get(subtask.getEpicId()); - if (epic == null) { - return null; - } + Epic epic = findEpicById(subtask.getEpicId()); if (hasTimeOverlap(subtask)) { throw new IllegalArgumentException("Подзадача пересекается по времени с другой задачей."); @@ -248,34 +264,30 @@ public Subtask createSubtask(Subtask subtask) { // Обновление задачи с синхронизацией списка приоритетов @Override public void updateTask(Task task) { - if (tasks.containsKey(task.getId())) { - Task oldTask = tasks.get(task.getId()); - - prioritizedTasks.remove(oldTask); + Task oldTask = findTaskById(task.getId()); - if (hasTimeOverlap(task)) { - addTaskToPrioritizedTasks(oldTask); - throw new IllegalArgumentException("Обновлённая задача пересекается по времени с другой задачей."); - } + prioritizedTasks.remove(oldTask); - tasks.put(task.getId(), task); - addTaskToPrioritizedTasks(task); + if (hasTimeOverlap(task)) { + addTaskToPrioritizedTasks(oldTask); + throw new IllegalArgumentException("Обновлённая задача пересекается по времени с другой задачей."); } + + tasks.put(task.getId(), task); + addTaskToPrioritizedTasks(task); } @Override public void updateEpic(Epic epic) { - if (epics.containsKey(epic.getId())) { - // Создание копии списка задач во избежание потери данных при обновлении - Epic oldEpic = epics.get(epic.getId()); - epic.setSubtaskIds(oldEpic.getSubtaskIds()); - epic.setStatus(oldEpic.getStatus()); - epic.setEpicDuration(oldEpic.getDuration()); - epic.setEpicStartTime(oldEpic.getStartTime()); - epic.setEpicEndTime(oldEpic.getEndTime()); - - epics.put(epic.getId(), epic); - } + // Создание копии списка задач во избежание потери данных при обновлении + Epic oldEpic = findEpicById(epic.getId()); + epic.setSubtaskIds(oldEpic.getSubtaskIds()); + epic.setStatus(oldEpic.getStatus()); + epic.setEpicDuration(oldEpic.getDuration()); + epic.setEpicStartTime(oldEpic.getStartTime()); + epic.setEpicEndTime(oldEpic.getEndTime()); + + epics.put(epic.getId(), epic); } // Пересчёт статуса эпика на основе его подзадач @@ -340,82 +352,74 @@ protected void updateEpicTime(Epic epic) { @Override public void updateSubtask(Subtask subtask) { - if (subtasks.containsKey(subtask.getId())) { - Subtask oldSubtask = subtasks.get(subtask.getId()); + Subtask oldSubtask = findSubtaskById(subtask.getId()); - subtask.setEpicId(oldSubtask.getEpicId()); + subtask.setEpicId(oldSubtask.getEpicId()); - Epic epic = epics.get(subtask.getEpicId()); - if (epic == null) { - return; - } + Epic epic = findEpicById(subtask.getEpicId()); - prioritizedTasks.remove(oldSubtask); + prioritizedTasks.remove(oldSubtask); - if (hasTimeOverlap(subtask)) { - addTaskToPrioritizedTasks(oldSubtask); - throw new IllegalArgumentException("Обновлённая подзадача пересекается по времени с другой задачей."); - } + if (hasTimeOverlap(subtask)) { + addTaskToPrioritizedTasks(oldSubtask); + throw new IllegalArgumentException("Обновлённая подзадача пересекается по времени с другой задачей."); + } - subtasks.put(subtask.getId(), subtask); - addTaskToPrioritizedTasks(subtask); + subtasks.put(subtask.getId(), subtask); + addTaskToPrioritizedTasks(subtask); - updateEpicStatus(epic); - updateEpicTime(epic); - } + updateEpicStatus(epic); + updateEpicTime(epic); } // Удаление задачи, эпика или подзадачи по идентификатору @Override public void deleteTask(int id) { - Task removedTask = tasks.remove(id); - if (removedTask != null) { - prioritizedTasks.remove(removedTask); - } + Task removedTask = findTaskById(id); + + tasks.remove(id); + prioritizedTasks.remove(removedTask); historyManager.remove(id); } @Override public void deleteEpic(int id) { - Epic epic = epics.remove(id); - if (epic != null) { - // Удаление подзадач, связанных с этим Эпиком - for (Integer subtaskId : epic.getSubtaskIds()) { - Subtask removedSubtask = subtasks.remove(subtaskId); - if (removedSubtask != null) { - prioritizedTasks.remove(removedSubtask); - } - historyManager.remove(subtaskId); + Epic epic = findEpicById(id); + + epics.remove(id); + + // Удаление подзадач, связанных с этим эпиком + for (Integer subtaskId : epic.getSubtaskIds()) { + Subtask removedSubtask = subtasks.remove(subtaskId); + if (removedSubtask != null) { + prioritizedTasks.remove(removedSubtask); } + historyManager.remove(subtaskId); } + historyManager.remove(id); } @Override public void deleteSubtask(int id) { - Subtask subtask = subtasks.remove(id); - if (subtask != null) { - prioritizedTasks.remove(subtask); + Subtask subtask = findSubtaskById(id); + + subtasks.remove(id); + prioritizedTasks.remove(subtask); + + // Удаление id подзадачи из эпика + Epic epic = findEpicById(subtask.getEpicId()); + epic.removeSubtaskId(id); + updateEpicStatus(epic); + updateEpicTime(epic); - // Удаление ID Подзадачи из Эпика - Epic epic = epics.get(subtask.getEpicId()); - if (epic != null) { - epic.removeSubtaskId(id); - updateEpicStatus(epic); - updateEpicTime(epic); - } - } historyManager.remove(id); } // Получение списка подзадач эпика с использованием Stream API @Override public List getEpicSubtasks(int epicId) { - Epic epic = epics.get(epicId); - - if (epic == null) { - return new ArrayList<>(); - } + Epic epic = findEpicById(epicId); return epic.getSubtaskIds().stream() .map(subtasks::get) From 255c7e7f0353f86300341c474046636b9ea51c10 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 16:29:07 +0300 Subject: [PATCH 34/45] feat: implement TasksHandler for HTTP API --- src/http/handler/BaseHttpHandler.java | 21 +++-- src/http/handler/TasksHandler.java | 106 +++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/src/http/handler/BaseHttpHandler.java b/src/http/handler/BaseHttpHandler.java index a2abae0..ee4813a 100644 --- a/src/http/handler/BaseHttpHandler.java +++ b/src/http/handler/BaseHttpHandler.java @@ -6,10 +6,10 @@ import java.nio.charset.StandardCharsets; // Базовый класс для всех HTTP-обработчиков. -// Содержит общие методы для отправки ответов клиенту. +// Содержит общие методы для отправки и чтения HTTP-данных. public class BaseHttpHandler { - // Отправка успешного ответа с текстом (JSON) + // Отправка ответа с текстом в формате JSON. protected void sendText(HttpExchange exchange, String text, int statusCode) throws IOException { byte[] response = text.getBytes(StandardCharsets.UTF_8); @@ -19,19 +19,30 @@ protected void sendText(HttpExchange exchange, String text, int statusCode) thro exchange.close(); } - // Ответ 404 — объект не найден + // Отправка ответа 201 без тела. + protected void sendCreated(HttpExchange exchange) throws IOException { + exchange.sendResponseHeaders(201, -1); + exchange.close(); + } + + // Чтение тела HTTP-запроса в виде строки. + protected String readText(HttpExchange exchange) throws IOException { + return new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + } + + // Ответ 404 — объект не найден. protected void sendNotFound(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(404, -1); exchange.close(); } - // Ответ 406 — пересечение задач по времени + // Ответ 406 — задача пересекается по времени. protected void sendHasInteractions(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(406, -1); exchange.close(); } - // Ответ 500 — внутренняя ошибка сервера + // Ответ 500 — внутренняя ошибка сервера. protected void sendInternalError(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(500, -1); exchange.close(); diff --git a/src/http/handler/TasksHandler.java b/src/http/handler/TasksHandler.java index 5251411..ce12203 100644 --- a/src/http/handler/TasksHandler.java +++ b/src/http/handler/TasksHandler.java @@ -1,23 +1,125 @@ package http.handler; +import com.google.gson.Gson; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import http.HttpTaskServer; +import model.Task; import service.TaskManager; +import service.exception.NotFoundException; import java.io.IOException; +import java.util.List; // Обработчик запросов по пути /tasks. -// Пока содержит только базовую заготовку, логика будет добавлена позже. +// Поддерживает получение списка задач, получение задачи по id, +// создание/обновление задачи и удаление задачи. public class TasksHandler extends BaseHttpHandler implements HttpHandler { + // Менеджер задач, с которым работает обработчик. private final TaskManager taskManager; + // Общий Gson для преобразования объектов в JSON и обратно. + private final Gson gson = HttpTaskServer.getGson(); + public TasksHandler(TaskManager taskManager) { this.taskManager = taskManager; } + // Главный метод обработчика HTTP-запроса. @Override public void handle(HttpExchange exchange) throws IOException { - sendText(exchange, "\"Tasks endpoint is not implemented yet\"", 200); + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + if ("GET".equals(method)) { + handleGet(exchange, path); + return; + } + + if ("POST".equals(method)) { + handlePost(exchange, path); + return; + } + + if ("DELETE".equals(method)) { + handleDelete(exchange, path); + return; + } + + sendInternalError(exchange); + + } catch (NotFoundException e) { + sendNotFound(exchange); + } catch (IllegalArgumentException e) { + sendHasInteractions(exchange); + } catch (Exception e) { + sendInternalError(exchange); + } + } + + // Обработка GET-запросов: + // GET /tasks -> список всех задач + // GET /tasks/{id} -> одна задача по id + private void handleGet(HttpExchange exchange, String path) throws IOException { + if ("/tasks".equals(path)) { + List tasks = taskManager.getTasks(); + String response = gson.toJson(tasks); + sendText(exchange, response, 200); + return; + } + + int id = extractId(path); + Task task = taskManager.getTask(id); + String response = gson.toJson(task); + sendText(exchange, response, 200); + } + + // Обработка POST-запроса: + // POST /tasks -> создание новой задачи или обновление существующей. + private void handlePost(HttpExchange exchange, String path) throws IOException { + if (!"/tasks".equals(path)) { + throw new NotFoundException("Некорректный путь для POST /tasks"); + } + + String body = readText(exchange); + Task task = gson.fromJson(body, Task.class); + + if (task == null) { + throw new IOException("Тело запроса пустое."); + } + + if (task.getId() == 0) { + taskManager.createTask(task); + } else { + taskManager.updateTask(task); + } + + sendCreated(exchange); + } + + // Обработка DELETE-запроса: + // DELETE /tasks/{id} -> удаление задачи по id. + private void handleDelete(HttpExchange exchange, String path) throws IOException { + int id = extractId(path); + taskManager.deleteTask(id); + sendText(exchange, "", 200); + } + + // Извлечение id задачи из пути. + // Ожидается путь вида /tasks/{id}. + private int extractId(String path) { + String[] pathParts = path.split("/"); + + if (pathParts.length != 3) { + throw new NotFoundException("Некорректный путь запроса."); + } + + try { + return Integer.parseInt(pathParts[2]); + } catch (NumberFormatException e) { + throw new NotFoundException("Некорректный идентификатор задачи."); + } } } \ No newline at end of file From ed7ba7fb51628a5a87b2656c681aa6e586ffc4d7 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 16:38:52 +0300 Subject: [PATCH 35/45] feat: implement SubtasksHandler for HTTP API --- src/http/handler/SubtasksHandler.java | 106 +++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/src/http/handler/SubtasksHandler.java b/src/http/handler/SubtasksHandler.java index 99f4c67..ab4db38 100644 --- a/src/http/handler/SubtasksHandler.java +++ b/src/http/handler/SubtasksHandler.java @@ -1,23 +1,125 @@ package http.handler; +import com.google.gson.Gson; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import http.HttpTaskServer; +import model.Subtask; import service.TaskManager; +import service.exception.NotFoundException; import java.io.IOException; +import java.util.List; // Обработчик запросов по пути /subtasks. -// Пока содержит только базовую заготовку, логика будет добавлена позже. +// Поддерживает получение списка подзадач, получение подзадачи по id, +// создание/обновление подзадачи и удаление подзадачи. public class SubtasksHandler extends BaseHttpHandler implements HttpHandler { + // Менеджер задач, с которым работает обработчик. private final TaskManager taskManager; + // Общий Gson для преобразования объектов в JSON и обратно. + private final Gson gson = HttpTaskServer.getGson(); + public SubtasksHandler(TaskManager taskManager) { this.taskManager = taskManager; } + // Главный метод обработки HTTP-запроса. @Override public void handle(HttpExchange exchange) throws IOException { - sendText(exchange, "\"Subtasks endpoint is not implemented yet\"", 200); + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + if ("GET".equals(method)) { + handleGet(exchange, path); + return; + } + + if ("POST".equals(method)) { + handlePost(exchange, path); + return; + } + + if ("DELETE".equals(method)) { + handleDelete(exchange, path); + return; + } + + sendInternalError(exchange); + + } catch (NotFoundException e) { + sendNotFound(exchange); + } catch (IllegalArgumentException e) { + sendHasInteractions(exchange); + } catch (Exception e) { + sendInternalError(exchange); + } + } + + // Обработка GET-запросов: + // GET /subtasks -> список всех подзадач + // GET /subtasks/{id} -> одна подзадача по id + private void handleGet(HttpExchange exchange, String path) throws IOException { + if ("/subtasks".equals(path)) { + List subtasks = taskManager.getSubtasks(); + String response = gson.toJson(subtasks); + sendText(exchange, response, 200); + return; + } + + int id = extractId(path); + Subtask subtask = taskManager.getSubtask(id); + String response = gson.toJson(subtask); + sendText(exchange, response, 200); + } + + // Обработка POST-запроса: + // POST /subtasks -> создание новой подзадачи или обновление существующей. + private void handlePost(HttpExchange exchange, String path) throws IOException { + if (!"/subtasks".equals(path)) { + throw new NotFoundException("Некорректный путь для POST /subtasks"); + } + + String body = readText(exchange); + Subtask subtask = gson.fromJson(body, Subtask.class); + + if (subtask == null) { + throw new IOException("Тело запроса пустое."); + } + + if (subtask.getId() == 0) { + taskManager.createSubtask(subtask); + } else { + taskManager.updateSubtask(subtask); + } + + sendCreated(exchange); + } + + // Обработка DELETE-запроса: + // DELETE /subtasks/{id} -> удаление подзадачи по id. + private void handleDelete(HttpExchange exchange, String path) throws IOException { + int id = extractId(path); + taskManager.deleteSubtask(id); + sendText(exchange, "", 200); + } + + // Извлечение id подзадачи из пути. + // Ожидается путь вида /subtasks/{id}. + private int extractId(String path) { + String[] pathParts = path.split("/"); + + if (pathParts.length != 3) { + throw new NotFoundException("Некорректный путь запроса."); + } + + try { + return Integer.parseInt(pathParts[2]); + } catch (NumberFormatException e) { + throw new NotFoundException("Некорректный идентификатор подзадачи."); + } } } \ No newline at end of file From 230dfb5c0b5954e418b61099e1709da7fbcca0a6 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 16:41:25 +0300 Subject: [PATCH 36/45] feat: implement EpicsHandler for HTTP API --- src/http/handler/EpicsHandler.java | 134 ++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 2 deletions(-) diff --git a/src/http/handler/EpicsHandler.java b/src/http/handler/EpicsHandler.java index 8422c1f..d49f449 100644 --- a/src/http/handler/EpicsHandler.java +++ b/src/http/handler/EpicsHandler.java @@ -1,23 +1,153 @@ package http.handler; +import com.google.gson.Gson; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import http.HttpTaskServer; +import model.Epic; +import model.Subtask; import service.TaskManager; +import service.exception.NotFoundException; import java.io.IOException; +import java.util.List; // Обработчик запросов по пути /epics. -// Пока содержит только базовую заготовку, логика будет добавлена позже. +// Поддерживает получение списка эпиков, получение эпика по id, +// получение подзадач эпика, создание/обновление эпика и удаление эпика. public class EpicsHandler extends BaseHttpHandler implements HttpHandler { + // Менеджер задач, с которым работает обработчик. private final TaskManager taskManager; + // Общий Gson для преобразования объектов в JSON и обратно. + private final Gson gson = HttpTaskServer.getGson(); + public EpicsHandler(TaskManager taskManager) { this.taskManager = taskManager; } + // Главный метод обработки HTTP-запроса. @Override public void handle(HttpExchange exchange) throws IOException { - sendText(exchange, "\"Epics endpoint is not implemented yet\"", 200); + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + if ("GET".equals(method)) { + handleGet(exchange, path); + return; + } + + if ("POST".equals(method)) { + handlePost(exchange, path); + return; + } + + if ("DELETE".equals(method)) { + handleDelete(exchange, path); + return; + } + + sendInternalError(exchange); + + } catch (NotFoundException e) { + sendNotFound(exchange); + } catch (IllegalArgumentException e) { + sendHasInteractions(exchange); + } catch (Exception e) { + sendInternalError(exchange); + } + } + + // Обработка GET-запросов: + // GET /epics -> список всех эпиков + // GET /epics/{id} -> один эпик по id + // GET /epics/{id}/subtasks -> список подзадач эпика + private void handleGet(HttpExchange exchange, String path) throws IOException { + if ("/epics".equals(path)) { + List epics = taskManager.getEpics(); + String response = gson.toJson(epics); + sendText(exchange, response, 200); + return; + } + + if (path.endsWith("/subtasks")) { + int epicId = extractEpicIdForSubtasks(path); + List subtasks = taskManager.getEpicSubtasks(epicId); + String response = gson.toJson(subtasks); + sendText(exchange, response, 200); + return; + } + + int id = extractId(path); + Epic epic = taskManager.getEpic(id); + String response = gson.toJson(epic); + sendText(exchange, response, 200); + } + + // Обработка POST-запроса: + // POST /epics -> создание нового эпика или обновление существующего. + private void handlePost(HttpExchange exchange, String path) throws IOException { + if (!"/epics".equals(path)) { + throw new NotFoundException("Некорректный путь для POST /epics"); + } + + String body = readText(exchange); + Epic epic = gson.fromJson(body, Epic.class); + + if (epic == null) { + throw new IOException("Тело запроса пустое."); + } + + if (epic.getId() == 0) { + taskManager.createEpic(epic); + } else { + taskManager.updateEpic(epic); + } + + sendCreated(exchange); + } + + // Обработка DELETE-запроса: + // DELETE /epics/{id} -> удаление эпика по id. + private void handleDelete(HttpExchange exchange, String path) throws IOException { + int id = extractId(path); + taskManager.deleteEpic(id); + sendText(exchange, "", 200); + } + + // Извлечение id эпика из пути вида /epics/{id}. + private int extractId(String path) { + String[] pathParts = path.split("/"); + + if (pathParts.length != 3) { + throw new NotFoundException("Некорректный путь запроса."); + } + + try { + return Integer.parseInt(pathParts[2]); + } catch (NumberFormatException e) { + throw new NotFoundException("Некорректный идентификатор эпика."); + } + } + + // Извлечение id эпика из пути вида /epics/{id}/subtasks. + private int extractEpicIdForSubtasks(String path) { + String[] pathParts = path.split("/"); + + if (pathParts.length != 4) { + throw new NotFoundException("Некорректный путь запроса."); + } + + if (!"subtasks".equals(pathParts[3])) { + throw new NotFoundException("Некорректный путь запроса."); + } + + try { + return Integer.parseInt(pathParts[2]); + } catch (NumberFormatException e) { + throw new NotFoundException("Некорректный идентификатор эпика."); + } } } \ No newline at end of file From 6e2adad74f0db0e505849ec3ec143e7b051d5f94 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 16:59:06 +0300 Subject: [PATCH 37/45] feat: implement HistoryHandler for HTTP API --- src/http/handler/HistoryHandler.java | 33 ++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/http/handler/HistoryHandler.java b/src/http/handler/HistoryHandler.java index 6476f69..8ae6c06 100644 --- a/src/http/handler/HistoryHandler.java +++ b/src/http/handler/HistoryHandler.java @@ -1,23 +1,52 @@ package http.handler; +import com.google.gson.Gson; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import http.HttpTaskServer; +import model.Task; import service.TaskManager; import java.io.IOException; +import java.util.List; // Обработчик запросов по пути /history. -// Пока содержит только базовую заготовку, логика будет добавлена позже. +// Поддерживает получение истории просмотров задач. public class HistoryHandler extends BaseHttpHandler implements HttpHandler { + // Менеджер задач, с которым работает обработчик. private final TaskManager taskManager; + // Общий Gson для преобразования объектов в JSON. + private final Gson gson = HttpTaskServer.getGson(); + public HistoryHandler(TaskManager taskManager) { this.taskManager = taskManager; } + // Главный метод обработки HTTP-запроса. @Override public void handle(HttpExchange exchange) throws IOException { - sendText(exchange, "\"History endpoint is not implemented yet\"", 200); + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + if (!"GET".equals(method)) { + sendInternalError(exchange); + return; + } + + if (!"/history".equals(path)) { + sendNotFound(exchange); + return; + } + + List history = taskManager.getHistory(); + String response = gson.toJson(history); + sendText(exchange, response, 200); + + } catch (Exception e) { + sendInternalError(exchange); + } } } \ No newline at end of file From ae6e0886333a51068397cd2d0444fc6217828c01 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 16:59:15 +0300 Subject: [PATCH 38/45] feat: implement PrioritizedHandler for HTTP API --- src/http/handler/PrioritizedHandler.java | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/http/handler/PrioritizedHandler.java b/src/http/handler/PrioritizedHandler.java index 74309ab..c54c9e1 100644 --- a/src/http/handler/PrioritizedHandler.java +++ b/src/http/handler/PrioritizedHandler.java @@ -1,23 +1,52 @@ package http.handler; +import com.google.gson.Gson; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import http.HttpTaskServer; +import model.Task; import service.TaskManager; import java.io.IOException; +import java.util.List; // Обработчик запросов по пути /prioritized. -// Пока содержит только базовую заготовку, логика будет добавлена позже. +// Поддерживает получение списка задач в порядке приоритета. public class PrioritizedHandler extends BaseHttpHandler implements HttpHandler { + // Менеджер задач, с которым работает обработчик. private final TaskManager taskManager; + // Общий Gson для преобразования объектов в JSON. + private final Gson gson = HttpTaskServer.getGson(); + public PrioritizedHandler(TaskManager taskManager) { this.taskManager = taskManager; } + // Главный метод обработки HTTP-запроса. @Override public void handle(HttpExchange exchange) throws IOException { - sendText(exchange, "\"Prioritized endpoint is not implemented yet\"", 200); + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + if (!"GET".equals(method)) { + sendInternalError(exchange); + return; + } + + if (!"/prioritized".equals(path)) { + sendNotFound(exchange); + return; + } + + List prioritizedTasks = taskManager.getPrioritizedTasks(); + String response = gson.toJson(prioritizedTasks); + sendText(exchange, response, 200); + + } catch (Exception e) { + sendInternalError(exchange); + } } } \ No newline at end of file From 5c3e4530a6ae2eb6d81dca6350b0dbe9c39faebd Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 17:13:58 +0300 Subject: [PATCH 39/45] test: add HTTP tests for tasks endpoint --- test/http/HttpTaskManagerTasksTest.java | 220 ++++++++++++++++++++++++ test/http/HttpTaskServerTestBase.java | 80 +++++++++ 2 files changed, 300 insertions(+) create mode 100644 test/http/HttpTaskManagerTasksTest.java create mode 100644 test/http/HttpTaskServerTestBase.java diff --git a/test/http/HttpTaskManagerTasksTest.java b/test/http/HttpTaskManagerTasksTest.java new file mode 100644 index 0000000..3529062 --- /dev/null +++ b/test/http/HttpTaskManagerTasksTest.java @@ -0,0 +1,220 @@ +package http; + +import model.Task; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +// Тесты для endpoint'а /tasks. +public class HttpTaskManagerTasksTest extends HttpTaskServerTestBase { + + // Проверка: GET /tasks должен возвращать пустой список, + // если задач в менеджере нет. + @Test + public void shouldReturnEmptyTasksList() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа."); + assertEquals("[]", response.body(), "Список задач должен быть пустым."); + } + + // Проверка: POST /tasks должен создавать новую задачу. + @Test + public void shouldCreateTask() throws IOException, InterruptedException { + Task task = createTestTask( + "Закодить 9 спринт", + "по Java", + 30, + LocalDateTime.of(2026, 4, 18, 19, 0) + ); + + String taskJson = gson.toJson(task); + + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + List tasksFromManager = manager.getTasks(); + + assertEquals(201, response.statusCode(), "Некорректный код ответа при создании задачи."); + assertNotNull(tasksFromManager, "Задачи не возвращаются."); + assertEquals(1, tasksFromManager.size(), "Некорректное количество задач."); + assertEquals("Закодить 9 спринт", tasksFromManager.get(0).getName(), "Некорректное имя задачи."); + } + + // Проверка: GET /tasks/{id} должен возвращать задачу по id. + @Test + public void shouldReturnTaskById() throws IOException, InterruptedException { + Task task = createTestTask( + "Почитать книгу", + "Перед сном", + 40, + LocalDateTime.of(2026, 4, 18, 21, 0) + ); + manager.createTask(task); + + int taskId = manager.getTasks().get(0).getId(); + + URI url = URI.create("http://localhost:8080/tasks/" + taskId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + Task taskFromResponse = gson.fromJson(response.body(), Task.class); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при получении задачи по id."); + assertNotNull(taskFromResponse, "Задача не пришла в ответе."); + assertEquals(taskId, taskFromResponse.getId(), "Некорректный id задачи."); + assertEquals("Почитать книгу", taskFromResponse.getName(), "Некорректное имя задачи."); + } + + // Проверка: GET /tasks/{id} должен возвращать 404, + // если задачи с таким id не существует. + @Test + public void shouldReturn404WhenTaskNotFound() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/tasks/999"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), "Должен вернуться статус 404, если задача не найдена."); + } + + // Проверка: POST /tasks должен обновлять задачу, + // если в JSON указан существующий id. + @Test + public void shouldUpdateTask() throws IOException, InterruptedException { + Task task = createTestTask( + "Сделать зарядку", + "Утром", + 15, + LocalDateTime.of(2026, 4, 19, 8, 0) + ); + manager.createTask(task); + + Task savedTask = manager.getTasks().get(0); + + Task updatedTask = createTestTask( + "Сделать зарядку дома", + "Утром после пробуждения", + 20, + LocalDateTime.of(2026, 4, 19, 8, 30) + ); + updatedTask.setId(savedTask.getId()); + + String updatedTaskJson = gson.toJson(updatedTask); + + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(updatedTaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + Task taskFromManager = manager.getTask(savedTask.getId()); + + assertEquals(201, response.statusCode(), "Некорректный код ответа при обновлении задачи."); + assertEquals("Сделать зарядку дома", taskFromManager.getName(), "Имя задачи не обновилось."); + assertEquals("Утром после пробуждения", taskFromManager.getDescription(), + "Описание задачи не обновилось."); + } + + // Проверка: POST /tasks должен возвращать 406, + // если новая задача пересекается по времени с существующей. + @Test + public void shouldReturn406WhenTaskHasTimeOverlap() throws IOException, InterruptedException { + Task firstTask = createTestTask( + "Первая задача", + "Описание первой задачи", + 60, + LocalDateTime.of(2026, 4, 20, 10, 0) + ); + manager.createTask(firstTask); + + Task secondTask = createTestTask( + "Вторая задача", + "Описание второй задачи", + 30, + LocalDateTime.of(2026, 4, 20, 10, 30) + ); + + String secondTaskJson = gson.toJson(secondTask); + + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(secondTaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(406, response.statusCode(), + "Должен вернуться статус 406 при пересечении задач по времени."); + assertEquals(1, manager.getTasks().size(), "В менеджере не должна сохраниться пересекающаяся задача."); + } + + // Проверка: DELETE /tasks/{id} должен удалять задачу. + @Test + public void shouldDeleteTaskById() throws IOException, InterruptedException { + Task task = createTestTask( + "Удаляемая задача", + "Нужно удалить", + 25, + LocalDateTime.of(2026, 4, 21, 12, 0) + ); + manager.createTask(task); + + int taskId = manager.getTasks().get(0).getId(); + + URI url = URI.create("http://localhost:8080/tasks/" + taskId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при удалении задачи."); + assertTrue(manager.getTasks().isEmpty(), "Задача не была удалена из менеджера."); + } + + // Проверка: DELETE /tasks/{id} должен возвращать 404, + // если задачи с таким id не существует. + @Test + public void shouldReturn404WhenDeletingMissingTask() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/tasks/999"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404 при удалении несуществующей задачи."); + } +} \ No newline at end of file diff --git a/test/http/HttpTaskServerTestBase.java b/test/http/HttpTaskServerTestBase.java new file mode 100644 index 0000000..2a3f4b8 --- /dev/null +++ b/test/http/HttpTaskServerTestBase.java @@ -0,0 +1,80 @@ +package http; + +import com.google.gson.Gson; +import model.Epic; +import model.Status; +import model.Subtask; +import model.Task; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import service.InMemoryTaskManager; +import service.TaskManager; + +import java.io.IOException; +import java.net.http.HttpClient; +import java.time.Duration; +import java.time.LocalDateTime; + +// Базовый класс для HTTP-тестов. +// Содержит общий код запуска и остановки сервера, +// а также общие объекты, которые пригодятся в наследниках. +public abstract class HttpTaskServerTestBase { + + // Менеджер задач, который будет использоваться сервером в тестах. + protected TaskManager manager; + + // Экземпляр HTTP-сервера. + protected HttpTaskServer taskServer; + + // Gson сервера для преобразования объектов в JSON и обратно. + protected Gson gson; + + // HTTP-клиент для отправки запросов к локальному серверу. + protected HttpClient client; + + // Запуск нового чистого окружения перед каждым тестом. + @BeforeEach + public void setUp() throws IOException { + manager = new InMemoryTaskManager(); + taskServer = new HttpTaskServer(manager); + gson = HttpTaskServer.getGson(); + client = HttpClient.newHttpClient(); + + taskServer.start(); + } + + // Остановка сервера после каждого теста. + @AfterEach + public void shutDown() { + taskServer.stop(); + } + + // Вспомогательный метод для быстрого создания обычной задачи. + protected Task createTestTask(String name, String description, int durationMinutes, LocalDateTime startTime) { + return new Task( + name, + description, + Status.NEW, + Duration.ofMinutes(durationMinutes), + startTime + ); + } + + // Вспомогательный метод для быстрого создания эпика. + protected Epic createTestEpic(String name, String description) { + return new Epic(name, description); + } + + // Вспомогательный метод для быстрого создания подзадачи. + protected Subtask createTestSubtask(String name, String description, int durationMinutes, + LocalDateTime startTime, int epicId) { + return new Subtask( + name, + description, + Status.NEW, + Duration.ofMinutes(durationMinutes), + startTime, + epicId + ); + } +} \ No newline at end of file From e248d6490694cbf3eac6fb1ea060ed5205b90266 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 17:20:10 +0300 Subject: [PATCH 40/45] test: update TaskManager tests for NotFoundException behavior --- test/service/TaskManagerTest.java | 84 ++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java index 2452b28..a45d295 100644 --- a/test/service/TaskManagerTest.java +++ b/test/service/TaskManagerTest.java @@ -6,6 +6,7 @@ import model.Task; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import service.exception.NotFoundException; import java.time.Duration; import java.time.LocalDateTime; @@ -321,7 +322,11 @@ void shouldClearAllTasks() { taskManager.clearTasks(); assertTrue(taskManager.getTasks().isEmpty(), "Список задач должен быть пустым."); - assertNull(taskManager.getTask(task.getId()), "Задача не должна находиться после очистки."); + assertThrows( + NotFoundException.class, + () -> taskManager.getTask(task.getId()), + "После очистки задача должна выбрасывать NotFoundException." + ); assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться от удалённых задач."); assertTrue(taskManager.getPrioritizedTasks().isEmpty(), "Приоритетный список должен очищаться."); } @@ -372,8 +377,17 @@ void shouldClearAllEpicsAndSubtasks() { assertTrue(taskManager.getEpics().isEmpty(), "Список эпиков должен быть пустым."); assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); - assertNull(taskManager.getEpic(epic.getId()), "Эпик не должен находиться после очистки."); - assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача не должна находиться после очистки."); + assertThrows( + NotFoundException.class, + () -> taskManager.getEpic(epic.getId()), + "После очистки эпик должен выбрасывать NotFoundException." + ); + + assertThrows( + NotFoundException.class, + () -> taskManager.getSubtask(subtask.getId()), + "После очистки подзадача должна выбрасывать NotFoundException." + ); assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться."); } @@ -486,9 +500,11 @@ void shouldReturnEmptyListForEpicWithoutSubtasks() { @Test void shouldReturnEmptyListForNonExistingEpicSubtasks() { - List epicSubtasks = taskManager.getEpicSubtasks(999); - - assertTrue(epicSubtasks.isEmpty(), "Для несуществующего эпика должен возвращаться пустой список."); + assertThrows( + NotFoundException.class, + () -> taskManager.getEpicSubtasks(999), + "Для несуществующего эпика должен выбрасываться NotFoundException." + ); } // Проверка сортировки задач и подзадач в приоритетном списке @@ -534,7 +550,11 @@ void shouldDeleteTaskById() { taskManager.getTask(task.getId()); taskManager.deleteTask(task.getId()); - assertNull(taskManager.getTask(task.getId()), "Задача должна быть удалена."); + assertThrows( + NotFoundException.class, + () -> taskManager.getTask(task.getId()), + "После удаления задача должна выбрасывать NotFoundException." + ); assertTrue(taskManager.getTasks().isEmpty(), "Список задач должен быть пустым."); assertTrue(taskManager.getHistory().isEmpty(), "История должна очищаться от удалённой задачи."); } @@ -556,7 +576,11 @@ void shouldDeleteSubtaskAndUpdateEpic() { Epic savedEpic = taskManager.getEpic(epic.getId()); - assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача должна быть удалена."); + assertThrows( + NotFoundException.class, + () -> taskManager.getSubtask(subtask.getId()), + "После удаления подзадача должна выбрасывать NotFoundException." + ); assertTrue(savedEpic.getSubtaskIds().isEmpty(), "У эпика не должно остаться подзадач."); assertEquals(Status.NEW, savedEpic.getStatus(), "Статус эпика должен пересчитаться."); assertNull(savedEpic.getDuration(), "Продолжительность эпика должна стать null."); @@ -577,26 +601,47 @@ void shouldDeleteEpicWithItsSubtasks() { taskManager.deleteEpic(epic.getId()); - assertNull(taskManager.getEpic(epic.getId()), "Эпик должен быть удалён."); - assertNull(taskManager.getSubtask(subtask.getId()), "Подзадача эпика тоже должна быть удалена."); + assertThrows( + NotFoundException.class, + () -> taskManager.getEpic(epic.getId()), + "После удаления эпик должен выбрасывать NotFoundException." + ); + + assertThrows( + NotFoundException.class, + () -> taskManager.getSubtask(subtask.getId()), + "После удаления эпика его подзадача тоже должна выбрасывать NotFoundException." + ); assertTrue(taskManager.getEpics().isEmpty(), "Список эпиков должен быть пустым."); assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым."); } // Проверка граничных случаев поиска задач, эпиков и подзадач @Test - void shouldReturnNullWhenTaskNotFound() { - assertNull(taskManager.getTask(999), "Несуществующая задача должна возвращать null."); + void shouldThrowNotFoundExceptionWhenTaskNotFound() { + assertThrows( + NotFoundException.class, + () -> taskManager.getTask(999), + "Несуществующая задача должна выбрасывать NotFoundException." + ); } @Test - void shouldReturnNullWhenEpicNotFound() { - assertNull(taskManager.getEpic(999), "Несуществующий эпик должен возвращать null."); + void shouldThrowNotFoundExceptionWhenEpicNotFound() { + assertThrows( + NotFoundException.class, + () -> taskManager.getEpic(999), + "Несуществующий эпик должен выбрасывать NotFoundException." + ); } @Test - void shouldReturnNullWhenSubtaskNotFound() { - assertNull(taskManager.getSubtask(999), "Несуществующая подзадача должна возвращать null."); + void shouldThrowNotFoundExceptionWhenSubtaskNotFound() { + assertThrows( + NotFoundException.class, + () -> taskManager.getSubtask(999), + "Несуществующая подзадача должна выбрасывать NotFoundException." + ); } // Проверка добавления задач в историю просмотров @@ -630,9 +675,12 @@ void shouldNotCreateSubtaskWithoutExistingEpic() { 999 ); - Subtask createdSubtask = taskManager.createSubtask(subtask); + assertThrows( + NotFoundException.class, + () -> taskManager.createSubtask(subtask), + "Подзадача без существующего эпика должна выбрасывать NotFoundException." + ); - assertNull(createdSubtask, "Подзадача не должна создаваться без существующего эпика."); assertTrue(taskManager.getSubtasks().isEmpty(), "Список подзадач должен остаться пустым."); } From c124b81c36f9474403494802516ebc5a10810b92 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 19:14:25 +0300 Subject: [PATCH 41/45] test: add HTTP tests for epics and subtasks endpoints --- test/http/HttpTaskManagerEpicsTest.java | 170 ++++++++++++++++ test/http/HttpTaskManagerSubtasksTest.java | 221 +++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 test/http/HttpTaskManagerEpicsTest.java create mode 100644 test/http/HttpTaskManagerSubtasksTest.java diff --git a/test/http/HttpTaskManagerEpicsTest.java b/test/http/HttpTaskManagerEpicsTest.java new file mode 100644 index 0000000..aa070aa --- /dev/null +++ b/test/http/HttpTaskManagerEpicsTest.java @@ -0,0 +1,170 @@ +package http; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import model.Epic; +import model.Subtask; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +// Тесты для endpoint'а /epics. +public class HttpTaskManagerEpicsTest extends HttpTaskServerTestBase { + + // Проверка: GET /epics должен возвращать пустой список, + // если эпиков в менеджере нет. + @Test + public void shouldReturnEmptyEpicsList() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/epics"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа."); + assertEquals("[]", response.body(), "Список эпиков должен быть пустым."); + } + + // Проверка: POST /epics должен создавать новый эпик. + @Test + public void shouldCreateEpic() throws IOException, InterruptedException { + Epic epic = createTestEpic("Покупки", "Список покупок на вечер"); + String epicJson = gson.toJson(epic); + + URI url = URI.create("http://localhost:8080/epics"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(epicJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + List epicsFromManager = manager.getEpics(); + + assertEquals(201, response.statusCode(), "Некорректный код ответа при создании эпика."); + assertNotNull(epicsFromManager, "Список эпиков не должен быть null."); + assertEquals(1, epicsFromManager.size(), "Некорректное количество эпиков."); + assertEquals("Покупки", epicsFromManager.get(0).getName(), "Некорректное имя эпика."); + } + + // Проверка: GET /epics/{id} должен возвращать эпик по id. + @Test + public void shouldReturnEpicById() throws IOException, InterruptedException { + Epic epic = createTestEpic("Учёба", "Подготовка к экзамену"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + URI url = URI.create("http://localhost:8080/epics/" + epicId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + Epic epicFromResponse = gson.fromJson(response.body(), Epic.class); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при получении эпика по id."); + assertNotNull(epicFromResponse, "Эпик не пришёл в ответе."); + assertEquals(epicId, epicFromResponse.getId(), "Некорректный id эпика."); + assertEquals("Учёба", epicFromResponse.getName(), "Некорректное имя эпика."); + } + + // Проверка: GET /epics/{id} должен возвращать 404, + // если эпика с таким id не существует. + @Test + public void shouldReturn404WhenEpicNotFound() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/epics/999"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), "Должен вернуться статус 404, если эпик не найден."); + } + + // Проверка: GET /epics/{id}/subtasks должен возвращать список подзадач эпика. + @Test + public void shouldReturnEpicSubtasks() throws IOException, InterruptedException { + Epic epic = createTestEpic("Переезд", "Подготовка к переезду"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Собрать коробки", + "Упаковать вещи", + 60, + LocalDateTime.of(2026, 4, 22, 10, 0), + epicId + ); + manager.createSubtask(subtask); + + URI url = URI.create("http://localhost:8080/epics/" + epicId + "/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + JsonArray jsonArray = JsonParser.parseString(response.body()).getAsJsonArray(); + JsonObject firstSubtask = jsonArray.get(0).getAsJsonObject(); + + assertEquals(200, response.statusCode(), + "Некорректный код ответа при получении подзадач эпика."); + assertEquals(1, jsonArray.size(), "У эпика должна быть одна подзадача."); + assertEquals("Собрать коробки", firstSubtask.get("name").getAsString(), + "Некорректное имя подзадачи в ответе."); + } + + // Проверка: GET /epics/{id}/subtasks должен возвращать 404, + // если эпика с таким id не существует. + @Test + public void shouldReturn404WhenEpicSubtasksRequestedForMissingEpic() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/epics/999/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404, если подзадачи запрашиваются у несуществующего эпика."); + } + + // Проверка: DELETE /epics/{id} должен удалять эпик. + @Test + public void shouldDeleteEpicById() throws IOException, InterruptedException { + Epic epic = createTestEpic("Отпуск", "Подготовка к отпуску"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + URI url = URI.create("http://localhost:8080/epics/" + epicId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при удалении эпика."); + assertTrue(manager.getEpics().isEmpty(), "Эпик не был удалён из менеджера."); + } +} \ No newline at end of file diff --git a/test/http/HttpTaskManagerSubtasksTest.java b/test/http/HttpTaskManagerSubtasksTest.java new file mode 100644 index 0000000..b955975 --- /dev/null +++ b/test/http/HttpTaskManagerSubtasksTest.java @@ -0,0 +1,221 @@ +package http; + +import model.Epic; +import model.Subtask; +import model.Task; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +// Тесты для endpoint'а /subtasks. +public class HttpTaskManagerSubtasksTest extends HttpTaskServerTestBase { + + // Проверка: GET /subtasks должен возвращать пустой список, + // если подзадач в менеджере нет. + @Test + public void shouldReturnEmptySubtasksList() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа."); + assertEquals("[]", response.body(), "Список подзадач должен быть пустым."); + } + + // Проверка: POST /subtasks должен создавать новую подзадачу, + // если эпик существует. + @Test + public void shouldCreateSubtask() throws IOException, InterruptedException { + Epic epic = createTestEpic("Домашние дела", "Список домашних дел"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Помыть окна", + "На кухне и в комнате", + 45, + LocalDateTime.of(2026, 4, 23, 14, 0), + epicId + ); + + String subtaskJson = gson.toJson(subtask); + + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + List subtasksFromManager = manager.getSubtasks(); + + assertEquals(201, response.statusCode(), "Некорректный код ответа при создании подзадачи."); + assertNotNull(subtasksFromManager, "Список подзадач не должен быть null."); + assertEquals(1, subtasksFromManager.size(), "Некорректное количество подзадач."); + assertEquals("Помыть окна", subtasksFromManager.get(0).getName(), "Некорректное имя подзадачи."); + } + + // Проверка: GET /subtasks/{id} должен возвращать подзадачу по id. + @Test + public void shouldReturnSubtaskById() throws IOException, InterruptedException { + Epic epic = createTestEpic("Учёба", "Подготовка к занятиям"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Решить задачи", + "Практика по Java", + 90, + LocalDateTime.of(2026, 4, 23, 18, 0), + epicId + ); + manager.createSubtask(subtask); + + int subtaskId = manager.getSubtasks().get(0).getId(); + + URI url = URI.create("http://localhost:8080/subtasks/" + subtaskId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + Subtask subtaskFromResponse = gson.fromJson(response.body(), Subtask.class); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при получении подзадачи по id."); + assertNotNull(subtaskFromResponse, "Подзадача не пришла в ответе."); + assertEquals(subtaskId, subtaskFromResponse.getId(), "Некорректный id подзадачи."); + assertEquals("Решить задачи", subtaskFromResponse.getName(), "Некорректное имя подзадачи."); + } + + // Проверка: GET /subtasks/{id} должен возвращать 404, + // если подзадачи с таким id не существует. + @Test + public void shouldReturn404WhenSubtaskNotFound() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/subtasks/999"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), "Должен вернуться статус 404, если подзадача не найдена."); + } + + // Проверка: POST /subtasks должен возвращать 404, + // если у подзадачи указан несуществующий epicId. + @Test + public void shouldReturn404WhenCreatingSubtaskWithoutExistingEpic() throws IOException, InterruptedException { + Subtask subtask = createTestSubtask( + "Несуществующий эпик", + "Подзадача не должна создаться", + 30, + LocalDateTime.of(2026, 4, 24, 10, 0), + 999 + ); + + String subtaskJson = gson.toJson(subtask); + + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404, если подзадача создаётся без существующего эпика."); + assertTrue(manager.getSubtasks().isEmpty(), "Подзадача не должна сохраниться в менеджере."); + } + + // Проверка: POST /subtasks должен возвращать 406, + // если новая подзадача пересекается по времени с существующей задачей. + @Test + public void shouldReturn406WhenSubtaskHasTimeOverlap() throws IOException, InterruptedException { + Epic epic = createTestEpic("Работа", "Рабочие задачи"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Task task = createTestTask( + "Основная задача", + "Занимает время", + 60, + LocalDateTime.of(2026, 4, 24, 12, 0) + ); + manager.createTask(task); + + Subtask overlappingSubtask = createTestSubtask( + "Пересекающаяся подзадача", + "Не должна сохраниться", + 30, + LocalDateTime.of(2026, 4, 24, 12, 30), + epicId + ); + + String subtaskJson = gson.toJson(overlappingSubtask); + + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(406, response.statusCode(), + "Должен вернуться статус 406 при пересечении подзадачи по времени."); + assertTrue(manager.getSubtasks().isEmpty(), "Пересекающаяся подзадача не должна сохраниться."); + } + + // Проверка: DELETE /subtasks/{id} должен удалять подзадачу. + @Test + public void shouldDeleteSubtaskById() throws IOException, InterruptedException { + Epic epic = createTestEpic("Проект", "Работа над проектом"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Написать код", + "Основной модуль", + 120, + LocalDateTime.of(2026, 4, 25, 11, 0), + epicId + ); + manager.createSubtask(subtask); + + int subtaskId = manager.getSubtasks().get(0).getId(); + + URI url = URI.create("http://localhost:8080/subtasks/" + subtaskId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при удалении подзадачи."); + assertTrue(manager.getSubtasks().isEmpty(), "Подзадача не была удалена из менеджера."); + } +} \ No newline at end of file From 11548aa869f042db8687f4805c85550f032a8e1d Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 19:17:48 +0300 Subject: [PATCH 42/45] test: add HTTP tests for history and prioritized endpoints --- test/http/HttpTaskManagerHistoryTest.java | 106 +++++++++++++++++ test/http/HttpTaskManagerPrioritizedTest.java | 110 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 test/http/HttpTaskManagerHistoryTest.java create mode 100644 test/http/HttpTaskManagerPrioritizedTest.java diff --git a/test/http/HttpTaskManagerHistoryTest.java b/test/http/HttpTaskManagerHistoryTest.java new file mode 100644 index 0000000..43663a7 --- /dev/null +++ b/test/http/HttpTaskManagerHistoryTest.java @@ -0,0 +1,106 @@ +package http; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import model.Epic; +import model.Subtask; +import model.Task; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +// Тесты для endpoint'а /history. +public class HttpTaskManagerHistoryTest extends HttpTaskServerTestBase { + + // Проверка: GET /history должен возвращать пустой список, + // если история просмотров пуста. + @Test + public void shouldReturnEmptyHistory() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/history"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа."); + assertEquals("[]", response.body(), "История должна быть пустой."); + } + + // Проверка: GET /history должен возвращать просмотренные задачи. + @Test + public void shouldReturnViewedTasksInHistory() throws IOException, InterruptedException { + Task task = createTestTask( + "Обычная задача", + "Описание задачи", + 30, + LocalDateTime.of(2026, 4, 26, 10, 0) + ); + manager.createTask(task); + + Epic epic = createTestEpic("Эпик", "Описание эпика"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Подзадача", + "Описание подзадачи", + 40, + LocalDateTime.of(2026, 4, 26, 12, 0), + epicId + ); + manager.createSubtask(subtask); + + int taskId = manager.getTasks().get(0).getId(); + int subtaskId = manager.getSubtasks().get(0).getId(); + + manager.getTask(taskId); + manager.getEpic(epicId); + manager.getSubtask(subtaskId); + + URI url = URI.create("http://localhost:8080/history"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + JsonArray jsonArray = JsonParser.parseString(response.body()).getAsJsonArray(); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при получении истории."); + assertEquals(3, jsonArray.size(), "В истории должно быть три просмотренные сущности."); + + JsonObject firstObject = jsonArray.get(0).getAsJsonObject(); + JsonObject secondObject = jsonArray.get(1).getAsJsonObject(); + JsonObject thirdObject = jsonArray.get(2).getAsJsonObject(); + + assertEquals(taskId, firstObject.get("id").getAsInt(), "Первой в истории должна быть обычная задача."); + assertEquals(epicId, secondObject.get("id").getAsInt(), "Вторым в истории должен быть эпик."); + assertEquals(subtaskId, thirdObject.get("id").getAsInt(), "Третьей в истории должна быть подзадача."); + } + + // Проверка: GET /history должен возвращать только GET-запросы. + @Test + public void shouldReturn500ForPostHistoryRequest() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/history"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString("")) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(500, response.statusCode(), + "Для неподдерживаемого POST-запроса к /history должен вернуться 500."); + } +} \ No newline at end of file diff --git a/test/http/HttpTaskManagerPrioritizedTest.java b/test/http/HttpTaskManagerPrioritizedTest.java new file mode 100644 index 0000000..4c96867 --- /dev/null +++ b/test/http/HttpTaskManagerPrioritizedTest.java @@ -0,0 +1,110 @@ +package http; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import model.Epic; +import model.Subtask; +import model.Task; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +// Тесты для endpoint'а /prioritized. +public class HttpTaskManagerPrioritizedTest extends HttpTaskServerTestBase { + + // Проверка: GET /prioritized должен возвращать пустой список, + // если задач с временем начала нет. + @Test + public void shouldReturnEmptyPrioritizedList() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/prioritized"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Некорректный код ответа."); + assertEquals("[]", response.body(), "Список приоритетных задач должен быть пустым."); + } + + // Проверка: GET /prioritized должен возвращать задачи в порядке приоритета. + @Test + public void shouldReturnTasksInPrioritizedOrder() throws IOException, InterruptedException { + Task lateTask = createTestTask( + "Поздняя задача", + "Будет второй", + 30, + LocalDateTime.of(2026, 4, 27, 18, 0) + ); + manager.createTask(lateTask); + + Task earlyTask = createTestTask( + "Ранняя задача", + "Будет первой", + 30, + LocalDateTime.of(2026, 4, 27, 9, 0) + ); + manager.createTask(earlyTask); + + Epic epic = createTestEpic("Эпик", "Описание эпика"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask middleSubtask = createTestSubtask( + "Средняя подзадача", + "Будет между задачами", + 45, + LocalDateTime.of(2026, 4, 27, 13, 0), + epicId + ); + manager.createSubtask(middleSubtask); + + URI url = URI.create("http://localhost:8080/prioritized"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + JsonArray jsonArray = JsonParser.parseString(response.body()).getAsJsonArray(); + + assertEquals(200, response.statusCode(), "Некорректный код ответа при получении приоритетных задач."); + assertEquals(3, jsonArray.size(), "В списке приоритетных задач должно быть три элемента."); + + JsonObject firstObject = jsonArray.get(0).getAsJsonObject(); + JsonObject secondObject = jsonArray.get(1).getAsJsonObject(); + JsonObject thirdObject = jsonArray.get(2).getAsJsonObject(); + + assertEquals("Ранняя задача", firstObject.get("name").getAsString(), + "Первой должна быть самая ранняя задача."); + assertEquals("Средняя подзадача", secondObject.get("name").getAsString(), + "Второй должна быть подзадача со средним временем."); + assertEquals("Поздняя задача", thirdObject.get("name").getAsString(), + "Третьей должна быть самая поздняя задача."); + } + + // Проверка: GET /prioritized должен возвращать только GET-запросы. + @Test + public void shouldReturn500ForPostPrioritizedRequest() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/prioritized"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString("")) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(500, response.statusCode(), + "Для неподдерживаемого POST-запроса к /prioritized должен вернуться 500."); + } +} \ No newline at end of file From 8517c9911d87d56bc0df76e06715f4ade805a60b Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sat, 18 Apr 2026 19:50:14 +0300 Subject: [PATCH 43/45] test: add missing HTTP API tests for update and not found cases --- test/http/HttpTaskManagerEpicsTest.java | 68 +++++++++++++++ test/http/HttpTaskManagerSubtasksTest.java | 97 ++++++++++++++++++++++ test/http/HttpTaskManagerTasksTest.java | 26 ++++++ 3 files changed, 191 insertions(+) diff --git a/test/http/HttpTaskManagerEpicsTest.java b/test/http/HttpTaskManagerEpicsTest.java index aa070aa..78a7b34 100644 --- a/test/http/HttpTaskManagerEpicsTest.java +++ b/test/http/HttpTaskManagerEpicsTest.java @@ -58,6 +58,58 @@ public void shouldCreateEpic() throws IOException, InterruptedException { assertEquals("Покупки", epicsFromManager.get(0).getName(), "Некорректное имя эпика."); } + // Проверка: POST /epics должен обновлять эпик, + // если в JSON указан существующий id. + @Test + public void shouldUpdateEpic() throws IOException, InterruptedException { + Epic epic = createTestEpic("Учёба", "Старое описание"); + manager.createEpic(epic); + + Epic savedEpic = manager.getEpics().get(0); + + Epic updatedEpic = createTestEpic("Учёба и практика", "Новое описание"); + updatedEpic.setId(savedEpic.getId()); + + String updatedEpicJson = gson.toJson(updatedEpic); + + URI url = URI.create("http://localhost:8080/epics"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(updatedEpicJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + Epic epicFromManager = manager.getEpic(savedEpic.getId()); + + assertEquals(201, response.statusCode(), "Некорректный код ответа при обновлении эпика."); + assertEquals("Учёба и практика", epicFromManager.getName(), "Имя эпика не обновилось."); + assertEquals("Новое описание", epicFromManager.getDescription(), "Описание эпика не обновилось."); + } + + // Проверка: POST /epics должен возвращать 404, + // если в JSON указан id несуществующего эпика. + @Test + public void shouldReturn404WhenUpdatingMissingEpic() throws IOException, InterruptedException { + Epic epic = createTestEpic("Несуществующий эпик", "Не должен обновиться"); + epic.setId(999); + + String epicJson = gson.toJson(epic); + + URI url = URI.create("http://localhost:8080/epics"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(epicJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404 при попытке обновить несуществующий эпик."); + } + // Проверка: GET /epics/{id} должен возвращать эпик по id. @Test public void shouldReturnEpicById() throws IOException, InterruptedException { @@ -167,4 +219,20 @@ public void shouldDeleteEpicById() throws IOException, InterruptedException { assertEquals(200, response.statusCode(), "Некорректный код ответа при удалении эпика."); assertTrue(manager.getEpics().isEmpty(), "Эпик не был удалён из менеджера."); } + + // Проверка: DELETE /epics/{id} должен возвращать 404, + // если эпика с таким id не существует. + @Test + public void shouldReturn404WhenDeletingMissingEpic() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/epics/999"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404 при удалении несуществующего эпика."); + } } \ No newline at end of file diff --git a/test/http/HttpTaskManagerSubtasksTest.java b/test/http/HttpTaskManagerSubtasksTest.java index b955975..d5178e6 100644 --- a/test/http/HttpTaskManagerSubtasksTest.java +++ b/test/http/HttpTaskManagerSubtasksTest.java @@ -69,6 +69,87 @@ public void shouldCreateSubtask() throws IOException, InterruptedException { assertEquals("Помыть окна", subtasksFromManager.get(0).getName(), "Некорректное имя подзадачи."); } + // Проверка: POST /subtasks должен обновлять подзадачу, + // если в JSON указан существующий id. + @Test + public void shouldUpdateSubtask() throws IOException, InterruptedException { + Epic epic = createTestEpic("Учёба", "Подготовка к занятиям"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Сделать домашку", + "Первая версия", + 60, + LocalDateTime.of(2026, 4, 25, 16, 0), + epicId + ); + manager.createSubtask(subtask); + + Subtask savedSubtask = manager.getSubtasks().get(0); + + Subtask updatedSubtask = createTestSubtask( + "Сделать домашку по Java", + "Обновлённая версия", + 90, + LocalDateTime.of(2026, 4, 25, 18, 0), + epicId + ); + updatedSubtask.setId(savedSubtask.getId()); + + String updatedSubtaskJson = gson.toJson(updatedSubtask); + + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(updatedSubtaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + Subtask subtaskFromManager = manager.getSubtask(savedSubtask.getId()); + + assertEquals(201, response.statusCode(), "Некорректный код ответа при обновлении подзадачи."); + assertEquals("Сделать домашку по Java", subtaskFromManager.getName(), "Имя подзадачи не обновилось."); + assertEquals("Обновлённая версия", subtaskFromManager.getDescription(), + "Описание подзадачи не обновилось."); + } + + // Проверка: POST /subtasks должен возвращать 404, + // если в JSON указан id несуществующей подзадачи. + @Test + public void shouldReturn404WhenUpdatingMissingSubtask() throws IOException, InterruptedException { + Epic epic = createTestEpic("Работа", "Рабочий эпик"); + manager.createEpic(epic); + + int epicId = manager.getEpics().get(0).getId(); + + Subtask subtask = createTestSubtask( + "Несуществующая подзадача", + "Не должна обновиться", + 40, + LocalDateTime.of(2026, 4, 26, 11, 0), + epicId + ); + subtask.setId(999); + + String subtaskJson = gson.toJson(subtask); + + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404 при попытке обновить несуществующую подзадачу."); + } + // Проверка: GET /subtasks/{id} должен возвращать подзадачу по id. @Test public void shouldReturnSubtaskById() throws IOException, InterruptedException { @@ -218,4 +299,20 @@ public void shouldDeleteSubtaskById() throws IOException, InterruptedException { assertEquals(200, response.statusCode(), "Некорректный код ответа при удалении подзадачи."); assertTrue(manager.getSubtasks().isEmpty(), "Подзадача не была удалена из менеджера."); } + + // Проверка: DELETE /subtasks/{id} должен возвращать 404, + // если подзадачи с таким id не существует. + @Test + public void shouldReturn404WhenDeletingMissingSubtask() throws IOException, InterruptedException { + URI url = URI.create("http://localhost:8080/subtasks/999"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404 при удалении несуществующей подзадачи."); + } } \ No newline at end of file diff --git a/test/http/HttpTaskManagerTasksTest.java b/test/http/HttpTaskManagerTasksTest.java index 3529062..7eaf581 100644 --- a/test/http/HttpTaskManagerTasksTest.java +++ b/test/http/HttpTaskManagerTasksTest.java @@ -143,6 +143,32 @@ public void shouldUpdateTask() throws IOException, InterruptedException { "Описание задачи не обновилось."); } + // Проверка: POST /tasks должен возвращать 404, + // если в JSON указан id несуществующей задачи. + @Test + public void shouldReturn404WhenUpdatingMissingTask() throws IOException, InterruptedException { + Task task = createTestTask( + "Несуществующая задача", + "Не должна обновиться", + 20, + LocalDateTime.of(2026, 4, 20, 15, 0) + ); + task.setId(999); + + String taskJson = gson.toJson(task); + + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode(), + "Должен вернуться статус 404 при попытке обновить несуществующую задачу."); + } + // Проверка: POST /tasks должен возвращать 406, // если новая задача пересекается по времени с существующей. @Test From ff987047276e82cfcf5ef6bb511830366fb30423 Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sun, 19 Apr 2026 13:22:21 +0300 Subject: [PATCH 44/45] refactor: extract HTTP status codes to constants in handlers --- src/http/handler/BaseHttpHandler.java | 15 +++++++++++---- src/http/handler/EpicsHandler.java | 8 ++++---- src/http/handler/HistoryHandler.java | 2 +- src/http/handler/PrioritizedHandler.java | 2 +- src/http/handler/SubtasksHandler.java | 6 +++--- src/http/handler/TasksHandler.java | 6 +++--- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/http/handler/BaseHttpHandler.java b/src/http/handler/BaseHttpHandler.java index ee4813a..1cb3bcf 100644 --- a/src/http/handler/BaseHttpHandler.java +++ b/src/http/handler/BaseHttpHandler.java @@ -9,6 +9,13 @@ // Содержит общие методы для отправки и чтения HTTP-данных. public class BaseHttpHandler { + // HTTP-статусы, используемые в обработчиках. + protected static final int STATUS_OK = 200; + protected static final int STATUS_CREATED = 201; + protected static final int STATUS_NOT_FOUND = 404; + protected static final int STATUS_NOT_ACCEPTABLE = 406; + protected static final int STATUS_INTERNAL_ERROR = 500; + // Отправка ответа с текстом в формате JSON. protected void sendText(HttpExchange exchange, String text, int statusCode) throws IOException { byte[] response = text.getBytes(StandardCharsets.UTF_8); @@ -21,7 +28,7 @@ protected void sendText(HttpExchange exchange, String text, int statusCode) thro // Отправка ответа 201 без тела. protected void sendCreated(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(201, -1); + exchange.sendResponseHeaders(STATUS_CREATED, -1); exchange.close(); } @@ -32,19 +39,19 @@ protected String readText(HttpExchange exchange) throws IOException { // Ответ 404 — объект не найден. protected void sendNotFound(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(404, -1); + exchange.sendResponseHeaders(STATUS_NOT_FOUND, -1); exchange.close(); } // Ответ 406 — задача пересекается по времени. protected void sendHasInteractions(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(406, -1); + exchange.sendResponseHeaders(STATUS_NOT_ACCEPTABLE, -1); exchange.close(); } // Ответ 500 — внутренняя ошибка сервера. protected void sendInternalError(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(500, -1); + exchange.sendResponseHeaders(STATUS_INTERNAL_ERROR, -1); exchange.close(); } } \ No newline at end of file diff --git a/src/http/handler/EpicsHandler.java b/src/http/handler/EpicsHandler.java index d49f449..8d4b96b 100644 --- a/src/http/handler/EpicsHandler.java +++ b/src/http/handler/EpicsHandler.java @@ -68,7 +68,7 @@ private void handleGet(HttpExchange exchange, String path) throws IOException { if ("/epics".equals(path)) { List epics = taskManager.getEpics(); String response = gson.toJson(epics); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); return; } @@ -76,14 +76,14 @@ private void handleGet(HttpExchange exchange, String path) throws IOException { int epicId = extractEpicIdForSubtasks(path); List subtasks = taskManager.getEpicSubtasks(epicId); String response = gson.toJson(subtasks); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); return; } int id = extractId(path); Epic epic = taskManager.getEpic(id); String response = gson.toJson(epic); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); } // Обработка POST-запроса: @@ -114,7 +114,7 @@ private void handlePost(HttpExchange exchange, String path) throws IOException { private void handleDelete(HttpExchange exchange, String path) throws IOException { int id = extractId(path); taskManager.deleteEpic(id); - sendText(exchange, "", 200); + sendText(exchange, "", STATUS_OK); } // Извлечение id эпика из пути вида /epics/{id}. diff --git a/src/http/handler/HistoryHandler.java b/src/http/handler/HistoryHandler.java index 8ae6c06..8e2902d 100644 --- a/src/http/handler/HistoryHandler.java +++ b/src/http/handler/HistoryHandler.java @@ -43,7 +43,7 @@ public void handle(HttpExchange exchange) throws IOException { List history = taskManager.getHistory(); String response = gson.toJson(history); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); } catch (Exception e) { sendInternalError(exchange); diff --git a/src/http/handler/PrioritizedHandler.java b/src/http/handler/PrioritizedHandler.java index c54c9e1..5d451f1 100644 --- a/src/http/handler/PrioritizedHandler.java +++ b/src/http/handler/PrioritizedHandler.java @@ -43,7 +43,7 @@ public void handle(HttpExchange exchange) throws IOException { List prioritizedTasks = taskManager.getPrioritizedTasks(); String response = gson.toJson(prioritizedTasks); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); } catch (Exception e) { sendInternalError(exchange); diff --git a/src/http/handler/SubtasksHandler.java b/src/http/handler/SubtasksHandler.java index ab4db38..e8b6892 100644 --- a/src/http/handler/SubtasksHandler.java +++ b/src/http/handler/SubtasksHandler.java @@ -66,14 +66,14 @@ private void handleGet(HttpExchange exchange, String path) throws IOException { if ("/subtasks".equals(path)) { List subtasks = taskManager.getSubtasks(); String response = gson.toJson(subtasks); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); return; } int id = extractId(path); Subtask subtask = taskManager.getSubtask(id); String response = gson.toJson(subtask); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); } // Обработка POST-запроса: @@ -104,7 +104,7 @@ private void handlePost(HttpExchange exchange, String path) throws IOException { private void handleDelete(HttpExchange exchange, String path) throws IOException { int id = extractId(path); taskManager.deleteSubtask(id); - sendText(exchange, "", 200); + sendText(exchange, "", STATUS_OK); } // Извлечение id подзадачи из пути. diff --git a/src/http/handler/TasksHandler.java b/src/http/handler/TasksHandler.java index ce12203..c10e538 100644 --- a/src/http/handler/TasksHandler.java +++ b/src/http/handler/TasksHandler.java @@ -66,14 +66,14 @@ private void handleGet(HttpExchange exchange, String path) throws IOException { if ("/tasks".equals(path)) { List tasks = taskManager.getTasks(); String response = gson.toJson(tasks); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); return; } int id = extractId(path); Task task = taskManager.getTask(id); String response = gson.toJson(task); - sendText(exchange, response, 200); + sendText(exchange, response, STATUS_OK); } // Обработка POST-запроса: @@ -104,7 +104,7 @@ private void handlePost(HttpExchange exchange, String path) throws IOException { private void handleDelete(HttpExchange exchange, String path) throws IOException { int id = extractId(path); taskManager.deleteTask(id); - sendText(exchange, "", 200); + sendText(exchange, "", STATUS_OK); } // Извлечение id задачи из пути. From c414e861f6a330f9723a8e31a92110ec3f8fb99e Mon Sep 17 00:00:00 2001 From: Ksenia Date: Sun, 19 Apr 2026 13:42:21 +0300 Subject: [PATCH 45/45] refactor: extract shared constants and URI builder for HTTP tests --- test/http/HttpTaskManagerEpicsTest.java | 20 +++++++++---------- test/http/HttpTaskManagerHistoryTest.java | 6 +++--- test/http/HttpTaskManagerPrioritizedTest.java | 6 +++--- test/http/HttpTaskManagerSubtasksTest.java | 20 +++++++++---------- test/http/HttpTaskManagerTasksTest.java | 18 ++++++++--------- test/http/HttpTaskServerTestBase.java | 10 ++++++++++ 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/test/http/HttpTaskManagerEpicsTest.java b/test/http/HttpTaskManagerEpicsTest.java index 78a7b34..85430a4 100644 --- a/test/http/HttpTaskManagerEpicsTest.java +++ b/test/http/HttpTaskManagerEpicsTest.java @@ -23,7 +23,7 @@ public class HttpTaskManagerEpicsTest extends HttpTaskServerTestBase { // если эпиков в менеджере нет. @Test public void shouldReturnEmptyEpicsList() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/epics"); + URI url = createUri("/epics"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -41,7 +41,7 @@ public void shouldCreateEpic() throws IOException, InterruptedException { Epic epic = createTestEpic("Покупки", "Список покупок на вечер"); String epicJson = gson.toJson(epic); - URI url = URI.create("http://localhost:8080/epics"); + URI url = createUri("/epics"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -72,7 +72,7 @@ public void shouldUpdateEpic() throws IOException, InterruptedException { String updatedEpicJson = gson.toJson(updatedEpic); - URI url = URI.create("http://localhost:8080/epics"); + URI url = createUri("/epics"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -97,7 +97,7 @@ public void shouldReturn404WhenUpdatingMissingEpic() throws IOException, Interru String epicJson = gson.toJson(epic); - URI url = URI.create("http://localhost:8080/epics"); + URI url = createUri("/epics"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -118,7 +118,7 @@ public void shouldReturnEpicById() throws IOException, InterruptedException { int epicId = manager.getEpics().get(0).getId(); - URI url = URI.create("http://localhost:8080/epics/" + epicId); + URI url = createUri("/epics/" + epicId); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -138,7 +138,7 @@ public void shouldReturnEpicById() throws IOException, InterruptedException { // если эпика с таким id не существует. @Test public void shouldReturn404WhenEpicNotFound() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/epics/999"); + URI url = createUri("/epics/" + MISSING_ID); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -166,7 +166,7 @@ public void shouldReturnEpicSubtasks() throws IOException, InterruptedException ); manager.createSubtask(subtask); - URI url = URI.create("http://localhost:8080/epics/" + epicId + "/subtasks"); + URI url = createUri("/epics/" + epicId + "/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -188,7 +188,7 @@ public void shouldReturnEpicSubtasks() throws IOException, InterruptedException // если эпика с таким id не существует. @Test public void shouldReturn404WhenEpicSubtasksRequestedForMissingEpic() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/epics/999/subtasks"); + URI url = createUri("/epics/" + MISSING_ID + "/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -208,7 +208,7 @@ public void shouldDeleteEpicById() throws IOException, InterruptedException { int epicId = manager.getEpics().get(0).getId(); - URI url = URI.create("http://localhost:8080/epics/" + epicId); + URI url = createUri("/epics/" + epicId); HttpRequest request = HttpRequest.newBuilder() .uri(url) .DELETE() @@ -224,7 +224,7 @@ public void shouldDeleteEpicById() throws IOException, InterruptedException { // если эпика с таким id не существует. @Test public void shouldReturn404WhenDeletingMissingEpic() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/epics/999"); + URI url = createUri("/epics/" + MISSING_ID); HttpRequest request = HttpRequest.newBuilder() .uri(url) .DELETE() diff --git a/test/http/HttpTaskManagerHistoryTest.java b/test/http/HttpTaskManagerHistoryTest.java index 43663a7..6232298 100644 --- a/test/http/HttpTaskManagerHistoryTest.java +++ b/test/http/HttpTaskManagerHistoryTest.java @@ -23,7 +23,7 @@ public class HttpTaskManagerHistoryTest extends HttpTaskServerTestBase { // если история просмотров пуста. @Test public void shouldReturnEmptyHistory() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/history"); + URI url = createUri("/history"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -67,7 +67,7 @@ public void shouldReturnViewedTasksInHistory() throws IOException, InterruptedEx manager.getEpic(epicId); manager.getSubtask(subtaskId); - URI url = URI.create("http://localhost:8080/history"); + URI url = createUri("/history"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -92,7 +92,7 @@ public void shouldReturnViewedTasksInHistory() throws IOException, InterruptedEx // Проверка: GET /history должен возвращать только GET-запросы. @Test public void shouldReturn500ForPostHistoryRequest() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/history"); + URI url = createUri("/history"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .POST(HttpRequest.BodyPublishers.ofString("")) diff --git a/test/http/HttpTaskManagerPrioritizedTest.java b/test/http/HttpTaskManagerPrioritizedTest.java index 4c96867..2000d6a 100644 --- a/test/http/HttpTaskManagerPrioritizedTest.java +++ b/test/http/HttpTaskManagerPrioritizedTest.java @@ -23,7 +23,7 @@ public class HttpTaskManagerPrioritizedTest extends HttpTaskServerTestBase { // если задач с временем начала нет. @Test public void shouldReturnEmptyPrioritizedList() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/prioritized"); + URI url = createUri("/prioritized"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -68,7 +68,7 @@ public void shouldReturnTasksInPrioritizedOrder() throws IOException, Interrupte ); manager.createSubtask(middleSubtask); - URI url = URI.create("http://localhost:8080/prioritized"); + URI url = createUri("/prioritized"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -96,7 +96,7 @@ public void shouldReturnTasksInPrioritizedOrder() throws IOException, Interrupte // Проверка: GET /prioritized должен возвращать только GET-запросы. @Test public void shouldReturn500ForPostPrioritizedRequest() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/prioritized"); + URI url = createUri("/prioritized"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .POST(HttpRequest.BodyPublishers.ofString("")) diff --git a/test/http/HttpTaskManagerSubtasksTest.java b/test/http/HttpTaskManagerSubtasksTest.java index d5178e6..b4b0bfd 100644 --- a/test/http/HttpTaskManagerSubtasksTest.java +++ b/test/http/HttpTaskManagerSubtasksTest.java @@ -21,7 +21,7 @@ public class HttpTaskManagerSubtasksTest extends HttpTaskServerTestBase { // если подзадач в менеджере нет. @Test public void shouldReturnEmptySubtasksList() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/subtasks"); + URI url = createUri("/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -52,7 +52,7 @@ public void shouldCreateSubtask() throws IOException, InterruptedException { String subtaskJson = gson.toJson(subtask); - URI url = URI.create("http://localhost:8080/subtasks"); + URI url = createUri("/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -100,7 +100,7 @@ public void shouldUpdateSubtask() throws IOException, InterruptedException { String updatedSubtaskJson = gson.toJson(updatedSubtask); - URI url = URI.create("http://localhost:8080/subtasks"); + URI url = createUri("/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -137,7 +137,7 @@ public void shouldReturn404WhenUpdatingMissingSubtask() throws IOException, Inte String subtaskJson = gson.toJson(subtask); - URI url = URI.create("http://localhost:8080/subtasks"); + URI url = createUri("/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -169,7 +169,7 @@ public void shouldReturnSubtaskById() throws IOException, InterruptedException { int subtaskId = manager.getSubtasks().get(0).getId(); - URI url = URI.create("http://localhost:8080/subtasks/" + subtaskId); + URI url = createUri("/subtasks/" + subtaskId); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -189,7 +189,7 @@ public void shouldReturnSubtaskById() throws IOException, InterruptedException { // если подзадачи с таким id не существует. @Test public void shouldReturn404WhenSubtaskNotFound() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/subtasks/999"); + URI url = createUri("/subtasks/" + MISSING_ID); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -214,7 +214,7 @@ public void shouldReturn404WhenCreatingSubtaskWithoutExistingEpic() throws IOExc String subtaskJson = gson.toJson(subtask); - URI url = URI.create("http://localhost:8080/subtasks"); + URI url = createUri("/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -255,7 +255,7 @@ public void shouldReturn406WhenSubtaskHasTimeOverlap() throws IOException, Inter String subtaskJson = gson.toJson(overlappingSubtask); - URI url = URI.create("http://localhost:8080/subtasks"); + URI url = createUri("/subtasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .header("Content-Type", "application/json") @@ -288,7 +288,7 @@ public void shouldDeleteSubtaskById() throws IOException, InterruptedException { int subtaskId = manager.getSubtasks().get(0).getId(); - URI url = URI.create("http://localhost:8080/subtasks/" + subtaskId); + URI url = createUri("/subtasks/" + subtaskId); HttpRequest request = HttpRequest.newBuilder() .uri(url) .DELETE() @@ -304,7 +304,7 @@ public void shouldDeleteSubtaskById() throws IOException, InterruptedException { // если подзадачи с таким id не существует. @Test public void shouldReturn404WhenDeletingMissingSubtask() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/subtasks/999"); + URI url = createUri("/subtasks/" + MISSING_ID); HttpRequest request = HttpRequest.newBuilder() .uri(url) .DELETE() diff --git a/test/http/HttpTaskManagerTasksTest.java b/test/http/HttpTaskManagerTasksTest.java index 7eaf581..21080ea 100644 --- a/test/http/HttpTaskManagerTasksTest.java +++ b/test/http/HttpTaskManagerTasksTest.java @@ -19,7 +19,7 @@ public class HttpTaskManagerTasksTest extends HttpTaskServerTestBase { // если задач в менеджере нет. @Test public void shouldReturnEmptyTasksList() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/tasks"); + URI url = createUri("/tasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -43,7 +43,7 @@ public void shouldCreateTask() throws IOException, InterruptedException { String taskJson = gson.toJson(task); - URI url = URI.create("http://localhost:8080/tasks"); + URI url = createUri("/tasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .POST(HttpRequest.BodyPublishers.ofString(taskJson)) @@ -72,7 +72,7 @@ public void shouldReturnTaskById() throws IOException, InterruptedException { int taskId = manager.getTasks().get(0).getId(); - URI url = URI.create("http://localhost:8080/tasks/" + taskId); + URI url = createUri("/tasks/" + taskId); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -92,7 +92,7 @@ public void shouldReturnTaskById() throws IOException, InterruptedException { // если задачи с таким id не существует. @Test public void shouldReturn404WhenTaskNotFound() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/tasks/999"); + URI url = createUri("/tasks/" + MISSING_ID); HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() @@ -127,7 +127,7 @@ public void shouldUpdateTask() throws IOException, InterruptedException { String updatedTaskJson = gson.toJson(updatedTask); - URI url = URI.create("http://localhost:8080/tasks"); + URI url = createUri("/tasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .POST(HttpRequest.BodyPublishers.ofString(updatedTaskJson)) @@ -157,7 +157,7 @@ public void shouldReturn404WhenUpdatingMissingTask() throws IOException, Interru String taskJson = gson.toJson(task); - URI url = URI.create("http://localhost:8080/tasks"); + URI url = createUri("/tasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .POST(HttpRequest.BodyPublishers.ofString(taskJson)) @@ -190,7 +190,7 @@ public void shouldReturn406WhenTaskHasTimeOverlap() throws IOException, Interrup String secondTaskJson = gson.toJson(secondTask); - URI url = URI.create("http://localhost:8080/tasks"); + URI url = createUri("/tasks"); HttpRequest request = HttpRequest.newBuilder() .uri(url) .POST(HttpRequest.BodyPublishers.ofString(secondTaskJson)) @@ -216,7 +216,7 @@ public void shouldDeleteTaskById() throws IOException, InterruptedException { int taskId = manager.getTasks().get(0).getId(); - URI url = URI.create("http://localhost:8080/tasks/" + taskId); + URI url = createUri("/tasks/" + taskId); HttpRequest request = HttpRequest.newBuilder() .uri(url) .DELETE() @@ -232,7 +232,7 @@ public void shouldDeleteTaskById() throws IOException, InterruptedException { // если задачи с таким id не существует. @Test public void shouldReturn404WhenDeletingMissingTask() throws IOException, InterruptedException { - URI url = URI.create("http://localhost:8080/tasks/999"); + URI url = createUri("/tasks/" + MISSING_ID); HttpRequest request = HttpRequest.newBuilder() .uri(url) .DELETE() diff --git a/test/http/HttpTaskServerTestBase.java b/test/http/HttpTaskServerTestBase.java index 2a3f4b8..02aea9a 100644 --- a/test/http/HttpTaskServerTestBase.java +++ b/test/http/HttpTaskServerTestBase.java @@ -11,6 +11,7 @@ import service.TaskManager; import java.io.IOException; +import java.net.URI; import java.net.http.HttpClient; import java.time.Duration; import java.time.LocalDateTime; @@ -32,6 +33,10 @@ public abstract class HttpTaskServerTestBase { // HTTP-клиент для отправки запросов к локальному серверу. protected HttpClient client; + // Общие константы для HTTP-тестов. + protected static final String BASE_URL = "http://localhost:8080"; + protected static final int MISSING_ID = 999; + // Запуск нового чистого окружения перед каждым тестом. @BeforeEach public void setUp() throws IOException { @@ -49,6 +54,11 @@ public void shutDown() { taskServer.stop(); } + // Вспомогательный метод для создания URI к локальному тестовому серверу. + protected URI createUri(String path) { + return URI.create(BASE_URL + path); + } + // Вспомогательный метод для быстрого создания обычной задачи. protected Task createTestTask(String name, String description, int durationMinutes, LocalDateTime startTime) { return new Task(