diff --git a/src/app/Main.java b/src/app/Main.java index d97f939..8e7beb8 100644 --- a/src/app/Main.java +++ b/src/app/Main.java @@ -1,5 +1,8 @@ package app; +import java.time.Duration; +import java.time.LocalDateTime; + import model.*; import service.Managers; import service.TaskManager; @@ -42,16 +45,21 @@ public static void main(String[] args) { private static TaskManager getTaskManager() { TaskManager manager = Managers.getDefault(); - Task taskA = new Task("Задача A", "Описание задачи A", Status.NEW); - Task taskB = new Task("Задача B", "Описание задачи B", Status.DONE); + LocalDateTime testDateTime = LocalDateTime.now(); + Duration testDuration = Duration.ofMinutes(30); + + Task taskA = new Task("Задача A", "Описание задачи A", Status.NEW, + testDuration, testDateTime); + Task taskB = new Task("Задача B", "Описание задачи B", Status.DONE, + testDuration, testDateTime.plus(Duration.ofHours(1))); Epic epicA = new Epic("Эпик A", "Описание эпика с тремя подзадачами"); Epic epicB = new Epic("Эпик B", "Описание эпика без подзадач"); SubTask subTaskA = new SubTask("Подзадача A1", "Подзадача 1 эпика A", - Status.NEW, 3); + Status.NEW, 3, testDuration, testDateTime.plus(Duration.ofHours(2))); SubTask subTaskB = new SubTask("Подзадача A2", "Подзадача 2 эпика A", - Status.DONE, 3); + Status.DONE, 3, testDuration, testDateTime.plus(Duration.ofHours(3))); SubTask subTaskC = new SubTask("Подзадача A3", "Подзадача 3 эпика A", - Status.NEW, 3); + Status.NEW, 3, testDuration, testDateTime.plus(Duration.ofHours(4))); manager.createTask(taskA); // id = 1 manager.createTask(taskB); // id = 2 @@ -66,9 +74,7 @@ private static TaskManager getTaskManager() { private static void printAllTasks(TaskManager manager) { System.out.println(); System.out.println("Задачи:"); - for (Task task : manager.getTasks()) { - System.out.println(task); - } + manager.getTasks().forEach(System.out::println); System.out.println(); System.out.println("Эпики:"); @@ -82,15 +88,11 @@ private static void printAllTasks(TaskManager manager) { System.out.println(); System.out.println("Подзадачи:"); - for (Task subtask : manager.getSubTasks()) { - System.out.println(subtask); - } + manager.getSubTasks().forEach(System.out::println); System.out.println(); System.out.println("История:"); - for (Task task : manager.getHistory()) { - System.out.println(task); - } + manager.getHistory().forEach(System.out::println); System.out.println(); } } \ No newline at end of file diff --git a/src/model/Epic.java b/src/model/Epic.java index 6a412bf..9386a23 100644 --- a/src/model/Epic.java +++ b/src/model/Epic.java @@ -1,9 +1,13 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; public class Epic extends Task { private Map relatedSubTaskMap; + private LocalDateTime endTime; public Epic(String title, String description) { super(title, description, Status.NEW); @@ -59,6 +63,33 @@ public void updateSubTask(SubTask subTask) { } } + public void updateTimeFrame(TreeSet sortedSubTasks) { + TreeSet allSubTasks = sortedSubTasks; + + Duration totalDuration = Duration.ofMinutes( + allSubTasks.stream() + .reduce( + 0L, + (subtotal, subTask) -> subtotal + subTask.getDuration().toMinutes(), Long::sum) + ); + + allSubTasks = sortedSubTasks.stream() + .filter(s -> s.getStartTime().isPresent()) + .collect(Collectors.toCollection(TreeSet::new)); + + LocalDateTime startTime; + if (allSubTasks.isEmpty()) { + startTime = null; + setEndTime(null); + } else { + startTime = allSubTasks.getFirst().getStartTime().get(); + SubTask lastSubTask = allSubTasks.getLast(); + setEndTime(lastSubTask.getStartTime().get().plus(lastSubTask.getDuration())); + } + setStartTime(startTime); + setDuration(totalDuration); + } + @Override public Status getStatus() { boolean isAllNew = true; @@ -86,6 +117,15 @@ public Status getStatus() { } } + @Override + public Optional getEndTime() { + return Optional.ofNullable(endTime); + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + @Override public String toCSV(int headersCount) { return super.toCSV(headersCount).replace("TASK", "EPIC"); @@ -114,12 +154,23 @@ public boolean equals(Object object) { @Override public String toString() { - return "Epic{" + - "subTaskIdList=" + getSubTaskIdList() + - ", id=" + super.getId() + - ", title='" + super.getTitle() + '\'' + - ", description.length='" + super.getDescription().length() + '\'' + - ", status=" + getStatus() + - '}'; + String durationString; + + try { + durationString = String.valueOf(super.getDuration().toMinutes()); + } catch (NullPointerException e) { + durationString = "N/A"; + } + return String.format("Epic{subTaskIdList=%s, id=%d, title='%s', description.length='%d', " + + "status=%s, startTime=%s, duration=%s min, endTime=%s}", + getSubTaskIdList(), + super.getId(), + super.getTitle(), + super.getDescription().length(), + super.getStatus(), + super.getFormatterStartTime(), + durationString, + super.getFormattedEndTime() + ); } } diff --git a/src/model/SubTask.java b/src/model/SubTask.java index 60ea069..a222e7b 100644 --- a/src/model/SubTask.java +++ b/src/model/SubTask.java @@ -1,15 +1,28 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.Objects; public class SubTask extends Task { private final int epicId; - public SubTask(String title, String description, Status status, int epicId) { + protected SubTask(String title, String description, Status status, int epicId) { super(title, description, status); this.epicId = epicId; } + public SubTask(String title, String description, Status status, int epicId, Duration duration) { + this(title, description, status, epicId); + super.setDuration(duration); + } + + public SubTask(String title, String description, Status status, int epicId, + Duration duration, LocalDateTime startTime) { + this(title, description, status, epicId, duration); + super.setStartTime(startTime); + } + public int getEpicId() { return epicId; } @@ -43,12 +56,16 @@ public boolean equals(Object object) { @Override public String toString() { - return "SubTask{" + - "epicId=" + getEpicId() + - ", id=" + super.getId() + - ", title='" + super.getTitle() + '\'' + - ", description.length='" + super.getDescription().length() + '\'' + - ", status=" + super.getStatus() + - '}'; + return String.format("SubTask{epicId=%d, id=%d, title='%s', description.length='%d', " + + "status=%s, startTime=%s, duration=%d min, endTime=%s}", + getEpicId(), + super.getId(), + super.getTitle(), + super.getDescription().length(), + super.getStatus(), + super.getFormatterStartTime(), + super.getDuration().toMinutes(), + super.getFormattedEndTime() + ); } } diff --git a/src/model/Task.java b/src/model/Task.java index 2f3060f..a5f9b1c 100644 --- a/src/model/Task.java +++ b/src/model/Task.java @@ -1,19 +1,36 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.Objects; +import java.util.Optional; -public class Task { +import service.utils.Utils; + +public class Task implements Comparable { private final String title; private final String description; private final Status status; + private Duration duration; + private LocalDateTime startTime; private int id; - public Task(String title, String description, Status status) { + protected Task(String title, String description, Status status) { this.title = title; this.description = description; this.status = status; } + public Task(String title, String description, Status status, Duration duration) { + this(title, description, status); + this.duration = duration; + } + + public Task(String title, String description, Status status, Duration duration, LocalDateTime startTime) { + this(title, description, status, duration); + this.startTime = startTime; + } + public int getId() { return id; } @@ -34,13 +51,82 @@ public Status getStatus() { return status; } + public Optional getEndTime() { + return Optional.ofNullable(startTime).map(time -> time.plus(duration)); + } + + public Duration getDuration() { + return duration; + } + + protected void setDuration(Duration duration) { + this.duration = duration; + } + + public Optional getStartTime() { + return Optional.ofNullable(startTime); + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + protected String getFormatterStartTime() { + String startTimeString = "N/A"; + + if (getStartTime().isPresent()) { + startTimeString = getStartTime().get().format(Utils.formatter); + } + + return startTimeString; + } + + protected String getFormattedEndTime() { + String endTimeString = "N/A"; + + if (getEndTime().isPresent()) { + endTimeString = getEndTime().get().format(Utils.formatter); + } + + return endTimeString; + } + public String toCSV(int headersCount) { - return String.format("%s,%s,%s,%s,%s", id, "TASK", title, status, description); + String duration; + try { + duration = String.valueOf(getDuration().toMinutes()); + } catch (NullPointerException e) { + duration = "0"; + } + return String.format("%s,%s,%s,%s,%s,%s,%s", + id, "TASK", title, status, description, + getFormatterStartTime(), duration); + } + + @Override + public int compareTo(Task task) { + Optional timeA = this.getStartTime(); + Optional timeB = task.getStartTime(); + + if (timeA.isEmpty() && timeB.isEmpty()) { + return 0; + } + + if (timeA.isPresent() && timeB.isEmpty()) { + return -1; + } + + if (timeA.isEmpty()) { + return 1; + } + + return timeA.get().compareTo(timeB.get()); } @Override public int hashCode() { - return Objects.hash(getId(), getTitle(), getDescription(), getStatus()); + return Objects.hash(getId(), getTitle(), getDescription(), + getStatus(), getStartTime(), getDuration(), getEndTime()); } @Override @@ -59,16 +145,23 @@ public boolean equals(Object object) { return (this.id == task.id && Objects.equals(this.title, task.title) && Objects.equals(this.description, task.description) + && Objects.equals(this.getStartTime(), task.getStartTime()) + && Objects.equals(this.duration, task.getDuration()) + && Objects.equals(this.getEndTime(), task.getEndTime()) && this.getStatus() == task.getStatus()); } @Override public String toString() { - return "Task{" + - "id=" + getId() + - ", title='" + getTitle() + '\'' + - ", description.length='" + getDescription().length() + '\'' + - ", status=" + getStatus() + - '}'; + return String.format("Task{id=%d, title='%s', description.length='%d', " + + "status=%s, startTime=%s, duration=%d min, endTime=%s}", + getId(), + getTitle(), + getDescription().length(), + getStatus(), + getFormatterStartTime(), + getDuration().toMinutes(), + getFormattedEndTime() + ); } } diff --git a/src/service/FileBackedTaskManager.java b/src/service/FileBackedTaskManager.java index b6d2671..8f9bdbf 100644 --- a/src/service/FileBackedTaskManager.java +++ b/src/service/FileBackedTaskManager.java @@ -3,16 +3,20 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; import model.*; import service.exceptions.ManagerSaveException; +import service.utils.Utils; import static service.utils.Utils.csvCommaEqualizer; public class FileBackedTaskManager extends InMemoryTaskManager implements TaskManager { private final Path saveFilePath; - private final String[] headers = new String[]{"id", "type", "name", "status", "description", "epic"}; + private final String[] headers = new String[]{"id", "type", "title", "status", "description", + "start_time", "duration", "epic"}; public FileBackedTaskManager(IdGenerator idGenerator, HistoryManager historyManager, Path saveFilePath) { super(idGenerator, historyManager); @@ -26,11 +30,16 @@ public FileBackedTaskManager(IdGenerator idGenerator, HistoryManager historyMana public static void main(String[] args) throws IOException { File testFile = File.createTempFile("test", "csv"); testFile.deleteOnExit(); + LocalDateTime testDateTime = LocalDateTime.of(2025, 6, 1, 22, 0); FileBackedTaskManager managerA = Managers.getFileBacked(testFile); - Task taskA = new Task("Задача 1", "Описание задачи 1", Status.NEW); - Task taskB = new Task("Задача 2", "Описание задачи 2", Status.DONE); + + Task taskA = new Task("Задача 1", "Описание задачи 1", Status.NEW, + Duration.ofMinutes(30), testDateTime); + Task taskB = new Task("Задача 2", "Описание задачи 2", Status.DONE, + Duration.ofMinutes(90), testDateTime.plus(Duration.ofMinutes(35))); Epic epicA = new Epic("Эпик 1", "Описание эпика"); - SubTask subTaskA = new SubTask("Подзадача 1", "Описание подзадачи", Status.NEW, 3); + SubTask subTaskA = new SubTask("Подзадача 1", "Описание подзадачи", Status.NEW, + 3, Duration.ofMinutes(10), testDateTime.plus(Duration.ofDays(1))); int taskAId = managerA.createTask(taskA); int taskBId = managerA.createTask(taskB); @@ -39,10 +48,10 @@ public static void main(String[] args) throws IOException { FileBackedTaskManager managerB = Managers.getFileBacked(testFile); - if (managerA.getTaskById(taskAId).equals(managerB.getTaskById(taskAId)) - && managerA.getTaskById(taskBId).equals(managerB.getTaskById(taskBId)) + if (managerA.getTaskById(taskAId).get().equals(managerB.getTaskById(taskAId).get()) + && managerA.getTaskById(taskBId).get().equals(managerB.getTaskById(taskBId).get()) && managerA.getEpicById(epicAId).equals(managerB.getEpicById(epicAId)) - && managerA.getSubTaskById(subTaskAId).equals(managerB.getSubTaskById(subTaskAId))) { + && managerA.getSubTaskById(subTaskAId).get().equals(managerB.getSubTaskById(subTaskAId).get())) { System.out.println("Менеджеры, созданные из одного и того же файла, имеют одинаковый набор задач."); } @@ -72,6 +81,7 @@ private String getAllTasksAsCSV() { for (Task task : allTasks) { if (task instanceof Epic epic) { + epic.updateTimeFrame(getAllSubTasksOfEpic(epic.getId())); result.append( csvCommaEqualizer(headers.length, epic.toCSV(headers.length))); } else if (task instanceof SubTask subTask) { @@ -88,11 +98,19 @@ private String getAllTasksAsCSV() { } public void loadTask(String[] data) { + Task task; String title = data[2]; String description = data[4]; Status status = Status.valueOf(data[3]); int id = Integer.parseInt(data[0]); - Task task = new Task(title, description, status); + Duration duration = Duration.ofMinutes(Long.parseLong(data[6])); + + if (data[5].equals("N/A")) { + task = new Task(title, description, status, duration); + } else { + LocalDateTime startTime = LocalDateTime.parse(data[5], Utils.formatter); + task = new Task(title, description, status, duration, startTime); + } createTask(task, id); } @@ -106,12 +124,21 @@ public void loadEpic(String[] data) { } public void loadSubTask(String[] data) { + SubTask subTask; String title = data[2]; String description = data[4]; Status status = Status.valueOf(data[3]); - int epicId = Integer.parseInt(data[5]); + int epicId = Integer.parseInt(data[7]); int id = Integer.parseInt(data[0]); - SubTask subTask = new SubTask(title, description, status, epicId); + Duration duration = Duration.ofMinutes(Long.parseLong(data[6])); + + if (data[5].equals("N/A")) { + subTask = new SubTask(title, description, status, epicId, duration); + } else { + LocalDateTime startTime = LocalDateTime.parse(data[5], Utils.formatter); + subTask = new SubTask(title, description, status, epicId, duration, startTime); + } + createSubTask(subTask, id); } diff --git a/src/service/IdGenerator.java b/src/service/IdGenerator.java index 2046a29..934ad4e 100644 --- a/src/service/IdGenerator.java +++ b/src/service/IdGenerator.java @@ -1,9 +1,24 @@ package service; +import java.util.Optional; +import java.util.TreeSet; + public class IdGenerator { + private final TreeSet freeIdSet = new TreeSet<>(); private int id = 1; public int generateId() { return id++; } + + public Optional getFreeId() { + if (!freeIdSet.isEmpty()) { + return Optional.of(freeIdSet.getFirst()); + } + return Optional.empty(); + } + + public void addFreeId(int id) { + freeIdSet.add(id); + } } \ No newline at end of file diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java index 397d292..d3cc8db 100644 --- a/src/service/InMemoryTaskManager.java +++ b/src/service/InMemoryTaskManager.java @@ -1,6 +1,8 @@ package service; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; import model.*; @@ -10,6 +12,7 @@ public class InMemoryTaskManager implements TaskManager { protected final Map allSubTasks = new HashMap<>(); private final IdGenerator idGenerator; private final HistoryManager historyManager; + private TreeSet sortedTasksAndSubTasks = new TreeSet<>(); public InMemoryTaskManager(IdGenerator idGenerator, HistoryManager historyManager) { this.idGenerator = idGenerator; @@ -21,12 +24,16 @@ private int generateUniqueId() { allIds.addAll(allTasks.keySet()); allIds.addAll(allEpics.keySet()); allIds.addAll(allSubTasks.keySet()); - - int id; - do { - id = idGenerator.generateId(); - } while (allIds.contains(id)); - return id; + Optional freeId = idGenerator.getFreeId(); + + if (freeId.isEmpty()) { + int id; + do { + id = idGenerator.generateId(); + } while (allIds.contains(id)); + return id; + } + return freeId.get(); } @Override @@ -51,12 +58,17 @@ public List getSubTasks() { @Override public void removeAllTasks() { + allTasks.keySet().forEach(idGenerator::addFreeId); allTasks.clear(); + sortedTasksAndSubTasks = sortedTasksAndSubTasks.stream() + .filter(task -> task instanceof SubTask) + .collect(Collectors.toCollection(TreeSet::new)); } @Override public void removeAllEpics() { removeAllSubTasks(); + allEpics.keySet().forEach(idGenerator::addFreeId); allEpics.clear(); } @@ -64,21 +76,24 @@ public void removeAllEpics() { public void removeAllSubTasks() { List epics = getEpics(); - for (Epic epic : epics) { - epic.unlinkAllSubtasks(); - } + epics.forEach(Epic::unlinkAllSubtasks); + allSubTasks.keySet().forEach(idGenerator::addFreeId); allSubTasks.clear(); + + sortedTasksAndSubTasks = sortedTasksAndSubTasks.stream() + .filter(task -> !(task instanceof SubTask)) + .collect(Collectors.toCollection(TreeSet::new)); } @Override - public Task getTaskById(int id) { + public Optional getTaskById(int id) { if (allTasks.containsKey(id)) { Task task = allTasks.get(id); historyManager.addTask(task); - return task; + return Optional.of(task); } System.out.println("Ошибка при вызове getTaskById(int id): Задачи с id " + id + " не существует"); - return null; + return Optional.empty(); } @Override @@ -94,34 +109,72 @@ public Epic getEpicById(int id) { } @Override - public SubTask getSubTaskById(int id) { + public Optional getSubTaskById(int id) { if (allSubTasks.containsKey(id)) { SubTask subTask = allSubTasks.get(id); historyManager.addTask(subTask); - return subTask; + return Optional.of(subTask); } else { System.out.println("Ошибка при вызове getSubTaskById(int id): Подзадачи с id " + id + " не существует"); - return null; + return Optional.empty(); } } @Override - public List getAllSubTasksOfEpic(int id) { - List subTaskList = new ArrayList<>(); + public TreeSet getAllSubTasksOfEpic(int id) { + TreeSet subTaskList = new TreeSet<>(); Epic epic = getEpicById(id); - for (Integer subTaskId : epic.getSubTaskIdList()) { - subTaskList.add(getSubTaskById(subTaskId)); - } + epic.getSubTaskIdList().forEach(stId -> getSubTaskById(stId).ifPresent(subTaskList::add)); return subTaskList; } + @Override + public TreeSet getPrioritizedTasks() { + return sortedTasksAndSubTasks; + } + + @Override + public boolean checkForOverlaps(Task task) { + if (task.getStartTime().isEmpty() || task.getEndTime().isEmpty()) { + return false; + } + + LocalDateTime taskStart = task.getStartTime().get(); + LocalDateTime taskEnd = task.getEndTime().get(); + + Task previousTask = sortedTasksAndSubTasks.floor(task); + if (previousTask != null && previousTask.getEndTime().isPresent()) { + return taskStart.isBefore(previousTask.getEndTime().get()); + } + + Task nextTask = sortedTasksAndSubTasks.ceiling(task); + if (nextTask != null && nextTask.getStartTime().isPresent()) { + return taskEnd.isAfter(nextTask.getStartTime().get()); + } + + return false; + } + @Override public int createTask(Task task) { + if (checkForOverlaps(task)) { + System.out.printf(""" + Ошибка добавления задачи id=%d: \ + обнаружено наложение времени выполнения.\s + Задача добавлена без указания времени начала выполнения.\s + """, task.getId()); + task.setStartTime(null); + } + int newId = generateUniqueId(); task.setId(newId); allTasks.put(newId, task); + if (task.getStartTime().isPresent()) { + addTaskToTreeSet(task); + } + return newId; } @@ -136,6 +189,14 @@ public int createEpic(Epic epic) { @Override public int createSubTask(SubTask subTask) { + if (checkForOverlaps(subTask)) { + System.out.printf(""" + Ошибка добавления подзадачи id=%d: \ + обнаружено наложение времени выполнения.\s + Подзадача добавлена без указания времени начала выполнения.\s + """, subTask.getId()); + subTask.setStartTime(null); + } int newId = generateUniqueId(); subTask.setId(newId); @@ -143,14 +204,41 @@ public int createSubTask(SubTask subTask) { relatedEpic.addSubTask(subTask.getId(), subTask.getStatus()); allSubTasks.put(newId, subTask); + if (subTask.getStartTime().isPresent()) { + addTaskToTreeSet(subTask); + } + + updateEpicTimeFrame(relatedEpic); + return newId; } + @Override + public void addTaskToTreeSet(Task task) { + if (task.getStartTime().isPresent()) { + sortedTasksAndSubTasks.add(task); + } + } + @Override public void updateTask(Task task, int id) { + sortedTasksAndSubTasks.remove(task); + + if (checkForOverlaps(task)) { + sortedTasksAndSubTasks.add(task); + System.out.printf("Ошибка обновления задачи %d:" + + " обнаружено наложение времени выполнения. \n" + + "Задача не была обновлена.", task.getId()); + return; + } + if (allTasks.containsKey(id)) { task.setId(id); allTasks.put(id, task); + if (getTaskById(id).isPresent()) { + sortedTasksAndSubTasks.remove(getTaskById(id).get()); + } + addTaskToTreeSet(task); } else { System.out.printf("Ошибка при вызове updateTask(Task task, int id): " + "Ошибка обновления задачи id %d - задача не найдена" + "%n", id); @@ -165,12 +253,35 @@ public void updateEpic(Epic epic, int id) { epic.setRelatedSubTaskMap(bufferedMap); epic.setId(id); allEpics.put(id, epic); + updateEpicTimeFrame(epic); + } + + @Override + public void updateEpicTimeFrame(int epicId) { + Epic epic = getEpicById(epicId); + TreeSet sortedSubTasks = getAllSubTasksOfEpic(epicId); + epic.updateTimeFrame(sortedSubTasks); + } + + @Override + public void updateEpicTimeFrame(Epic epic) { + updateEpicTimeFrame(epic.getId()); } @Override public void updateSubTask(SubTask subTask, int id) { - if (allSubTasks.containsKey(id)) { - SubTask oldSubTask = getSubTaskById(id); + sortedTasksAndSubTasks.remove(getSubTaskById(id).get()); + + if (checkForOverlaps(subTask)) { + sortedTasksAndSubTasks.add(getSubTaskById(id).get()); + System.out.printf("Ошибка обновления подзадачи %d:" + + " обнаружено наложение времени выполнения. \n" + + "Подзадача не была обновлена.", subTask.getId()); + return; + } + + if (allSubTasks.containsKey(id) && getSubTaskById(id).isPresent()) { + SubTask oldSubTask = getSubTaskById(id).get(); Epic oldEpic = getEpicById(oldSubTask.getEpicId()); Epic newEpic = getEpicById(subTask.getEpicId()); @@ -183,6 +294,9 @@ public void updateSubTask(SubTask subTask, int id) { newEpic.addSubTask(id, subTask.getStatus()); } allSubTasks.put(id, subTask); + addTaskToTreeSet(subTask); + + updateEpicTimeFrame(subTask.getEpicId()); } else { System.out.printf("Ошибка при вызове updateSubTask(SubTask subTask, int id): " + "Ошибка обновления подзадачи id %d - подзадача не найдена." + "%n", id); @@ -192,8 +306,11 @@ public void updateSubTask(SubTask subTask, int id) { @Override public void removeTaskById(int id) { if (allTasks.containsKey(id)) { + Optional task = getTaskById(id); + task.ifPresent(sortedTasksAndSubTasks::remove); allTasks.remove(id); historyManager.remove(id); + idGenerator.addFreeId(id); } else { System.out.printf("Ошибка при вызове removeTaskById(int id):" + " Невозможно удалить задачу id %d по id: задача не найдена." + "%n", id); @@ -205,12 +322,10 @@ public void removeEpicById(int id) { if (allEpics.containsKey(id)) { Epic epic = getEpicById(id); List relatedSubTasks = epic.getSubTaskIdList(); - - for (Integer subTaskId : relatedSubTasks) { - removeSubTaskById(subTaskId); - } + relatedSubTasks.forEach(this::removeSubTaskById); allEpics.remove(id); historyManager.remove(id); + idGenerator.addFreeId(id); } else { System.out.printf("Ошибка при вызове removeEpicById(int id): " + "Невозможно удалить эпик id %d по id - эпик не найден." + "%n", id); @@ -219,13 +334,17 @@ public void removeEpicById(int id) { @Override public void removeSubTaskById(int id) { - if (allSubTasks.containsKey(id)) { - SubTask subTask = getSubTaskById(id); + Optional subTaskOptional = getSubTaskById(id); + if (allSubTasks.containsKey(id) && subTaskOptional.isPresent()) { + SubTask subTask = subTaskOptional.get(); Epic epic = getEpicById(subTask.getEpicId()); epic.unlinkSubTask(id); allSubTasks.remove(id); historyManager.remove(id); + updateEpicTimeFrame(subTask.getEpicId()); + sortedTasksAndSubTasks.remove(subTask); + idGenerator.addFreeId(id); } else { System.out.printf("Ошибка при вызове removeSubTaskById(int id): " + "Невозможно удалить подзадачу id %d по id: подзадача не найдена." + "%n", id); diff --git a/src/service/Managers.java b/src/service/Managers.java index 4e05c8f..c1a2ba2 100644 --- a/src/service/Managers.java +++ b/src/service/Managers.java @@ -10,7 +10,7 @@ public class Managers { - public static TaskManager getDefault() { + public static InMemoryTaskManager getDefault() { IdGenerator idGenerator = new IdGenerator(); HistoryManager historyManager = getDefaultHistory(); @@ -57,6 +57,8 @@ private static FileBackedTaskManager loadFromFile(File file) throws ManagerLoadE System.out.println("Во время чтения файла сохранения произошла ошибка: " + e); throw new ManagerLoadException("Ошибка при чтении CSV строки: " + e.getMessage()); } + + manager.getEpics().forEach(epic -> epic.updateTimeFrame(manager.getAllSubTasksOfEpic(epic.getId()))); } return manager; diff --git a/src/service/TaskManager.java b/src/service/TaskManager.java index 8acfea8..982135b 100644 --- a/src/service/TaskManager.java +++ b/src/service/TaskManager.java @@ -1,6 +1,6 @@ package service; -import java.util.List; +import java.util.*; import model.*; @@ -19,13 +19,17 @@ public interface TaskManager { void removeAllSubTasks(); - Task getTaskById(int id); + Optional getTaskById(int id); Epic getEpicById(int id); - SubTask getSubTaskById(int id); + Optional getSubTaskById(int id); - List getAllSubTasksOfEpic(int id); + Set getAllSubTasksOfEpic(int id); + + TreeSet getPrioritizedTasks(); + + boolean checkForOverlaps(Task task); int createTask(Task task); @@ -33,10 +37,16 @@ public interface TaskManager { int createSubTask(SubTask subTask); + void addTaskToTreeSet(Task task); + void updateTask(Task task, int id); void updateEpic(Epic epic, int id); + void updateEpicTimeFrame(int epicId); + + void updateEpicTimeFrame(Epic epic); + void updateSubTask(SubTask subTask, int id); void removeTaskById(int id); diff --git a/src/service/utils/Utils.java b/src/service/utils/Utils.java index 43d1484..aebc0a7 100644 --- a/src/service/utils/Utils.java +++ b/src/service/utils/Utils.java @@ -2,9 +2,11 @@ import java.io.*; import java.nio.charset.StandardCharsets; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; public class Utils { + public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm dd.MM.yyyy"); public static int substringCounter(String s, String substring) { int substringCount = 0; diff --git a/test/common/TestConstants.java b/test/common/TestConstants.java new file mode 100644 index 0000000..330c421 --- /dev/null +++ b/test/common/TestConstants.java @@ -0,0 +1,18 @@ +package common; + +import java.time.Duration; +import java.time.LocalDateTime; + +public class TestConstants { + public static final Duration duration30 = Duration.ofMinutes(30); + public static final Duration duration60 = Duration.ofHours(1); + public static final Duration duration90 = Duration.ofMinutes(90); + public static final int headersCount = 8; + public static LocalDateTime date1 = LocalDateTime.of(2025, 6, 1, 10, 0); + public static LocalDateTime date2 = LocalDateTime.of(2025, 6, 2, 10, 0); + public static LocalDateTime date3 = LocalDateTime.of(2025, 6, 3, 10, 0); + + public TestConstants() { + + } +} diff --git a/test/model/EpicTest.java b/test/model/EpicTest.java index 2dcc9d2..f99854a 100644 --- a/test/model/EpicTest.java +++ b/test/model/EpicTest.java @@ -4,8 +4,10 @@ import org.junit.jupiter.api.*; -import service.*; +import service.Managers; +import service.TaskManager; +import static common.TestConstants.*; import static org.junit.jupiter.api.Assertions.*; class EpicTest { @@ -47,20 +49,33 @@ void shouldUpdateEpicStatusCorrectly() { taskManager.createEpic(epicA); + // NO SUBTASKS = NEW assertEquals(Status.NEW, taskManager.getEpicById(1).getStatus()); + // NEW + NEW = NEW taskManager.createSubTask(new SubTask("Подзадача", - "Описание подзадачи", Status.NEW, 1)); + "Описание подзадачи", Status.NEW, 1, duration60)); taskManager.createSubTask(new SubTask("Подзадача", - "Описание подзадачи", Status.NEW, 1)); + "Описание подзадачи", Status.NEW, 1, duration60)); assertEquals(Status.NEW, taskManager.getEpicById(1).getStatus()); + // IN_PROGRESS + IN_PROGRESS = IN_PROGRESS taskManager.updateSubTask(new SubTask("Подзадача", - "Описание подзадачи", Status.DONE, 1), 2); + "Описание подзадачи", Status.IN_PROGRESS, 1, duration60), 2); + taskManager.updateSubTask(new SubTask("Подзадача", + "Описание подзадачи", Status.IN_PROGRESS, 1, duration60), 3); + assertEquals(Status.IN_PROGRESS, taskManager.getEpicById(1).getStatus()); + + // NEW + DONE = IN_PROGRESS + taskManager.updateSubTask(new SubTask("Подзадача", + "Описание подзадачи", Status.NEW, 1, duration60), 2); + taskManager.updateSubTask(new SubTask("Подзадача", + "Описание подзадачи", Status.DONE, 1, duration60), 3); assertEquals(Status.IN_PROGRESS, taskManager.getEpicById(1).getStatus()); + // DONE + DONE = DONE taskManager.updateSubTask(new SubTask("Подзадача", - "Описание подзадачи", Status.DONE, 1), 3); + "Описание подзадачи", Status.DONE, 1, duration60), 2); assertEquals(Status.DONE, taskManager.getEpicById(1).getStatus()); } @@ -89,8 +104,24 @@ void shouldCreateEpicWithStatus() { @Test @DisplayName("Метод toCSV() возвращает корректную строку в формате CSV") void shouldReturnValidCSV() { - String expectedCSV = "1,EPIC,Тестовый эпик,NEW,Описание тестового эпика"; + String expectedCSV = "1,EPIC,Тестовый эпик,NEW,Описание тестового эпика,N/A,0"; assertEquals(expectedCSV, epicA.toCSV(5)); } + @Test + @DisplayName("Время начала/окончание выполнение эпика и его продолжительность рассчитываются корректно") + void shouldCalculateEpicTimeFrameCorrectly() { + TaskManager taskManager = Managers.getDefault(); + + taskManager.createEpic(epicA); + taskManager.createSubTask(new SubTask("Подзадача1", + "Описание подзадачи1", Status.NEW, 1, duration30, date1)); + taskManager.createSubTask(new SubTask("Подзадача2", + "Описание подзадачи2", Status.NEW, 1, duration60, date2)); + + assertEquals(date1, epicA.getStartTime().get()); + assertEquals(date2.plus(duration60), epicA.getEndTime().get()); + assertEquals(duration90, epicA.getDuration()); + } + } \ No newline at end of file diff --git a/test/model/SubTaskTest.java b/test/model/SubTaskTest.java index c21009b..133ee11 100644 --- a/test/model/SubTaskTest.java +++ b/test/model/SubTaskTest.java @@ -1,8 +1,11 @@ package model; import org.junit.jupiter.api.*; -import service.*; +import service.Managers; +import service.TaskManager; + +import static common.TestConstants.*; import static org.junit.jupiter.api.Assertions.*; class SubTaskTest { @@ -17,11 +20,13 @@ static void initEnv() { @DisplayName("Две одинаковых подзадачи должны быть равны при сравнении через equals()") void shouldConsiderTwoIdenticalSubTasksEqual() { SubTask subTaskA = new SubTask("Тестовая подзадача", - "Описание тестовой подзадачи", Status.NEW, 1); + "Описание тестовой подзадачи", Status.NEW, 1, + duration60, date1); subTaskA.setId(1); SubTask subTaskB = new SubTask("Тестовая подзадача", - "Описание тестовой подзадачи", Status.NEW, 1); + "Описание тестовой подзадачи", Status.NEW, 1, + duration60, date1); subTaskB.setId(1); assertEquals(subTaskA, subTaskB); @@ -31,11 +36,13 @@ void shouldConsiderTwoIdenticalSubTasksEqual() { @DisplayName("Две одинаковых по содержанию подзадачи не должны быть равны при разных ID") void shouldNotConsiderTwoIdenticalSubTasksWithDifferentIdEqual() { SubTask subTaskA = new SubTask("Тестовая подзадача", - "Описание тестовой подзадачи", Status.NEW, 1); + "Описание тестовой подзадачи", Status.NEW, 1, + duration60, date1); subTaskA.setId(1); SubTask subTaskB = new SubTask("Тестовая подзадача", - "Описание тестовой подзадачи", Status.NEW, 1); + "Описание тестовой подзадачи", Status.NEW, 1, + duration60, date1); subTaskB.setId(2); assertNotEquals(subTaskA, subTaskB); @@ -46,7 +53,8 @@ void shouldNotConsiderTwoIdenticalSubTasksWithDifferentIdEqual() { void shouldNotLetAddingSubTaskAsItsOwnEpic() { taskManager.createEpic(new Epic("Эпик", "Описание эпика")); taskManager.createSubTask(new SubTask("Подзадача 1 эпика 1", - "Описание подзадачи 1 эпика 1", Status.NEW, 1)); + "Описание подзадачи 1 эпика 1", Status.NEW, 1, + duration60, date1)); assertThrows(NullPointerException.class, () -> taskManager.createSubTask(new SubTask("Подзадача 1 эпика 1", @@ -57,10 +65,26 @@ void shouldNotLetAddingSubTaskAsItsOwnEpic() { @DisplayName("Метод toCSV() возвращает корректную строку в формате CSV") void shouldReturnValidCSV() { SubTask subTask = new SubTask("Тестовая подзадача", - "Описание тестовой подзадачи", Status.DONE, 4); + "Описание тестовой подзадачи", Status.DONE, 4, + duration60, + date1); subTask.setId(6); - String expectedCSV = "6,SUBTASK,Тестовая подзадача,DONE,Описание тестовой подзадачи,4"; + String expectedCSV = "6,SUBTASK,Тестовая подзадача,DONE,Описание тестовой подзадачи,10:00 01.06.2025,60,4"; + + assertEquals(expectedCSV, subTask.toCSV(headersCount)); + } + + @Test + @DisplayName("Подзадача может существовать без указания времени начала") + void shouldCreateValidSubtaskWithoutStartTime() { + Epic epic = new Epic("Название эпика", "Описание эпика"); + SubTask subTask = new SubTask("Тестовая подзадача", + "Описание тестовой подзадачи", Status.DONE, 1, duration90); + taskManager.createEpic(epic); + taskManager.createSubTask(subTask); + assertEquals(subTask, taskManager.getSubTaskById(subTask.getId()).get()); - assertEquals(expectedCSV, subTask.toCSV(6)); + assertTrue(epic.getStartTime().isEmpty()); + assertEquals(duration90, epic.getDuration()); } } \ No newline at end of file diff --git a/test/model/TaskTest.java b/test/model/TaskTest.java index 122136f..8291225 100644 --- a/test/model/TaskTest.java +++ b/test/model/TaskTest.java @@ -1,30 +1,38 @@ package model; +import java.time.LocalDateTime; + import org.junit.jupiter.api.*; +import static common.TestConstants.date1; +import static common.TestConstants.duration60; import static org.junit.jupiter.api.Assertions.*; class TaskTest { + private final Task taskA = new Task("Тестовая задача", "Описание тестовой задачи", + Status.NEW, duration60, date1); + + @BeforeEach + void initTask() { + taskA.setId(1); + } @Test @DisplayName("Две одинаковые задачи должны быть равны при сравнении через equals()") void shouldConsiderTwoIdenticalTasksEqual() { - - Task taskA = new Task("Тестовая задача", "Описание тестовой задачи", Status.NEW); - taskA.setId(1); - Task taskB = new Task("Тестовая задача", "Описание тестовой задачи", Status.NEW); + Task taskB = new Task("Тестовая задача", "Описание тестовой задачи", + Status.NEW, duration60, date1); taskB.setId(1); - assertEquals(taskA, taskB); + assertTrue(taskA.equals(taskB)); } @Test @DisplayName("Две одинаковые по содержимому задачи не должны быть равны при разных ID") void shouldNotConsiderTwoIdenticalTasksWithDifferentIdEqual() { - Task taskA = new Task("Тестовая задача", "Описание тестовой задачи", Status.NEW); - taskA.setId(1); - Task taskB = new Task("Тестовая задача", "Описание тестовой задачи", Status.NEW); + Task taskB = new Task("Тестовая задача", "Описание тестовой задачи", + Status.NEW, duration60, date1); taskB.setId(2); assertNotEquals(taskA, taskB); @@ -33,11 +41,18 @@ void shouldNotConsiderTwoIdenticalTasksWithDifferentIdEqual() { @Test @DisplayName("Метод toCSV() возвращает корректную строку в формате CSV") void shouldReturnValidCSV() { - Task taskA = new Task("Тестовая задача", "Описание тестовой задачи", Status.NEW); - taskA.setId(1); - String expectedCSV = "1,TASK,Тестовая задача,NEW,Описание тестовой задачи"; + + String expectedCSV = "1,TASK,Тестовая задача,NEW,Описание тестовой задачи,10:00 01.06.2025,60"; assertEquals(expectedCSV, taskA.toCSV(5)); } + @Test + @DisplayName("Время окончания задачи должно расчитываться корректно") + void shouldCalculateEndTimeCorrectly() { + LocalDateTime expectedEndTime = date1.plus(duration60); + + assertEquals(expectedEndTime, taskA.getEndTime().get()); + } + } \ No newline at end of file diff --git a/test/service/FileBackedTaskManagerTest.java b/test/service/FileBackedTaskManagerTest.java index 2b8c5a8..97a4f33 100644 --- a/test/service/FileBackedTaskManagerTest.java +++ b/test/service/FileBackedTaskManagerTest.java @@ -1,36 +1,24 @@ package service; import java.io.*; -import java.lang.reflect.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Optional; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import model.*; +import static common.TestConstants.*; import static org.junit.jupiter.api.Assertions.*; -public class FileBackedTaskManagerTest { +public class FileBackedTaskManagerTest extends TaskManagerTest { File testSaveFile; - FileBackedTaskManager manager; - Task task; - Epic epic; - SubTask subTask; void initTestFile() throws IOException { testSaveFile = File.createTempFile("test", "csv"); testSaveFile.deleteOnExit(); - String testCSVData = """ - id,type,name,status,description,epic - 1,TASK,Task1,NEW,Description task1, - 2,EPIC,Epic2,DONE,Description epic2, - 3,SUBTASK,Sub Task2,DONE,Description sub task3,2 - """; - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(testSaveFile))) { - writer.write(testCSVData); - } catch (IOException e) { - e.printStackTrace(); - } } String readFileContent(File file) throws IOException { @@ -45,42 +33,32 @@ String readFileContent(File file) throws IOException { } } - @BeforeEach - void initManager() throws IOException { - initTestFile(); - manager = Managers.getFileBacked(testSaveFile); - task = new Task("Задача", "Описание задачи", Status.NEW); - task.setId(1); - - epic = new Epic("Эпик", "Описание эпика"); - epic.setId(2); - - subTask = new SubTask("Подзадача", "Описание подзадачи", Status.DONE, 2); - subTask.setId(3); + @Override + protected Optional createTaskManager() { + try { + initTestFile(); + return Optional.of(Managers.getFileBacked(testSaveFile)); + } catch (IOException e) { + System.out.println(e.getMessage()); + return Optional.empty(); + } } @Test @DisplayName("Метод getAllTasksAsCSV возвращает валидное CSV представление всех задач") void shouldReturnAllTasksInValidCSVString() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - // Предварительно очистим менеджер от задач, подгруженных из файла при инициализации - manager.removeAllTasks(); - manager.removeAllEpics(); - - manager.createTask(task); - manager.createEpic(epic); - manager.createSubTask(subTask); - Method method = manager.getClass().getDeclaredMethod("getAllTasksAsCSV"); + Method method = taskManager.getClass().getDeclaredMethod("getAllTasksAsCSV"); method.setAccessible(true); String expectedCSV = """ - id,type,name,status,description,epic - 1,TASK,Задача,NEW,Описание задачи, - 2,EPIC,Эпик,NEW,Описание эпика, - 3,SUBTASK,Подзадача,DONE,Описание подзадачи,2 + id,type,title,status,description,start_time,duration,epic + 1,TASK,Задача,NEW,Описание задачи,10:00 01.06.2025,30, + 2,EPIC,Эпик,NEW,Описание эпика,10:00 02.06.2025,60, + 3,SUBTASK,Подзадача,DONE,Описание подзадачи,10:00 02.06.2025,60,2 """; - String actualCSV = (String) method.invoke(manager); + String actualCSV = (String) method.invoke(taskManager); assertEquals(expectedCSV, actualCSV); } @@ -88,64 +66,73 @@ void shouldReturnAllTasksInValidCSVString() throws NoSuchMethodException, @Test @DisplayName("Реализации createTask/Epic/Subtask, принимающие id в параметре, корректно задают переданный id") void shouldSetCorrectId() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - Method taskMethod = manager.getClass().getDeclaredMethod("createTask", Task.class, int.class); - Method epicMethod = manager.getClass().getDeclaredMethod("createEpic", Epic.class, int.class); - Method subTaskMethod = manager.getClass().getDeclaredMethod("createSubTask", SubTask.class, int.class); + Method taskMethod = taskManager.getClass().getDeclaredMethod("createTask", Task.class, int.class); + Method epicMethod = taskManager.getClass().getDeclaredMethod("createEpic", Epic.class, int.class); + Method subTaskMethod = taskManager.getClass().getDeclaredMethod("createSubTask", SubTask.class, int.class); SubTask subTaskTest = new SubTask("Поздача тест", "Описание подзадачи", - Status.DONE, 100); + Status.DONE, 100, duration90, date3); taskMethod.setAccessible(true); epicMethod.setAccessible(true); subTaskMethod.setAccessible(true); - taskMethod.invoke(manager, task, 99); - epicMethod.invoke(manager, epic, 100); - subTaskMethod.invoke(manager, subTaskTest, 101); + taskMethod.invoke(taskManager, task, 99); + epicMethod.invoke(taskManager, epic, 100); + subTaskMethod.invoke(taskManager, subTaskTest, 101); - assertEquals(manager.getTaskById(99), task); - assertEquals(manager.getEpicById(100), epic); - assertEquals(manager.getSubTaskById(101), subTaskTest); + assertEquals(taskManager.getTaskById(99).get(), task); + assertEquals(taskManager.getEpicById(100), epic); + assertEquals(taskManager.getSubTaskById(101).get(), subTaskTest); } @Test @DisplayName("Методы loadTask/Epic/SubTask корректно создают задачи из массива полей, полученного из CSV") void shouldLoadTasksCorrectly() { - String[] taskCSV = new String[]{"100", "TASK", "Название задачи", "NEW", "Описание задачи"}; - manager.loadTask(taskCSV); - Task task = new Task("Название задачи", "Описание задачи", Status.NEW); + String[] taskCSV = new String[]{"100", "TASK", "Название задачи", "NEW", "Описание задачи", + "10:00 02.06.2025", "60"}; + taskManager.loadTask(taskCSV); + Task task = new Task("Название задачи", "Описание задачи", Status.NEW, duration60, date2); task.setId(100); - String[] epicCSV = new String[]{"101", "EPIC", "Название эпика", "DONE", "Описание эпика"}; - manager.loadEpic(epicCSV); + String[] epicCSV = new String[]{"101", "EPIC", "Название эпика", "DONE", "Описание эпика", + "10:00 03.06.2025", "30"}; + taskManager.loadEpic(epicCSV); Epic epic = new Epic("Название эпика", "Описание эпика", Status.DONE); epic.setId(101); - String[] subTaskCSV = new String[]{"102", "SUBTASK", "Название подзадачи", "NEW", "Описание подзадачи", "101"}; - manager.loadSubTask(subTaskCSV); + String[] subTaskCSV = new String[]{"102", "SUBTASK", "Название подзадачи", "NEW", "Описание подзадачи", + "10:00 03.06.2025", "30", "101"}; + taskManager.loadSubTask(subTaskCSV); SubTask subTask = new SubTask("Название подзадачи", "Описание подзадачи", - Status.NEW, 101); + Status.NEW, 101, duration30, date3); subTask.setId(102); epic.addSubTask(102, Status.NEW); - assertEquals(manager.getTaskById(100), task); - assertEquals(manager.getEpicById(101), epic); - assertEquals(manager.getSubTaskById(102), subTask); + assertEquals(taskManager.getTaskById(100).get(), task); + assertEquals(taskManager.getEpicById(101), epic); + assertEquals(taskManager.getSubTaskById(102).get(), subTask); } @Test @DisplayName("Переопределённые реализации стандартных методов менеджера корректно автосохраняют задачи в файл") void shouldAutosaveCorrectly() { - String taskCSV = "4,TASK,Название задачи,NEW,описание задачи,"; - String subtaskCSV = "5,SUBTASK,Подзадача,NEW,описание,2"; - Task testTask = new Task("Название задачи", "описание задачи", Status.NEW); - SubTask testSubTask = new SubTask("Подзадача", "описание", Status.NEW, 2); + String taskCSV = "4,TASK,Название задачи,NEW,описание задачи,10:00 03.06.2025,30,"; + String subtaskCSV = "5,SUBTASK,Подзадача,NEW,описание,11:00 01.06.2025,60,2"; + + Task testTask = new Task("Название задачи", "описание задачи", Status.NEW, duration30, date3); + testTask.setId(4); + + SubTask testSubTask = new SubTask("Подзадача", "описание", + Status.NEW, 2, duration60, date1.plus(duration60)); + testSubTask.setId(5); + try { String contentBeforeSave = readFileContent(testSaveFile); assertFalse(contentBeforeSave.contains(taskCSV)); assertFalse(contentBeforeSave.contains(subtaskCSV)); - manager.createTask(testTask); - manager.createSubTask(testSubTask); + taskManager.createTask(testTask); + taskManager.createSubTask(testSubTask); String contentAfterSave = readFileContent(testSaveFile); assertTrue(contentAfterSave.contains(taskCSV)); @@ -165,7 +152,7 @@ void shouldHandleEmptySaveFile() { Managers.getDefaultHistory(), emptyFile); String taskCsv = "1,TASK,Title,NEW,Description,"; - manager.createTask(new Task("Title", "Description", Status.NEW)); + manager.createTask(new Task("Title", "Description", Status.NEW, duration90, date1)); assertTrue(readFileContent(emptyFile).contains(taskCsv)); } catch (IOException e) { diff --git a/test/service/InMemoryHistoryManagerTest.java b/test/service/InMemoryHistoryManagerTest.java index a52785d..f10c137 100644 --- a/test/service/InMemoryHistoryManagerTest.java +++ b/test/service/InMemoryHistoryManagerTest.java @@ -1,18 +1,20 @@ package service; -import model.*; -import service.InMemoryHistoryManager.*; +import java.util.*; + import org.junit.jupiter.api.*; -import java.util.*; +import model.*; +import service.InMemoryHistoryManager.Node; +import static common.TestConstants.*; import static org.junit.jupiter.api.Assertions.*; public class InMemoryHistoryManagerTest { - private static final Task task = new Task("Задача", "Описание задачи", Status.NEW); + private static final Task task = new Task("Задача", "Описание задачи", Status.NEW, duration30, date1); private static final Epic epic = new Epic("Эпик", "Описание эпика"); private static final SubTask subTask = new SubTask("Подзадача", "Описание подзадачи", - Status.DONE, 2); + Status.DONE, 2, duration60, date2); private HistoryManager historyManager; @BeforeEach @@ -36,10 +38,27 @@ void shouldAddTasksToHistory() { @Test @DisplayName("Задачи корректно удаляются из истории по ID задачи") void shouldRemoveViewRecordByTaskId() { - List expectedHistory = new ArrayList<>(List.of(task, subTask)); + // Из начала истории + List expectedHistory = new ArrayList<>(List.of(epic, subTask)); + historyManager.remove(1); + + assertEquals(expectedHistory, historyManager.getHistory()); + + initHistoryManager(); + + // Из середины истории + expectedHistory = new ArrayList<>(List.of(task, subTask)); historyManager.remove(2); assertEquals(expectedHistory, historyManager.getHistory()); + + initHistoryManager(); + + // Из конца истории + expectedHistory = new ArrayList<>(List.of(task, epic)); + historyManager.remove(3); + + assertEquals(expectedHistory, historyManager.getHistory()); } @Test @@ -56,7 +75,7 @@ void shouldKeepOnlyUniqueLatestViews() { // Обновление записи из начала истории (head node) initHistoryManager(); - Task updatedTask = new Task("Задача", "Описание задачи", Status.DONE); + Task updatedTask = new Task("Задача", "Описание задачи", Status.DONE, duration30, date1); updatedTask.setId(1); expectedHistory = new ArrayList<>(List.of(epic, subTask, updatedTask)); @@ -67,7 +86,7 @@ void shouldKeepOnlyUniqueLatestViews() { // Обновление записи из конца истории (tail node) initHistoryManager(); Task updatedSubTask = new SubTask("Подзадача", "Описание подзадачи", - Status.NEW, 2); + Status.NEW, 2, duration60, date2); updatedSubTask.setId(3); expectedHistory = new ArrayList<>(List.of(task, epic, updatedSubTask)); @@ -76,6 +95,12 @@ void shouldKeepOnlyUniqueLatestViews() { assertEquals(expectedHistory, historyManager.getHistory()); } + @Test + @DisplayName("Пустая история функционирует корректно") + void emptyHistoryWorksCorrectly() { + assertEquals(Managers.getDefault().getHistory(), Managers.getDefaultHistory().getHistory()); + } + @Nested class NodeTest { Node testNodeA = new Node<>("Node A"); @@ -120,7 +145,7 @@ void shouldBeEqualIfIdentical() { // Намеренное использование assertTrue вместо assertEquals, // чтобы проверить метод equals() - assertTrue(testNodeB.equals(copyOfTestNodeB)); + assertEquals(testNodeB, copyOfTestNodeB); assertNotEquals(testNodeB, testNodeC); } } diff --git a/test/service/InMemoryTaskManagerTest.java b/test/service/InMemoryTaskManagerTest.java index 989d76e..f41e091 100644 --- a/test/service/InMemoryTaskManagerTest.java +++ b/test/service/InMemoryTaskManagerTest.java @@ -1,133 +1,10 @@ package service; -import model.*; -import org.junit.jupiter.api.*; +import java.util.Optional; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; - -public class InMemoryTaskManagerTest { - Task task = new Task("Задача", "Описание задачи", Status.NEW); - Epic epic = new Epic("Эпик", "Описание эпика"); - SubTask subTask = new SubTask("Подзадача", "Описание подзадачи", - Status.DONE, 2); - private TaskManager taskManager; - - @BeforeEach - void addTasksOfEachType() { - taskManager = Managers.getDefault(); - taskManager.createTask(task);// id=1 - taskManager.createEpic(epic); // id=2 - taskManager.createSubTask(subTask); // id=3, epicId=2 - } - - @Test - @DisplayName("Класс InMemoryTaskManager корректно получает задачи (добавлены в @BeforeEach методе)") - void shouldAddTasksCorrectly() { - assertEquals(task, taskManager.getTaskById(1)); - assertEquals(epic, taskManager.getEpicById(2)); - assertEquals(subTask, taskManager.getSubTaskById(3)); - assertEquals(subTask.getEpicId(), 2); - } - - @Test - @DisplayName("Класс InMemoryTaskManager обеспечивает неизменность задачи при добавлении") - void shouldNotModifyTaskWhenAdded() { - Task task = new Task("Оригинальная задача", "Описание", Status.NEW); - int addedTaskId = taskManager.createTask(task); - - assertEquals(task, taskManager.getTaskById(addedTaskId)); - } - - @Test - @DisplayName("Класс InMemoryTaskManager корректно удаляет задачи") - void shouldDeleteTaskCorrectly() { - taskManager.removeTaskById(1); - assertNull(taskManager.getTaskById(1)); - } - - @Test - @DisplayName("Записи о просмотрах удаляются из истории при удалении задачи") - void shouldDeleteViewHistoryRecordsCorrectly() { - Task task = taskManager.getTaskById(1); - List expectedHistory = new ArrayList<>(List.of(epic, task)); - - assertEquals(expectedHistory, taskManager.getHistory()); - - taskManager.removeTaskById(1); - assertEquals(new ArrayList(List.of(epic)), taskManager.getHistory()); - } - - @Test - @DisplayName("Класс InMemoryTaskManager корректно удаляет эпики и подзадачи") - void shouldDeleteEpicCorrectly() { - taskManager.removeEpicById(2); - assertNull(taskManager.getEpicById(2)); - assertNull(taskManager.getSubTaskById(3)); - } - - @Test - @DisplayName("Класс InMemoryTaskManager корректно удаляет подзадачу из эпика") - void shouldDeleteSubTaskCorrectly() { - taskManager.removeSubTaskById(3); - assertNull(taskManager.getSubTaskById(3)); - assertTrue(taskManager.getEpicById(2).getSubTaskIdList().isEmpty()); - } - - @Test - @DisplayName("Класс InMemoryTaskManager корректно обновляет задачи") - void shouldUpdateTasksCorrectly() { - taskManager.updateTask(new Task("Задача новая", "Описание другой задачи", - Status.NEW), 1); - assertNotEquals(task, taskManager.getTaskById(1)); - - taskManager.updateEpic(new Epic("Эпик новый", "Описание другого эпика"), 2); - assertNotEquals(task, taskManager.getEpicById(2)); - assertEquals(subTask.getId(), taskManager.getEpicById(2).getSubTaskIdList().getFirst()); - - taskManager.updateSubTask(new SubTask("Подзадача новая", "Описание другой подзадачи", - Status.NEW, 2), 3); - assertNotEquals(subTask, taskManager.getSubTaskById(3)); +public class InMemoryTaskManagerTest extends TaskManagerTest { + @Override + protected Optional createTaskManager() { + return Optional.of(Managers.getDefault()); } - - @Test - @DisplayName("История просмотров сохраняет состояние задачи на момент её просмотра") - void shouldPreserveTaskStateAsWhenAdded() { - TaskManager taskManager = Managers.getDefault(); - Task originalTask = new Task("Оригинал", "Описание оригинала", Status.NEW); - - // Добавляем задачу в менеджер и дёргаем get, чтобы она попала в историю - int addedTaskId = taskManager.createTask(originalTask); - taskManager.getTaskById(1); - - // Проверяем, что первая (единственная) запись в истории - исходная задача - assertEquals(taskManager.getHistory().getFirst(), originalTask); - - // Создаём обновлённую задачу и через updateTask заменяем исходную задачу в менеджере - Task updatedTask = new Task("Уже не оригинал", "Другое описание", Status.IN_PROGRESS); - taskManager.updateTask(updatedTask, addedTaskId); - - // Проверяем, что в менеджере лежит уже обновлённая задача + дёргаем get, чтобы обновить историю - assertEquals(updatedTask, taskManager.getTaskById(1)); - - // Проверяем, что первая (единственная) запись в истории - обновлённая задача - assertEquals(taskManager.getHistory().getFirst(), updatedTask); - } - - @Test - @DisplayName("InMemoryTaskManager проверяет сгенерированный ID на уникальность при добавлении задачи") - void shouldAvoidExistingIdConflict() { - int existingTaskId = taskManager.createTask(task); - Task existingTask = taskManager.getTaskById(existingTaskId); - - Task newTask = new Task("Задача", "Описание", Status.NEW); - newTask.setId(existingTaskId); - int newTaskId = taskManager.createTask(newTask); - - assertNotEquals(existingTaskId, newTaskId); - assertEquals(existingTask, taskManager.getTaskById(existingTaskId)); - assertEquals(newTask, taskManager.getTaskById(newTaskId)); - } - } diff --git a/test/service/ManagersTest.java b/test/service/ManagersTest.java index dc0dc1b..0a2e448 100644 --- a/test/service/ManagersTest.java +++ b/test/service/ManagersTest.java @@ -2,7 +2,8 @@ import java.io.*; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedList; import org.junit.jupiter.api.*; @@ -10,6 +11,7 @@ import service.exceptions.ManagerLoadException; import service.utils.Utils; +import static common.TestConstants.headersCount; import static org.junit.jupiter.api.Assertions.*; public class ManagersTest { @@ -52,10 +54,10 @@ void shouldReturnCorrectlyInitializedHistoryManager() { void shouldReturnCorrectlyInitializedFileBackedManager() throws IOException { Path testFilePath = testFile.toPath(); String testFileString = testFile.toString(); - String taskCSV = "1,TASK,Task1,NEW,Description task1,"; + String taskCSV = "1,TASK,Task1,NEW,Description task1,19:20 28.09.2005,40,"; try (BufferedWriter writer = new BufferedWriter(new FileWriter(testFile))) { - writer.write("id,type,name,status,description,epic"); + writer.write("id,type,name,status,description,start_time,duration,epic"); writer.write("\n"); writer.write(taskCSV); } catch (IOException e) { @@ -66,12 +68,12 @@ void shouldReturnCorrectlyInitializedFileBackedManager() throws IOException { FileBackedTaskManager managerFromPath = Managers.getFileBacked(testFilePath); FileBackedTaskManager managerFromString = Managers.getFileBacked(testFileString); - String expectedFromFile = Utils.csvCommaEqualizer(6, - managerFromFile.getTaskById(1).toCSV(6)); - String expectedFromPath = Utils.csvCommaEqualizer(6, - managerFromPath.getTaskById(1).toCSV(6)); - String expectedFromString = Utils.csvCommaEqualizer(6, - managerFromString.getTaskById(1).toCSV(6)); + String expectedFromFile = Utils.csvCommaEqualizer(headersCount, + managerFromFile.getTaskById(1).get().toCSV(headersCount)); + String expectedFromPath = Utils.csvCommaEqualizer(headersCount, + managerFromPath.getTaskById(1).get().toCSV(headersCount)); + String expectedFromString = Utils.csvCommaEqualizer(headersCount, + managerFromString.getTaskById(1).get().toCSV(headersCount)); assertEquals(expectedFromFile, taskCSV); assertEquals(expectedFromPath, taskCSV); @@ -91,10 +93,10 @@ void shouldThrowManagerLoadException() { void shouldCorrectlyLoadFromCSVFile() throws IOException { try (BufferedWriter writer = new BufferedWriter(new FileWriter(testFile))) { writer.write(""" - id,type,name,status,description,epic - 1,TASK,Task1,NEW,Description task1, - 2,EPIC,Epic2,DONE,Description epic2, - 3,SUBTASK,Sub Task2,DONE,Description sub task3,2 + id,type,name,status,description,start_time,duration,epic + 1,TASK,Task1,NEW,Description task1,19:11 06.10.2015,28 + 2,EPIC,Epic2,DONE,Description epic2,,, + 3,SUBTASK,Sub Task2,DONE,Description sub task3,06:15 01.01.2027,900,2 """); } catch (IOException e) { e.printStackTrace(); @@ -102,8 +104,8 @@ void shouldCorrectlyLoadFromCSVFile() throws IOException { FileBackedTaskManager manager = Managers.getFileBacked(testFile); - assertEquals("3,SUBTASK,Sub Task2,DONE,Description sub task3,2", - manager.getSubTaskById(3).toCSV(6)); + assertEquals("3,SUBTASK,Sub Task2,DONE,Description sub task3,06:15 01.01.2027,900,2", + manager.getSubTaskById(3).get().toCSV(headersCount)); } @Test @@ -111,8 +113,8 @@ void shouldCorrectlyLoadFromCSVFile() throws IOException { void shouldThrowManagerLoadExceptionForInvalidCSV() { try (BufferedWriter writer = new BufferedWriter(new FileWriter(testFile))) { writer.write(""" - id,type,name,status,description,epic - 1,INVALID_TYPE,Task1,NEW,Description task1, + id,type,name,status,description,start_time,duration,epic + 1,INVALID_TYPE,Task1,NEW,Description task1,06:15 01.01.2027,90, NOT_A_CSV_LINE """); } catch (IOException e) { diff --git a/test/service/TaskManagerTest.java b/test/service/TaskManagerTest.java new file mode 100644 index 0000000..c7b757a --- /dev/null +++ b/test/service/TaskManagerTest.java @@ -0,0 +1,175 @@ +package service; + +import java.time.Duration; +import java.util.*; + +import org.junit.jupiter.api.*; + +import model.*; + +import static common.TestConstants.*; +import static org.junit.jupiter.api.Assertions.*; + +abstract class TaskManagerTest { + protected T taskManager; + protected Task task = new Task("Задача", "Описание задачи", Status.NEW, duration30, date1); + protected Epic epic = new Epic("Эпик", "Описание эпика"); + protected SubTask subTask = new SubTask("Подзадача", "Описание подзадачи", + Status.DONE, 2, duration60, date2); + + protected abstract Optional createTaskManager(); + + @BeforeEach + void init() { + if (createTaskManager().isPresent()) { + taskManager = createTaskManager().get(); + } else { + throw new RuntimeException("Ошибка создания экземпляра менеджера"); + } + taskManager.createTask(task);// id=1 + taskManager.createEpic(epic); // id=2 + taskManager.createSubTask(subTask); // id=3, epicId=2 + } + + @Test + @DisplayName("TaskManager корректно получает задачи (добавлены в @BeforeEach методе)") + void shouldAddTasksCorrectly() { + assertEquals(task, taskManager.getTaskById(1).get()); + assertEquals(epic, taskManager.getEpicById(2)); + assertEquals(subTask, taskManager.getSubTaskById(3).get()); + assertEquals(subTask.getEpicId(), 2); + } + + @Test + @DisplayName("TaskManager обеспечивает неизменность задачи при добавлении") + void shouldNotModifyTaskWhenAdded() { + Task task = new Task("Оригинальная задача", "Описание", Status.NEW, duration90, date3); + int addedTaskId = taskManager.createTask(task); + + assertEquals(task, taskManager.getTaskById(addedTaskId).get()); + } + + @Test + @DisplayName("TaskManager корректно удаляет задачи") + void shouldDeleteTaskCorrectly() { + taskManager.removeTaskById(1); + assertTrue(taskManager.getTaskById(1).isEmpty()); + } + + @Test + @DisplayName("Записи о просмотрах удаляются из истории при удалении задачи") + void shouldDeleteViewHistoryRecordsCorrectly() { + Task task = taskManager.getTaskById(1).get(); + List expectedHistory = new ArrayList<>(List.of(epic, subTask, task)); + + assertEquals(expectedHistory, taskManager.getHistory()); + + taskManager.removeTaskById(1); + assertEquals(new ArrayList<>(List.of(epic, subTask)), taskManager.getHistory()); + } + + @Test + @DisplayName("TaskManager корректно удаляет эпики и подзадачи") + void shouldDeleteEpicCorrectly() { + taskManager.removeEpicById(2); + assertNull(taskManager.getEpicById(2)); + assertTrue(taskManager.getSubTaskById(3).isEmpty()); + } + + @Test + @DisplayName("TaskManager корректно удаляет подзадачу из эпика") + void shouldDeleteSubTaskCorrectly() { + taskManager.removeSubTaskById(3); + assertTrue(taskManager.getSubTaskById(3).isEmpty()); + assertTrue(taskManager.getEpicById(2).getSubTaskIdList().isEmpty()); + } + + @Test + @DisplayName("TaskManager корректно обновляет задачи") + void shouldUpdateTasksCorrectly() { + taskManager.updateTask(new Task("Задача новая", "Описание другой задачи", + Status.NEW, duration30, date3), 1); + assertNotEquals(task, taskManager.getTaskById(1).get()); + + taskManager.updateEpic(new Epic("Эпик новый", "Описание другого эпика"), 2); + assertNotEquals(epic, taskManager.getEpicById(2)); + assertEquals(subTask.getId(), taskManager.getEpicById(2).getSubTaskIdList().getFirst()); + + taskManager.updateSubTask(new SubTask("Подзадача новая", "Описание другой подзадачи", + Status.NEW, 2, duration60, date2), 3); + assertNotEquals(subTask, taskManager.getSubTaskById(3).get()); + } + + @Test + @DisplayName("История просмотров сохраняет состояние задачи на момент её просмотра") + void shouldPreserveTaskStateAsWhenAdded() { + TaskManager taskManager = Managers.getDefault(); + Task originalTask = new Task("Оригинал", "Описание оригинала", Status.NEW, duration90, date3); + + // Добавляем задачу в менеджер и дёргаем get, чтобы она попала в историю + int addedTaskId = taskManager.createTask(originalTask); + taskManager.getTaskById(addedTaskId).get(); + + // Проверяем, что первая (единственная) запись в истории - исходная задача + assertEquals(taskManager.getHistory().getFirst(), originalTask); + + // Создаём обновлённую задачу и через updateTask заменяем исходную задачу в менеджере + Task updatedTask = new Task("Уже не оригинал", "Другое описание", + Status.IN_PROGRESS, duration30, date2); + taskManager.updateTask(updatedTask, addedTaskId); + + // Проверяем, что в менеджере лежит уже обновлённая задача + дёргаем get, чтобы обновить историю + assertEquals(updatedTask, taskManager.getTaskById(addedTaskId).get()); + + // Проверяем, что первая (единственная) запись в истории - обновлённая задача + assertEquals(taskManager.getHistory().getFirst(), updatedTask); + } + + @Test + @DisplayName("InMemoryTaskManager проверяет сгенерированный ID на уникальность при добавлении задачи") + void shouldAvoidExistingIdConflict() { + int existingTaskId = taskManager.createTask(task); + Task existingTask = taskManager.getTaskById(existingTaskId).get(); + + Task newTask = new Task("Задача", "Описание", Status.NEW, duration60, date2); + newTask.setId(existingTaskId); + int newTaskId = taskManager.createTask(newTask); + + assertNotEquals(existingTaskId, newTaskId); + assertEquals(existingTask, taskManager.getTaskById(existingTaskId).get()); + assertEquals(newTask, taskManager.getTaskById(newTaskId).get()); + } + + @Test + @DisplayName("Корректная проверка на пересечение временных интервалов у задач") + void shouldCorrectlyCheckForOverlaps() { + // Пересечение есть + Task anotherTask = new Task("Ещё задача", "Очередная задача...", Status.NEW, + duration30, date1.plus(Duration.ofMinutes(10))); + + taskManager.createTask(anotherTask); // id=4, start_time = N/A + // Добавилась без start_time + assertTrue(taskManager.getTaskById(4).get().getStartTime().isEmpty()); + taskManager.removeTaskById(4); + + // Пересечения нет + anotherTask = new Task("Ещё задача", "Очередная задача...", Status.NEW, + duration30, date3); + taskManager.createTask(anotherTask); + + // Добавилась с указанием времени + assertEquals(anotherTask, taskManager.getTaskById(4).get()); + } + + @Test + @DisplayName("Задачи с/без времени начала попадают/не попадают в список приоритетных") + void shouldCorrectlyAddPrioritizedTasks() { + Task notPriorityTask = new Task("Не приоритет", "Не очень срочная задача", + Status.NEW, duration30); + taskManager.createTask(notPriorityTask); + + assertTrue(taskManager.getPrioritizedTasks().contains(task)); + assertFalse(taskManager.getPrioritizedTasks().contains(notPriorityTask)); + + } +} \ No newline at end of file diff --git a/test/service/utils/UtilsTest.java b/test/service/utils/UtilsTest.java index ba8541e..d23885c 100644 --- a/test/service/utils/UtilsTest.java +++ b/test/service/utils/UtilsTest.java @@ -3,9 +3,11 @@ import java.io.*; import java.util.ArrayList; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class UtilsTest {