diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..54b5b20 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index a9182a4..f88aef9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,13 @@ + + + + diff --git a/java-kanban.iml b/java-kanban.iml index 66c15d2..048fe69 100644 --- a/java-kanban.iml +++ b/java-kanban.iml @@ -1,28 +1,9 @@ - - - + + - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4366965 --- /dev/null +++ b/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + ru.yandex.practicum + java-kanban + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + com.google.code.gson + gson + 2.10.1 + + + org.junit.jupiter + junit-jupiter + 5.9.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0 + + + + \ No newline at end of file diff --git a/src/http/BaseHttpHandler.java b/src/http/BaseHttpHandler.java new file mode 100644 index 0000000..57988a9 --- /dev/null +++ b/src/http/BaseHttpHandler.java @@ -0,0 +1,62 @@ +package http; + +import com.sun.net.httpserver.HttpExchange; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class BaseHttpHandler { + + protected void sendText(HttpExchange exchange, String text) throws IOException { + byte[] response = text.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(200, response.length); + exchange.getResponseBody().write(response); + exchange.close(); + } + + protected void sendSuccess(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(201, -1); + exchange.close(); + } + + protected void sendNotFound(HttpExchange exchange) throws IOException { + String response = String.format("{\"error\": \"%s\"}", "Объект не найден"); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(404, bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + } + + protected void sendHasInteractions(HttpExchange exchange) throws IOException { + String response = String.format("{\"error\": \"%s\"}", "Задача пересекается с существующими"); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(406, bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + } + + protected void sendInternalError(HttpExchange exchange) throws IOException { + String response = String.format("{\"error\": \"%s\"}", "Внутренняя ошибка сервера"); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(500, bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + } + + protected void sendBadRequest(HttpExchange exchange, String message) throws IOException { + String response = String.format("{\"error\": \"%s\"}", message); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(400, bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + } + + protected String readRequestBody(HttpExchange exchange) throws IOException { + return new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/src/http/EpicsHandler.java b/src/http/EpicsHandler.java new file mode 100644 index 0000000..e7827a0 --- /dev/null +++ b/src/http/EpicsHandler.java @@ -0,0 +1,116 @@ +package http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import manager.TaskManager; +import model.Epic; +import model.Subtask; +import java.io.IOException; +import java.util.List; + +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 { + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + switch (method) { + case "GET": + handleGet(exchange, path); + break; + case "POST": + handlePost(exchange); + break; + case "DELETE": + handleDelete(exchange, path); + break; + default: + sendBadRequest(exchange, "Метод не поддерживается"); + } + } catch (Exception e) { + sendInternalError(exchange); + } + } + + private void handleGet(HttpExchange exchange, String path) throws IOException { + if (path.equals("/epics")) { + List epics = taskManager.getAllEpics(); + String response = JsonConverter.epicsToJson(epics); + sendText(exchange, response); + } else if (path.matches("/epics/\\d+")) { + String[] pathParts = path.split("/"); + int id = Integer.parseInt(pathParts[2]); + + Epic epic = taskManager.getEpicById(id); + if (epic != null) { + String response = JsonConverter.epicToJson(epic); + sendText(exchange, response); + } else { + sendNotFound(exchange); + } + } else if (path.matches("/epics/\\d+/subtasks")) { + String[] pathParts = path.split("/"); + int epicId = Integer.parseInt(pathParts[2]); + + Epic epic = taskManager.getEpicById(epicId); + if (epic != null) { + List subtasks = taskManager.getSubtasksByEpicId(epicId); + String response = JsonConverter.subtasksToJson(subtasks); + sendText(exchange, response); + } else { + sendNotFound(exchange); + } + } else { + sendNotFound(exchange); + } + } + + private void handlePost(HttpExchange exchange) throws IOException { + String body = readRequestBody(exchange); + Epic epic = JsonConverter.epicFromJson(body); + + if (epic == null) { + sendBadRequest(exchange, "Неверный формат эпика"); + return; + } + + if (epic.getId() == 0) { + Epic created = taskManager.createEpic(epic); + if (created != null) { + sendSuccess(exchange); + } else { + sendInternalError(exchange); + } + } else { + taskManager.updateEpic(epic); + sendSuccess(exchange); + } + } + + private void handleDelete(HttpExchange exchange, String path) throws IOException { + if (path.equals("/epics")) { + taskManager.deleteAllEpics(); + sendSuccess(exchange); + } else if (path.matches("/epics/\\d+")) { + String[] pathParts = path.split("/"); + int id = Integer.parseInt(pathParts[2]); + + Epic epic = taskManager.getEpicById(id); + if (epic != null) { + taskManager.deleteEpicById(id); + sendSuccess(exchange); + } else { + sendNotFound(exchange); + } + } else { + sendNotFound(exchange); + } + } +} \ No newline at end of file diff --git a/src/http/HistoryHandler.java b/src/http/HistoryHandler.java new file mode 100644 index 0000000..7c52c6d --- /dev/null +++ b/src/http/HistoryHandler.java @@ -0,0 +1,31 @@ +package http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import manager.TaskManager; +import model.Task; +import java.io.IOException; +import java.util.List; + +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 { + try { + if ("GET".equals(exchange.getRequestMethod())) { + List history = taskManager.getHistory(); + String response = JsonConverter.tasksToJson(history); + sendText(exchange, response); + } else { + sendBadRequest(exchange, "Метод не поддерживается"); + } + } catch (Exception e) { + sendInternalError(exchange); + } + } +} \ No newline at end of file diff --git a/src/http/HttpTaskServer.java b/src/http/HttpTaskServer.java new file mode 100644 index 0000000..0bff963 --- /dev/null +++ b/src/http/HttpTaskServer.java @@ -0,0 +1,52 @@ +package http; + +import com.sun.net.httpserver.HttpServer; +import manager.TaskManager; +import manager.Managers; +import java.io.IOException; +import java.net.InetSocketAddress; + +public class HttpTaskServer { + private static final int PORT = 8080; + private final TaskManager taskManager; + private HttpServer server; + + public HttpTaskServer(TaskManager taskManager) { + this.taskManager = taskManager; + } + + public void start() { + try { + server = HttpServer.create(new InetSocketAddress(PORT), 0); + + // Регистрируем обработчики + server.createContext("/tasks", new TasksHandler(taskManager)); + server.createContext("/epics", new EpicsHandler(taskManager)); + server.createContext("/subtasks", new SubtasksHandler(taskManager)); + server.createContext("/history", new HistoryHandler(taskManager)); + server.createContext("/prioritized", new PrioritizedHandler(taskManager)); + + server.start(); + System.out.println(String.format("HTTP Task Server запущен на порту %d", PORT)); + System.out.println(String.format("Доступные эндпоинты: /tasks, /epics, /subtasks, /history, /prioritized")); + } catch (IOException exception) { + throw new RuntimeException(String.format("Не удалось запустить HTTP сервер на порту %d", PORT), exception); + } + } + + public void stop() { + if (server != null) { + server.stop(0); + System.out.println(String.format("HTTP Task Server остановлен")); + } + } + + public static void main(String[] arguments) { + TaskManager manager = Managers.getDefault(); + HttpTaskServer server = new HttpTaskServer(manager); + server.start(); + + // Добавляем shutdown hook для graceful shutdown + Runtime.getRuntime().addShutdownHook(new Thread(server::stop)); + } +} \ No newline at end of file diff --git a/src/http/JsonConverter.java b/src/http/JsonConverter.java new file mode 100644 index 0000000..fefd1f5 --- /dev/null +++ b/src/http/JsonConverter.java @@ -0,0 +1,349 @@ +package http; + +import model.Task; +import model.Epic; +import model.Subtask; +import model.Status; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +public class JsonConverter { + + public static String taskToJson(Task task) { + if (task == null) return "null"; + + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("{"); + jsonBuilder.append("\"id\":").append(task.getId()).append(","); + jsonBuilder.append("\"name\":\"").append(escapeJson(task.getName())).append("\","); + jsonBuilder.append("\"description\":\"").append(escapeJson(task.getDescription())).append("\","); + jsonBuilder.append("\"status\":\"").append(task.getStatus()).append("\","); + jsonBuilder.append("\"type\":\"TASK\","); + + if (task.getDuration() != null) { + jsonBuilder.append("\"duration\":").append(task.getDuration().toMinutes()).append(","); + } else { + jsonBuilder.append("\"duration\":0,"); + } + + if (task.getStartTime() != null) { + jsonBuilder.append("\"startTime\":\"").append(task.getStartTime()).append("\""); + } else { + jsonBuilder.append("\"startTime\":null"); + } + + jsonBuilder.append("}"); + return jsonBuilder.toString(); + } + + public static String epicToJson(Epic epic) { + if (epic == null) return "null"; + + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("{"); + jsonBuilder.append("\"id\":").append(epic.getId()).append(","); + jsonBuilder.append("\"name\":\"").append(escapeJson(epic.getName())).append("\","); + jsonBuilder.append("\"description\":\"").append(escapeJson(epic.getDescription())).append("\","); + jsonBuilder.append("\"status\":\"").append(epic.getStatus()).append("\","); + jsonBuilder.append("\"type\":\"EPIC\","); + + if (epic.getDuration() != null) { + jsonBuilder.append("\"duration\":").append(epic.getDuration().toMinutes()).append(","); + } else { + jsonBuilder.append("\"duration\":0,"); + } + + if (epic.getStartTime() != null) { + jsonBuilder.append("\"startTime\":\"").append(epic.getStartTime()).append("\","); + } else { + jsonBuilder.append("\"startTime\":null,"); + } + + if (epic.getEndTime() != null) { + jsonBuilder.append("\"endTime\":\"").append(epic.getEndTime()).append("\","); + } else { + jsonBuilder.append("\"endTime\":null,"); + } + + jsonBuilder.append("\"subtaskIds\":").append(epic.getSubtaskIds().toString()); + jsonBuilder.append("}"); + return jsonBuilder.toString(); + } + + public static String subtaskToJson(Subtask subtask) { + if (subtask == null) return "null"; + + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("{"); + jsonBuilder.append("\"id\":").append(subtask.getId()).append(","); + jsonBuilder.append("\"name\":\"").append(escapeJson(subtask.getName())).append("\","); + jsonBuilder.append("\"description\":\"").append(escapeJson(subtask.getDescription())).append("\","); + jsonBuilder.append("\"status\":\"").append(subtask.getStatus()).append("\","); + jsonBuilder.append("\"type\":\"SUBTASK\","); + jsonBuilder.append("\"epicId\":").append(subtask.getEpicId()).append(","); + + if (subtask.getDuration() != null) { + jsonBuilder.append("\"duration\":").append(subtask.getDuration().toMinutes()).append(","); + } else { + jsonBuilder.append("\"duration\":0,"); + } + + if (subtask.getStartTime() != null) { + jsonBuilder.append("\"startTime\":\"").append(subtask.getStartTime()).append("\""); + } else { + jsonBuilder.append("\"startTime\":null"); + } + + jsonBuilder.append("}"); + return jsonBuilder.toString(); + } + + public static String tasksToJson(List tasks) { + if (tasks == null) return "[]"; + + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("["); + for (int taskIndex = 0; taskIndex < tasks.size(); taskIndex++) { + jsonBuilder.append(taskToJson(tasks.get(taskIndex))); + if (taskIndex < tasks.size() - 1) { + jsonBuilder.append(","); + } + } + jsonBuilder.append("]"); + return jsonBuilder.toString(); + } + + public static String epicsToJson(List epics) { + if (epics == null) return "[]"; + + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("["); + for (int epicIndex = 0; epicIndex < epics.size(); epicIndex++) { + jsonBuilder.append(epicToJson(epics.get(epicIndex))); + if (epicIndex < epics.size() - 1) { + jsonBuilder.append(","); + } + } + jsonBuilder.append("]"); + return jsonBuilder.toString(); + } + + public static String subtasksToJson(List subtasks) { + if (subtasks == null) return "[]"; + + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("["); + for (int subtaskIndex = 0; subtaskIndex < subtasks.size(); subtaskIndex++) { + jsonBuilder.append(subtaskToJson(subtasks.get(subtaskIndex))); + if (subtaskIndex < subtasks.size() - 1) { + jsonBuilder.append(","); + } + } + jsonBuilder.append("]"); + return jsonBuilder.toString(); + } + + public static Task taskFromJson(String json) { + try { + if (json == null || json.trim().isEmpty() || json.equals("null")) { + return null; + } + + // Простой парсинг JSON + String cleanJson = json.trim().replaceAll("[{}\"]", ""); + String[] keyValuePairs = cleanJson.split(","); + + int taskId = 0; + String taskName = ""; + String taskDescription = ""; + Status taskStatus = Status.NEW; + Duration taskDuration = Duration.ZERO; + LocalDateTime taskStartTime = null; + + for (String keyValuePair : keyValuePairs) { + String[] keyValue = keyValuePair.split(":", 2); + if (keyValue.length != 2) continue; + + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + switch (key) { + case "id": + taskId = Integer.parseInt(value); + break; + case "name": + taskName = unescapeJson(value); + break; + case "description": + taskDescription = unescapeJson(value); + break; + case "status": + taskStatus = Status.valueOf(value); + break; + case "duration": + taskDuration = Duration.ofMinutes(Long.parseLong(value)); + break; + case "startTime": + if (!value.equals("null")) { + taskStartTime = LocalDateTime.parse(value); + } + break; + } + } + + if (taskId == 0) { + return new Task(taskName, taskDescription, taskStatus, taskDuration, taskStartTime); + } else { + return new Task(taskName, taskDescription, taskId, taskStatus, taskDuration, taskStartTime); + } + } catch (Exception exception) { + return null; + } + } + + public static Epic epicFromJson(String json) { + try { + if (json == null || json.trim().isEmpty() || json.equals("null")) { + return null; + } + + String cleanJson = json.trim().replaceAll("[{}\"]", ""); + String[] keyValuePairs = cleanJson.split(","); + + int epicId = 0; + String epicName = ""; + String epicDescription = ""; + Status epicStatus = Status.NEW; + Duration epicDuration = Duration.ZERO; + LocalDateTime epicStartTime = null; + LocalDateTime epicEndTime = null; + + for (String keyValuePair : keyValuePairs) { + String[] keyValue = keyValuePair.split(":", 2); + if (keyValue.length != 2) continue; + + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + switch (key) { + case "id": + epicId = Integer.parseInt(value); + break; + case "name": + epicName = unescapeJson(value); + break; + case "description": + epicDescription = unescapeJson(value); + break; + case "status": + epicStatus = Status.valueOf(value); + break; + case "duration": + epicDuration = Duration.ofMinutes(Long.parseLong(value)); + break; + case "startTime": + if (!value.equals("null")) { + epicStartTime = LocalDateTime.parse(value); + } + break; + case "endTime": + if (!value.equals("null")) { + epicEndTime = LocalDateTime.parse(value); + } + break; + } + } + + Epic epic; + if (epicId == 0) { + epic = new Epic(epicName, epicDescription); + } else { + epic = new Epic(epicName, epicDescription, epicId, epicStatus, epicDuration, epicStartTime, epicEndTime); + } + + return epic; + } catch (Exception exception) { + return null; + } + } + + public static Subtask subtaskFromJson(String json) { + try { + if (json == null || json.trim().isEmpty() || json.equals("null")) { + return null; + } + + String cleanJson = json.trim().replaceAll("[{}\"]", ""); + String[] keyValuePairs = cleanJson.split(","); + + int subtaskId = 0; + String subtaskName = ""; + String subtaskDescription = ""; + Status subtaskStatus = Status.NEW; + int subtaskEpicId = 0; + Duration subtaskDuration = Duration.ZERO; + LocalDateTime subtaskStartTime = null; + + for (String keyValuePair : keyValuePairs) { + String[] keyValue = keyValuePair.split(":", 2); + if (keyValue.length != 2) continue; + + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + switch (key) { + case "id": + subtaskId = Integer.parseInt(value); + break; + case "name": + subtaskName = unescapeJson(value); + break; + case "description": + subtaskDescription = unescapeJson(value); + break; + case "status": + subtaskStatus = Status.valueOf(value); + break; + case "epicId": + subtaskEpicId = Integer.parseInt(value); + break; + case "duration": + subtaskDuration = Duration.ofMinutes(Long.parseLong(value)); + break; + case "startTime": + if (!value.equals("null")) { + subtaskStartTime = LocalDateTime.parse(value); + } + break; + } + } + + if (subtaskId == 0) { + return new Subtask(subtaskName, subtaskDescription, subtaskStatus, subtaskEpicId, subtaskDuration, subtaskStartTime); + } else { + return new Subtask(subtaskName, subtaskDescription, subtaskId, subtaskStatus, subtaskEpicId, subtaskDuration, subtaskStartTime); + } + } catch (Exception exception) { + return null; + } + } + + private static String escapeJson(String inputString) { + if (inputString == null) return ""; + return inputString.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } + + private static String unescapeJson(String inputString) { + if (inputString == null) return ""; + return inputString.replace("\\\"", "\"") + .replace("\\\\", "\\") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t"); + } +} \ No newline at end of file diff --git a/src/http/PrioritizedHandler.java b/src/http/PrioritizedHandler.java new file mode 100644 index 0000000..158a5b4 --- /dev/null +++ b/src/http/PrioritizedHandler.java @@ -0,0 +1,31 @@ +package http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import manager.TaskManager; +import model.Task; +import java.io.IOException; +import java.util.List; + +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 { + try { + if ("GET".equals(exchange.getRequestMethod())) { + List prioritizedTasks = taskManager.getPrioritizedTasks(); + String response = JsonConverter.tasksToJson(prioritizedTasks); + sendText(exchange, response); + } else { + sendBadRequest(exchange, "Метод не поддерживается"); + } + } catch (Exception e) { + sendInternalError(exchange); + } + } +} \ No newline at end of file diff --git a/src/http/SubtasksHandler.java b/src/http/SubtasksHandler.java new file mode 100644 index 0000000..8c32e28 --- /dev/null +++ b/src/http/SubtasksHandler.java @@ -0,0 +1,107 @@ +package http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import manager.TaskManager; +import model.Subtask; +import java.io.IOException; +import java.util.List; + +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 { + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + switch (method) { + case "GET": + handleGet(exchange, path); + break; + case "POST": + handlePost(exchange); + break; + case "DELETE": + handleDelete(exchange, path); + break; + default: + sendBadRequest(exchange, "Метод не поддерживается"); + } + } catch (Exception e) { + sendInternalError(exchange); + } + } + + private void handleGet(HttpExchange exchange, String path) throws IOException { + if (path.equals("/subtasks")) { + List subtasks = taskManager.getAllSubtasks(); + String response = JsonConverter.subtasksToJson(subtasks); + sendText(exchange, response); + } else if (path.matches("/subtasks/\\d+")) { + String[] pathParts = path.split("/"); + int id = Integer.parseInt(pathParts[2]); + + Subtask subtask = taskManager.getSubtaskById(id); + if (subtask != null) { + String response = JsonConverter.subtaskToJson(subtask); + sendText(exchange, response); + } else { + sendNotFound(exchange); + } + } else { + sendNotFound(exchange); + } + } + + private void handlePost(HttpExchange exchange) throws IOException { + String body = readRequestBody(exchange); + Subtask subtask = JsonConverter.subtaskFromJson(body); + + if (subtask == null) { + sendBadRequest(exchange, "Неверный формат подзадачи"); + return; + } + + if (subtask.getId() == 0) { + Subtask created = taskManager.createSubtask(subtask); + if (created != null) { + sendSuccess(exchange); + } else { + if (taskManager.isTaskOverlapping(subtask)) { + sendHasInteractions(exchange); + } else { + sendInternalError(exchange); + } + } + } else { + taskManager.updateSubtask(subtask); + sendSuccess(exchange); + } + } + + private void handleDelete(HttpExchange exchange, String path) throws IOException { + if (path.equals("/subtasks")) { + taskManager.deleteAllSubtasks(); + sendSuccess(exchange); + } else if (path.matches("/subtasks/\\d+")) { + String[] pathParts = path.split("/"); + int id = Integer.parseInt(pathParts[2]); + + Subtask subtask = taskManager.getSubtaskById(id); + if (subtask != null) { + taskManager.deleteSubtaskById(id); + sendSuccess(exchange); + } else { + sendNotFound(exchange); + } + } else { + sendNotFound(exchange); + } + } +} \ No newline at end of file diff --git a/src/http/TasksHandler.java b/src/http/TasksHandler.java new file mode 100644 index 0000000..e907428 --- /dev/null +++ b/src/http/TasksHandler.java @@ -0,0 +1,109 @@ +package http; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import manager.TaskManager; +import model.Task; +import java.io.IOException; +import java.util.List; + +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 { + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + switch (method) { + case "GET": + handleGet(exchange, path); + break; + case "POST": + handlePost(exchange); + break; + case "DELETE": + handleDelete(exchange, path); + break; + default: + sendBadRequest(exchange, "Метод не поддерживается"); + } + } catch (Exception exception) { + sendInternalError(exchange); + } + } + + private void handleGet(HttpExchange exchange, String path) throws IOException { + if (path.equals("/tasks")) { + List tasks = taskManager.getAllTasks(); + String response = JsonConverter.tasksToJson(tasks); + sendText(exchange, response); + } else if (path.matches("/tasks/\\d+")) { + String[] pathParts = path.split("/"); + int taskId = Integer.parseInt(pathParts[2]); + + Task task = taskManager.getTaskById(taskId); + if (task != null) { + String response = JsonConverter.taskToJson(task); + sendText(exchange, response); + } else { + sendNotFound(exchange); + } + } else { + sendNotFound(exchange); + } + } + + private void handlePost(HttpExchange exchange) throws IOException { + String requestBody = readRequestBody(exchange); + Task task = JsonConverter.taskFromJson(requestBody); + + if (task == null) { + sendBadRequest(exchange, "Неверный формат задачи"); + return; + } + + if (task.getId() == 0) { + // Создание новой задачи + Task createdTask = taskManager.createTask(task); + if (createdTask != null) { + sendSuccess(exchange); + } else { + if (taskManager.isTaskOverlapping(task)) { + sendHasInteractions(exchange); + } else { + sendInternalError(exchange); + } + } + } else { + // Обновление существующей задачи + taskManager.updateTask(task); + sendSuccess(exchange); + } + } + + private void handleDelete(HttpExchange exchange, String path) throws IOException { + if (path.equals("/tasks")) { + taskManager.deleteAllTasks(); + sendSuccess(exchange); + } else if (path.matches("/tasks/\\d+")) { + String[] pathParts = path.split("/"); + int taskId = Integer.parseInt(pathParts[2]); + + Task task = taskManager.getTaskById(taskId); + if (task != null) { + taskManager.deleteTaskById(taskId); + sendSuccess(exchange); + } else { + sendNotFound(exchange); + } + } else { + sendNotFound(exchange); + } + } +} \ No newline at end of file diff --git a/src/manager/FileBackedTaskManager.java b/src/manager/FileBackedTaskManager.java new file mode 100644 index 0000000..fae8d25 --- /dev/null +++ b/src/manager/FileBackedTaskManager.java @@ -0,0 +1,272 @@ +package manager; + +import model.*; +import model.TaskType; + +import java.io.*; +import java.nio.file.Files; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class FileBackedTaskManager extends InMemoryTaskManager { + private final File file; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + + public FileBackedTaskManager(File file) { + this.file = file; + } + + @Override + public Task createTask(Task task) { + Task createdTask = super.createTask(task); + save(); + return createdTask; + } + + @Override + public Epic createEpic(Epic epic) { + Epic createdEpic = super.createEpic(epic); + save(); + return createdEpic; + } + + @Override + public Subtask createSubtask(Subtask subtask) { + Subtask createdSubtask = super.createSubtask(subtask); + save(); + return createdSubtask; + } + + @Override + public void updateTask(Task task) { + super.updateTask(task); + save(); + } + + @Override + public void updateEpic(Epic epic) { + super.updateEpic(epic); + save(); + } + + @Override + public void updateSubtask(Subtask subtask) { + super.updateSubtask(subtask); + save(); + } + + @Override + public void deleteTaskById(int id) { + super.deleteTaskById(id); + save(); + } + + @Override + public void deleteEpicById(int id) { + super.deleteEpicById(id); + save(); + } + + @Override + public void deleteSubtaskById(int id) { + super.deleteSubtaskById(id); + save(); + } + + @Override + public void deleteAllTasks() { + super.deleteAllTasks(); + save(); + } + + @Override + public void deleteAllEpics() { + super.deleteAllEpics(); + save(); + } + + @Override + public void deleteAllSubtasks() { + super.deleteAllSubtasks(); + save(); + } + + protected void save() { + try (PrintWriter writer = new PrintWriter(file)) { + writer.println("id,type,name,status,description,epic,duration,startTime"); + + for (Task task : getAllTasks()) { + writer.println(taskToString(task)); + } + for (Epic epic : getAllEpics()) { + writer.println(taskToString(epic)); + } + for (Subtask subtask : getAllSubtasks()) { + writer.println(taskToString(subtask)); + } + } catch (IOException e) { + throw new ManagerSaveException("Ошибка сохранения в файл", e); + } + } + + private String taskToString(Task task) { + String durationStr = task.getDuration() != null + ? String.valueOf(task.getDuration().toMinutes()) : ""; + String startTimeStr = task.getStartTime() != null + ? task.getStartTime().format(DATE_TIME_FORMATTER) : ""; + + if (task instanceof Epic) { + Epic epic = (Epic) task; + String endTimeStr = epic.getEndTime() != null + ? epic.getEndTime().format(DATE_TIME_FORMATTER) : ""; + return String.format("%d,EPIC,%s,%s,%s,,%s,%s,%s", + task.getId(), + task.getName(), + task.getStatus(), + task.getDescription(), + durationStr, + startTimeStr, + endTimeStr); + } else if (task instanceof Subtask) { + Subtask subtask = (Subtask) task; + return String.format("%d,SUBTASK,%s,%s,%s,%d,%s,%s", + subtask.getId(), + subtask.getName(), + subtask.getStatus(), + subtask.getDescription(), + subtask.getEpicId(), + durationStr, + startTimeStr); + } else { + return String.format("%d,TASK,%s,%s,%s,,%s,%s", + task.getId(), + task.getName(), + task.getStatus(), + task.getDescription(), + durationStr, + startTimeStr); + } + } + + private Task taskFromString(String value) { + String[] fields = value.split(",", -1); + int id = Integer.parseInt(fields[0]); + TaskType type = TaskType.valueOf(fields[1]); + String name = fields[2]; + Status status = Status.valueOf(fields[3]); + String description = fields[4]; + + Duration duration = fields[6].isEmpty() ? Duration.ZERO + : Duration.ofMinutes(Long.parseLong(fields[6])); + LocalDateTime startTime = fields[7].isEmpty() ? null + : LocalDateTime.parse(fields[7], DATE_TIME_FORMATTER); + + switch (type) { + case TASK: + Task task = new Task(name, description, id, status, duration, startTime); + return task; + case EPIC: + Epic epic = new Epic(name, description, id, status, duration, startTime, null); + if (fields.length > 8 && !fields[8].isEmpty()) { + LocalDateTime endTime = LocalDateTime.parse(fields[8], DATE_TIME_FORMATTER); + epic.setEndTime(endTime); + } + return epic; + case SUBTASK: + int epicId = fields[5].isEmpty() ? 0 : Integer.parseInt(fields[5]); + Subtask subtask = new Subtask(name, description, id, status, epicId, duration, startTime); + return subtask; + default: + throw new IllegalArgumentException(String.format("Неизвестный тип задачи: %s", type)); + } + } + + public static FileBackedTaskManager loadFromFile(File file) { + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + try { + if (!file.exists()) { + return manager; + } + + String content = Files.readString(file.toPath()); + String[] fileLines = content.split("\n"); + + for (int lineNumber = 1; lineNumber < fileLines.length; lineNumber++) { + String currentLine = fileLines[lineNumber].trim(); + if (currentLine.isEmpty()) { + continue; + } + + Task task = manager.taskFromString(currentLine); + if (task instanceof Epic) { + manager.epics.put(task.getId(), (Epic) task); + } else if (task instanceof Subtask) { + manager.subtasks.put(task.getId(), (Subtask) task); + } else { + manager.tasks.put(task.getId(), task); + } + + if (task.getId() >= manager.nextId) { + manager.nextId = task.getId() + 1; + } + + if (task.getStartTime() != null) { + manager.prioritizedTasks.add(task); + } + } + + for (Subtask subtask : manager.subtasks.values()) { + Epic epic = manager.epics.get(subtask.getEpicId()); + if (epic != null) { + epic.addSubtaskId(subtask.getId()); + } + } + + } catch (IOException e) { + throw new ManagerSaveException("Ошибка загрузки из файла", e); + } + + return manager; + } + + public static void main(String[] args) { + try { + File tempFile = File.createTempFile("tasks", ".csv"); + + FileBackedTaskManager manager1 = new FileBackedTaskManager(tempFile); + + Task task1 = manager1.createTask(new Task("Task 1", "Description 1", Status.NEW, + Duration.ofMinutes(30), LocalDateTime.now())); + manager1.createTask(new Task("Task 2", "Description 2", Status.IN_PROGRESS, + Duration.ofHours(1), LocalDateTime.now().plusHours(2))); + + Epic epic1 = manager1.createEpic(new Epic("Epic 1", "Epic description")); + Subtask subtask1 = manager1.createSubtask(new Subtask("Subtask 1", "Sub description", + Status.DONE, epic1.getId(), Duration.ofMinutes(45), + LocalDateTime.now().plusHours(1))); + manager1.createSubtask(new Subtask("Subtask 2", "Sub description 2", + Status.NEW, epic1.getId(), Duration.ofMinutes(15), + LocalDateTime.now().plusHours(3))); + + manager1.getTaskById(task1.getId()); + manager1.getEpicById(epic1.getId()); + manager1.getSubtaskById(subtask1.getId()); + + FileBackedTaskManager manager2 = FileBackedTaskManager.loadFromFile(tempFile); + + System.out.println(String.format("Проверка восстановления данных:")); + System.out.println(String.format("Задачи в manager2: %d", manager2.getAllTasks().size())); + System.out.println(String.format("Эпики в manager2: %d", manager2.getAllEpics().size())); + System.out.println(String.format("Подзадачи в manager2: %d", manager2.getAllSubtasks().size())); + System.out.println(String.format("История в manager2: %d", manager2.getHistory().size())); + System.out.println(String.format("Приоритетные задачи в manager2: %d", manager2.getPrioritizedTasks().size())); + + tempFile.deleteOnExit(); + + } catch (IOException e) { + System.err.println(String.format("Ошибка при работе с файлом: %s", e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/manager/HistoryManager.java b/src/manager/HistoryManager.java index 6070997..1aa53c9 100644 --- a/src/manager/HistoryManager.java +++ b/src/manager/HistoryManager.java @@ -5,5 +5,8 @@ public interface HistoryManager { void add(Task task); + + void remove(int id); + List getHistory(); } \ No newline at end of file diff --git a/src/manager/InMemoryHistoryManager.java b/src/manager/InMemoryHistoryManager.java index aee6b0d..bf7c45b 100644 --- a/src/manager/InMemoryHistoryManager.java +++ b/src/manager/InMemoryHistoryManager.java @@ -1,25 +1,119 @@ package manager; import model.Task; +import model.Epic; +import model.Subtask; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class InMemoryHistoryManager implements HistoryManager { - private static final int MAX_HISTORY_SIZE = 10; - private final List history = new ArrayList<>(); + private static class Node { + Task task; + Node prev; + Node next; + + Node(Task task) { + this.task = task; + } + } + + private final Map historyMap = new HashMap<>(); + private Node head; + private Node tail; @Override public void add(Task task) { if (task == null) return; - if (history.size() >= MAX_HISTORY_SIZE) { - history.removeFirst(); // Удаляем самый старый элемент + remove(task.getId()); + + + Task taskCopy = copyTask(task); + Node newNode = linkLast(taskCopy); + historyMap.put(taskCopy.getId(), newNode); + } + + private Task copyTask(Task original) { + if (original instanceof Epic) { + Epic epic = (Epic) original; + Epic copy = new Epic(epic.getName(), epic.getDescription()); + copy.setId(epic.getId()); + copy.updateStatus(epic.getStatus()); + // Копируем список подзадач + for (Integer subtaskId : epic.getSubtaskIds()) { + copy.addSubtaskId(subtaskId); + } + return copy; + } else if (original instanceof Subtask) { + Subtask subtask = (Subtask) original; + return new Subtask(subtask.getName(), subtask.getDescription(), + subtask.getId(), subtask.getStatus(), subtask.getEpicId()); + } else { + return new Task(original.getName(), original.getDescription(), + original.getId(), original.getStatus()); + } + } + + + @Override + public void remove(int id) { + Node node = historyMap.get(id); + if (node != null) { + removeNode(node); + historyMap.remove(id); } - history.add(task); } @Override public List getHistory() { - return new ArrayList<>(history); + return getTasks(); + } + + private Node linkLast(Task task) { + Node newNode = new Node(task); + + if (tail == null) { + head = newNode; + tail = newNode; + } else { + tail.next = newNode; + newNode.prev = tail; + tail = newNode; + } + + return newNode; + } + + private void removeNode(Node node) { + if (node == null) return; + + if (node.prev != null) { + node.prev.next = node.next; + } else { + head = node.next; + } + + if (node.next != null) { + node.next.prev = node.prev; + } else { + tail = node.prev; + } + + node.prev = null; + node.next = null; + } + + private List getTasks() { + List tasks = new ArrayList<>(); + Node current = head; + + while (current != null) { + tasks.add(current.task); + current = current.next; + } + + return tasks; } } \ No newline at end of file diff --git a/src/manager/InMemoryTaskManager.java b/src/manager/InMemoryTaskManager.java index 9b107ab..9491856 100644 --- a/src/manager/InMemoryTaskManager.java +++ b/src/manager/InMemoryTaskManager.java @@ -4,103 +4,264 @@ import model.Epic; import model.Subtask; import model.Status; + +import java.time.Duration; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; +import java.util.Optional; public class InMemoryTaskManager implements TaskManager { - private final Map tasks = new HashMap<>(); - private final Map epics = new HashMap<>(); - private final Map subtasks = new HashMap<>(); - private final HistoryManager historyManager; - private int nextId = 1; + protected final Map tasks = new HashMap<>(); + protected final Map epics = new HashMap<>(); + protected final Map subtasks = new HashMap<>(); + protected final HistoryManager historyManager; + protected int nextId = 1; + protected final Set prioritizedTasks = new TreeSet<>( + Comparator.comparing(Task::getStartTime, + Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(Task::getId) + ); public InMemoryTaskManager() { this.historyManager = Managers.getDefaultHistory(); } - private int generateId() { return nextId++; } + protected int generateId() { + return nextId++; + } - // Модифицируем методы получения задач для добавления в историю @Override - public Task getTaskById(int id) { + public Optional getTaskByIdOptional(int id) { Task task = tasks.get(id); if (task != null) { historyManager.add(task); + return Optional.of(createTaskCopy(task)); } - return task; + return Optional.empty(); } @Override - public Epic getEpicById(int id) { + public Optional getEpicByIdOptional(int id) { Epic epic = epics.get(id); if (epic != null) { historyManager.add(epic); + return Optional.of(createEpicCopy(epic)); } - return epic; + return Optional.empty(); } @Override - public Subtask getSubtaskById(int id) { + public Optional getSubtaskByIdOptional(int id) { Subtask subtask = subtasks.get(id); if (subtask != null) { historyManager.add(subtask); + return Optional.of(createSubtaskCopy(subtask)); } - return subtask; + return Optional.empty(); } - // Добавляем метод для получения истории @Override - public List getHistory() { - return historyManager.getHistory(); + public boolean isTasksOverlapping(Task task1, Task task2) { + if (task1.getStartTime() == null || task2.getStartTime() == null + || task1.getEndTime() == null || task2.getEndTime() == null) { + return false; + } + + return !task1.getEndTime().isBefore(task2.getStartTime()) + && !task1.getStartTime().isAfter(task2.getEndTime()); } - // Остальные методы остаются без изменений... - @Override public List getAllTasks() { return new ArrayList<>(tasks.values()); } + @Override + public boolean hasAnyTimeOverlaps() { + List prioritized = getPrioritizedTasks(); + + for (int i = 1; i < prioritized.size(); i++) { + Task current = prioritized.get(i); + Task previous = prioritized.get(i - 1); + + if (isTasksOverlapping(previous, current)) { + return true; + } + } + return false; + } @Override - public void deleteAllTasks() { tasks.clear(); } + public boolean isTaskOverlapping(Task task) { + if (task.getStartTime() == null) { + return false; + } + + LocalDateTime taskStart = task.getStartTime(); + LocalDateTime taskEnd = task.getEndTime(); + + if (taskEnd == null) { + return false; + } + + return prioritizedTasks.stream() + .filter(t -> t.getStartTime() != null && t.getEndTime() != null) + .filter(t -> t.getId() != task.getId()) + .anyMatch(existingTask -> isTasksOverlapping(task, existingTask)); + } + + @Override + public List getSubtasksByEpicId(int epicId) { + Epic epic = epics.get(epicId); + if (epic == null) { + return Collections.emptyList(); + } + + return epic.getSubtaskIds().stream() + .map(subtasks::get) + .filter(Objects::nonNull) + .map(this::createSubtaskCopy) + .collect(Collectors.toList()); + } + + private Task createTaskCopy(Task original) { + return new Task(original.getName(), original.getDescription(), + original.getId(), original.getStatus(), + original.getDuration(), original.getStartTime()); + } + + private Epic createEpicCopy(Epic original) { + Epic copy = new Epic(original.getName(), original.getDescription(), + original.getId(), original.getStatus(), + original.getDuration(), original.getStartTime(), original.getEndTime()); + for (Integer subtaskId : original.getSubtaskIds()) { + copy.addSubtaskId(subtaskId); + } + return copy; + } + + private Subtask createSubtaskCopy(Subtask original) { + return new Subtask(original.getName(), original.getDescription(), + original.getId(), original.getStatus(), original.getEpicId(), + original.getDuration(), original.getStartTime()); + } + + @Override + public List getAllTasks() { + return tasks.values().stream() + .map(this::createTaskCopy) + .collect(Collectors.toList()); + } + + @Override + public void deleteAllTasks() { + for (Integer taskId : tasks.keySet()) { + historyManager.remove(taskId); + prioritizedTasks.removeIf(task -> task.getId() == taskId); + } + tasks.clear(); + } + + @Override + public Task getTaskById(int id) { + return getTaskByIdOptional(id).orElse(null); + } @Override public Task createTask(Task task) { - if (task == null) return null; - Task newTask = new Task(task.getName(), task.getDescription(), generateId(), task.getStatus()); + if (task == null) { + return null; + } + + if (isTaskOverlapping(task)) { + return null; + } + + Task newTask = new Task(task.getName(), task.getDescription(), + generateId(), task.getStatus(), + task.getDuration(), task.getStartTime()); tasks.put(newTask.getId(), newTask); - return newTask; + + if (newTask.getStartTime() != null) { + prioritizedTasks.add(newTask); + } + + return createTaskCopy(newTask); } @Override public void updateTask(Task task) { - if (task == null || !tasks.containsKey(task.getId())) return; + if (task == null || !tasks.containsKey(task.getId())) { + return; + } + + Task existingTask = tasks.get(task.getId()); + prioritizedTasks.remove(existingTask); + + if (isTaskOverlapping(task)) { + prioritizedTasks.add(existingTask); + return; + } + tasks.put(task.getId(), task); + + if (task.getStartTime() != null) { + prioritizedTasks.add(task); + } } @Override - public void deleteTaskById(int id) { tasks.remove(id); } + public void deleteTaskById(int id) { + Task task = tasks.remove(id); + if (task != null) { + historyManager.remove(id); + prioritizedTasks.remove(task); + } + } @Override - public List getAllEpics() { return new ArrayList<>(epics.values()); } + public List getAllEpics() { + return epics.values().stream() + .map(this::createEpicCopy) + .collect(Collectors.toList()); + } @Override public void deleteAllEpics() { + for (Integer epicId : epics.keySet()) { + historyManager.remove(epicId); + } + for (Integer subtaskId : subtasks.keySet()) { + historyManager.remove(subtaskId); + prioritizedTasks.removeIf(task -> task.getId() == subtaskId); + } + for (Epic epic : epics.values()) { for (Integer subtaskId : epic.getSubtaskIds()) { subtasks.remove(subtaskId); } } epics.clear(); + subtasks.clear(); + } + + @Override + public Epic getEpicById(int id) { + return getEpicByIdOptional(id).orElse(null); } @Override public Epic createEpic(Epic epic) { - if (epic == null) return null; + if (epic == null) { + return null; + } Epic newEpic = new Epic(epic.getName(), epic.getDescription()); newEpic.setId(generateId()); epics.put(newEpic.getId(), newEpic); - return newEpic; + return createEpicCopy(newEpic); } @Override public void updateEpic(Epic epic) { - if (epic == null || !epics.containsKey(epic.getId())) return; + if (epic == null || !epics.containsKey(epic.getId())) { + return; + } Epic existingEpic = epics.get(epic.getId()); existingEpic.setName(epic.getName()); existingEpic.setDescription(epic.getDescription()); @@ -110,40 +271,101 @@ public void updateEpic(Epic epic) { public void deleteEpicById(int id) { Epic epic = epics.get(id); if (epic != null) { + historyManager.remove(id); for (Integer subtaskId : epic.getSubtaskIds()) { subtasks.remove(subtaskId); + historyManager.remove(subtaskId); + prioritizedTasks.removeIf(task -> task.getId() == subtaskId); } epics.remove(id); } } @Override - public List getAllSubtasks() { return new ArrayList<>(subtasks.values()); } + public List getAllSubtasks() { + return subtasks.values().stream() + .map(this::createSubtaskCopy) + .collect(Collectors.toList()); + } @Override public void deleteAllSubtasks() { + for (Integer subtaskId : subtasks.keySet()) { + historyManager.remove(subtaskId); + prioritizedTasks.removeIf(task -> task.getId() == subtaskId); + } + for (Epic epic : epics.values()) { epic.clearSubtasks(); updateEpicStatus(epic.getId()); + updateEpicTiming(epic.getId()); } subtasks.clear(); } + @Override + public Subtask getSubtaskById(int id) { + return getSubtaskByIdOptional(id).orElse(null); + } + @Override public Subtask createSubtask(Subtask subtask) { - if (subtask == null || !epics.containsKey(subtask.getEpicId())) return null; - Subtask newSubtask = new Subtask(subtask.getName(), subtask.getDescription(), generateId(), subtask.getStatus(), subtask.getEpicId()); + if (subtask == null || !epics.containsKey(subtask.getEpicId())) { + return null; + } + + Epic epic = epics.get(subtask.getEpicId()); + + if (subtask.getId() != 0) { + if (subtask.getEpicId() == subtask.getId()) { + return null; + } + if (epic.getId() == subtask.getId()) { + return null; + } + } + + if (isTaskOverlapping(subtask)) { + return null; + } + + Subtask newSubtask = new Subtask(subtask.getName(), subtask.getDescription(), + generateId(), subtask.getStatus(), subtask.getEpicId(), + subtask.getDuration(), subtask.getStartTime()); subtasks.put(newSubtask.getId(), newSubtask); - Epic epic = epics.get(newSubtask.getEpicId()); epic.addSubtaskId(newSubtask.getId()); + + if (newSubtask.getStartTime() != null) { + prioritizedTasks.add(newSubtask); + } + updateEpicStatus(epic.getId()); - return newSubtask; + updateEpicTiming(epic.getId()); + return createSubtaskCopy(newSubtask); } @Override public void updateSubtask(Subtask subtask) { - if (subtask == null || !subtasks.containsKey(subtask.getId())) return; + if (subtask == null || !subtasks.containsKey(subtask.getId())) { + return; + } + + if (subtask.getEpicId() == subtask.getId()) { + return; + } + + if (!epics.containsKey(subtask.getEpicId())) { + return; + } + Subtask existingSubtask = subtasks.get(subtask.getId()); + prioritizedTasks.remove(existingSubtask); + + if (isTaskOverlapping(subtask)) { + prioritizedTasks.add(existingSubtask); + return; + } + int oldEpicId = existingSubtask.getEpicId(); int newEpicId = subtask.getEpicId(); @@ -152,6 +374,7 @@ public void updateSubtask(Subtask subtask) { if (oldEpic != null) { oldEpic.removeSubtaskId(subtask.getId()); updateEpicStatus(oldEpicId); + updateEpicTiming(oldEpicId); } Epic newEpic = epics.get(newEpicId); if (newEpic != null) { @@ -159,8 +382,19 @@ public void updateSubtask(Subtask subtask) { } } - subtasks.put(subtask.getId(), subtask); + existingSubtask.setName(subtask.getName()); + existingSubtask.setDescription(subtask.getDescription()); + existingSubtask.setStatus(subtask.getStatus()); + existingSubtask.setEpicId(subtask.getEpicId()); + existingSubtask.setDuration(subtask.getDuration()); + existingSubtask.setStartTime(subtask.getStartTime()); + + if (existingSubtask.getStartTime() != null) { + prioritizedTasks.add(existingSubtask); + } + updateEpicStatus(subtask.getEpicId()); + updateEpicTiming(subtask.getEpicId()); } @Override @@ -171,43 +405,96 @@ public void deleteSubtaskById(int id) { if (epic != null) { epic.removeSubtaskId(id); updateEpicStatus(epic.getId()); + updateEpicTiming(epic.getId()); } subtasks.remove(id); + historyManager.remove(id); + prioritizedTasks.remove(subtask); } } @Override - public List getSubtasksByEpicId(int epicId) { - List result = new ArrayList<>(); - Epic epic = epics.get(epicId); - if (epic != null) { - for (Integer subtaskId : epic.getSubtaskIds()) { - Subtask subtask = subtasks.get(subtaskId); - if (subtask != null) result.add(subtask); - } - } - return result; + public List getHistory() { + return historyManager.getHistory(); + } + + @Override + public List getPrioritizedTasks() { + return new ArrayList<>(prioritizedTasks); } private void updateEpicStatus(int epicId) { Epic epic = epics.get(epicId); - if (epic == null) return; + if (epic == null) { + return; + } List subtaskIds = epic.getSubtaskIds(); if (subtaskIds.isEmpty()) { epic.updateStatus(Status.NEW); return; } + boolean allDone = true; boolean allNew = true; + for (Integer subtaskId : subtaskIds) { Subtask subtask = subtasks.get(subtaskId); - if (subtask == null) continue; - if (subtask.getStatus() != Status.DONE) allDone = false; - if (subtask.getStatus() != Status.NEW) allNew = false; - if (!allDone && !allNew) break; - } - if (allDone) epic.updateStatus(Status.DONE); - else if (allNew) epic.updateStatus(Status.NEW); - else epic.updateStatus(Status.IN_PROGRESS); + if (subtask == null) { + continue; + } + if (subtask.getStatus() != Status.DONE) { + allDone = false; + } + if (subtask.getStatus() != Status.NEW) { + allNew = false; + } + } + + if (allDone) { + epic.updateStatus(Status.DONE); + } else if (allNew) { + epic.updateStatus(Status.NEW); + } else { + epic.updateStatus(Status.IN_PROGRESS); + } + } + + private void updateEpicTiming(int epicId) { + Epic epic = epics.get(epicId); + if (epic == null) { + return; + } + + List epicSubtasks = getSubtasksByEpicId(epicId).stream() + .map(subtask -> subtasks.get(subtask.getId())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (epicSubtasks.isEmpty()) { + epic.setDuration(Duration.ZERO); + epic.setStartTime(null); + epic.setEndTime(null); + return; + } + + Duration totalDuration = epicSubtasks.stream() + .map(Subtask::getDuration) + .filter(Objects::nonNull) + .reduce(Duration.ZERO, Duration::plus); + epic.setDuration(totalDuration); + + LocalDateTime earliestStart = epicSubtasks.stream() + .map(Subtask::getStartTime) + .filter(Objects::nonNull) + .min(LocalDateTime::compareTo) + .orElse(null); + epic.setStartTime(earliestStart); + + LocalDateTime latestEnd = epicSubtasks.stream() + .map(Subtask::getEndTime) + .filter(Objects::nonNull) + .max(LocalDateTime::compareTo) + .orElse(null); + epic.setEndTime(latestEnd); } } \ No newline at end of file diff --git a/src/manager/ManagerSaveException.java b/src/manager/ManagerSaveException.java new file mode 100644 index 0000000..4ff500a --- /dev/null +++ b/src/manager/ManagerSaveException.java @@ -0,0 +1,11 @@ +package manager; + +public class ManagerSaveException extends RuntimeException { + public ManagerSaveException(String message) { + super(message); + } + + public ManagerSaveException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/manager/Managers.java b/src/manager/Managers.java index 64dfb6f..e5d3bce 100644 --- a/src/manager/Managers.java +++ b/src/manager/Managers.java @@ -1,5 +1,7 @@ package manager; +import java.io.File; + public class Managers { public static TaskManager getDefault() { @@ -9,4 +11,8 @@ public static TaskManager getDefault() { public static HistoryManager getDefaultHistory() { return new InMemoryHistoryManager(); } + + public static FileBackedTaskManager getFileBackedTaskManager(File file) { + return new FileBackedTaskManager(file); + } } \ No newline at end of file diff --git a/src/manager/TaskManager.java b/src/manager/TaskManager.java index 09e55fa..e10d5c3 100644 --- a/src/manager/TaskManager.java +++ b/src/manager/TaskManager.java @@ -4,31 +4,60 @@ import model.Epic; import model.Subtask; import java.util.List; +import java.util.Optional; public interface TaskManager { List getAllTasks(); + void deleteAllTasks(); + Task getTaskById(int id); + + Optional getTaskByIdOptional(int id); + Task createTask(Task task); + void updateTask(Task task); + void deleteTaskById(int id); List getAllEpics(); + void deleteAllEpics(); + Epic getEpicById(int id); + + Optional getEpicByIdOptional(int id); + Epic createEpic(Epic epic); + void updateEpic(Epic epic); + void deleteEpicById(int id); List getAllSubtasks(); + void deleteAllSubtasks(); + Subtask getSubtaskById(int id); + + Optional getSubtaskByIdOptional(int id); + Subtask createSubtask(Subtask subtask); + void updateSubtask(Subtask subtask); + void deleteSubtaskById(int id); List getSubtasksByEpicId(int epicId); - // Новый метод для получения истории List getHistory(); + + List getPrioritizedTasks(); + + boolean isTaskOverlapping(Task task); + + boolean isTasksOverlapping(Task task1, Task task2); + + boolean hasAnyTimeOverlaps(); } \ No newline at end of file diff --git a/src/manager/TaskType.java b/src/manager/TaskType.java new file mode 100644 index 0000000..b95e27a --- /dev/null +++ b/src/manager/TaskType.java @@ -0,0 +1,7 @@ +package model; + +public enum TaskType { + TASK, + EPIC, + SUBTASK +} \ No newline at end of file diff --git a/src/model/Epic.java b/src/model/Epic.java index 6634160..726cb44 100644 --- a/src/model/Epic.java +++ b/src/model/Epic.java @@ -1,31 +1,67 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; public class Epic extends Task { private List subtaskIds; + private LocalDateTime endTime; public Epic(String name, String description) { super(name, description, Status.NEW); this.subtaskIds = new ArrayList<>(); + this.duration = Duration.ZERO; } - public List getSubtaskIds() { return new ArrayList<>(subtaskIds); } - public void addSubtaskId(int subtaskId) { if (!subtaskIds.contains(subtaskId)) subtaskIds.add(subtaskId); } - public void removeSubtaskId(int subtaskId) { subtaskIds.remove(Integer.valueOf(subtaskId)); } - public void clearSubtasks() { subtaskIds.clear(); } + public Epic(String name, String description, int id, Status status, Duration duration, + LocalDateTime startTime, LocalDateTime endTime) { + super(name, description, id, status, duration, startTime); + this.subtaskIds = new ArrayList<>(); + this.endTime = endTime; + } + + public List getSubtaskIds() { + return new ArrayList<>(subtaskIds); + } + + public void addSubtaskId(int subtaskId) { + if (!subtaskIds.contains(subtaskId)) { + subtaskIds.add(subtaskId); + } + } + + public void removeSubtaskId(int subtaskId) { + subtaskIds.remove(Integer.valueOf(subtaskId)); + } + + public void clearSubtasks() { + subtaskIds.clear(); + } @Override public void setStatus(Status status) { throw new UnsupportedOperationException("Статус эпика рассчитывается автоматически"); } - public void updateStatus(Status status) { this.status = status; } + public void updateStatus(Status status) { + this.status = status; + } + + @Override + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } @Override public String toString() { - return String.format("Epic{name='%s', description='%s', id=%d, status=%s, subtaskIds=%s}", - name, description, id, status, subtaskIds); + return String.format("Epic{name='%s', description='%s', id=%d, status=%s, " + + "duration=%s, startTime=%s, endTime=%s, subtaskIds=%s}", + name, description, id, status, duration, startTime, endTime, subtaskIds); } } \ No newline at end of file diff --git a/src/model/Subtask.java b/src/model/Subtask.java index d1547d7..dfe9b32 100644 --- a/src/model/Subtask.java +++ b/src/model/Subtask.java @@ -1,24 +1,47 @@ package model; +import java.time.Duration; +import java.time.LocalDateTime; + public class Subtask extends Task { private int epicId; public Subtask(String name, String description, Status status, int epicId) { super(name, description, status); this.epicId = epicId; + this.duration = Duration.ZERO; } public Subtask(String name, String description, int id, Status status, int epicId) { super(name, description, id, status); this.epicId = epicId; + this.duration = Duration.ZERO; + } + + public Subtask(String name, String description, Status status, int epicId, + Duration duration, LocalDateTime startTime) { + super(name, description, status, duration, startTime); + this.epicId = epicId; } - public int getEpicId() { return epicId; } - public void setEpicId(int epicId) { this.epicId = epicId; } + public Subtask(String name, String description, int id, Status status, int epicId, + Duration duration, LocalDateTime startTime) { + super(name, description, id, status, duration, startTime); + this.epicId = epicId; + } + + public int getEpicId() { + return epicId; + } + + public void setEpicId(int epicId) { + this.epicId = epicId; + } @Override public String toString() { - return String.format("Subtask{name='%s', description='%s', id=%d, status=%s, epicId=%d}", - name, description, id, status, epicId); + return String.format("Subtask{name='%s', description='%s', id=%d, status=%s, " + + "duration=%s, startTime=%s, epicId=%d}", + name, description, id, status, duration, startTime, epicId); } } \ No newline at end of file diff --git a/src/model/Task.java b/src/model/Task.java index 1547b36..35689d8 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,14 @@ public class Task { protected String description; protected int id; protected Status status; + protected Duration duration; + protected LocalDateTime startTime; public Task(String name, String description, Status status) { this.name = name; this.description = description; this.status = status; + this.duration = Duration.ZERO; } public Task(String name, String description, int id, Status status) { @@ -19,31 +24,101 @@ public Task(String name, String description, int id, Status status) { this.description = description; this.id = id; this.status = status; + this.duration = Duration.ZERO; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public Status getStatus() { return status; } - public void setStatus(Status status) { this.status = status; } + 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 Task(String name, String description, int id, Status status, Duration duration, LocalDateTime startTime) { + this.name = name; + this.description = description; + this.id = id; + this.status = status; + this.duration = duration; + this.startTime = startTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Status getStatus() { + return status; + } + + 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); + } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Task task = (Task) o; return id == task.id; } @Override - public int hashCode() { return Objects.hash(id); } + public int hashCode() { + return Objects.hash(id); + } @Override public String toString() { - return String.format("Task{name='%s', description='%s', id=%d, status=%s}", - name, description, id, status); + return String.format("Task{name='%s', description='%s', id=%d, status=%s, duration=%s, startTime=%s}", + name, description, id, status, duration, startTime); } } \ No newline at end of file diff --git a/test/TaskManagerTest.java b/test/TaskManagerTest.java new file mode 100644 index 0000000..a5f7ca8 --- /dev/null +++ b/test/TaskManagerTest.java @@ -0,0 +1,313 @@ + +import manager.TaskManager; +import model.Epic; +import model.Status; +import model.Subtask; +import model.Task; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +public abstract class TaskManagerTest { + protected T taskManager; + + @Test + void testIsTasksOverlapping() { + LocalDateTime baseTime = LocalDateTime.now(); + + Task task1 = new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(60), baseTime); + Task task2 = new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusMinutes(30)); + + assertTrue(taskManager.isTasksOverlapping(task1, task2)); + } + + @Test + void testIsTasksNotOverlapping() { + LocalDateTime baseTime = LocalDateTime.now(); + + Task task1 = new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime); + Task task2 = new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusHours(1)); + + assertFalse(taskManager.isTasksOverlapping(task1, task2)); + } + + @Test + void testIsTasksTouchingNotOverlapping() { + LocalDateTime baseTime = LocalDateTime.now(); + + Task task1 = new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime); + Task task2 = new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusMinutes(30)); + + assertFalse(taskManager.isTasksOverlapping(task1, task2)); + } + + @Test + void testHasAnyTimeOverlaps() { + LocalDateTime baseTime = LocalDateTime.now(); + + taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(60), baseTime)); + taskManager.createTask(new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusMinutes(30))); + + assertTrue(taskManager.hasAnyTimeOverlaps()); + } + + @Test + void testHasNoTimeOverlaps() { + LocalDateTime baseTime = LocalDateTime.now(); + + taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime)); + taskManager.createTask(new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusHours(1))); + + assertFalse(taskManager.hasAnyTimeOverlaps()); + } + + @Test + void testGetTaskByIdOptional() { + Task task = taskManager.createTask(new Task("Test", "Description", Status.NEW)); + + Optional result = taskManager.getTaskByIdOptional(task.getId()); + assertTrue(result.isPresent()); + assertEquals(task.getId(), result.get().getId()); + } + + @Test + void testGetTaskByIdOptionalNotFound() { + Optional result = taskManager.getTaskByIdOptional(999); + assertFalse(result.isPresent()); + } + + @Test + void testGetEpicByIdOptional() { + Epic epic = taskManager.createEpic(new Epic("Test", "Description")); + + Optional result = taskManager.getEpicByIdOptional(epic.getId()); + assertTrue(result.isPresent()); + assertEquals(epic.getId(), result.get().getId()); + } + + @Test + void testGetSubtaskByIdOptional() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + Subtask subtask = taskManager.createSubtask(new Subtask("Test", "Description", Status.NEW, epic.getId())); + + Optional result = taskManager.getSubtaskByIdOptional(subtask.getId()); + assertTrue(result.isPresent()); + assertEquals(subtask.getId(), result.get().getId()); + } + + @Test + void getPrioritizedTasks_shouldReturnSortedTasks() { + LocalDateTime now = LocalDateTime.now(); + + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(30), now.plusHours(2))); + Task task2 = taskManager.createTask(new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(45), now.plusHours(1))); + Task task3 = taskManager.createTask(new Task("Task 3", "Description", Status.NEW, + Duration.ofMinutes(15), null)); + + List prioritized = taskManager.getPrioritizedTasks(); + + assertEquals(2, prioritized.size()); + assertEquals(task2.getId(), prioritized.get(0).getId()); + assertEquals(task1.getId(), prioritized.get(1).getId()); + } + + @Test + void testTaskOverlapping() { + LocalDateTime baseTime = LocalDateTime.now(); + + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(60), baseTime)); + + Task overlappingTask = new Task("Overlapping", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusMinutes(30)); + + assertTrue(taskManager.isTaskOverlapping(overlappingTask)); + + Task nonOverlappingTask = new Task("Non-overlapping", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusHours(2)); + + assertFalse(taskManager.isTaskOverlapping(nonOverlappingTask)); + } + + @Test + void testEpicTimingCalculation() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + LocalDateTime start1 = LocalDateTime.now(); + LocalDateTime start2 = start1.plusHours(1); + + Subtask subtask1 = taskManager.createSubtask(new Subtask("Sub 1", "Description", Status.NEW, epic.getId(), + Duration.ofMinutes(30), start1)); + Subtask subtask2 = taskManager.createSubtask(new Subtask("Sub 2", "Description", Status.NEW, epic.getId(), + Duration.ofMinutes(45), start2)); + + Epic retrievedEpic = taskManager.getEpicById(epic.getId()); + + assertEquals(Duration.ofMinutes(75), retrievedEpic.getDuration()); + assertEquals(start1, retrievedEpic.getStartTime()); + assertEquals(start2.plusMinutes(45), retrievedEpic.getEndTime()); + } + + @Test + void testTaskWithNullTimeNotInPrioritized() { + Task task = taskManager.createTask(new Task("Task", "Description", Status.NEW, + Duration.ofMinutes(30), null)); + + List prioritized = taskManager.getPrioritizedTasks(); + + assertTrue(prioritized.isEmpty()); + } + + @Test + void testGetEndTime() { + LocalDateTime startTime = LocalDateTime.now(); + Duration duration = Duration.ofMinutes(90); + Task task = new Task("Task", "Description", Status.NEW, duration, startTime); + + LocalDateTime endTime = task.getEndTime(); + + assertEquals(startTime.plusMinutes(90), endTime); + } + + @Test + void testGetEndTimeWithNullStartTime() { + Task task = new Task("Task", "Description", Status.NEW, Duration.ofMinutes(30), null); + + assertNull(task.getEndTime()); + } + + @Test + void testEpicStatusAllNew() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + taskManager.createSubtask(new Subtask("Sub 1", "Description", Status.NEW, epic.getId())); + taskManager.createSubtask(new Subtask("Sub 2", "Description", Status.NEW, epic.getId())); + + assertEquals(Status.NEW, taskManager.getEpicById(epic.getId()).getStatus()); + } + + @Test + void testEpicStatusAllDone() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask sub1 = taskManager.createSubtask(new Subtask("Sub 1", "Description", Status.NEW, epic.getId())); + Subtask sub2 = taskManager.createSubtask(new Subtask("Sub 2", "Description", Status.NEW, epic.getId())); + + taskManager.updateSubtask(new Subtask("Sub 1", "Description", sub1.getId(), Status.DONE, epic.getId())); + taskManager.updateSubtask(new Subtask("Sub 2", "Description", sub2.getId(), Status.DONE, epic.getId())); + + assertEquals(Status.DONE, taskManager.getEpicById(epic.getId()).getStatus()); + } + + @Test + void testEpicStatusMixedNewAndDone() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask sub1 = taskManager.createSubtask(new Subtask("Sub 1", "Description", Status.NEW, epic.getId())); + Subtask sub2 = taskManager.createSubtask(new Subtask("Sub 2", "Description", Status.NEW, epic.getId())); + + taskManager.updateSubtask(new Subtask("Sub 1", "Description", sub1.getId(), Status.DONE, epic.getId())); + + assertEquals(Status.IN_PROGRESS, taskManager.getEpicById(epic.getId()).getStatus()); + } + + @Test + void testEpicStatusAllInProgress() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); + + Subtask sub1 = taskManager.createSubtask(new Subtask("Sub 1", "Description", Status.NEW, epic.getId())); + Subtask sub2 = taskManager.createSubtask(new Subtask("Sub 2", "Description", Status.NEW, epic.getId())); + + taskManager.updateSubtask(new Subtask("Sub 1", "Description", sub1.getId(), Status.IN_PROGRESS, epic.getId())); + taskManager.updateSubtask(new Subtask("Sub 2", "Description", sub2.getId(), Status.IN_PROGRESS, epic.getId())); + + assertEquals(Status.IN_PROGRESS, taskManager.getEpicById(epic.getId()).getStatus()); + } + + @Test + void testHistoryManagerEmptyHistory() { + assertTrue(taskManager.getHistory().isEmpty()); + } + + @Test + void testHistoryManagerDuplicates() { + Task task = taskManager.createTask(new Task("Task", "Description", Status.NEW)); + + taskManager.getTaskById(task.getId()); + taskManager.getTaskById(task.getId()); + taskManager.getTaskById(task.getId()); + + assertEquals(1, taskManager.getHistory().size()); + } + + @Test + void testHistoryManagerRemoveFromBeginning() { + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW)); + Task task2 = taskManager.createTask(new Task("Task 2", "Description", Status.NEW)); + Task task3 = taskManager.createTask(new Task("Task 3", "Description", Status.NEW)); + + taskManager.getTaskById(task1.getId()); + taskManager.getTaskById(task2.getId()); + taskManager.getTaskById(task3.getId()); + + taskManager.deleteTaskById(task1.getId()); + + List history = taskManager.getHistory(); + assertEquals(2, history.size()); + assertEquals(task2.getId(), history.get(0).getId()); + assertEquals(task3.getId(), history.get(1).getId()); + } + + @Test + void testHistoryManagerRemoveFromMiddle() { + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW)); + Task task2 = taskManager.createTask(new Task("Task 2", "Description", Status.NEW)); + Task task3 = taskManager.createTask(new Task("Task 3", "Description", Status.NEW)); + + taskManager.getTaskById(task1.getId()); + taskManager.getTaskById(task2.getId()); + taskManager.getTaskById(task3.getId()); + + taskManager.deleteTaskById(task2.getId()); + + List history = taskManager.getHistory(); + assertEquals(2, history.size()); + assertEquals(task1.getId(), history.get(0).getId()); + assertEquals(task3.getId(), history.get(1).getId()); + } + + @Test + void testHistoryManagerRemoveFromEnd() { + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW)); + Task task2 = taskManager.createTask(new Task("Task 2", "Description", Status.NEW)); + Task task3 = taskManager.createTask(new Task("Task 3", "Description", Status.NEW)); + + taskManager.getTaskById(task1.getId()); + taskManager.getTaskById(task2.getId()); + taskManager.getTaskById(task3.getId()); + + taskManager.deleteTaskById(task3.getId()); + + List history = taskManager.getHistory(); + assertEquals(2, history.size()); + assertEquals(task1.getId(), history.get(0).getId()); + assertEquals(task2.getId(), history.get(1).getId()); + } +} \ No newline at end of file diff --git a/test/http/HttpTaskServerTest.java b/test/http/HttpTaskServerTest.java new file mode 100644 index 0000000..122ad57 --- /dev/null +++ b/test/http/HttpTaskServerTest.java @@ -0,0 +1,153 @@ +package http; + +import http.HttpTaskServer; +import manager.Managers; +import manager.TaskManager; +import model.Task; +import model.Status; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpTaskServerTest { + private TaskManager manager; + private HttpTaskServer server; + private HttpClient client; + + @BeforeEach + void setUp() { + manager = Managers.getDefault(); + server = new HttpTaskServer(manager); + server.start(); + client = HttpClient.newHttpClient(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + void testCreateTask() throws Exception { + String taskJson = "{\"name\":\"Test Task\",\"description\":\"Test Description\"," + + "\"status\":\"NEW\",\"duration\":30,\"startTime\":\"2023-01-01T10:00:00\"}"; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/tasks")) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(201, response.statusCode()); + assertEquals(1, manager.getAllTasks().size()); + assertEquals("Test Task", manager.getAllTasks().get(0).getName()); + } + + @Test + void testGetTask() throws Exception { + Task task = manager.createTask(new Task("Test", "Desc", Status.NEW)); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/tasks/" + task.getId())) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode()); + assertTrue(response.body().contains("Test")); + } + + @Test + void testGetAllTasks() throws Exception { + manager.createTask(new Task("Task 1", "Desc 1", Status.NEW)); + manager.createTask(new Task("Task 2", "Desc 2", Status.IN_PROGRESS)); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/tasks")) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode()); + assertTrue(response.body().contains("Task 1")); + assertTrue(response.body().contains("Task 2")); + } + + @Test + void testDeleteTask() throws Exception { + Task task = manager.createTask(new Task("Test", "Desc", Status.NEW)); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/tasks/" + task.getId())) + .DELETE() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(201, response.statusCode()); + assertEquals(0, manager.getAllTasks().size()); + } + + @Test + void testGetHistory() throws Exception { + Task task = manager.createTask(new Task("Test", "Desc", Status.NEW)); + manager.getTaskById(task.getId()); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/history")) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode()); + assertTrue(response.body().contains("Test")); + } + + @Test + void testGetPrioritized() throws Exception { + Task task1 = manager.createTask(new Task("Task 1", "Desc 1", Status.NEW, + Duration.ofMinutes(30), LocalDateTime.now())); + Task task2 = manager.createTask(new Task("Task 2", "Desc 2", Status.NEW, + Duration.ofMinutes(45), LocalDateTime.now().plusHours(1))); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/prioritized")) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode()); + assertTrue(response.body().contains("Task 1")); + assertTrue(response.body().contains("Task 2")); + + // Используем переменные, чтобы убрать предупреждения + assertNotNull(task1); + assertNotNull(task2); + } + + @Test + void testTaskNotFound() throws Exception { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/tasks/999")) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(404, response.statusCode()); + } +} \ No newline at end of file diff --git a/test/manager/ExceptionHandlingTest.java b/test/manager/ExceptionHandlingTest.java new file mode 100644 index 0000000..445d384 --- /dev/null +++ b/test/manager/ExceptionHandlingTest.java @@ -0,0 +1,35 @@ +package manager; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +class ExceptionHandlingTest { + + @TempDir + Path tempDir; + + @Test + void managerSaveException_shouldBeThrownOnSaveError() { + File invalidFile = new File("/invalid/path/tasks.csv"); + FileBackedTaskManager manager = new FileBackedTaskManager(invalidFile); + + assertThrows(ManagerSaveException.class, () -> { + manager.save(); + }); + } + + @Test + void noException_withValidFile() { + File validFile = tempDir.resolve("valid.csv").toFile(); + FileBackedTaskManager manager = new FileBackedTaskManager(validFile); + + assertDoesNotThrow(() -> { + manager.save(); + }); + } +} \ No newline at end of file diff --git a/test/manager/FileBackedTest.java b/test/manager/FileBackedTest.java new file mode 100644 index 0000000..a9a682f --- /dev/null +++ b/test/manager/FileBackedTest.java @@ -0,0 +1,159 @@ +package manager; + +import manager.FileBackedTaskManager; +import manager.ManagerSaveException; +import model.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class FileBackedTaskManagerTest { + + @TempDir + Path tempDir; + + @Test + void testSaveAndLoadEmptyFile() { + File file = tempDir.resolve("test.csv").toFile(); + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + // Сохраняем пустой менеджер + manager.save(); + + // Загружаем из файла + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + + assertTrue(loadedManager.getAllTasks().isEmpty()); + assertTrue(loadedManager.getAllEpics().isEmpty()); + assertTrue(loadedManager.getAllSubtasks().isEmpty()); + } + + @Test + void testSaveAndLoadMultipleTasks() { + File file = tempDir.resolve("test.csv").toFile(); + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + // Создаем задачи + Task task1 = manager.createTask(new Task("Task 1", "Description 1", Status.NEW)); + manager.createTask(new Task("Task 2", "Description 2", Status.DONE)); + + Epic epic = manager.createEpic(new Epic("Epic", "Epic description")); + manager.createSubtask(new Subtask("Subtask", "Sub description", Status.IN_PROGRESS, epic.getId())); + + // Загружаем из файла + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + + assertEquals(2, loadedManager.getAllTasks().size()); + assertEquals(1, loadedManager.getAllEpics().size()); + assertEquals(1, loadedManager.getAllSubtasks().size()); + + Task loadedTask = loadedManager.getTaskById(task1.getId()); + assertNotNull(loadedTask); + assertEquals("Task 1", loadedTask.getName()); + assertEquals(Status.NEW, loadedTask.getStatus()); + } + + @Test + void testSaveException() { + File file = new File("/invalid/path/tasks.csv"); + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + assertThrows(ManagerSaveException.class, () -> { + manager.createTask(new Task("Test", "Test", Status.NEW)); + }); + } + + @Test + void testLoadFromNonExistentFile() { + File file = new File("nonexistent.csv"); + FileBackedTaskManager manager = FileBackedTaskManager.loadFromFile(file); + + assertNotNull(manager); + assertTrue(manager.getAllTasks().isEmpty()); + } + + // Новые тесты для спринта 8 + @Test + void testSaveAndLoadWithTimeFields() { + File file = tempDir.resolve("test_time.csv").toFile(); + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + LocalDateTime startTime = LocalDateTime.now(); + Duration duration = Duration.ofMinutes(45); + + // Создаем задачи с временными полями + Task task = manager.createTask(new Task("Task", "Description", Status.NEW, duration, startTime)); + Epic epic = manager.createEpic(new Epic("Epic", "Description")); + Subtask subtask = manager.createSubtask(new Subtask("Subtask", "Description", Status.NEW, + epic.getId(), Duration.ofMinutes(30), startTime.plusHours(1))); + + // Загружаем из файла + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + + Task loadedTask = loadedManager.getTaskById(task.getId()); + assertNotNull(loadedTask); + assertEquals(duration, loadedTask.getDuration()); + assertEquals(startTime, loadedTask.getStartTime()); + + Subtask loadedSubtask = loadedManager.getSubtaskById(subtask.getId()); + assertNotNull(loadedSubtask); + assertEquals(Duration.ofMinutes(30), loadedSubtask.getDuration()); + assertEquals(startTime.plusHours(1), loadedSubtask.getStartTime()); + + Epic loadedEpic = loadedManager.getEpicById(epic.getId()); + assertNotNull(loadedEpic); + assertEquals(Duration.ofMinutes(30), loadedEpic.getDuration()); + assertEquals(startTime.plusHours(1), loadedEpic.getStartTime()); + } + + @Test + void testPrioritizedTasksAfterLoad() { + File file = tempDir.resolve("test_prioritized.csv").toFile(); + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + LocalDateTime now = LocalDateTime.now(); + + manager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(30), now.plusHours(2))); + manager.createTask(new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(45), now.plusHours(1))); + + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + + List prioritized = loadedManager.getPrioritizedTasks(); + assertEquals(2, prioritized.size()); + assertEquals(now.plusHours(1), prioritized.get(0).getStartTime()); + assertEquals(now.plusHours(2), prioritized.get(1).getStartTime()); + } + + @Test + void testSaveAndLoadEpicWithTimeCalculation() { + File file = tempDir.resolve("test_epic_time.csv").toFile(); + FileBackedTaskManager manager = new FileBackedTaskManager(file); + + Epic epic = manager.createEpic(new Epic("Epic", "Description")); + + LocalDateTime start1 = LocalDateTime.of(2023, 1, 1, 10, 0); + LocalDateTime start2 = LocalDateTime.of(2023, 1, 1, 11, 0); + + manager.createSubtask(new Subtask("Sub 1", "Description", Status.NEW, epic.getId(), + Duration.ofMinutes(30), start1)); + manager.createSubtask(new Subtask("Sub 2", "Description", Status.NEW, epic.getId(), + Duration.ofMinutes(45), start2)); + + FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file); + + Epic loadedEpic = loadedManager.getEpicById(epic.getId()); + assertNotNull(loadedEpic); + assertEquals(Duration.ofMinutes(75), loadedEpic.getDuration()); + assertEquals(start1, loadedEpic.getStartTime()); + assertEquals(start2.plusMinutes(45), loadedEpic.getEndTime()); + } +} \ No newline at end of file diff --git a/test/manager/HistoryDataPreservationTest.java b/test/manager/HistoryDataPreservationTest.java index b041735..32fab8d 100644 --- a/test/manager/HistoryDataPreservationTest.java +++ b/test/manager/HistoryDataPreservationTest.java @@ -15,39 +15,30 @@ void setUp() { } @Test - void historyPreservesTaskData() { - // Создаем задачу + void historyKeepsData() { Task originalTask = new Task("Original", "Original Description", 1, Status.NEW); - // Добавляем в историю historyManager.add(originalTask); - // Изменяем оригинальную задачу originalTask.setName("Modified"); originalTask.setDescription("Modified Description"); originalTask.setStatus(Status.DONE); - // Получаем задачу из истории Task historicalTask = historyManager.getHistory().get(0); - // Проверяем, что данные в истории сохранились оригинальными - assertEquals("Original", historicalTask.getName(), "История должна сохранить оригинальное имя"); - assertEquals("Original Description", historicalTask.getDescription(), - "История должна сохранить оригинальное описание"); - assertEquals(Status.NEW, historicalTask.getStatus(), "История должна сохранить оригинальный статус"); - assertEquals(1, historicalTask.getId(), "История должна сохранить оригинальный ID"); + assertEquals("Original", historicalTask.getName()); + assertEquals("Original Description", historicalTask.getDescription()); + assertEquals(Status.NEW, historicalTask.getStatus()); + assertEquals(1, historicalTask.getId()); } @Test - void historyContainsIndependentCopies() { + void historyHasCopies() { Task task = new Task("Test", "Description", 1, Status.NEW); historyManager.add(task); - // Изменяем оригинальную задачу task.setName("Changed"); - // Задача в истории не должна измениться - assertNotEquals(task.getName(), historyManager.getHistory().get(0).getName(), - "Задача в истории не должна зависеть от изменений оригинала"); + assertNotEquals(task.getName(), historyManager.getHistory().get(0).getName()); } } \ No newline at end of file diff --git a/test/manager/InMemoryTaskManagerTest.java b/test/manager/InMemoryTaskManagerTest.java new file mode 100644 index 0000000..9d06c71 --- /dev/null +++ b/test/manager/InMemoryTaskManagerTest.java @@ -0,0 +1,461 @@ +package manager; + +import model.Task; +import model.Epic; +import model.Subtask; +import model.Status; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.time.Duration; +import java.time.LocalDateTime; + +class InMemoryTaskManagerTest { + private TaskManager taskManager; + + @BeforeEach + void setUp() { + taskManager = new InMemoryTaskManager(); + } + + @Test + void testCreateTask() { + Task task = new Task("Test Task", "Test Description", Status.NEW); + Task createdTask = taskManager.createTask(task); + + assertNotNull(createdTask); + assertNotNull(createdTask.getId()); + assertEquals("Test Task", createdTask.getName()); + assertEquals("Test Description", createdTask.getDescription()); + assertEquals(Status.NEW, createdTask.getStatus()); + + Task retrievedTask = taskManager.getTaskById(createdTask.getId()); + assertEquals(createdTask, retrievedTask); + } + + @Test + void testCreateEpic() { + Epic epic = new Epic("Test Epic", "Test Epic Description"); + Epic createdEpic = taskManager.createEpic(epic); + + assertNotNull(createdEpic); + assertNotNull(createdEpic.getId()); + assertEquals("Test Epic", createdEpic.getName()); + assertEquals("Test Epic Description", createdEpic.getDescription()); + assertEquals(Status.NEW, createdEpic.getStatus()); + assertTrue(createdEpic.getSubtaskIds().isEmpty()); + + Epic retrievedEpic = taskManager.getEpicById(createdEpic.getId()); + assertEquals(createdEpic, retrievedEpic); + } + + @Test + void testCreateSubtask() { + Epic epic = taskManager.createEpic(new Epic("Test Epic", "Description")); + Subtask subtask = new Subtask("Test Subtask", "Test Description", Status.NEW, epic.getId()); + Subtask createdSubtask = taskManager.createSubtask(subtask); + + assertNotNull(createdSubtask); + assertNotNull(createdSubtask.getId()); + assertEquals("Test Subtask", createdSubtask.getName()); + assertEquals("Test Description", createdSubtask.getDescription()); + assertEquals(Status.NEW, createdSubtask.getStatus()); + assertEquals(epic.getId(), createdSubtask.getEpicId()); + + Subtask retrievedSubtask = taskManager.getSubtaskById(createdSubtask.getId()); + assertEquals(createdSubtask, retrievedSubtask); + + List epicSubtasks = taskManager.getSubtasksByEpicId(epic.getId()); + assertEquals(1, epicSubtasks.size()); + assertEquals(createdSubtask.getId(), epicSubtasks.get(0).getId()); + } + + @Test + void testUpdateTask() { + Task task = taskManager.createTask(new Task("Original", "Desc", Status.NEW)); + Task updatedTask = new Task("Updated", "Updated Desc", task.getId(), Status.IN_PROGRESS); + + taskManager.updateTask(updatedTask); + + Task retrievedTask = taskManager.getTaskById(task.getId()); + assertEquals("Updated", retrievedTask.getName()); + assertEquals("Updated Desc", retrievedTask.getDescription()); + assertEquals(Status.IN_PROGRESS, retrievedTask.getStatus()); + } + + @Test + void testUpdateEpic() { + Epic epic = taskManager.createEpic(new Epic("Original", "Desc")); + Epic updatedEpic = new Epic("Updated", "Updated Desc"); + updatedEpic.setId(epic.getId()); + + taskManager.updateEpic(updatedEpic); + + Epic retrievedEpic = taskManager.getEpicById(epic.getId()); + assertEquals("Updated", retrievedEpic.getName()); + assertEquals("Updated Desc", retrievedEpic.getDescription()); + } + + @Test + void testUpdateSubtask() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Desc")); + Subtask subtask = taskManager.createSubtask(new Subtask("Original", "Desc", Status.NEW, epic.getId())); + Subtask updatedSubtask = new Subtask("Updated", "Updated Desc", subtask.getId(), Status.DONE, epic.getId()); + + taskManager.updateSubtask(updatedSubtask); + + Subtask retrievedSubtask = taskManager.getSubtaskById(subtask.getId()); + assertEquals("Updated", retrievedSubtask.getName()); + assertEquals("Updated Desc", retrievedSubtask.getDescription()); + assertEquals(Status.DONE, retrievedSubtask.getStatus()); + } + + @Test + void testDeleteTask() { + Task task = taskManager.createTask(new Task("Test", "Desc", Status.NEW)); + int taskId = task.getId(); + + taskManager.deleteTaskById(taskId); + + assertNull(taskManager.getTaskById(taskId)); + assertTrue(taskManager.getAllTasks().isEmpty()); + } + + @Test + void testDeleteEpic() { + Epic epic = taskManager.createEpic(new Epic("Test", "Desc")); + int epicId = epic.getId(); + + taskManager.deleteEpicById(epicId); + + assertNull(taskManager.getEpicById(epicId)); + assertTrue(taskManager.getAllEpics().isEmpty()); + } + + @Test + void testDeleteSubtask() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Desc")); + Subtask subtask = taskManager.createSubtask(new Subtask("Test", "Desc", Status.NEW, epic.getId())); + int subtaskId = subtask.getId(); + + taskManager.deleteSubtaskById(subtaskId); + + assertNull(taskManager.getSubtaskById(subtaskId)); + assertTrue(taskManager.getAllSubtasks().isEmpty()); + assertTrue(epic.getSubtaskIds().isEmpty()); + } + + @Test + void testDeleteAllTasks() { + taskManager.createTask(new Task("Task 1", "Desc", Status.NEW)); + taskManager.createTask(new Task("Task 2", "Desc", Status.IN_PROGRESS)); + + taskManager.deleteAllTasks(); + + assertTrue(taskManager.getAllTasks().isEmpty()); + } + + @Test + void testDeleteAllEpics() { + taskManager.createEpic(new Epic("Epic 1", "Desc")); + taskManager.createEpic(new Epic("Epic 2", "Desc")); + + taskManager.deleteAllEpics(); + + assertTrue(taskManager.getAllEpics().isEmpty()); + assertTrue(taskManager.getAllSubtasks().isEmpty()); + } + + @Test + void testDeleteAllSubtasks() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Desc")); + taskManager.createSubtask(new Subtask("Sub 1", "Desc", Status.NEW, epic.getId())); + taskManager.createSubtask(new Subtask("Sub 2", "Desc", Status.DONE, epic.getId())); + + taskManager.deleteAllSubtasks(); + + assertTrue(taskManager.getAllSubtasks().isEmpty()); + assertTrue(epic.getSubtaskIds().isEmpty()); + } + + @Test + void testGetAllTasks() { + Task task1 = taskManager.createTask(new Task("Task 1", "Desc", Status.NEW)); + Task task2 = taskManager.createTask(new Task("Task 2", "Desc", Status.IN_PROGRESS)); + + List allTasks = taskManager.getAllTasks(); + + assertEquals(2, allTasks.size()); + assertTrue(allTasks.contains(task1)); + assertTrue(allTasks.contains(task2)); + } + + @Test + void testGetAllEpics() { + Epic epic1 = taskManager.createEpic(new Epic("Epic 1", "Desc")); + Epic epic2 = taskManager.createEpic(new Epic("Epic 2", "Desc")); + + List allEpics = taskManager.getAllEpics(); + + assertEquals(2, allEpics.size()); + assertTrue(allEpics.contains(epic1)); + assertTrue(allEpics.contains(epic2)); + } + + @Test + void testGetAllSubtasks() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Desc")); + Subtask subtask1 = taskManager.createSubtask(new Subtask("Sub 1", "Desc", Status.NEW, epic.getId())); + Subtask subtask2 = taskManager.createSubtask(new Subtask("Sub 2", "Desc", Status.DONE, epic.getId())); + + List allSubtasks = taskManager.getAllSubtasks(); + + assertEquals(2, allSubtasks.size()); + assertTrue(allSubtasks.contains(subtask1)); + assertTrue(allSubtasks.contains(subtask2)); + } + + @Test + void testEpicStatus() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Desc")); + + assertEquals(Status.NEW, epic.getStatus()); + + Subtask sub1 = taskManager.createSubtask(new Subtask("Sub 1", "Desc", Status.NEW, epic.getId())); + Epic updatedEpic1 = taskManager.getEpicById(epic.getId()); + assertEquals(Status.NEW, updatedEpic1.getStatus()); + + taskManager.updateSubtask(new Subtask("Sub 1", "Desc", sub1.getId(), Status.DONE, epic.getId())); + Epic updatedEpic2 = taskManager.getEpicById(epic.getId()); + assertEquals(Status.DONE, updatedEpic2.getStatus()); + + Subtask sub2 = taskManager.createSubtask(new Subtask("Sub 2", "Desc", Status.NEW, epic.getId())); + Epic updatedEpic3 = taskManager.getEpicById(epic.getId()); + assertEquals(Status.IN_PROGRESS, updatedEpic3.getStatus()); + + taskManager.updateSubtask(new Subtask("Sub 1", "Desc", sub1.getId(), Status.IN_PROGRESS, epic.getId())); + taskManager.updateSubtask(new Subtask("Sub 2", "Desc", sub2.getId(), Status.IN_PROGRESS, epic.getId())); + Epic updatedEpic4 = taskManager.getEpicById(epic.getId()); + assertEquals(Status.IN_PROGRESS, updatedEpic4.getStatus()); + } + + @Test + void testHistoryNoDuplicates() { + Task task = taskManager.createTask(new Task("Test", "Description", Status.NEW)); + + taskManager.getTaskById(task.getId()); + taskManager.getTaskById(task.getId()); + taskManager.getTaskById(task.getId()); + + assertEquals(1, taskManager.getHistory().size()); + } + + @Test + void testTaskImmutability() { + Task task = taskManager.createTask(new Task("Test", "Description", Status.NEW)); + int originalId = task.getId(); + + task.setId(999); + task.setName("Modified"); + + Task managedTask = taskManager.getTaskById(originalId); + + assertEquals(originalId, managedTask.getId()); + assertEquals("Test", managedTask.getName()); + } + + @Test + void testEpicImmutability() { + Epic epic = taskManager.createEpic(new Epic("Test Epic", "Description")); + int originalId = epic.getId(); + + epic.setId(999); + epic.setName("Modified"); + + Epic managedEpic = taskManager.getEpicById(originalId); + + assertEquals(originalId, managedEpic.getId()); + assertEquals("Test Epic", managedEpic.getName()); + } + + @Test + void testHistoryAfterDelete() { + Task task = taskManager.createTask(new Task("Test", "Description", Status.NEW)); + + taskManager.getTaskById(task.getId()); + assertEquals(1, taskManager.getHistory().size()); + + taskManager.deleteTaskById(task.getId()); + + assertTrue(taskManager.getHistory().isEmpty()); + } + + @Test + void testDataIntegrity() { + Epic epic = taskManager.createEpic(new Epic("Test Epic", "Description")); + Subtask subtask = taskManager.createSubtask(new Subtask("Subtask", "Desc", Status.NEW, epic.getId())); + + int subtaskId = subtask.getId(); + int epicId = epic.getId(); + + taskManager.deleteSubtaskById(subtaskId); + + Epic updatedEpic = taskManager.getEpicById(epicId); + assertFalse(updatedEpic.getSubtaskIds().contains(subtaskId)); + + assertNull(taskManager.getSubtaskById(subtaskId)); + } + + @Test + void testCreateNullTask() { + Task result = taskManager.createTask(null); + assertNull(result); + } + + @Test + void testCreateNullEpic() { + Epic result = taskManager.createEpic(null); + assertNull(result); + } + + @Test + void testCreateNullSubtask() { + Subtask result = taskManager.createSubtask(null); + assertNull(result); + } + + @Test + void testCreateSubtaskBadEpic() { + Subtask subtask = new Subtask("Test", "Desc", Status.NEW, 999); + Subtask result = taskManager.createSubtask(subtask); + assertNull(result); + } + + @Test + void testUpdateBadTask() { + Task task = new Task("Test", "Desc", 999, Status.NEW); + taskManager.updateTask(task); + + assertNull(taskManager.getTaskById(999)); + } + + @Test + void testUpdateBadEpic() { + Epic epic = new Epic("Test", "Desc"); + epic.setId(999); + taskManager.updateEpic(epic); + + assertNull(taskManager.getEpicById(999)); + } + + @Test + void testUpdateBadSubtask() { + Epic epic = taskManager.createEpic(new Epic("Epic", "Desc")); + Subtask subtask = new Subtask("Test", "Desc", 999, Status.NEW, epic.getId()); + taskManager.updateSubtask(subtask); + + assertNull(taskManager.getSubtaskById(999)); + } + + @Test + void testDeleteBadTask() { + taskManager.deleteTaskById(999); + } + + @Test + void testDeleteBadEpic() { + taskManager.deleteEpicById(999); + } + + @Test + void testDeleteBadSubtask() { + taskManager.deleteSubtaskById(999); + } + + @Test + void testGetBadTask() { + Task task = taskManager.getTaskById(999); + assertNull(task); + } + + @Test + void testGetBadEpic() { + Epic epic = taskManager.getEpicById(999); + assertNull(epic); + } + + @Test + void testGetBadSubtask() { + Subtask subtask = taskManager.getSubtaskById(999); + assertNull(subtask); + } + + @Test + void testGetSubtasksBadEpic() { + List subtasks = taskManager.getSubtasksByEpicId(999); + assertTrue(subtasks.isEmpty()); + } + + // Новые тесты для спринта 8 + @Test + void testCreateTaskWithTime() { + LocalDateTime startTime = LocalDateTime.now(); + Duration duration = Duration.ofMinutes(30); + Task task = new Task("Test Task", "Test Description", Status.NEW, duration, startTime); + Task createdTask = taskManager.createTask(task); + + assertNotNull(createdTask); + assertEquals(duration, createdTask.getDuration()); + assertEquals(startTime, createdTask.getStartTime()); + assertEquals(startTime.plus(duration), createdTask.getEndTime()); + } + + @Test + void testGetPrioritizedTasks() { + LocalDateTime now = LocalDateTime.now(); + + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(30), now.plusHours(2))); + Task task2 = taskManager.createTask(new Task("Task 2", "Description", Status.NEW, + Duration.ofMinutes(45), now.plusHours(1))); + Task task3 = taskManager.createTask(new Task("Task 3", "Description", Status.NEW, + Duration.ofMinutes(15), null)); + + List prioritized = taskManager.getPrioritizedTasks(); + + assertEquals(2, prioritized.size()); + assertEquals(task2.getId(), prioritized.get(0).getId()); + assertEquals(task1.getId(), prioritized.get(1).getId()); + } + + @Test + void testTaskOverlapping() { + LocalDateTime baseTime = LocalDateTime.now(); + + Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(60), baseTime)); + + Task overlappingTask = new Task("Overlapping", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusMinutes(30)); + + assertTrue(taskManager.isTaskOverlapping(overlappingTask)); + } + + @Test + void testCreateOverlappingTaskShouldFail() { + LocalDateTime baseTime = LocalDateTime.now(); + + taskManager.createTask(new Task("Task 1", "Description", Status.NEW, + Duration.ofMinutes(60), baseTime)); + + Task overlappingTask = new Task("Overlapping", "Description", Status.NEW, + Duration.ofMinutes(30), baseTime.plusMinutes(30)); + + Task result = taskManager.createTask(overlappingTask); + + assertNull(result); + assertEquals(1, taskManager.getAllTasks().size()); + } +} \ No newline at end of file diff --git a/test/manager/TaskInheritanceTest.java b/test/manager/TaskInheritanceTest.java deleted file mode 100644 index b723370..0000000 --- a/test/manager/TaskInheritanceTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package manager; - -import model.Epic; -import model.Status; -import model.Subtask; -import model.Task; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class TaskInheritanceTest { - - @Test - void taskAndSubtaskWithSameIdShouldNotBeEqual() { - Task task = new Task("Task", "Description", 1, Status.NEW); - Subtask subtask = new Subtask("Subtask", "Description", 1, Status.NEW, 2); - - assertNotEquals(task, subtask, "Task и Subtask с одинаковым ID не должны быть равны"); - } - - @Test - void subtasksWithSameIdShouldBeEqual() { - Subtask subtask1 = new Subtask("Subtask 1", "Description 1", 1, Status.NEW, 10); - Subtask subtask2 = new Subtask("Subtask 2", "Description 2", 1, Status.DONE, 20); - - assertEquals(subtask1, subtask2, "Subtask с одинаковым ID должны быть равны"); - } - - @Test - void epicsWithSameIdShouldBeEqual() { - Epic epic1 = new Epic("Epic 1", "Description 1"); - epic1.setId(1); - Epic epic2 = new Epic("Epic 2", "Description 2"); - epic2.setId(1); - - assertEquals(epic1, epic2, "Epic с одинаковым ID должны быть равны"); - } -} \ No newline at end of file diff --git a/test/manager/TaskManagerFunctionalityTest.java b/test/manager/TaskManagerFunctionalityTest.java index 36e024b..60e4e49 100644 --- a/test/manager/TaskManagerFunctionalityTest.java +++ b/test/manager/TaskManagerFunctionalityTest.java @@ -18,24 +18,20 @@ void setUp() { @Test void canAddAndFindAllTaskTypes() { - // Создаем задачи всех типов Task task = taskManager.createTask(new Task("Task", "Description", Status.NEW)); Epic epic = taskManager.createEpic(new Epic("Epic", "Description")); Subtask subtask = taskManager.createSubtask(new Subtask("Subtask", "Description", Status.NEW, epic.getId())); - // Проверяем, что все создались - assertNotNull(task, "Задача должна быть создана"); - assertNotNull(epic, "Epic должен быть создан"); - assertNotNull(subtask, "Subtask должен быть создан"); + assertNotNull(task); + assertNotNull(epic); + assertNotNull(subtask); - // Проверяем поиск по ID - assertNotNull(taskManager.getTaskById(task.getId()), "Должна найтись задача по ID"); - assertNotNull(taskManager.getEpicById(epic.getId()), "Должен найтись epic по ID"); - assertNotNull(taskManager.getSubtaskById(subtask.getId()), "Должен найтись subtask по ID"); + assertNotNull(taskManager.getTaskById(task.getId())); + assertNotNull(taskManager.getEpicById(epic.getId())); + assertNotNull(taskManager.getSubtaskById(subtask.getId())); - // Проверяем получение всех задач - assertEquals(1, taskManager.getAllTasks().size(), "Должна быть 1 задача"); - assertEquals(1, taskManager.getAllEpics().size(), "Должен быть 1 epic"); - assertEquals(1, taskManager.getAllSubtasks().size(), "Должен быть 1 subtask"); + assertEquals(1, taskManager.getAllTasks().size()); + assertEquals(1, taskManager.getAllEpics().size()); + assertEquals(1, taskManager.getAllSubtasks().size()); } } \ No newline at end of file diff --git a/test/model/EpicIntegrationTest.java b/test/model/EpicIntegrationTest.java index 772bb13..dfeb69f 100644 --- a/test/model/EpicIntegrationTest.java +++ b/test/model/EpicIntegrationTest.java @@ -18,10 +18,10 @@ void setUp() { void epicCannotBeAddedAsItsOwnSubtask() { Epic epic = taskManager.createEpic(new Epic("Test Epic", "Description")); - // Пытаемся создать подзадачу с epicId равным ID самого эпика - Subtask invalidSubtask = new Subtask("Invalid", "Description", Status.NEW, epic.getId()); + // Пытаемся создать подзадачу с epicId равным ID самого эпика и id равным epicId + Subtask invalidSubtask = new Subtask("Invalid", "Description", epic.getId(), Status.NEW, epic.getId()); - // Должен вернуть null, так как epic не может быть своим же эпиком + // Должен вернуть null, так как epic не может быть своей же подзадачей assertNull(taskManager.createSubtask(invalidSubtask), "Epic не должен быть добавлен в качестве своей же подзадачи"); } diff --git a/test/model/InMemoryHistoryManagerTest.java b/test/model/InMemoryHistoryManagerTest.java new file mode 100644 index 0000000..430cfcb --- /dev/null +++ b/test/model/InMemoryHistoryManagerTest.java @@ -0,0 +1,153 @@ +package manager; + +import model.Task; +import model.Status; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +class InMemoryHistoryManagerTest { + private HistoryManager historyManager; + + @BeforeEach + void setUp() { + historyManager = new InMemoryHistoryManager(); + } + + @Test + void testAddAndGetHistory() { + Task task = new Task("Test", "Description", Status.NEW); + task.setId(1); + + historyManager.add(task); + List history = historyManager.getHistory(); + + assertEquals(1, history.size()); + assertEquals(task, history.get(0)); + } + + @Test + void testRemoveDuplicates() { + Task task = new Task("Test", "Description", Status.NEW); + task.setId(1); + + historyManager.add(task); + historyManager.add(task); // Дубликат + historyManager.add(task); // Еще один дубликат + + List history = historyManager.getHistory(); + assertEquals(1, history.size()); + } + + @Test + void testRemoveFromHistory() { + Task task1 = new Task("Test1", "Description1", Status.NEW); + Task task2 = new Task("Test2", "Description2", Status.NEW); + task1.setId(1); + task2.setId(2); + + historyManager.add(task1); + historyManager.add(task2); + + historyManager.remove(1); + List history = historyManager.getHistory(); + + assertEquals(1, history.size()); + assertEquals(task2, history.get(0)); + } + + @Test + void testHistoryOrderPreservation() { + Task task1 = new Task("Test1", "Description1", Status.NEW); + Task task2 = new Task("Test2", "Description2", Status.NEW); + Task task3 = new Task("Test3", "Description3", Status.NEW); + task1.setId(1); + task2.setId(2); + task3.setId(3); + + historyManager.add(task1); + historyManager.add(task2); + historyManager.add(task3); + + List history = historyManager.getHistory(); + + assertEquals(3, history.size()); + assertEquals(task1, history.get(0)); + assertEquals(task2, history.get(1)); + assertEquals(task3, history.get(2)); + } + + @Test + void testRemoveNonExistentTask() { + // Удаление несуществующей задачи не должно вызывать исключений + assertDoesNotThrow(() -> historyManager.remove(999)); + } + + @Test + void testEmptyHistory() { + assertTrue(historyManager.getHistory().isEmpty()); + } + + // Новые тесты для спринта 8 + @Test + void testRemoveFromBeginning() { + Task task1 = createTask(1, "Task 1"); + Task task2 = createTask(2, "Task 2"); + Task task3 = createTask(3, "Task 3"); + + historyManager.add(task1); + historyManager.add(task2); + historyManager.add(task3); + + historyManager.remove(1); + + List history = historyManager.getHistory(); + assertEquals(2, history.size()); + assertEquals(task2.getId(), history.get(0).getId()); + assertEquals(task3.getId(), history.get(1).getId()); + } + + @Test + void testRemoveFromMiddle() { + Task task1 = createTask(1, "Task 1"); + Task task2 = createTask(2, "Task 2"); + Task task3 = createTask(3, "Task 3"); + + historyManager.add(task1); + historyManager.add(task2); + historyManager.add(task3); + + historyManager.remove(2); + + List history = historyManager.getHistory(); + assertEquals(2, history.size()); + assertEquals(task1.getId(), history.get(0).getId()); + assertEquals(task3.getId(), history.get(1).getId()); + } + + @Test + void testRemoveFromEnd() { + Task task1 = createTask(1, "Task 1"); + Task task2 = createTask(2, "Task 2"); + Task task3 = createTask(3, "Task 3"); + + historyManager.add(task1); + historyManager.add(task2); + historyManager.add(task3); + + historyManager.remove(3); + + List history = historyManager.getHistory(); + assertEquals(2, history.size()); + assertEquals(task1.getId(), history.get(0).getId()); + assertEquals(task2.getId(), history.get(1).getId()); + } + + private Task createTask(int id, String name) { + Task task = new Task(name, "Description", Status.NEW); + task.setId(id); + return task; + } +} \ No newline at end of file diff --git a/test/model/SubtaskIntegrationTest.java b/test/model/SubtaskIntegrationTest.java index 084c298..aff2157 100644 --- a/test/model/SubtaskIntegrationTest.java +++ b/test/model/SubtaskIntegrationTest.java @@ -19,12 +19,11 @@ void subtaskCannotBeItsOwnEpic() { Epic epic = taskManager.createEpic(new Epic("Test Epic", "Description")); Subtask subtask = taskManager.createSubtask(new Subtask("Subtask", "Description", Status.NEW, epic.getId())); - // Пытаемся изменить epicId подзадачи на её собственный ID - subtask.setEpicId(subtask.getId()); - taskManager.updateSubtask(subtask); + // Создаем подзадачу, которая пытается быть своим же эпиком + Subtask invalidSubtask = new Subtask("Invalid", "Description", subtask.getId(), Status.NEW, subtask.getId()); - // Epic ID не должен измениться на собственный ID подзадачи - assertNotEquals(subtask.getId(), subtask.getEpicId(), - "Subtask не должен быть своим же эпиком"); + // Попытка создания должна вернуть null + Subtask result = taskManager.createSubtask(invalidSubtask); + assertNull(result, "Subtask не должен быть своим же эпиком"); } } \ No newline at end of file diff --git a/test/model/TaskIdConflictTest.java b/test/model/TaskIdConflictTest.java index 222e673..2859284 100644 --- a/test/model/TaskIdConflictTest.java +++ b/test/model/TaskIdConflictTest.java @@ -1,7 +1,7 @@ package model; -import model.Task; -import model.Status; +import manager.InMemoryTaskManager; +import manager.TaskManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -15,28 +15,23 @@ void setUp() { } @Test - void noConflictBetweenGeneratedAndManualIds() { - // Создаем задачу с автоматически сгенерированным ID + void testNoIdConflict() { Task autoTask = taskManager.createTask(new Task("Auto", "Description", Status.NEW)); - // Создаем задачу вручную с таким же ID Task manualTask = new Task("Manual", "Description", autoTask.getId(), Status.NEW); - // Пытаемся обновить задачу с существующим ID taskManager.updateTask(manualTask); - // Проверяем, что задача обновилась, а не создалась новая - assertEquals(1, taskManager.getAllTasks().size(), "Должна остаться одна задача"); - assertEquals("Manual", taskManager.getTaskById(autoTask.getId()).getName(), - "Задача должна быть обновлена, а не создана новая"); + assertEquals(1, taskManager.getAllTasks().size()); + assertEquals("Manual", taskManager.getTaskById(autoTask.getId()).getName()); } @Test - void tasksWithDifferentIdsCoexist() { + void testDifferentIds() { Task task1 = taskManager.createTask(new Task("Task 1", "Description", Status.NEW)); Task task2 = taskManager.createTask(new Task("Task 2", "Description", Status.NEW)); - assertNotEquals(task1.getId(), task2.getId(), "Задачи должны иметь разные ID"); - assertEquals(2, taskManager.getAllTasks().size(), "Обе задачи должны существовать"); + assertNotEquals(task1.getId(), task2.getId()); + assertEquals(2, taskManager.getAllTasks().size()); } } \ No newline at end of file diff --git a/test/model/TaskImmutabilityTest.java b/test/model/TaskImmutabilityTest.java deleted file mode 100644 index ff8b490..0000000 --- a/test/model/TaskImmutabilityTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package model; - -import model.Task; -import model.Status; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class TaskImmutabilityTest { - private TaskManager taskManager; - - @BeforeEach - void setUp() { - taskManager = new InMemoryTaskManager(); - } - - @Test - void taskRemainsUnchangedWhenAddedToManager() { - // Создаем оригинальную задачу - Task originalTask = new Task("Original", "Original Description", Status.NEW); - - // Сохраняем оригинальные значения - String originalName = originalTask.getName(); - String originalDescription = originalTask.getDescription(); - Status originalStatus = originalTask.getStatus(); - - // Добавляем в менеджер - Task createdTask = taskManager.createTask(originalTask); - - // Проверяем, что оригинальная задача не изменилась - assertEquals(originalName, originalTask.getName(), "Оригинальное имя не должно измениться"); - assertEquals(originalDescription, originalTask.getDescription(), "Оригинальное описание не должно измениться"); - assertEquals(originalStatus, originalTask.getStatus(), "Оригинальный статус не должен измениться"); - - // Проверяем, что созданная задача имеет те же значения - assertEquals(originalName, createdTask.getName(), "Имя должно сохраниться"); - assertEquals(originalDescription, createdTask.getDescription(), "Описание должно сохраниться"); - assertEquals(originalStatus, createdTask.getStatus(), "Статус должен сохраниться"); - } -} \ No newline at end of file diff --git a/test/model/TaskTest.java b/test/model/TaskTest.java deleted file mode 100644 index 67329c9..0000000 --- a/test/model/TaskTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package model; - -import model.Status; -import model.Task; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class TaskTest { - - @Test - void tasksWithSameIdShouldBeEqual() { - Task task1 = new Task("Task 1", "Description 1", 1, Status.NEW); - Task task2 = new Task("Task 2", "Description 2", 1, Status.DONE); - - assertEquals(task1, task2, "Задачи с одинаковым ID должны быть равны"); - assertEquals(task1.hashCode(), task2.hashCode(), "HashCode должен совпадать для одинаковых ID"); - } - - @Test - void tasksWithDifferentIdShouldNotBeEqual() { - Task task1 = new Task("Task", "Description", 1, Status.NEW); - Task task2 = new Task("Task", "Description", 2, Status.NEW); - - assertNotEquals(task1, task2, "Задачи с разными ID не должны быть равны"); - } -} \ No newline at end of file