diff --git a/.idea/dictionaries/Andrey.xml b/.idea/dictionaries/Andrey.xml new file mode 100644 index 0000000..59fb6f7 --- /dev/null +++ b/.idea/dictionaries/Andrey.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/libraries/gson_2_9_0.xml b/.idea/libraries/gson_2_9_0.xml new file mode 100644 index 0000000..2377008 --- /dev/null +++ b/.idea/libraries/gson_2_9_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index ec0749b..8620317 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,7 @@ Сохранение списка задач в файл. Восстановление задач из файла. ## Спринт №8 -Добавляем дату и время \ No newline at end of file +Добавляем дату и время + +## Спринт №9 +Реализуем HTTP API \ No newline at end of file diff --git a/data/tasks.csv b/data/tasks.csv index 738a868..346a5e3 100644 --- a/data/tasks.csv +++ b/data/tasks.csv @@ -1,6 +1,6 @@ id;DateTime;Duration(min);type;name;status;description;epic -0;2024.12.31 12:05;15;TASK;Задача №1;DONE;Описание задачи №1; -1;2024.12.31 23:55;10;TASK;Задача №2;NEW;С новым годом!; +1;2024.12.31 12:05;15;TASK;Задача №1;DONE;Описание задачи №1; +8;2024.12.31 23:55;10;TASK;Задача №...;NEW;С новым годом!; 2;2024.12.31 10:55;110;EPIC;Эпик №1;IN_PROGRESS;-; 6;2024.12.31 14:45;10;EPIC;Эпик №2;NEW;-; 3;2024.12.31 12:30;15;SUBTASK;Подзадача №1;DONE;-;2 diff --git a/java-kanban.iml b/java-kanban.iml index d6e13ce..08cac80 100644 --- a/java-kanban.iml +++ b/java-kanban.iml @@ -25,5 +25,6 @@ + \ No newline at end of file diff --git a/src/Main.java b/src/Main.java index f411f87..d18283d 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,6 +1,9 @@ +import managers.Managers; +import managers.TaskManager; import tasks.Epic; import tasks.Subtask; import tasks.Task; +import managers.FileBackedTaskManager; import java.time.Duration; import java.time.LocalDateTime; @@ -18,7 +21,7 @@ public static void main(String[] args) { System.out.println("\nПроверяем статус эпика : " + manager.getEpic(6).toString()); - System.out.println("\nПроверяем статус задачи : " + manager.getTask(0).toString()); + System.out.println("\nПроверяем статус задачи : " + manager.getTask(1).toString()); System.out.println("\nПроверяем статус подзадачи : " + manager.getSubtask(5).toString()); diff --git a/src/adapters/DurationAdapter.java b/src/adapters/DurationAdapter.java new file mode 100644 index 0000000..0812e5d --- /dev/null +++ b/src/adapters/DurationAdapter.java @@ -0,0 +1,25 @@ +package adapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.time.Duration; + +public class DurationAdapter extends TypeAdapter { + @Override + public void write(final JsonWriter jsonWriter, final Duration duration) throws IOException { + if (duration == null) { + jsonWriter.value(0); + return; + } + jsonWriter.value(duration.toMinutes()); + } + + @Override + public Duration read(final JsonReader jsonReader) throws IOException { + return Duration.ofMinutes(Integer.decode(jsonReader.nextString())); + } + +} diff --git a/src/adapters/JsFormatter.java b/src/adapters/JsFormatter.java new file mode 100644 index 0000000..4ecde31 --- /dev/null +++ b/src/adapters/JsFormatter.java @@ -0,0 +1,11 @@ +package adapters; + +public class JsFormatter { + public static final String MESSAGE = "{\n \"message\":\"%s\"\n}"; + + public static final String ID_MESSAGE = "{\n \"id\":%d," + + "\n \"message\":\"%s\"\n}"; + + private JsFormatter() { + } +} diff --git a/src/adapters/LocalDateTimeAdapter.java b/src/adapters/LocalDateTimeAdapter.java new file mode 100644 index 0000000..84ef5b9 --- /dev/null +++ b/src/adapters/LocalDateTimeAdapter.java @@ -0,0 +1,32 @@ +package adapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import tasks.Task; + +import java.io.IOException; +import java.time.LocalDateTime; + + +public class LocalDateTimeAdapter extends TypeAdapter { + + @Override + public void write(final JsonWriter jsonWriter, final LocalDateTime localDateTime) throws IOException { + if (localDateTime == null) { + jsonWriter.value("null"); + return; + } + jsonWriter.value(localDateTime.format(Task.DATE_TIME_FORMATTER)); + } + + @Override + public LocalDateTime read(final JsonReader jsonReader) throws IOException { + String value = jsonReader.nextString(); + if (value.equals("null")) { + return null; + } + return LocalDateTime.parse(value, Task.DATE_TIME_FORMATTER); + } + +} diff --git a/src/adapters/TaskStatusAdapter.java b/src/adapters/TaskStatusAdapter.java new file mode 100644 index 0000000..fea94f3 --- /dev/null +++ b/src/adapters/TaskStatusAdapter.java @@ -0,0 +1,29 @@ +package adapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import tasks.TaskStatus; + +import java.io.IOException; + +public class TaskStatusAdapter extends TypeAdapter { + @Override + public void write(final JsonWriter jsonWriter, final TaskStatus taskStatus) throws IOException { + if (taskStatus == null) { + jsonWriter.value(TaskStatus.NEW.toString()); + return; + } + jsonWriter.value(taskStatus.toString()); + } + + @Override + public TaskStatus read(final JsonReader jsonReader) throws IOException { + TaskStatus taskStatus = TaskStatus.valueOf(jsonReader.nextString()); + if (taskStatus == null) { + return TaskStatus.NEW; + } + return taskStatus; + } + +} diff --git a/src/exceptions/NotFoundException.java b/src/exceptions/NotFoundException.java new file mode 100644 index 0000000..ee3806e --- /dev/null +++ b/src/exceptions/NotFoundException.java @@ -0,0 +1,15 @@ +package exceptions; + +public class NotFoundException extends RuntimeException { + private final String taskInfo; + + public NotFoundException(final String text, final String taskInfo) { + super(text); + this.taskInfo = taskInfo; + } + + public String getDetailMessage() { + return getMessage() + " " + taskInfo; + } + +} diff --git a/src/exceptions/TaskCrossTimeException.java b/src/exceptions/TimeIntersectionException.java similarity index 60% rename from src/exceptions/TaskCrossTimeException.java rename to src/exceptions/TimeIntersectionException.java index 22f7912..e977c87 100644 --- a/src/exceptions/TaskCrossTimeException.java +++ b/src/exceptions/TimeIntersectionException.java @@ -1,9 +1,9 @@ package exceptions; -public class TaskCrossTimeException extends RuntimeException { +public class TimeIntersectionException extends RuntimeException { private final String existsTasks; - public TaskCrossTimeException(final String text, final String existsTasks) { + public TimeIntersectionException(final String text, final String existsTasks) { super(text); this.existsTasks = existsTasks; } diff --git a/src/httpapi/BaseHttpHandler.java b/src/httpapi/BaseHttpHandler.java new file mode 100644 index 0000000..42300c6 --- /dev/null +++ b/src/httpapi/BaseHttpHandler.java @@ -0,0 +1,60 @@ +package httpapi; + +import adapters.JsFormatter; +import com.sun.net.httpserver.HttpExchange; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +public class BaseHttpHandler { + protected static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + /** + * Передач ответа на HTTP запрос + * + * @param h- объект http запроса + * @param text - текст ответа + * @param retCode - код завершения обработки запроса + */ + public void sendText(HttpExchange h, String text, int retCode) /*throws IOException*/ { + try { + byte[] resp = text.getBytes(StandardCharsets.UTF_8); + h.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + h.sendResponseHeaders(retCode, resp.length); + h.getResponseBody().write(resp); + } catch (IOException e) { + System.out.println("Произошла ошибка при передаче ответа на запрос.\n" + + e.getMessage()); + } + } + + /** + * Чтение параметра http запроса + * + * @param h - объект http запроса + * @return - идентификатор элемента в списке задач + */ + public Optional getElementId(HttpExchange h) { + String[] pathParts = h.getRequestURI().getPath().split("/"); + if (pathParts.length < 3) { + return Optional.empty(); + } + try { + return Optional.of(Integer.parseInt(pathParts[2])); + } catch (NumberFormatException exception) { + return Optional.empty(); + } + } + + /** + * Обработка Нераспознанного запроса + */ + protected void handleUnknown(HttpExchange exchange, String method) { + sendText(exchange, + String.format(JsFormatter.MESSAGE, "Метод не поддержмвается - " + method), + 406); + } + +} \ No newline at end of file diff --git a/src/httpapi/HttpEpicsHandler.java b/src/httpapi/HttpEpicsHandler.java new file mode 100644 index 0000000..20f2738 --- /dev/null +++ b/src/httpapi/HttpEpicsHandler.java @@ -0,0 +1,107 @@ +package httpapi; + +import adapters.JsFormatter; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import exceptions.NotFoundException; +import exceptions.TimeIntersectionException; +import managers.TaskManager; +import tasks.Epic; +import tasks.Subtask; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; + +/** + * класс обработки http запросов по контексту /epics + */ +public class HttpEpicsHandler extends BaseHttpHandler implements HttpHandler { + private final TaskManager manager; + + public HttpEpicsHandler(TaskManager manager) { + this.manager = manager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String endpointName = method + exchange.getRequestURI().getPath(); + + try { + Optional epicIdOpt = getElementId(exchange); + if (method.equals("GET")) { + if (epicIdOpt.isEmpty()) { + List epics = manager.getEpicList(); + sendText(exchange, HttpTaskServer.gson.toJson(epics), 200); + } else { + int epicId = epicIdOpt.get(); + Epic epic = manager.getEpic(epicId); + + String path = exchange.getRequestURI().getPath(); + if (!path.contains("subtasks")) { + sendText(exchange, HttpTaskServer.gson.toJson(epic), 200); + } else { + List subtasks = manager.getSubtasksByEpic(epicId); + sendText(exchange, HttpTaskServer.gson.toJson(subtasks), 200); + } + } + } else if (method.equals("POST")) { + // читаем описание эпика переданной в запросе + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), BaseHttpHandler.DEFAULT_CHARSET); + Epic newEpic = HttpTaskServer.gson.fromJson(body, Epic.class); + + if (epicIdOpt.isEmpty()) { + // Добавление нового эпика если не указан идентификатор + int id = manager.addNewEpic(newEpic); + if (id > 0) { + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, id, "Эпик успешно добавлена."), + 201); + } else { + throw new Exception(endpointName + + " Ошибка при добавлении эпика. retCode=" + id); + } + } else { + // Обновление эпика по идентификатору + int updateId = epicIdOpt.get(); + newEpic.setId(updateId); // на всякий случай устанавлмваем id из параметра + manager.updateEpic(newEpic); + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, updateId, "Задача успешно обновлена."), + 201); + } + } else if (method.equals("DELETE")) { + // Удаление задачи + if (epicIdOpt.isEmpty()) { + sendText(exchange, + String.format(JsFormatter.MESSAGE, "Идентификатор эпика не указан."), + 406); + } else { + int epicId = epicIdOpt.get(); + manager.removeEpic(epicId); + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, epicId, "эпик успешно удален."), + 200); + } + } else { + handleUnknown(exchange, endpointName); + } + } catch (NotFoundException e) { // Данные не найдены + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 404); + } catch (TimeIntersectionException e) { // Пересечение времени выполнения задач + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 406); + } catch (Exception e) { // Прочие ошибки + sendText(exchange, + String.format(JsFormatter.MESSAGE, endpointName + "Ошибка программы.\n" + e.getMessage()), + 500); + } + } + +} diff --git a/src/httpapi/HttpHistoryHandler.java b/src/httpapi/HttpHistoryHandler.java new file mode 100644 index 0000000..04e3853 --- /dev/null +++ b/src/httpapi/HttpHistoryHandler.java @@ -0,0 +1,44 @@ +package httpapi; + +import adapters.JsFormatter; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import exceptions.NotFoundException; +import managers.TaskManager; +import tasks.Task; + +import java.io.IOException; +import java.util.List; + +public class HttpHistoryHandler extends BaseHttpHandler implements HttpHandler { + private final TaskManager manager; + + public HttpHistoryHandler(TaskManager manager) { + this.manager = manager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String endpointName = method + exchange.getRequestURI().getPath(); + + try { + if (!method.equals("GET")) { + handleUnknown(exchange, endpointName); + return; + } + + List history = manager.getHistory(); + sendText(exchange, HttpTaskServer.gson.toJson(history), 200); + + } catch (NotFoundException e) { // Данные не найдены + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 404); + } catch (Exception e) { // Прочие ошибки + String.format(JsFormatter.MESSAGE, endpointName + + "Ошибка программы.\n" + e.getMessage(), + 500); + } + } +} diff --git a/src/httpapi/HttpPrioritizedHandler.java b/src/httpapi/HttpPrioritizedHandler.java new file mode 100644 index 0000000..b8465b8 --- /dev/null +++ b/src/httpapi/HttpPrioritizedHandler.java @@ -0,0 +1,46 @@ +package httpapi; + +import adapters.JsFormatter; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import exceptions.NotFoundException; +import managers.TaskManager; +import tasks.Task; + +import java.io.IOException; +import java.util.List; + +public class HttpPrioritizedHandler extends BaseHttpHandler implements HttpHandler { + private final TaskManager manager; + + public HttpPrioritizedHandler(TaskManager manager) { + this.manager = manager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String endpointName = method + exchange.getRequestURI().getPath(); + try { + if (!method.equals("GET")) { + handleUnknown(exchange, endpointName); + return; + } + + List prioritizedTasks = manager.getPrioritizedTasks(); + sendText(exchange, HttpTaskServer.gson.toJson(prioritizedTasks), + 200); + + } catch (NotFoundException e) { // Данные не найдены + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 404); + } catch (Exception e) { // Прочие ошибки + sendText(exchange, + String.format(JsFormatter.MESSAGE, endpointName + + "Ошибка программы.\n" + e.getMessage()), + 500); + } + } + +} diff --git a/src/httpapi/HttpSubtasksHandler.java b/src/httpapi/HttpSubtasksHandler.java new file mode 100644 index 0000000..dea4027 --- /dev/null +++ b/src/httpapi/HttpSubtasksHandler.java @@ -0,0 +1,96 @@ +package httpapi; + +import adapters.JsFormatter; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import exceptions.NotFoundException; +import exceptions.TimeIntersectionException; +import managers.TaskManager; +import tasks.Subtask; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; + +public class HttpSubtasksHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager manager; + + public HttpSubtasksHandler(TaskManager manager) { + this.manager = manager; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String endpointName = method + exchange.getRequestURI().getPath(); + + try { + Optional subtaskIdOpt = getElementId(exchange); + if (method.equals("GET")) { + if (subtaskIdOpt.isEmpty()) { + List subtasks = manager.getSubtaskList(); + sendText(exchange, HttpTaskServer.gson.toJson(subtasks), 200); + } else { + Subtask subtask = manager.getSubtask(subtaskIdOpt.get()); + sendText(exchange, HttpTaskServer.gson.toJson(subtask), 200); + } + } else if (method.equals("POST")) { + // читаем описание подзадачи переданной в запросе + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), BaseHttpHandler.DEFAULT_CHARSET); + Subtask newSubtask = HttpTaskServer.gson.fromJson(body, Subtask.class); + + if (subtaskIdOpt.isEmpty()) { + // Добавление новой подзадачи если не указан идентификатор + int id = manager.addNewSubtask(newSubtask); + if (id > 0) { + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, id, "Подзадача успешно добавлена."), + 201); + } else { + throw new Exception(endpointName + + " Ошибка при добавлении подзадачи. retCode=" + id); + } + } else { + // Обновление подзадачи по идентификатору + int updateId = subtaskIdOpt.get(); + newSubtask.setId(updateId); // на всякий случай устанавлмваем id из параметра запроса + manager.updateSubtask(newSubtask); + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, updateId, "Подзадача успешно обновлена."), + 201); + } + } else if (method.equals("DELETE")) { + // Удаление подзадачи + if (subtaskIdOpt.isEmpty()) { + sendText(exchange, + String.format(JsFormatter.MESSAGE, "Идентификатор подзадачи не указан."), + 406); + } else { + int subtaskId = subtaskIdOpt.get(); + manager.removeSubtask(subtaskId); + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, subtaskId, "Задача успешно удалена."), + 200); + } + } else { + handleUnknown(exchange, endpointName); + } + } catch (NotFoundException e) { // Данные не найдены + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 404); + } catch (TimeIntersectionException e) { // Пересечение времени выполнения задач + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 406); + } catch (Exception e) { // Прочие ошибки + sendText(exchange, + String.format(JsFormatter.MESSAGE, endpointName + "Ошибка программы.\n" + e.getMessage()), + 500); + } + } + +} diff --git a/src/httpapi/HttpTaskServer.java b/src/httpapi/HttpTaskServer.java new file mode 100644 index 0000000..243659b --- /dev/null +++ b/src/httpapi/HttpTaskServer.java @@ -0,0 +1,53 @@ +package httpapi; + +import adapters.DurationAdapter; +import adapters.LocalDateTimeAdapter; +import adapters.TaskStatusAdapter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.sun.net.httpserver.HttpServer; +import managers.Managers; +import managers.TaskManager; +import tasks.TaskStatus; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.time.LocalDateTime; + +public class HttpTaskServer { + private static final int PORT = 8080; + private static HttpServer httpServer; + private static TaskManager manager = Managers.getDefault(); + + public static Gson gson = new GsonBuilder() + .setPrettyPrinting() + .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) + .registerTypeAdapter(Duration.class, new DurationAdapter()) + .registerTypeAdapter(TaskStatus.class, new TaskStatusAdapter()) + .create(); + + public HttpTaskServer(TaskManager manager) { + this.manager = manager; + } + + public static void start() throws IOException { + httpServer = HttpServer.create(new InetSocketAddress(PORT), 0); + httpServer.createContext("/tasks", new HttpTasksHandler(manager)); + httpServer.createContext("/subtasks", new HttpSubtasksHandler(manager)); + httpServer.createContext("/epics", new HttpEpicsHandler(manager)); + httpServer.createContext("/history", new HttpHistoryHandler(manager)); + httpServer.createContext("/prioritized", new HttpPrioritizedHandler(manager)); + httpServer.start(); + } + + public static void stop() { + httpServer.stop(0); + } + + public static void main(String[] args) throws IOException, InterruptedException { + + start(); + System.out.println("HTTP-сервер запущен на " + PORT + " порту!"); + } +} diff --git a/src/httpapi/HttpTasksHandler.java b/src/httpapi/HttpTasksHandler.java new file mode 100644 index 0000000..e4e53d9 --- /dev/null +++ b/src/httpapi/HttpTasksHandler.java @@ -0,0 +1,97 @@ +package httpapi; + +import adapters.JsFormatter; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import exceptions.NotFoundException; +import exceptions.TimeIntersectionException; +import managers.TaskManager; +import tasks.Task; + +import java.io.InputStream; +import java.util.List; +import java.util.Optional; + +/** + * класс обработки http запросов по контексту /tasks + */ +public class HttpTasksHandler extends BaseHttpHandler implements HttpHandler { + + private final TaskManager manager; + + public HttpTasksHandler(TaskManager manager) { + this.manager = manager; + } + + @Override + public void handle(HttpExchange exchange) { + String method = exchange.getRequestMethod(); + String endpointName = method + exchange.getRequestURI().getPath(); + + try { + Optional taskIdOpt = getElementId(exchange); + if (method.equals("GET")) { + if (taskIdOpt.isEmpty()) { + List tasks = manager.getTaskList(); + sendText(exchange, HttpTaskServer.gson.toJson(tasks), 200); + } else { + Task task = manager.getTask(taskIdOpt.get()); + sendText(exchange, HttpTaskServer.gson.toJson(task), 200); + } + } else if (method.equals("POST")) { + // читаем описание задачи переданной в запросе + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), BaseHttpHandler.DEFAULT_CHARSET); + Task newTask = HttpTaskServer.gson.fromJson(body, Task.class); + + if (taskIdOpt.isEmpty()) { + // Добавление новой задачи если не указан идентификатор + int id = manager.addNewTask(newTask); + if (id > 0) { + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, id, "Задача успешно добавлена."), + 201); + } else { + throw new Exception(endpointName + + " Ошибка при добавлении задачи. retCode=" + id); + } + } else { + // Обновление задачи по идентификатору + int updateId = taskIdOpt.get(); + newTask.setId(updateId); // на всякий случай устанавлмваем id из параметра + manager.updateTask(newTask); + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, updateId, "Задача успешно обновлена."), + 201); + } + } else if (method.equals("DELETE")) { + // Удаление задачи + if (taskIdOpt.isEmpty()) { + sendText(exchange, + String.format(JsFormatter.MESSAGE, "Идентификатор задачи не указан."), + 406); + } else { + int taskId = taskIdOpt.get(); + manager.removeTask(taskId); + sendText(exchange, + String.format(JsFormatter.ID_MESSAGE, taskId, "Задача успешно удалена."), + 200); + } + } else { + handleUnknown(exchange, endpointName); + } + } catch (NotFoundException e) { // Данные не найдены + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 404); + } catch (TimeIntersectionException e) { // Пересечение времени выполнения задач + sendText(exchange, + String.format(JsFormatter.MESSAGE, e.getDetailMessage()), + 406); + } catch (Exception e) { // Прочие ошибки + sendText(exchange, + String.format(JsFormatter.MESSAGE, endpointName + "Ошибка программы.\n" + e.getMessage()), + 500); + } + } +} diff --git a/src/FileBackedTaskManager.java b/src/managers/FileBackedTaskManager.java similarity index 90% rename from src/FileBackedTaskManager.java rename to src/managers/FileBackedTaskManager.java index 9a1d153..1d4d902 100644 --- a/src/FileBackedTaskManager.java +++ b/src/managers/FileBackedTaskManager.java @@ -1,3 +1,5 @@ +package managers; + import exceptions.LoadException; import exceptions.SaveException; import tasks.*; @@ -12,7 +14,7 @@ */ public class FileBackedTaskManager extends InMemoryTaskManager { private String fileName; - private boolean loadInprogres; + private boolean loadInprogres = false; /** * Конструктор @@ -84,26 +86,30 @@ public void save() throws SaveException { fileWriter.write("id;DateTime;Duration(min);type;name;status;description;epic\n"); // сохраняем задачи - String taskType = TaskType.TASK.toString(); - for (Task task : getTaskList()) { - fileWriter.write(toString(task).replaceFirst("#type#", taskType) - + "\n"); + if (!taskList.isEmpty()) { + String taskType = TaskType.TASK.toString(); + for (Task task : taskList.values()) { + fileWriter.write(toString(task).replaceFirst("#type#", taskType) + + "\n"); + } } // сохраняем эпики - taskType = TaskType.EPIC.toString(); - for (Epic epic : getEpicList()) { - fileWriter.write(toString(epic).replaceFirst("#type#", taskType) - + "\n"); + if (!epicList.isEmpty()) { + String taskType = TaskType.EPIC.toString(); + for (Epic epic : epicList.values()) { + fileWriter.write(toString(epic).replaceFirst("#type#", taskType) + + "\n"); + } } - // сохраняем подзадачи - taskType = TaskType.SUBTASK.toString(); - for (Subtask subtask : getSubtaskList()) { - fileWriter.write(toString(subtask).replaceFirst("#type#", taskType) - + subtask.getEpicId() + "\n"); + if (!subtaskList.isEmpty()) { + String taskType = TaskType.SUBTASK.toString(); + for (Subtask subtask : subtaskList.values()) { + fileWriter.write(toString(subtask).replaceFirst("#type#", taskType) + + subtask.getEpicId() + "\n"); + } } - fileWriter.flush(); } catch (IOException e) { @@ -118,7 +124,7 @@ public void save() throws SaveException { * @param file - файл с описанием задач * @return - ссылка на объект менеджера задач */ - static FileBackedTaskManager loadFromFile(File file) throws LoadException { + public static FileBackedTaskManager loadFromFile(File file) throws LoadException { FileBackedTaskManager manager; manager = new FileBackedTaskManager(file.getAbsolutePath()); diff --git a/src/HistoryManager.java b/src/managers/HistoryManager.java similarity index 90% rename from src/HistoryManager.java rename to src/managers/HistoryManager.java index b5fb84e..95d9099 100644 --- a/src/HistoryManager.java +++ b/src/managers/HistoryManager.java @@ -1,3 +1,5 @@ +package managers; + import tasks.Task; import java.util.List; diff --git a/src/InMemoryHistoryManager.java b/src/managers/InMemoryHistoryManager.java similarity index 99% rename from src/InMemoryHistoryManager.java rename to src/managers/InMemoryHistoryManager.java index 264e6b8..8d87494 100644 --- a/src/InMemoryHistoryManager.java +++ b/src/managers/InMemoryHistoryManager.java @@ -1,3 +1,5 @@ +package managers; + import tasks.Task; import util.Node; import util.SimpleLinkedList; diff --git a/src/InMemoryTaskManager.java b/src/managers/InMemoryTaskManager.java similarity index 63% rename from src/InMemoryTaskManager.java rename to src/managers/InMemoryTaskManager.java index e70afc3..d1b17e4 100644 --- a/src/InMemoryTaskManager.java +++ b/src/managers/InMemoryTaskManager.java @@ -1,4 +1,7 @@ -import exceptions.TaskCrossTimeException; +package managers; + +import exceptions.NotFoundException; +import exceptions.TimeIntersectionException; import tasks.Epic; import tasks.Subtask; import tasks.Task; @@ -10,14 +13,14 @@ import java.util.stream.Collectors; public class InMemoryTaskManager implements TaskManager { - private final Map taskList; - private final Map epicList; - private final Map subtaskList; - private Integer idMain = 0; + protected final Map taskList; + protected final Map epicList; + protected final Map subtaskList; + private Integer idMain = 1; private final Map tasksSortedByTime; private final HistoryManager viewHistory = Managers.getDefaultHistory(); - // компаратор для упоорядочивания задач по ремени запуска, + // Компаратор для упоорядочивания задач по ремени запуска, // а при совпадении по возрастанию идентификатора. // Задачи с временем null помещаются в начало. private final Comparator taskComparator = Comparator.comparing(Task::getStartTime, @@ -33,10 +36,24 @@ public InMemoryTaskManager() { // Метод добавления новой задачи @Override - public int addNewTask(Task newTask) { + public int addNewTask(Task newTask) throws TimeIntersectionException { if (newTask == null) { return -1; } + if (newTask.getStartTime() == null) { + return -2; // Время начала выполнения задачи обязательный параметр + } + if (newTask.getStatus() == null) { + newTask.setStatus(TaskStatus.NEW); + } + if (newTask.getDuration() == null) { + newTask.setDuration(Duration.ofMinutes(15)); + } + int intersections = getTaskIntersectionsNum(newTask); + if (intersections > 0) { + String message = "Конфликт по времени исполнения. Новая задача:\n " + newTask.toString(); + throw new TimeIntersectionException(message, "число конфликтов - " + intersections); + } Integer id = idMain++; newTask.setId(id); taskList.put(id, newTask); @@ -58,14 +75,28 @@ public int addNewEpic(Epic newEpic) { // Метод добавления новой подзадачи @Override - public int addNewSubtask(Subtask newSubtask) { + public int addNewSubtask(Subtask newSubtask) throws TimeIntersectionException { if (newSubtask == null) { return -1; } + if (newSubtask.getStartTime() == null) { + return -2; // Время начала выполнения задачи обязательный параметр + } + if (newSubtask.getStatus() == null) { + newSubtask.setStatus(TaskStatus.NEW); + } + if (newSubtask.getDuration() == null) { + newSubtask.setDuration(Duration.ofMinutes(15)); + } Epic epic = epicList.get(newSubtask.getEpicId()); if (epic == null) { return -2; } + int intersections = getTaskIntersectionsNum(newSubtask); + if (intersections > 0) { + String message = "Конфликт по времени исполнения. Новая подзадача:\n " + newSubtask.toString(); + throw new TimeIntersectionException(message, "число конфликтов - " + intersections); + } Integer id = idMain++; newSubtask.setId(id); subtaskList.put(id, newSubtask); @@ -83,31 +114,51 @@ public int getNumberOfObjects() { // Метод получения задачи по индексу @Override - public Task getTask(Integer id) { + public Task getTask(Integer id) throws NotFoundException { Task task = taskList.get(id); + if (task == null) { + throw new NotFoundException("Задача не найдена", "id=" + id); + } viewHistory.add(task); return task; } // Метод получения эпика по индексу @Override - public Epic getEpic(Integer id) { + public Epic getEpic(Integer id) throws NotFoundException { Epic epic = epicList.get(id); + if (epic == null) { + throw new NotFoundException("Эпик не найден", "id=" + id); + } viewHistory.add(epic); return epic; } // Метод получения подзадачи по индексу @Override - public Subtask getSubtask(Integer id) { + public Subtask getSubtask(Integer id) throws NotFoundException { Subtask s = subtaskList.get(id); + if (s == null) { + throw new NotFoundException("Подзадача не найдена", "id=" + id); + } viewHistory.add(s); return s; } // Метод обновления задачи @Override - public int updateTask(Task task) { + public int updateTask(Task task) throws TimeIntersectionException { + if (task.getStatus() == null) { + task.setStatus(TaskStatus.NEW); + } + if (task.getDuration() == null) { + task.setDuration(Duration.ofMinutes(15)); + } + int intersections = getTaskIntersectionsNum(task); + if (intersections > 0) { + String message = "Конфликт по времени исполнения. Обновление задачи:\n " + task.toString(); + throw new TimeIntersectionException(message, "число конфликтов - " + intersections); + } int id = task.getId(); taskList.put(id, task); addTaskToSortedMap(task); @@ -118,6 +169,14 @@ public int updateTask(Task task) { @Override public int updateEpic(Epic newEpic) { int id = newEpic.getId(); + Epic oldEpic = epicList.get(id); + if (oldEpic != null) { + // перепмсываум идентификаторы подзадач старого эппика в новый + for (Integer idSubtask : oldEpic.getSubtasks()) { + newEpic.addSubtask(idSubtask); + } + } + // заменяем старый эпик на новый epicList.put(id, newEpic); newEpic.reloadSubtakList(getEpic(id).getSubtasks()); setStatusEpic(id); @@ -134,7 +193,18 @@ public int updateEpic(Epic newEpic) { * @return - id обновленно подзадачи, или меньше нуля если произошла ошибка */ @Override - public int updateSubtask(Subtask newSubtask) { + public int updateSubtask(Subtask newSubtask) throws TimeIntersectionException { + if (newSubtask.getStatus() == null) { + newSubtask.setStatus(TaskStatus.NEW); + } + if (newSubtask.getDuration() == null) { + newSubtask.setDuration(Duration.ofMinutes(15)); + } + int intersections = getTaskIntersectionsNum(newSubtask); + if (intersections > 0) { + String message = "Конфликт по времени исполнения. Обновление подзадачи:\n " + newSubtask.toString(); + throw new TimeIntersectionException(message, "число конфликтов - " + intersections); + } int id = newSubtask.getId(); int epicId = newSubtask.getEpicId(); if (!epicList.containsKey(epicId)) { @@ -158,7 +228,7 @@ private void setStatusEpic(Integer epicId) { Epic epic = epicList.get(epicId); setEpicTime(epicId); - if (epic.getSubtasks().isEmpty()) { + if (subtaskList.isEmpty() || epic.getSubtasks().isEmpty()) { epic.setStatus(TaskStatus.NEW); return; } @@ -194,8 +264,12 @@ private void setStatusEpic(Integer epicId) { @Override - public void removeTask(Integer taskId) { + public void removeTask(Integer taskId) throws NotFoundException { Task task = taskList.get(taskId); + if (task == null) { + throw new NotFoundException("Задача не найдена", + "id=" + taskId); + } tasksSortedByTime.remove(task); taskList.remove(taskId); viewHistory.remove(taskId); @@ -207,9 +281,9 @@ public void removeTask(Integer taskId) { * @param epicId- идентификатор объекта */ @Override - public void removeEpic(Integer epicId) { + public void removeEpic(Integer epicId) throws NotFoundException { if (!epicList.containsKey(epicId)) { - return; + throw new NotFoundException("Эпик не найден", "id=" + epicId); } for (Integer idSubtask : epicList.get(epicId).getSubtasks()) { Task task = subtaskList.get(idSubtask); @@ -223,13 +297,17 @@ public void removeEpic(Integer epicId) { /** * Удаление подзадачи по идентификатору - * Удаляем предварительно из спика соответствующего эпика + * удаляем предварительно из спика соответствующего эпика * и из общего списка позадач. * * @param subtaskId - идентификатор подзадачи */ @Override - public void removeSubtask(Integer subtaskId) { + public void removeSubtask(Integer subtaskId) throws NotFoundException { + if (subtaskList.isEmpty()) { + throw new NotFoundException("подзадача не найдена", + "id=" + subtaskId); + } Task task = subtaskList.get(subtaskId); tasksSortedByTime.remove(task); @@ -241,17 +319,26 @@ public void removeSubtask(Integer subtaskId) { } @Override - public List getTaskList() { + public List getTaskList() throws NotFoundException { + if (taskList.isEmpty()) { + throw new NotFoundException("Информация не найдена.", "Список задач пуст."); + } return new ArrayList<>(taskList.values()); } @Override - public List getEpicList() { + public List getEpicList() throws NotFoundException { + if (epicList.isEmpty()) { + throw new NotFoundException("Информация не найдена.", "Список эпиков пуст."); + } return new ArrayList<>(epicList.values()); } @Override - public ArrayList getSubtaskList() { + public ArrayList getSubtaskList() throws NotFoundException { + if (subtaskList.isEmpty()) { + throw new NotFoundException("Информация не найдена.", "Список лодзадач пуст."); + } return new ArrayList<>(subtaskList.values()); } @@ -278,7 +365,7 @@ public void removeAllEpics() { // Удаление всех объектов класса Subtask @Override public void removeAllSubtasks() { - for (Epic epic : getEpicList()) { + for (Epic epic : epicList.values()) { epic.removeAllSubtasks(); } for (Subtask subtask : subtaskList.values()) { @@ -295,12 +382,15 @@ public void removeAllSubtasks() { * @return - список подзадач */ @Override - public List getSubtasksByEpic(Integer epicId) { + public List getSubtasksByEpic(Integer epicId) throws NotFoundException { List subtasks = new ArrayList<>(); subtasks = subtaskList.values().stream() .filter((Subtask subtask) -> subtask.getEpicId() == epicId) .collect(Collectors.toList()); + if (subtasks.isEmpty()) { + throw new NotFoundException("Информация не найдена.", "Список лодзадач пуст."); + } return subtasks; } @@ -310,23 +400,32 @@ public List getSubtasksByEpic(Integer epicId) { * @return - возвращает список использованных объектов */ @Override - public List getHistory() { + public List getHistory() throws NotFoundException { + if (viewHistory.getHistory().isEmpty()) { + throw new NotFoundException("Информация не найдена.", "История отсутствует."); + } return viewHistory.getHistory(); } // очистка всех задач и эпиков public void clear() { - removeAllTasks(); - removeAllEpics(); - tasksSortedByTime.clear(); - idMain = 0; + if (!taskList.isEmpty()) { + removeAllTasks(); + } + if (!epicList.isEmpty()) { + removeAllEpics(); + } + if (!tasksSortedByTime.isEmpty()) { + tasksSortedByTime.clear(); + } + idMain = 1; } /** * Пересчет идентификатора задач после загрузки данных из файла */ public void resetMainId() { - int maxId = 0; + int maxId = 1; for (int i : taskList.keySet()) { if (i > maxId) maxId = i; } @@ -387,8 +486,11 @@ private void setEpicTime(Integer epicId) { * @return - отсортированный список */ @Override - public List getPrioritizedTasks() { + public List getPrioritizedTasks() throws NotFoundException { + if (tasksSortedByTime.isEmpty()) { + throw new NotFoundException("Информация не найдена", "список задач пуст"); + } List sortedTaskList = new ArrayList<>(); for (Map.Entry entry : tasksSortedByTime.entrySet()) { @@ -411,30 +513,37 @@ private void addTaskToSortedMap(Task task) { */ return; } - LocalDateTime curentTime = LocalDateTime.now(); - if (tasksSortedByTime.size() == 0) { - tasksSortedByTime.put(task, curentTime.format(Task.DATE_TIME_FORMATTER)); - return; - } - - // Проверяем пересечение времени добавляемой задачи с существующими задачами - List crossTime = getPrioritizedTasks().stream() - .filter((Task existsTask) -> !checkTimeFree(task, existsTask)) - .collect(Collectors.toList()); - - if (crossTime.isEmpty()) { - tasksSortedByTime.put(task, curentTime.format(Task.DATE_TIME_FORMATTER)); - } else { - String message = "Конфликт по времени исполнения.\n " + task.toString(); - throw new TaskCrossTimeException(message, "число конфликтов - " + crossTime.size()); - } + tasksSortedByTime.put(task, curentTime.format(Task.DATE_TIME_FORMATTER)); } + /** + * Удаление задачи из отсортированного списка + * + * @param task + */ private void removeFromSortedList(Task task) { tasksSortedByTime.remove(task); } + /** + * Проверка пересечения времени выполнения задачи с временами задач в отсортированном списке + * + * @param newTask - проверяемая задача + * @return - число пересечений по времени с существующими задачами + */ + private int getTaskIntersectionsNum(Task newTask) { + // Проверяем пересечение времени добавляемой задачи с существующими задачами + if (tasksSortedByTime.isEmpty()) { + return 0; + } + List intersections = getPrioritizedTasks().stream() + .filter((Task existsTask) -> !checkTimeFree(newTask, existsTask)) + .collect(Collectors.toList()); + + return intersections.size(); + } + /** * Определение непересечения временных интервалов двух задач * @@ -444,7 +553,7 @@ private void removeFromSortedList(Task task) { */ private boolean checkTimeFree(Task task1, Task task2) { if (task1.equals(task2)) { - return true; + return true; // пересечение с собой не учитываем (update) } LocalDateTime task1Start = task1.getStartTime(); LocalDateTime task1End = task1.getEndTime(); @@ -455,5 +564,4 @@ private boolean checkTimeFree(Task task1, Task task2) { task2Start.isAfter(task1End); } - } \ No newline at end of file diff --git a/src/Managers.java b/src/managers/Managers.java similarity index 83% rename from src/Managers.java rename to src/managers/Managers.java index 342e1c7..6156708 100644 --- a/src/Managers.java +++ b/src/managers/Managers.java @@ -1,3 +1,5 @@ +package managers; + public final class Managers { // объект утилитарного класса не должен создаваться! // конструктор объявлен с модификатором "private" @@ -6,7 +8,8 @@ private Managers() { // определение объекта меджера задач public static TaskManager getDefault() { - return new FileBackedTaskManager(); + InMemoryTaskManager manager = new InMemoryTaskManager(); + return manager; } // определение объекта журнала событий diff --git a/src/TaskManager.java b/src/managers/TaskManager.java similarity index 54% rename from src/TaskManager.java rename to src/managers/TaskManager.java index 3dce9f8..3ebaa91 100644 --- a/src/TaskManager.java +++ b/src/managers/TaskManager.java @@ -1,3 +1,7 @@ +package managers; + +import exceptions.NotFoundException; +import exceptions.TimeIntersectionException; import tasks.Epic; import tasks.Subtask; import tasks.Task; @@ -6,42 +10,42 @@ public interface TaskManager { // Метод добавления новой задачи - int addNewTask(Task newTask); + int addNewTask(Task newTask) throws TimeIntersectionException; // Метод добавления нового эпика int addNewEpic(Epic newEpic); // Метод добавления новоq подзадачи - int addNewSubtask(Subtask newSubtask); + int addNewSubtask(Subtask newSubtask) throws TimeIntersectionException; // Метод получения задачи по индексу - Task getTask(Integer id); + Task getTask(Integer id) throws NotFoundException; // Метод получения эпика по индексу - Epic getEpic(Integer id); + Epic getEpic(Integer id) throws NotFoundException; // Метод получения подзадачи по индексу - Subtask getSubtask(Integer id); + Subtask getSubtask(Integer id) throws NotFoundException; // Метод обновления задачи - int updateTask(Task task); + int updateTask(Task task) throws TimeIntersectionException; // Метод обновления эпика int updateEpic(Epic newEpic); - int updateSubtask(Subtask newSubtask); + int updateSubtask(Subtask newSubtask) throws TimeIntersectionException; - void removeTask(Integer taskId); + void removeTask(Integer taskId) throws NotFoundException; - void removeEpic(Integer epicId); + void removeEpic(Integer epicId) throws NotFoundException; - void removeSubtask(Integer subtaskId); + void removeSubtask(Integer subtaskId) throws NotFoundException; - List getTaskList(); + List getTaskList() throws NotFoundException; - List getEpicList(); + List getEpicList() throws NotFoundException; - List getSubtaskList(); + List getSubtaskList() throws NotFoundException; // Удаление всех объектов класса Task void removeAllTasks(); @@ -58,7 +62,7 @@ public interface TaskManager { int getNumberOfObjects(); // просмотр использованных задач - List getHistory(); + List getHistory() throws NotFoundException; - List getPrioritizedTasks(); + List getPrioritizedTasks() throws NotFoundException; } diff --git a/src/tasks/Epic.java b/src/tasks/Epic.java index f649043..4eb50db 100644 --- a/src/tasks/Epic.java +++ b/src/tasks/Epic.java @@ -1,31 +1,30 @@ package tasks; -import java.time.Duration; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class Epic extends Task { - private List subtaskList; + private List subtasks; private LocalDateTime endTime; - private Duration duration; // конструктор с параметрами "имя", "описание" public Epic(String title, String description) { super(title, description); - subtaskList = new ArrayList<>(); + subtasks = new ArrayList<>(); } // конструктор с одним параметром "имя" public Epic(String title) { super(title); - subtaskList = new ArrayList<>(); + subtasks = new ArrayList<>(); } // Конструктор копирования объекта public Epic(Epic original) { super(original); - subtaskList = new ArrayList<>(original.getSubtasks()); + subtasks = new ArrayList<>(original.getSubtasks()); } // Переопределяем метод отображения объекта @@ -50,44 +49,44 @@ public String toString() { result += ", title='" + getTitle() + '\'' + ", description='" + getDescription() + '\'' + ", status=" + getStatus() + - ", subtaskNumber=" + subtaskList.size() + + ", subtasks=" + Arrays.toString(subtasks.toArray()) + '}'; return result; } // получение списка идентификаторов подзадач public ArrayList getSubtasks() { - return new ArrayList<>(subtaskList); + return new ArrayList<>(subtasks); } // метод добавления идентификатора подзадачи в список эпика public void addSubtask(Integer subtaskId) { - int index = subtaskList.indexOf(subtaskId); + int index = subtasks.indexOf(subtaskId); // Если идентификатор уже существует или равен идентификатору эпика // выходим без добавления задачи if ((index >= 0) || (subtaskId == getId())) return; - subtaskList.add(subtaskId); + subtasks.add(subtaskId); } // Удаление идентификатора подзадачи из списка эпика public void removeSubtask(Integer subtaskId) { - int index = subtaskList.indexOf(subtaskId); + int index = subtasks.indexOf(subtaskId); if (index < 0) { return; } - subtaskList.remove(index); + subtasks.remove(index); } public void removeAllSubtasks() { - subtaskList.clear(); + subtasks.clear(); setStatus(TaskStatus.NEW); } // метод переписывания списка подзадач, массивом новых идентификаторов - public void reloadSubtakList(ArrayList newSubtaskList) { - subtaskList.clear(); - subtaskList.addAll(newSubtaskList); + public void reloadSubtakList(ArrayList newsubtasks) { + subtasks.clear(); + subtasks.addAll(newsubtasks); } @Override @@ -98,13 +97,5 @@ public LocalDateTime getEndTime() { public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; } -/* - @Override - public Duration getDuration() { - if (subtaskList.isEmpty()) { - return null; - } - return duration; - } -*/ + } diff --git a/src/tasks/Task.java b/src/tasks/Task.java index 15d042e..7f201e1 100644 --- a/src/tasks/Task.java +++ b/src/tasks/Task.java @@ -4,10 +4,11 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; + public class Task { public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"); - private final String title; // заголовок задачи не меняется в течении жизни + private String title; private String description; private int id; private TaskStatus status; @@ -105,6 +106,10 @@ public int hashCode() { return hash + id; } + public void setTitle(String title) { + this.title = title; + } + // методчтения заголовка задачи // Во избежание путаниц заголовок задачи задается только в конструкторе public String getTitle() { diff --git a/test/httpapi/HttpEpicsHandlerTest.java b/test/httpapi/HttpEpicsHandlerTest.java new file mode 100644 index 0000000..ffa72e7 --- /dev/null +++ b/test/httpapi/HttpEpicsHandlerTest.java @@ -0,0 +1,287 @@ +package httpapi; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; +import managers.InMemoryTaskManager; +import managers.TaskManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tasks.Epic; +import tasks.Subtask; + +import java.io.IOException; +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 java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpEpicsHandlerTest { + TaskManager manager = new InMemoryTaskManager(); + HttpTaskServer taskServer = new HttpTaskServer(manager); + + @BeforeEach + void setUp() throws IOException { + manager.removeAllTasks(); + manager.removeAllEpics(); + taskServer.start(); + } + + @AfterEach + void tearDown() { + taskServer.stop(); + } + + /** + * Тестируем добавление эпика + */ + @Test + public void testAddEpic() throws IOException, InterruptedException { + Epic epic = new Epic("Test AddEpic", + "Teting AddNewEpic"); + + String epicJson = HttpTaskServer.gson.toJson(epic); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(epicJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + // проверяем код ответа + assertEquals(201, response.statusCode(), + "Добавление эпика: неожиданный код ответа сервера"); + + // проверяем, что создался один эпик с корректным именем + List epicsFromManager = manager.getEpicList(); + + assertNotNull(epicsFromManager, + "Эпик не создан"); + assertEquals(1, epicsFromManager.size(), + "Некорректное количество эпиков"); + assertEquals("Test AddEpic", epicsFromManager.get(0).getTitle(), + "Некорректное имя эпика"); + + } + + /** + * Тестируем обновление информации об эпике через HTTP сервер + */ + @Test + public void testUpdateEpic() throws IOException, InterruptedException { + makeEpicList(1); + + Epic epic = new Epic("Test UpdateEpic", + "Teting AddNewEpic"); + + String epicJson = HttpTaskServer.gson.toJson(epic); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics/1"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(epicJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + // проверяем код ответа + assertEquals(201, response.statusCode(), + "Обновление эпика: неожиданный код ответа сервера"); + + List epicsFromManager = manager.getEpicList(); + + assertNotNull(epicsFromManager, + "Эпик не создан"); + assertEquals(1, epicsFromManager.size(), + "Некорректное количество эпиков"); + assertEquals("Test UpdateEpic", epicsFromManager.get(0).getTitle(), + "Эпик не обновлен"); + } + + /** + * Тестируем чтение эпика через HTTP сервер + */ + @Test + public void tetGetEpic() throws IOException, InterruptedException { + makeEpicList(1); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics/1"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение эпика: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + JsonElement jsonElement = JsonParser.parseString(response.body()); + assertTrue(jsonElement.isJsonObject(), + "Ошибка чтения ответа сервера."); + Epic newEpic = null; + newEpic = HttpTaskServer.gson.fromJson(jsonElement, Epic.class); + assertNotNull(newEpic, "Ошибка десериализации эпика."); + assertEquals(1, newEpic.getId(), + "Неожиданный идентификатор эпика"); + } + } + + /** + * Вспомогательный класс определение типа объекта десериализации + */ + class EpicListTypeToken extends TypeToken> { + } + + class SubtaskListTypeToken extends TypeToken> { + } + + /** + * Тестируем чтение списка эпиков черех HTTP сервер + */ + @Test + public void testGetEpics() throws IOException, InterruptedException { + makeEpicList(3); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение эпика: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + List epicListFromServer = HttpTaskServer.gson.fromJson(response.body(), new EpicListTypeToken().getType()); + + assertNotNull(epicListFromServer, + "Список эпиков не прочитан."); + + int expectedSize = manager.getEpicList().size(); + assertEquals(expectedSize, epicListFromServer.size(), + "Список эпиков не полный."); + } + } + + /** + * Тестируем удаление эпика + */ + @Test + public void testDeleteEpic() throws IOException, InterruptedException { + makeEpicList(3); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics/2"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).DELETE().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Удаление эпика: неожиданный код ответа сервера"); + + assertEquals(2, manager.getEpicList().size(), + "Неверная длина списка эпиков после удаления."); + } + + /** + * Тестируем чтение подзадач эпика. + */ + @Test + public void testGetSubtasksByEpic() throws IOException, InterruptedException { + makeEpicList(3); + manager.addNewSubtask(new Subtask(2, + "test Subtask1", "-", + LocalDateTime.now(), + Duration.ofMinutes(15))); + manager.addNewSubtask(new Subtask(2, + "test Subtask2", "-", + LocalDateTime.now().plusMinutes(20), + Duration.ofMinutes(15))); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics/2/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение подзадач эпика: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + List subtasksListFromServer = HttpTaskServer.gson.fromJson(response.body(), new SubtaskListTypeToken().getType()); + + assertNotNull(subtasksListFromServer, + "Список подзадач эпика не прочитан."); + + int expectedSize = manager.getSubtasksByEpic(2).size(); + assertEquals(expectedSize, subtasksListFromServer.size(), + "Список подзадач эпика не полный."); + } + } + + /** + * Тестируем ошибку 404 + */ + @Test + public void testError404() throws IOException, InterruptedException { + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/epics/100"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(404, response.statusCode(), + "Чтение несуществующего эпика: неожиданный код ответа сервера"); + } + + /** + * Создание заданного числа эпиков в менеджере + * + * @param size - число эпиков + */ + private void makeEpicList(int size) { + if(size == 0) { + return; + } + for (int i = 0; i < size; i ++) { + manager.addNewEpic(new Epic("epicList element:" + i, + "Testing epicList")); + } + } + +} \ No newline at end of file diff --git a/test/httpapi/HttpHistoryHandlerTest.java b/test/httpapi/HttpHistoryHandlerTest.java new file mode 100644 index 0000000..8d2f6a5 --- /dev/null +++ b/test/httpapi/HttpHistoryHandlerTest.java @@ -0,0 +1,93 @@ +package httpapi; + +import com.google.gson.reflect.TypeToken; +import managers.InMemoryTaskManager; +import managers.TaskManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tasks.Epic; +import tasks.Subtask; +import tasks.Task; + +import java.io.IOException; +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 java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class HttpHistoryHandlerTest { + TaskManager manager = new InMemoryTaskManager(); + HttpTaskServer taskServer = new HttpTaskServer(manager); + + @BeforeEach + void setUp() throws IOException { + manager.removeAllTasks(); + manager.removeAllEpics(); + taskServer.start(); + } + + @AfterEach + void tearDown() { + taskServer.stop(); + } + + /** + * Вспомогательный класс определение типа объекта десериализации + */ + class HistoryListTypeToken extends TypeToken> { + } + + /** + * Тестируем получение истории оюращения к задачам + */ + @Test + public void testHistory() throws IOException, InterruptedException { + int taskId = manager.addNewTask(new Task("Test AddTask", + "Testing AddTask", + LocalDateTime.now(), + Duration.ofMinutes(15))); + + int epicId = manager.addNewEpic(new Epic("Test subtask epic", + "Epic for testing Sabtask")); + + int subtaskId = manager.addNewSubtask(new Subtask(epicId, "Test AddSubtask", "-", + LocalDateTime.now().plusMinutes(30), + Duration.ofMinutes(15))); + + Task task = manager.getTask(taskId); + Epic epic = manager.getEpic(epicId); + Subtask subtask = manager.getSubtask(subtaskId); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/history"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение истории задач: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + List historyFromServer = HttpTaskServer.gson.fromJson(response.body(), + new HistoryListTypeToken().getType()); + + assertNotNull(historyFromServer, + "история не прочитана."); + + int expectedSize = manager.getHistory().size(); + assertEquals(expectedSize, historyFromServer.size(), + "Список истории не полный."); + } + } +} \ No newline at end of file diff --git a/test/httpapi/HttpPrioritizedHandlerTest.java b/test/httpapi/HttpPrioritizedHandlerTest.java new file mode 100644 index 0000000..130db8e --- /dev/null +++ b/test/httpapi/HttpPrioritizedHandlerTest.java @@ -0,0 +1,104 @@ +package httpapi; + +import com.google.gson.reflect.TypeToken; +import managers.InMemoryTaskManager; +import managers.TaskManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tasks.Epic; +import tasks.Subtask; +import tasks.Task; + +import java.io.IOException; +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 java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpPrioritizedHandlerTest { + TaskManager manager = new InMemoryTaskManager(); + HttpTaskServer taskServer = new HttpTaskServer(manager); + + @BeforeEach + void setUp() throws IOException { + manager.removeAllTasks(); + manager.removeAllEpics(); + taskServer.start(); + } + + @AfterEach + void tearDown() { + taskServer.stop(); + } + + /** + * Вспомогательный класс определение типа объекта десериализации + */ + class PrioritizedTypeToken extends TypeToken> { + } + + /** + * тестируем получение хронологического списка + */ + @Test + public void testPrioritizedList() throws IOException, InterruptedException { + manager.addNewTask(new Task("Test AddTask1", + "Testing AddTask", + LocalDateTime.now(), + Duration.ofMinutes(15))); + + int epicId = manager.addNewEpic(new Epic("Test subtask epic", + "Epic for testing Sabtask")); + + manager.addNewSubtask(new Subtask(epicId, "Test AddSubtask2", "-", + LocalDateTime.now().plusMinutes(30), + Duration.ofMinutes(15))); + + manager.addNewTask(new Task("Test AddTask2", + "Testing AddTask2", + LocalDateTime.now().plusMinutes(80), + Duration.ofMinutes(15))); + + manager.addNewSubtask(new Subtask(epicId, "Test AddSubtask2", "-", + LocalDateTime.now().plusMinutes(50), + Duration.ofMinutes(15))); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/prioritized"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение хронологии задач: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + List prioritizedFromServer = HttpTaskServer.gson.fromJson(response.body(), + new PrioritizedTypeToken().getType()); + + assertNotNull(prioritizedFromServer, + "Хронология не прочитана."); + + int expectedSize = manager.getPrioritizedTasks().size(); + assertEquals(expectedSize, prioritizedFromServer.size(), + "хронологический cписок не полный."); + + // проверяем хронологическую последовательность полученного списка задач + for (int i = 0; i < (prioritizedFromServer.size() - 1); i++) { + assertTrue(prioritizedFromServer.get(i).getEndTime() + .isBefore(prioritizedFromServer.get(i + 1).getStartTime()), + "Нарушена хронология задач"); + } + } + } +} \ No newline at end of file diff --git a/test/httpapi/HttpSubtasksHandlerTest.java b/test/httpapi/HttpSubtasksHandlerTest.java new file mode 100644 index 0000000..2e6cef6 --- /dev/null +++ b/test/httpapi/HttpSubtasksHandlerTest.java @@ -0,0 +1,292 @@ +package httpapi; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; +import managers.InMemoryTaskManager; +import managers.TaskManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tasks.Epic; +import tasks.Subtask; + +import java.io.IOException; +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 java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpSubtasksHandlerTest { + TaskManager manager = new InMemoryTaskManager(); + HttpTaskServer taskServer = new HttpTaskServer(manager); + + @BeforeEach + void setUp() throws IOException { + manager.removeAllTasks(); + manager.removeAllEpics(); + taskServer.start(); + } + + @AfterEach + void tearDown() { + taskServer.stop(); + } + + /** + * Тестируем создание подзадачи через HTTP сервер + */ + @Test + public void testAddSubtask() throws IOException, InterruptedException { + + int epicId = manager.addNewEpic(new Epic("Test subtask epic", + "Epic for testing Sabtask")); + + Subtask subtask = new Subtask(epicId, "Test AddSubtask", "-", + LocalDateTime.now(), + Duration.ofMinutes(15)); + + String subtaskJson = HttpTaskServer.gson.toJson(subtask); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + // проверяем код ответа + assertEquals(201, response.statusCode(), + "Добавление подзадачи: неожиданный код ответа сервера"); + + // проверяем, что создана подзадача с корректным именем + List subtasksFromManager = manager.getSubtaskList(); + + assertNotNull(subtasksFromManager, + "Подзадача не создана"); + assertEquals(1, subtasksFromManager.size(), + "Некорректное количество подзадач"); + assertEquals("Test AddSubtask", subtasksFromManager.get(0).getTitle(), + "Некорректное имя подзадачи"); + } + + /** + * Тестируем обновление подзадачи через HTTP сервер + */ + @Test + public void tetUpdateSubtask() throws IOException, InterruptedException { + int epicId = manager.addNewEpic(new Epic("Test subtask epic", + "Epic for testing Sabtask")); + + int subtaskId = manager.addNewSubtask(new Subtask(epicId, "Test UpdateSubtask", + "-", + LocalDateTime.now(), + Duration.ofMinutes(15))); + + // создаем подзадачу с измененными данными для обновления + Subtask subtask = new Subtask(epicId, "Subtask Updated", + "subtask updated successfully", + LocalDateTime.now().plusMinutes(30), + Duration.ofMinutes(15)); + subtask.setId(subtaskId); + + String subtaskJson = HttpTaskServer.gson.toJson(subtask); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks/" + subtaskId); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + // проверяем код ответа + assertEquals(201, response.statusCode(), + "обновление подзадачи: неожиданный код ответа сервера"); + + // проверяем, что подзадача обновлена + List subtasksFromManager = manager.getSubtaskList(); + + assertNotNull(subtasksFromManager, + "Подзадача не создана"); + assertEquals(1, subtasksFromManager.size(), + "Некорректное количество подзадач"); + assertEquals("Subtask Updated", subtasksFromManager.get(0).getTitle(), + "Подзадача не обновилась"); + } + + /** + * Тестируем чтение подзадачи через HTTP сервер + */ + @Test + public void testGetSubtask() throws IOException, InterruptedException { + makeSubtaskList(3); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks/2"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение подзадачи: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + JsonElement jsonElement = JsonParser.parseString(response.body()); + assertTrue(jsonElement.isJsonObject(), + "Ошибка чтения ответа сервера."); + Subtask newSubtask = null; + newSubtask = HttpTaskServer.gson.fromJson(jsonElement, Subtask.class); + assertNotNull(newSubtask, "Ошибка десериализации подзадачи."); + assertEquals(2, newSubtask.getId(), + "Неожиданный идентификатор подзадачи"); + } + } + + class SubtaskListTypeToken extends TypeToken> { + } + + /** + * Тестируем чтение списка подзадач + */ + @Test + public void testGetSubtasks() throws IOException, InterruptedException { + // создаем набор подзадач + makeSubtaskList(5); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение подзадач: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + List subtaskListFromServer = HttpTaskServer.gson.fromJson(response.body(), + new SubtaskListTypeToken().getType()); + + assertNotNull(subtaskListFromServer, + "Список подзадач не прочитан."); + + int expectedSize = manager.getSubtaskList().size(); + assertEquals(expectedSize, subtaskListFromServer.size(), + "Список подзадач не полный."); + } + } + + /** + * Тестируем удаление подзадачи через HTTP сервер + */ + @Test + public void testDeleteSubtask() throws IOException, InterruptedException { + makeSubtaskList(4); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks/3"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).DELETE().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "удаление подзадачи: неожиданный код ответа сервера"); + + assertEquals(3, manager.getSubtaskList().size(), + "Неверная длина списка подзадач после удаления."); + } + + /** + * Тестируем ошибку 404 + */ + @Test + public void testError404() throws IOException, InterruptedException { + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks/100"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(404, response.statusCode(), + "Чтение несуществующей подзадачи: неожиданный код ответа сервера"); + } + + /** + * Тестируем ошибку 406 + */ + @Test + public void testError406() throws IOException, InterruptedException { + makeSubtaskList(3); + + Subtask subtask = new Subtask(1, "Test timeIntersection Subtask", + "timeIntersection", + LocalDateTime.now(), + Duration.ofMinutes(15)); + + String subtaskJson = HttpTaskServer.gson.toJson(subtask); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/subtasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(subtaskJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + // проверяем код ответа + assertEquals(406, response.statusCode(), + "Подзадача в недопустимом интервале: неожиданный код ответа сервера"); + } + + /** + * Метод генерации заданного числа подзадач + * + * @param size - число подзадач для генерации + */ + private void makeSubtaskList(int size) { + if (size == 0) { + return; + } + int epicId = manager.addNewEpic(new Epic("Test subtask epic", + "Epic for testing Sabtask")); + + for (int i = 0; i < size; i++) { + manager.addNewSubtask(new Subtask(epicId, + "test Subtask:" + epicId + "." + (i + 1), "-", + LocalDateTime.now().plusMinutes(20 * i), + Duration.ofMinutes(15))); + } + } +} \ No newline at end of file diff --git a/test/httpapi/HttpTasksHandlerTest.java b/test/httpapi/HttpTasksHandlerTest.java new file mode 100644 index 0000000..74109ad --- /dev/null +++ b/test/httpapi/HttpTasksHandlerTest.java @@ -0,0 +1,321 @@ +package httpapi; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; +import managers.InMemoryTaskManager; +import managers.TaskManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tasks.Task; + +import java.io.IOException; +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 java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpTasksHandlerTest { + TaskManager manager = new InMemoryTaskManager(); + HttpTaskServer taskServer = new HttpTaskServer(manager); + + public HttpTasksHandlerTest() { + } + + @BeforeEach + void setUp() throws IOException { + manager.removeAllTasks(); + manager.removeAllEpics(); + taskServer.start(); + } + + @AfterEach + void tearDown() { + taskServer.stop(); + } + + /** + * Тестируем добавление задачи через Http сервер + */ + @Test + public void testAddTask() throws IOException, InterruptedException { + Task task = new Task("Test AddTask", + "Testing AddTask", + LocalDateTime.now(), + Duration.ofMinutes(15)); + + String taskJson = HttpTaskServer.gson.toJson(task); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(201, response.statusCode(), + "Добавление задачи: неожиданный код ответа сервера"); + + // проверяем, что создалась одна задача с корректным именем + List tasksFromManager = manager.getTaskList(); + + assertNotNull(tasksFromManager, + "Задача не создана"); + assertEquals(1, tasksFromManager.size(), + "Некорректное количество задач"); + assertEquals("Test AddTask", tasksFromManager.get(0).getTitle(), + "Некорректное имя задачи"); + } + + /** + * Тестируем обновление задачи через Http сервер + */ + @Test + public void testUpdateTask() throws IOException, InterruptedException { + // добавляем задачу + makeTaskList(1); + + Task task = new Task("Test UpdateTask", + "Testing UpdateTask", + LocalDateTime.now(), + Duration.ofMinutes(15)); + + String taskJson = HttpTaskServer.gson.toJson(task); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks/1"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(201, response.statusCode(), + "Обновление задачи: неожиданный код ответа сервера"); + + // читаем, задачу из хранилища + List tasksFromManager = manager.getTaskList(); + + assertNotNull(tasksFromManager, + "Задача не создана"); + assertEquals(1, tasksFromManager.size(), + "Некорректное количество задач"); + assertEquals("Test UpdateTask", tasksFromManager.get(0).getTitle(), + "Задача не обновилась."); + + } + + /** + * Тестируем чтение задачи по заданному идентификатору + */ + @Test + public void testGetTask() throws IOException, InterruptedException { + // добавляем задачу + makeTaskList(1); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks/1"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение задачи: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + JsonElement jsonElement = JsonParser.parseString(response.body()); + assertTrue(jsonElement.isJsonObject(), + "Ошибка чтения ответа сервера."); + Task newTask = null; + newTask = HttpTaskServer.gson.fromJson(jsonElement, Task.class); + assertNotNull(newTask, "Ошибка десериализации задачи."); + assertEquals(1, newTask.getId(), + "Неожиданный идентификатор задачи"); + } + } + + /** + * Вспомогательный класс определение типа объекта десериализации + */ + class TaskListTypeToken extends TypeToken> { + } + + /** + * Тестируем чтение списка задач через HTTP сервер + */ + @Test + public void testGetTasks() throws IOException, InterruptedException { + // создаем список задач в менеджере + makeTaskList(3); + + // создаём HTTP-клиент + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks"); + + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .GET() + .build(); + + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Чтение списка задач: неожиданный код ответа сервера"); + + if (response.statusCode() == 200) { + List taskListFromServer = HttpTaskServer.gson.fromJson(response.body(), new TaskListTypeToken().getType()); + + assertNotNull(taskListFromServer, + "Список задач не прочитан."); + + int expectedSize = manager.getTaskList().size(); + assertEquals(expectedSize, taskListFromServer.size(), + "Список задач не полный."); + } + } + + /** + * Тестируем удаление задачи + */ + @Test + public void testDelete() throws IOException, InterruptedException { + // создаем список задач + makeTaskList(3); + + // создаём HTTP-клиент + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks/2"); + + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .DELETE() + .build(); + + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(200, response.statusCode(), + "Удаление задачи: неожиданный код ответа сервера"); + + assertEquals(2, manager.getTaskList().size(), + "Неверная длина списка задач после удаления."); + } + + /** + * Тестируем добавление задачи пересекающейся по времени + */ + @Test + public void testTimeIntersection() throws IOException, InterruptedException { + // создаем список задач + makeTaskList(3); + + // создаем задачу с текущим временем + Task task = new Task("Test TimeIntersection", + "Testing TimeIntersection", + LocalDateTime.now(), + Duration.ofMinutes(15)); + + String taskJson = HttpTaskServer.gson.toJson(task); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)).build(); + + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + assertEquals(406, response.statusCode(), + "Добавление пересекающейся задачи: неожиданный код ответа сервера"); + assertEquals(3, manager.getTaskList().size(), + "Неверная длина списка задач."); + } + + /** + * Тестируем ошибку 404 + */ + @Test + public void testError404() throws IOException, InterruptedException { + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks/100"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url).GET().build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + // проверяем код ответа + assertEquals(404, response.statusCode(), + "Чтение несуществующей задачи: неожиданный код ответа сервера"); + } + + /** + * Тестируем ошибку 406 + */ + @Test + public void testError406() throws IOException, InterruptedException { + makeTaskList(3); + + Task task = new Task("Test timeIntersection Task", + "timeIntersection", + LocalDateTime.now(), + Duration.ofMinutes(15)); + + String taskJson = HttpTaskServer.gson.toJson(task); + + // создаём HTTP-клиент и запрос + HttpClient client = HttpClient.newHttpClient(); + URI url = URI.create("http://localhost:8080/tasks"); + HttpRequest request = HttpRequest.newBuilder() + .uri(url) + .POST(HttpRequest.BodyPublishers.ofString(taskJson)).build(); + + // обработчик запроса с конвертацией тела запроса в строку + HttpResponse response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + + // проверяем код ответа + assertEquals(406, response.statusCode(), + "Задача в недопустимом интервале: неожиданный код ответа сервера"); + } + + /** + * Генерация набора задач в менеджере напрямую + * + * @param size - число генерируемых задач + */ + private void makeTaskList(int size) { + if (size == 0) { + return; + } + for (int i = 0; i < size; i++) { + manager.addNewTask(new Task("taskList element:" + i, + "Testing taskList", + LocalDateTime.now().plusMinutes(20L * i), + Duration.ofMinutes(10))); + } + } +} \ No newline at end of file diff --git a/test/FileBackedTaskManagerTest.java b/test/managers/FileBackedTaskManagerTest.java similarity index 95% rename from test/FileBackedTaskManagerTest.java rename to test/managers/FileBackedTaskManagerTest.java index 37a6913..25de22c 100644 --- a/test/FileBackedTaskManagerTest.java +++ b/test/managers/FileBackedTaskManagerTest.java @@ -1,3 +1,5 @@ +package managers; + import exceptions.LoadException; import exceptions.SaveException; import org.junit.jupiter.api.AfterEach; @@ -88,7 +90,7 @@ void loadFromFile() { Duration.ofMinutes(15))); // удаляем первую задачу что бы внести путаницу в идентификаторы - manager.removeTask(0); + manager.removeTask(1); tmpFile = new File(filename); FileBackedTaskManager manager2 = FileBackedTaskManager.loadFromFile(tmpFile); @@ -112,7 +114,7 @@ void loadFromFile() { "task3", LocalDateTime.now().plusMinutes(80), Duration.ofMinutes(15))); - assertEquals(4, taskId, + assertEquals(5, taskId, "Некорректный счетчмк идентификаторов после загрузки файла."); } @@ -122,7 +124,10 @@ void loadFromFile() { @Test void saveAndLoadEmptyManager() { int taskId = manager.addNewTask(new Task("Task 1", - "Description task 1")); + "Description task 1", + LocalDateTime.now(), + Duration.ofMinutes(15))); + manager.removeTask(taskId); assertEquals(0, manager.getNumberOfObjects(), "Список задач не пуст."); @@ -145,8 +150,11 @@ void loadEmptyFile() { tmpFile.createNewFile(); // создаем пустой файл manager2 = FileBackedTaskManager.loadFromFile(tmpFile); assertNotNull(manager2, "Ошибка создания менеджера задач."); + int taskId = manager2.addNewTask(new Task("Task 1", - "Description task 1")); + "Description task 1", + LocalDateTime.now(), + Duration.ofMinutes(15))); assertEquals(1, manager2.getNumberOfObjects(), "Ошибка работы с созданным менеджером."); } catch (IOException e) { diff --git a/test/InMemoryHistoryManagerTest.java b/test/managers/InMemoryHistoryManagerTest.java similarity index 99% rename from test/InMemoryHistoryManagerTest.java rename to test/managers/InMemoryHistoryManagerTest.java index a25b666..08911ca 100644 --- a/test/InMemoryHistoryManagerTest.java +++ b/test/managers/InMemoryHistoryManagerTest.java @@ -1,3 +1,5 @@ +package managers; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/test/InMemoryTaskManagerTest.java b/test/managers/InMemoryTaskManagerTest.java similarity index 92% rename from test/InMemoryTaskManagerTest.java rename to test/managers/InMemoryTaskManagerTest.java index e5ca8c2..acfa700 100644 --- a/test/InMemoryTaskManagerTest.java +++ b/test/managers/InMemoryTaskManagerTest.java @@ -1,4 +1,6 @@ -import exceptions.TaskCrossTimeException; +package managers; + +import exceptions.TimeIntersectionException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tasks.Epic; @@ -76,36 +78,40 @@ void timeConflict() { "task2", LocalDateTime.now().plusMinutes(10), Duration.ofMinutes(20)); - assertThrows(TaskCrossTimeException.class, + assertThrows(TimeIntersectionException.class, () -> { manager.addNewTask(task); }, - "Наложение времени задач должно приводить к исключению."); + "Наложение времени задач должно приводить к исключению.\n" + + task.toString()); // изменяем период задачи на перекрвыающий окончание существующей задачи task.setStartTime(LocalDateTime.now().plusMinutes(30)); - assertThrows(TaskCrossTimeException.class, + assertThrows(TimeIntersectionException.class, () -> { manager.addNewTask(task); }, - "Наложение времени задач должно приводить к исключению."); + "Наложение времени задач должно приводить к исключению.\n" + + task.toString()); // изменяем период задачи на вложенный во время выполнения существующей задачи task.setDuration(Duration.ofMinutes(5)); - assertThrows(TaskCrossTimeException.class, + assertThrows(TimeIntersectionException.class, () -> { manager.addNewTask(task); }, - "Наложение времени задач должно приводить к исключению."); + "Наложение времени задач должно приводить к исключению.\n" + + task.toString()); // изменяем период задачи на перекрвыающий все время выполнения существующей задачи task.setStartTime(LocalDateTime.now().plusMinutes(10)); task.setDuration(Duration.ofMinutes(50)); - assertThrows(TaskCrossTimeException.class, + assertThrows(TimeIntersectionException.class, () -> { manager.addNewTask(task); }, - "Наложение времени задач должно приводить к исключению."); + "Наложение времени задач должно приводить к исключению.\n" + + task.toString()); } /** diff --git a/test/ManagersTest.java b/test/managers/ManagersTest.java similarity index 96% rename from test/ManagersTest.java rename to test/managers/ManagersTest.java index 7792ab1..c9d1033 100644 --- a/test/ManagersTest.java +++ b/test/managers/ManagersTest.java @@ -1,3 +1,5 @@ +package managers; + import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/test/TaskManagerTest.java b/test/managers/TaskManagerTest.java similarity index 85% rename from test/TaskManagerTest.java rename to test/managers/TaskManagerTest.java index 1e3651b..1b0bbd2 100644 --- a/test/TaskManagerTest.java +++ b/test/managers/TaskManagerTest.java @@ -1,3 +1,6 @@ +package managers; + +import exceptions.NotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tasks.Epic; @@ -190,10 +193,17 @@ void removeTask() { "-", LocalDateTime.now(), Duration.ofMinutes(10))); + + assertTrue(!manager.getTaskList().isEmpty(), + "Отсутствует задача для удаления."); + manager.removeTask(taskId); - Task task = manager.getTask(taskId); - assertNull(task, "Задача не удалена."); + assertThrows(NotFoundException.class, + () -> { + manager.getTask(taskId); + }, + "Попытка чтения несуществующей задачи должна приводить к исключению."); } @Test @@ -206,13 +216,21 @@ void removeEpic() { LocalDateTime.now(), Duration.ofMinutes(30))); - manager.removeEpic(epicId); + assertTrue(!manager.getEpicList().isEmpty(), + "Отсутствует эпик для удаления."); - Epic epic = manager.getEpic(epicId); - Subtask subtask = manager.getSubtask(subtaskId); + manager.removeEpic(epicId); - assertNull(epic, "Эпик не удален."); - assertNull(subtask, "Подзадача удаленного эпика не удалена."); + assertThrows(NotFoundException.class, + () -> { + manager.getEpic(epicId); + }, + "Попытка чтения несуществующго эпика должна приводить к исключению."); + assertThrows(NotFoundException.class, + () -> { + manager.getSubtask(subtaskId); + }, + "Попытка чтения несуществующей подзадачи должна приводить к исключению."); } @Test @@ -225,11 +243,16 @@ void removeSubtask() { LocalDateTime.now(), Duration.ofMinutes(30))); - manager.removeSubtask(subtaskId); + assertTrue(!manager.getSubtaskList().isEmpty(), + "Отсутствует подзадача для удаления."); - Subtask subtask = manager.getSubtask(subtaskId); + manager.removeSubtask(subtaskId); - assertNull(subtask, "Подзадача не удалена."); + assertThrows(NotFoundException.class, + () -> { + manager.getSubtask(subtaskId); + }, + "Попытка чтения несуществующей подзадачи должна приводить к исключению."); } @Test @@ -292,9 +315,16 @@ void removeAllTasks() { manager.addNewTask(new Task("Test removeAllTasks task2", "2", LocalDateTime.now().plusMinutes(40), Duration.ofMinutes(30))); + assertTrue(!manager.getTaskList().isEmpty(), + "Отсутствуют задачи для удаления."); + manager.removeAllTasks(); - assertTrue(manager.getTaskList().isEmpty(), "Задачи не удалены."); + assertThrows(NotFoundException.class, + () -> { + manager.getTaskList(); + }, + "Попытка чтения пустого списка задач должна приводить к исключению."); } @Test @@ -307,11 +337,16 @@ void removeAllEpics() { "Test removeAllEpics Subtask1", "1", LocalDateTime.now(), Duration.ofMinutes(30))); + assertTrue(!manager.getEpicList().isEmpty(), + "Отсутствуют эпики для удаления."); + manager.removeAllEpics(); - assertTrue(manager.getEpicList().isEmpty(), "Эпики не удалены."); - assertTrue(manager.getSubtaskList().isEmpty(), - "Подзадачи удаленных эпиков не удалены."); + assertThrows(NotFoundException.class, + () -> { + manager.getEpicList(); + }, + "Попытка чтения пустого списка эпиков должна приводить к исключению."); } @Test @@ -331,15 +366,23 @@ void removeAllSubtasks() { "Test removeAllSubtasks Subtask3", "3", LocalDateTime.now().plusMinutes(80), Duration.ofMinutes(30))); + assertTrue(!manager.getSubtaskList().isEmpty(), + "Отсутствуют подзадачи для удаления."); + manager.removeAllSubtasks(); - assertTrue(manager.getSubtaskList().isEmpty(), "Подзадачи не удалены."); assertTrue(manager.getEpic(epicId).getSubtasks().isEmpty(), "не удалены идениификаторы подзадач эпика:\n" + manager.getEpic(epicId).toString()); assertTrue(manager.getEpic(epicId2).getSubtasks().isEmpty(), "не удалены идениификаторы подзадач эпика:\n" + manager.getEpic(epicId2).toString()); + + assertThrows(NotFoundException.class, + () -> { + manager.getSubtaskList(); + }, + "Попытка чтения пустого списка подзадач должна приводить к исключению."); } @Test