From 3a4a30800785e5009a9999798312d290f967cc8a Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Fri, 21 Nov 2025 17:04:15 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=20=D0=BA=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=BC=D1=83=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=B4=D0=B5=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/data/Endpoint.java | 38 ++++ src/data/Property.java | 12 + src/data/Task.java | 1 + src/manager/FileBackedTaskManager.java | 7 +- src/manager/InMemoryTaskManager.java | 47 ++-- src/manager/Managers.java | 18 +- src/manager/TaskManager.java | 27 +++ src/server/HttpTaskServer.java | 46 ++++ src/server/adapters/DurationAdapter.java | 30 +++ src/server/adapters/LocalDateTimeAdapter.java | 31 +++ src/server/handlers/BaseHttpHandler.java | 178 +++++++++++++++ src/server/handlers/EpicsHandler.java | 183 +++++++++++++++ src/server/handlers/HistoryHandler.java | 50 +++++ src/server/handlers/PrioritizedHandler.java | 50 +++++ src/server/handlers/SubtasksHandler.java | 146 ++++++++++++ src/server/handlers/TasksHandler.java | 149 +++++++++++++ test/manager/TaskManagerTest.java | 2 +- test/server/HttpTaskServerTest.java | 96 ++++++++ test/server/handlers/EpicsHandlerTest.java | 150 +++++++++++++ test/server/handlers/HistoryHandlerTest.java | 28 +++ .../handlers/PrioritizedHandlerTest.java | 38 ++++ test/server/handlers/SubtasksHandlerTest.java | 117 ++++++++++ test/server/handlers/TasksHandlerTest.java | 209 ++++++++++++++++++ test/server/tokens/TaskListTypeToken.java | 9 + 24 files changed, 1628 insertions(+), 34 deletions(-) create mode 100644 src/data/Endpoint.java create mode 100644 src/data/Property.java create mode 100644 src/server/HttpTaskServer.java create mode 100644 src/server/adapters/DurationAdapter.java create mode 100644 src/server/adapters/LocalDateTimeAdapter.java create mode 100644 src/server/handlers/BaseHttpHandler.java create mode 100644 src/server/handlers/EpicsHandler.java create mode 100644 src/server/handlers/HistoryHandler.java create mode 100644 src/server/handlers/PrioritizedHandler.java create mode 100644 src/server/handlers/SubtasksHandler.java create mode 100644 src/server/handlers/TasksHandler.java create mode 100644 test/server/HttpTaskServerTest.java create mode 100644 test/server/handlers/EpicsHandlerTest.java create mode 100644 test/server/handlers/HistoryHandlerTest.java create mode 100644 test/server/handlers/PrioritizedHandlerTest.java create mode 100644 test/server/handlers/SubtasksHandlerTest.java create mode 100644 test/server/handlers/TasksHandlerTest.java create mode 100644 test/server/tokens/TaskListTypeToken.java diff --git a/src/data/Endpoint.java b/src/data/Endpoint.java new file mode 100644 index 0000000..7bb54fd --- /dev/null +++ b/src/data/Endpoint.java @@ -0,0 +1,38 @@ +package data; + +public enum Endpoint { + + BASE, + + // Задачи + GET_TASK, // tasks/{id} + GET_ALL_TASKS, // tasks + POST_NEW_TASK, // tasks/{id} + POST_UPDATE_TASK, // tasks + DELETE_TASK, // tasks/{id} + + // Подзадачи + GET_SUBTASK, // subtasks/{id} + GET_ALL_SUBTASKS, // subtasks + POST_NEW_SUBTASK, // subtasks + POST_UPDATE_SUBTASK, // subtasks/{id} + DELETE_SUBTASK, // subtasks{id} + + GET_ALL_SUBTASKS_BY_EPIC, // epic/{id}/subtasks + + // Эпики + GET_EPIC, // epics/{id} + GET_ALL_EPICS, // epics + POST_NEW_EPIC, // epics + POST_UPDATE_EPIC, // epics/{id} + DELETE_EPIC, // epics/{id} + + // История + GET_HISTORY, // history + + // Приоритеты + GET_PRIORITIZED, // prioritized + + // Пустой + UNKNOWN +} diff --git a/src/data/Property.java b/src/data/Property.java new file mode 100644 index 0000000..e5dccb0 --- /dev/null +++ b/src/data/Property.java @@ -0,0 +1,12 @@ +package data; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.format.DateTimeFormatter; + +public interface Property { + Charset CHARSET = StandardCharsets.UTF_8; + DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + int PORT = 8080; + +} diff --git a/src/data/Task.java b/src/data/Task.java index ba57ee2..542f202 100644 --- a/src/data/Task.java +++ b/src/data/Task.java @@ -25,6 +25,7 @@ public class Task implements Comparable { /// Продолжительность задачи в минутах protected Duration duration; + /// Формат даты и времени protected static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); /// Конструктор 1 diff --git a/src/manager/FileBackedTaskManager.java b/src/manager/FileBackedTaskManager.java index 3b5265f..43dc13a 100644 --- a/src/manager/FileBackedTaskManager.java +++ b/src/manager/FileBackedTaskManager.java @@ -4,16 +4,13 @@ import exception.ManagerSaveException; import java.io.*; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.*; -public class FileBackedTaskManager extends InMemoryTaskManager { +public class FileBackedTaskManager extends InMemoryTaskManager implements Property { private static File file; private static FileBackedTaskManager instance; @@ -21,8 +18,6 @@ public class FileBackedTaskManager extends InMemoryTaskManager { /// Параметры CSV файла // 0 1 2 3 4 5 6 7 8 private static final String TABLE_HEADER = "id,type,name,status,description,start,end,duration,epic"; - private static final Charset CHARSET = StandardCharsets.UTF_8; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private static final String DELIMITER = ","; private static final String QUOTE = "\""; diff --git a/src/manager/InMemoryTaskManager.java b/src/manager/InMemoryTaskManager.java index 63b35fe..02633df 100644 --- a/src/manager/InMemoryTaskManager.java +++ b/src/manager/InMemoryTaskManager.java @@ -7,7 +7,9 @@ import java.util.*; import java.util.stream.Stream; -class InMemoryTaskManager implements TaskManager { +public class InMemoryTaskManager implements TaskManager { + + private static InMemoryTaskManager instance; /// Хранилище задач protected final Map idToTask = new HashMap<>(); @@ -32,7 +34,18 @@ private int generateNewId() { return counter++; } - /// Получить объект по id с сохранением в историю + /// Конструктор + public InMemoryTaskManager() { + } + + /// Получить синглтон класса + public static InMemoryTaskManager getInstance() { + if (instance == null) { + instance = new InMemoryTaskManager(); + } + return instance; + } + @Override public Task get(int id) { if (idToTask.containsKey(id)) { @@ -52,7 +65,6 @@ public Task get(int id) { return null; } - /// Получить объект по id без сохранения в историю @Override public Task getWithoutHistory(int id) { if (idToTask.containsKey(id)) { @@ -66,7 +78,6 @@ public Task getWithoutHistory(int id) { return null; } - /// Получить тип объекта по id @Override public Type getType(int id) { Task taskObject = getWithoutHistory(id); @@ -76,7 +87,6 @@ public Type getType(int id) { return taskObject.getType(); } - /// Получить все объекты из всех хранилищ public TreeSet getAll() { if (counter == 1) { return new TreeSet<>(); @@ -89,7 +99,6 @@ public TreeSet getAll() { return new TreeSet<>(all); } - /// Внесение задачи в хранилище по объекту @Override public Task addTask(Task newTask) { if (newTask.getType() != Type.TASK) { @@ -116,7 +125,6 @@ public Task addTask(Task newTask) { return newTask; } - /// Внесение эпика в хранилище по объекту @Override public Epic addEpic(Epic newEpic) { if (newEpic.getType() != Type.EPIC) { @@ -137,7 +145,6 @@ public Epic addEpic(Epic newEpic) { return newEpic; } - /// Внесение подзадачи в хранилище по объекту @Override public Subtask addSubtask(Subtask newSubtask) { Integer epicId = newSubtask.getEpicId(); @@ -172,7 +179,6 @@ public Subtask addSubtask(Subtask newSubtask) { return newSubtask; } - /// Получение задачи по ID с сохранением в историю @Override public Task getTask(int taskID) { Task newTask = copyOfTask(taskID); @@ -183,7 +189,6 @@ public Task getTask(int taskID) { return newTask; } - /// Получение эпика по ID с сохранением в историю @Override public Epic getEpic(int epicID) { Epic newEpic = copyOfEpic(epicID); @@ -194,7 +199,6 @@ public Epic getEpic(int epicID) { return newEpic; } - /// Получение подзадачи по ID с сохранением в историю @Override public Subtask getSubtask(int subTaskID) { Subtask newSubtask = copyOfSubtask(subTaskID); @@ -229,25 +233,22 @@ private Subtask copyOfSubtask(int subtaskId) { return idToSubtask.get(subtaskId).getCopy(); } - /// Получение списка всех задач в виде объекта @Override public List getAllTasks() { return idToTask.values().stream().sorted(Task::compareTo).toList(); } - /// Получение списка всех эпиков в виде объекта + @Override public List getAllEpics() { return idToEpic.values().stream().sorted(Task::compareTo).toList(); } - /// Получение списка всех подзадач в виде объекта @Override public List getAllSubtasks() { return idToSubtask.values().stream().sorted(Task::compareTo).toList(); } - /// Получение списка всех подзадач эпика в виде объекта @Override public List getAllSubTasksByEpic(int epicId) { if (!idToEpic.containsKey(epicId)) { @@ -259,7 +260,11 @@ public List getAllSubTasksByEpic(int epicId) { .toList(); } - /// Обновление задачи по объекту + @Override + public List getPrioritizedTasks() { + return new ArrayList<>(prioritizedTasks); + } + @Override public Task updateTask(Task newTask) { Integer id = newTask.getId(); @@ -312,7 +317,6 @@ public Task updateTask(Task newTask) { return newTask; } - /// Обновление эпика по объекту @Override public Epic updateEpic(Epic newEpic) { Integer id = newEpic.getId(); @@ -336,7 +340,6 @@ public Epic updateEpic(Epic newEpic) { return newEpic; } - /// Обновление подзадачи по объекту @Override public Subtask updateSubtask(Subtask newSubtask) { Integer subtaskId = newSubtask.getId(); @@ -514,7 +517,6 @@ public List findIntersections(Task taskObject) { .toList(); } - /// Удаление задачи по ID @Override public boolean removeTask(int taskId) { if (!idToTask.containsKey(taskId)) { @@ -526,7 +528,6 @@ public boolean removeTask(int taskId) { return true; } - /// Удаление эпика по ID @Override public boolean removeEpic(int epicId) { if (!idToEpic.containsKey(epicId)) { @@ -538,7 +539,6 @@ public boolean removeEpic(int epicId) { return true; } - /// Удаление подзадачи по ID @Override public boolean removeSubtask(int subtaskId) { if (!idToSubtask.containsKey(subtaskId)) { @@ -556,7 +556,6 @@ public boolean removeSubtask(int subtaskId) { return true; } - /// Удаление всех подзадач эпика по ID эпика @Override public void removeAllSubtasksByEpic(int epicId) { if (!idToEpic.containsKey(epicId)) { @@ -573,7 +572,6 @@ public void removeAllSubtasksByEpic(int epicId) { clearEpicTime(epic); } - /// Удаление всех задач @Override public void removeAllTasks() { for (int taskId : idToTask.keySet()) { @@ -583,7 +581,6 @@ public void removeAllTasks() { idToTask.clear(); } - /// Удаление всех эпиков @Override public void removeAllEpics() { for (int epicId : idToEpic.keySet()) { @@ -598,7 +595,6 @@ public void removeAllEpics() { idToSubtask.clear(); } - /// Удаление всех подзадач @Override public void removeAllSubTasks() { for (int subtaskId : idToSubtask.keySet()) { @@ -614,7 +610,6 @@ public void removeAllSubTasks() { } } - /// Получить список с историей @Override public List getHistory() { return historyManager.getHistory(); diff --git a/src/manager/Managers.java b/src/manager/Managers.java index f4ab859..bbd2726 100644 --- a/src/manager/Managers.java +++ b/src/manager/Managers.java @@ -1,13 +1,20 @@ package manager; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import server.adapters.DurationAdapter; +import server.adapters.LocalDateTimeAdapter; + import java.io.File; +import java.time.Duration; +import java.time.LocalDateTime; public class Managers { public static TaskManager getTaskManager(TaskManagerType type) { switch (type) { case IN_MEMORY -> { - return new InMemoryTaskManager(); + return InMemoryTaskManager.getInstance(); } case FILE_BACKED -> { return FileBackedTaskManager.getInstance(new File( @@ -24,4 +31,13 @@ public static TaskManager getTaskManager(TaskManagerType type) { public static HistoryManager getHistoryManager() { return new InMemoryHistoryManager(); } + + public static Gson getGson() { + return new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) + .registerTypeAdapter(Duration.class, new DurationAdapter()) + .create(); + } } diff --git a/src/manager/TaskManager.java b/src/manager/TaskManager.java index 7b4998d..73354a0 100644 --- a/src/manager/TaskManager.java +++ b/src/manager/TaskManager.java @@ -10,53 +10,80 @@ public interface TaskManager { + /// Внесение задачи в хранилище по объекту Task addTask(Task newTask); + /// Внесение эпика в хранилище по объекту Epic addEpic(Epic newEpic); + /// Внесение подзадачи в хранилище по объекту Subtask addSubtask(Subtask newSubtask); + /// Получение задачи по ID с сохранением в историю Task getTask(int taskID); + /// Получение эпика по ID с сохранением в историю Epic getEpic(int epicID); + /// Получение подзадачи по ID с сохранением в историю Subtask getSubtask(int subTaskID); + /// Получение списка всех задач в виде объекта List getAllTasks(); + /// Получение списка всех эпиков в виде объекта List getAllEpics(); + /// Получение списка всех подзадач в виде объекта List getAllSubtasks(); + /// Получение списка всех подзадач эпика в виде объекта List getAllSubTasksByEpic(int epicId); + List getPrioritizedTasks(); + + /// Обновление задачи по объекту, в случае неудачи возвращает null Task updateTask(Task newTask); + /// Обновление эпика по объекту, в случае неудачи возвращает null Epic updateEpic(Epic newEpic); + /// Обновление подзадачи по объекту, в случае неудачи возвращает null Subtask updateSubtask(Subtask newSubtask); + /// Удаление задачи по ID boolean removeTask(int taskId); + /// Удаление эпика по ID boolean removeEpic(int epicId); + /// Удаление подзадачи по ID boolean removeSubtask(int subtaskId); + /// Удаление всех подзадач эпика по ID эпика void removeAllSubtasksByEpic(int epicId); + /// Удаление всех задач void removeAllTasks(); + /// Удаление всех эпиков void removeAllEpics(); + /// Удаление всех подзадач void removeAllSubTasks(); + /// Получить список с историей List getHistory(); + /// Получить все объекты из всех хранилищ TreeSet getAll(); + /// Получить объект по id с сохранением в историю Task get(int id); + /// Получить объект по id без сохранения в историю Task getWithoutHistory(int id); + /// Получить тип объекта по id Type getType(int id); } diff --git a/src/server/HttpTaskServer.java b/src/server/HttpTaskServer.java new file mode 100644 index 0000000..fb0c6a2 --- /dev/null +++ b/src/server/HttpTaskServer.java @@ -0,0 +1,46 @@ +package server; + +import com.sun.net.httpserver.HttpServer; +import data.Property; +import manager.Managers; +import manager.TaskManager; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import manager.TaskManagerType; +import server.handlers.*; + +public class HttpTaskServer implements Property { + + private static HttpServer httpServer; + private static TaskManager manager; + + HttpTaskServer(TaskManager manager) throws IOException { + HttpTaskServer.manager = manager; + httpServer = HttpServer.create(); + } + + public static void main(String[] args) throws IOException { + HttpTaskServer server = new HttpTaskServer(Managers.getTaskManager(TaskManagerType.FILE_BACKED)); + server.start(); + } + + public void start() throws IOException { + httpServer.bind(new InetSocketAddress(PORT), 0); + httpServer.createContext("/tasks", new TasksHandler(manager)); + httpServer.createContext("/subtasks", new SubtasksHandler(manager)); + httpServer.createContext("/epics", new EpicsHandler(manager)); + httpServer.createContext("/history", new HistoryHandler(manager)); + httpServer.createContext("/prioritized", new PrioritizedHandler(manager)); + httpServer.createContext("/", new BaseHttpHandler(manager)); + httpServer.start(); + + System.out.println("HTTP-сервер запущен на http://localhost:" + PORT + '/'); + } + + public void stop() { + httpServer.stop(1); + } + +} diff --git a/src/server/adapters/DurationAdapter.java b/src/server/adapters/DurationAdapter.java new file mode 100644 index 0000000..b3b45de --- /dev/null +++ b/src/server/adapters/DurationAdapter.java @@ -0,0 +1,30 @@ +package server.adapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +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.nullValue(); + return; + } + jsonWriter.value(duration.toMinutes()); + } + + @Override + public Duration read(final JsonReader jsonReader) throws IOException { + JsonToken peek = jsonReader.peek(); + if (peek.name().equals("NULL")) { + jsonReader.nextNull(); + return null; + } + return Duration.ofMinutes(jsonReader.nextInt()); + } +} diff --git a/src/server/adapters/LocalDateTimeAdapter.java b/src/server/adapters/LocalDateTimeAdapter.java new file mode 100644 index 0000000..7617909 --- /dev/null +++ b/src/server/adapters/LocalDateTimeAdapter.java @@ -0,0 +1,31 @@ +package server.adapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import data.Property; + +import java.io.IOException; +import java.time.LocalDateTime; + +public class LocalDateTimeAdapter extends TypeAdapter implements Property { + @Override + public void write(final JsonWriter jsonWriter, final LocalDateTime dateTime) throws IOException { + if (dateTime == null) { + jsonWriter.nullValue(); + return; + } + jsonWriter.value(dateTime.format(DATE_TIME_FORMATTER)); + } + + @Override + public LocalDateTime read(final JsonReader jsonReader) throws IOException { + JsonToken peek = jsonReader.peek(); + if (peek.name().equals("NULL")) { + jsonReader.nextNull(); + return null; + } + return LocalDateTime.parse(jsonReader.nextString(), DATE_TIME_FORMATTER); + } +} diff --git a/src/server/handlers/BaseHttpHandler.java b/src/server/handlers/BaseHttpHandler.java new file mode 100644 index 0000000..bcc3c53 --- /dev/null +++ b/src/server/handlers/BaseHttpHandler.java @@ -0,0 +1,178 @@ +package server.handlers; + +import com.google.gson.Gson; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import data.Endpoint; +import data.Property; +import data.Task; +import manager.Managers; +import manager.TaskManager; + +import java.io.IOException; +import java.util.regex.Pattern; + +public class BaseHttpHandler implements HttpHandler, Property { + + protected final TaskManager manager; + protected static Gson gson; + + public BaseHttpHandler(TaskManager manager) { + this.manager = manager; + gson = Managers.getGson(); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + Endpoint endpoint = getEndpoint(method, path); + + switch (endpoint) { + case BASE -> { + String text = + "\tWelcome to java server.\n" + + "\tUse next endpoints:\n" + + "\thttp://localhost:" + PORT + "/tasks\n" + + "\thttp://localhost:" + PORT + "/subtasks\n" + + "\thttp://localhost:" + PORT + "/epics\n" + + "\thttp://localhost:" + PORT + "/history\n" + + "\thttp://localhost:" + PORT + ":/prioritized"; + + sendText(exchange, Endpoint.BASE, text); + } + + case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + } + } + + /// Для отправки общего ответа в случае успеха + protected void sendText(HttpExchange exchange, Endpoint endpoint, String text) throws IOException { + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(200, resp.length); + exchange.getResponseBody().write(resp); + System.out.println("\t" + endpoint + " 200 success"); + exchange.close(); + } + + /// Для отправки ответа в случае, если объект не был найден + protected void sendNotFound(HttpExchange exchange, Endpoint endpoint) throws IOException { + String text = ("\t" + endpoint + " 404 not found"); + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(404, resp.length); + exchange.getResponseBody().write(resp); + System.out.println(text); + exchange.close(); + } + + /// Для отправки ответа в случае, если объект не был найден + protected void sendFormatException(HttpExchange exchange, Endpoint endpoint, String path) throws IOException { + String text = ("\t" + endpoint + " 405 path format exception: " + path); + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(405, resp.length); + exchange.getResponseBody().write(resp); + System.out.println(text); + exchange.close(); + } + + /// Для отправки ответа, если при создании или обновлении задача пересекается с уже существующими + protected void sendHasOverlaps(HttpExchange exchange, Endpoint endpoint, Task taskObject) throws IOException { + String text = ("\t" + endpoint + " 406 time overlaps: " + + taskObject.getStartTime().format(DATE_TIME_FORMATTER) + " - " + + taskObject.getEndTime().format(DATE_TIME_FORMATTER)); + + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(406, resp.length); + exchange.getResponseBody().write(resp); + System.out.println(text); + exchange.close(); + } + + /// Для отправки ответа, если произошла ошибка на сервере + protected void sendServerError(HttpExchange exchange, Endpoint endpoint) throws IOException { + String text = ("\t" + endpoint + " 500 Internal Server Error"); + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(500, resp.length); + exchange.getResponseBody().write(resp); + System.out.println(text); + exchange.close(); + } + + protected static int parsePathId(String pathId) { + try { + return Integer.parseInt(pathId); + } catch (NumberFormatException exception) { + return -1; + } + } + + protected Endpoint getEndpoint(String method, String path) { + + switch (method) { + case "GET": { + if (Pattern.matches("^/$", path)) { + return Endpoint.BASE; + } + + if (Pattern.matches("^/tasks/\\d+$", path)) { + return Endpoint.GET_TASK; + } else if (Pattern.matches("^/tasks$", path)) { + return Endpoint.GET_ALL_TASKS; + } else if (Pattern.matches("^/subtasks/\\d+$", path)) { + return Endpoint.GET_SUBTASK; + } else if (Pattern.matches("^/subtasks$", path)) { + return Endpoint.GET_ALL_SUBTASKS; + } else if (Pattern.matches("^/epics/\\d+/subtasks$", path)) { + return Endpoint.GET_ALL_SUBTASKS_BY_EPIC; + } else if (Pattern.matches("^/epics/\\d+$", path)) { + return Endpoint.GET_EPIC; + } else if (Pattern.matches("^/epics$", path)) { + return Endpoint.GET_ALL_EPICS; + } else if (Pattern.matches("^/history$", path)) { + return Endpoint.GET_HISTORY; + } else if (Pattern.matches("^/prioritized$", path)) { + return Endpoint.GET_PRIORITIZED; + } + break; + } + case "POST": { + if (Pattern.matches("^/tasks/\\d+$", path)) { + return Endpoint.POST_UPDATE_TASK; + } else if (Pattern.matches("^/tasks$", path)) { + return Endpoint.POST_NEW_TASK; + } else if (Pattern.matches("^/subtasks/\\d+$", path)) { + return Endpoint.POST_UPDATE_SUBTASK; + } else if (Pattern.matches("^/subtasks$", path)) { + return Endpoint.POST_NEW_SUBTASK; + } else if (Pattern.matches("^/epics/\\d+$", path)) { + return Endpoint.POST_UPDATE_EPIC; + } else if (Pattern.matches("^/epics$", path)) { + return Endpoint.POST_NEW_EPIC; + } + break; + } + case "DELETE": { + if (Pattern.matches("^/tasks/\\d+$", path)) { + return Endpoint.DELETE_TASK; + } else if (Pattern.matches("^/subtasks/\\d+$", path)) { + return Endpoint.DELETE_SUBTASK; + } else if (Pattern.matches("^/epics/\\d+$", path)) { + return Endpoint.DELETE_EPIC; + } + break; + } + default: { + return Endpoint.UNKNOWN; + } + } + + return Endpoint.UNKNOWN; + } + +} diff --git a/src/server/handlers/EpicsHandler.java b/src/server/handlers/EpicsHandler.java new file mode 100644 index 0000000..9b35395 --- /dev/null +++ b/src/server/handlers/EpicsHandler.java @@ -0,0 +1,183 @@ +package server.handlers; + +import com.sun.net.httpserver.HttpExchange; +import data.Endpoint; +import data.Epic; +import data.Subtask; +import manager.TaskManager; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class EpicsHandler extends BaseHttpHandler { + + public EpicsHandler(TaskManager manager) { + super(manager); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + Endpoint endpoint = getEndpoint(method, path); + + switch (endpoint) { + + case GET_EPIC -> { + + String pathId = path.replaceFirst("/epics/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.GET_EPIC, path); + return; + } + + Epic epic = manager.getEpic(id); + if (epic == null) { + sendNotFound(exchange, Endpoint.GET_EPIC); + return; + } + + String jsonEpic; + try { + jsonEpic = gson.toJson(epic); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_EPIC); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_EPIC, jsonEpic); + + } + + case GET_ALL_EPICS -> { + + List epicList = manager.getAllEpics(); + if (epicList.isEmpty()) { + sendNotFound(exchange, Endpoint.GET_ALL_EPICS); + return; + } + + String jsonEpicList; + try { + jsonEpicList = gson.toJson(epicList); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_ALL_EPICS); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_ALL_EPICS, jsonEpicList); + + } + + case POST_NEW_EPIC -> { + + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), CHARSET); + + Epic epic; + try { + epic = gson.fromJson(body, Epic.class); + } catch (Exception e) { + sendServerError(exchange, Endpoint.POST_NEW_EPIC); + System.out.println(e.getMessage()); + return; + } + + if (manager.addEpic(epic) == null) { + sendHasOverlaps(exchange, Endpoint.POST_NEW_EPIC, epic); + } else { + sendText(exchange, Endpoint.POST_NEW_EPIC,"epic adding success"); + } + + } + + case POST_UPDATE_EPIC -> { + + String pathId = path.replaceFirst("/epics/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.POST_UPDATE_EPIC, path); + return; + } + + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), CHARSET); + + Epic epic; + try { + epic = gson.fromJson(body, Epic.class); + } catch (Exception e) { + sendServerError(exchange, Endpoint.POST_UPDATE_EPIC); + System.out.println(e.getMessage()); + return; + } + + if (manager.updateEpic(epic) == null) { + sendNotFound(exchange, Endpoint.POST_UPDATE_EPIC); + } else { + sendText(exchange, Endpoint.POST_UPDATE_EPIC,"epic updated success"); + } + + } + + case DELETE_EPIC -> { + + String pathId = path.replaceFirst("/epics/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.DELETE_EPIC, path); + return; + } + + if (manager.removeEpic(id)) { + sendText(exchange, Endpoint.DELETE_EPIC, "epic removed success"); + } else { + sendServerError(exchange, Endpoint.DELETE_EPIC); + } + + } + + case GET_ALL_SUBTASKS_BY_EPIC -> { + + String pathId = path.replaceFirst("/epics/", ""); + pathId = pathId.replaceFirst("/subtasks", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC, path); + return; + } + + Epic epic = manager.getEpic(id); + if (epic == null) { + sendNotFound(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC); + return; + } + + List subtaskList = manager.getAllSubTasksByEpic(id); + if (subtaskList.isEmpty()) { + sendNotFound(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC); + return; + } + + String jsonSubtaskList; + try { + jsonSubtaskList = gson.toJson(subtaskList); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC, jsonSubtaskList); + + } + + case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + } + } +} diff --git a/src/server/handlers/HistoryHandler.java b/src/server/handlers/HistoryHandler.java new file mode 100644 index 0000000..c8883af --- /dev/null +++ b/src/server/handlers/HistoryHandler.java @@ -0,0 +1,50 @@ +package server.handlers; + +import com.sun.net.httpserver.HttpExchange; +import data.Endpoint; +import data.Task; +import manager.TaskManager; + +import java.io.IOException; +import java.util.List; + +public class HistoryHandler extends BaseHttpHandler { + + public HistoryHandler(TaskManager manager) { + super(manager); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + Endpoint endpoint = getEndpoint(method, path); + + switch (endpoint) { + + case GET_HISTORY -> { + + List historyList = manager.getHistory(); + if (historyList.isEmpty()) { + sendNotFound(exchange, Endpoint.GET_HISTORY); + return; + } + + String jsonHistoryList; + try { + jsonHistoryList = gson.toJson(historyList); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_HISTORY); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_HISTORY, jsonHistoryList); + + } + + case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + } + } +} diff --git a/src/server/handlers/PrioritizedHandler.java b/src/server/handlers/PrioritizedHandler.java new file mode 100644 index 0000000..759fc79 --- /dev/null +++ b/src/server/handlers/PrioritizedHandler.java @@ -0,0 +1,50 @@ +package server.handlers; + +import com.sun.net.httpserver.HttpExchange; +import data.Endpoint; +import data.Task; +import manager.TaskManager; + +import java.io.IOException; +import java.util.List; + +public class PrioritizedHandler extends BaseHttpHandler { + + public PrioritizedHandler(TaskManager manager) { + super(manager); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + Endpoint endpoint = getEndpoint(method, path); + + switch (endpoint) { + + case GET_PRIORITIZED -> { + + List prioritizedList = manager.getPrioritizedTasks(); + if (prioritizedList.isEmpty()) { + sendNotFound(exchange, Endpoint.GET_PRIORITIZED); + return; + } + + String jsonPrioritizedList; + try { + jsonPrioritizedList = gson.toJson(prioritizedList); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_PRIORITIZED); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_PRIORITIZED, jsonPrioritizedList); + + } + + case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + } + } +} diff --git a/src/server/handlers/SubtasksHandler.java b/src/server/handlers/SubtasksHandler.java new file mode 100644 index 0000000..4da73af --- /dev/null +++ b/src/server/handlers/SubtasksHandler.java @@ -0,0 +1,146 @@ +package server.handlers; + +import com.sun.net.httpserver.HttpExchange; +import data.Endpoint; +import data.Subtask; +import manager.TaskManager; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class SubtasksHandler extends BaseHttpHandler { + + public SubtasksHandler(TaskManager manager) { + super(manager); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + Endpoint endpoint = getEndpoint(method, path); + + switch (endpoint) { + + case GET_SUBTASK -> { + + String pathId = path.replaceFirst("/subtasks/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.GET_SUBTASK, path); + return; + } + + Subtask subtask = manager.getSubtask(id); + if (subtask == null) { + sendNotFound(exchange, Endpoint.GET_SUBTASK); + return; + } + + String jsonSubtask; + try { + jsonSubtask = gson.toJson(subtask); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_SUBTASK); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_SUBTASK, jsonSubtask); + + } + + case GET_ALL_SUBTASKS -> { + + List subtaskList = manager.getAllSubtasks(); + if (subtaskList.isEmpty()) { + sendNotFound(exchange, Endpoint.GET_ALL_SUBTASKS); + return; + } + + String jsonSubtaskList; + try { + jsonSubtaskList = gson.toJson(subtaskList); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_ALL_SUBTASKS); + System.out.println(exception.getMessage()); + return; + } + sendText(exchange, Endpoint.GET_ALL_SUBTASKS, jsonSubtaskList); + + } + + case POST_NEW_SUBTASK -> { + + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), CHARSET); + + Subtask subtask; + try { + subtask = gson.fromJson(body, Subtask.class); + } catch (Exception e) { + sendServerError(exchange, Endpoint.POST_NEW_SUBTASK); + System.out.println(e.getMessage()); + return; + } + + if (manager.addSubtask(subtask) == null) { + sendHasOverlaps(exchange, Endpoint.POST_NEW_SUBTASK, subtask); + } else { + sendText(exchange, Endpoint.POST_NEW_SUBTASK,"subtask adding success"); + } + + } + + case POST_UPDATE_SUBTASK -> { + + String pathId = path.replaceFirst("/subtasks/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.POST_UPDATE_SUBTASK, path); + return; + } + + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), CHARSET); + + Subtask subtask; + try { + subtask = gson.fromJson(body, Subtask.class); + } catch (Exception e) { + sendServerError(exchange, Endpoint.POST_UPDATE_SUBTASK); + System.out.println(e.getMessage()); + return; + } + + if (manager.updateSubtask(subtask) == null) { + sendNotFound(exchange, Endpoint.POST_UPDATE_SUBTASK); + } else { + sendText(exchange, Endpoint.POST_UPDATE_SUBTASK,"subtask updated success"); + } + + } + + case DELETE_SUBTASK -> { + + String pathId = path.replaceFirst("/subtasks/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.DELETE_SUBTASK, path); + return; + } + + if (manager.removeSubtask(id)) { + sendText(exchange, Endpoint.DELETE_SUBTASK, "subtask removed success"); + } else { + sendServerError(exchange, Endpoint.DELETE_SUBTASK); + } + + } + + case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + } + } +} diff --git a/src/server/handlers/TasksHandler.java b/src/server/handlers/TasksHandler.java new file mode 100644 index 0000000..37f804c --- /dev/null +++ b/src/server/handlers/TasksHandler.java @@ -0,0 +1,149 @@ +package server.handlers; + +import com.sun.net.httpserver.HttpExchange; +import data.Endpoint; +import data.Task; +import manager.TaskManager; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class TasksHandler extends BaseHttpHandler { + + public TasksHandler(TaskManager manager) { + super(manager); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + + Endpoint endpoint = getEndpoint(method, path); + + switch (endpoint) { + + case GET_TASK -> { + + String pathId = path.replaceFirst("/tasks/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.GET_TASK, path); + return; + } + + Task task = manager.getTask(id); + if (task == null) { + sendNotFound(exchange, Endpoint.GET_TASK); + return; + } + + String jsonTask; + try { + jsonTask = gson.toJson(task); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_TASK); + System.out.println(exception.getMessage()); + return; + } + + sendText(exchange, Endpoint.GET_TASK, jsonTask); + + } + + case GET_ALL_TASKS -> { + + List taskList = manager.getAllTasks(); + if (taskList.isEmpty()) { + sendNotFound(exchange, Endpoint.GET_ALL_TASKS); + return; + } + + String jsonTaskList; + try { + jsonTaskList = gson.toJson(taskList); + } catch (Exception exception) { + sendServerError(exchange, Endpoint.GET_ALL_TASKS); + System.out.println(exception.getMessage()); + return; + } + sendText(exchange, Endpoint.GET_ALL_TASKS, jsonTaskList); + + } + + case POST_NEW_TASK -> { + + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), CHARSET); + + Task task; + try { + task = gson.fromJson(body, Task.class); + } catch (Exception e) { + sendServerError(exchange, Endpoint.POST_NEW_TASK); + System.out.println(e.getMessage()); + return; + } + + if (manager.addTask(task) == null) { + sendHasOverlaps(exchange, Endpoint.POST_NEW_TASK, task); + } else { + sendText(exchange, Endpoint.POST_NEW_TASK,"task adding success"); + } + + } + + case POST_UPDATE_TASK -> { + + String pathId = path.replaceFirst("/tasks/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.POST_UPDATE_TASK, path); + return; + } + + InputStream bodyInputStream = exchange.getRequestBody(); + String body = new String(bodyInputStream.readAllBytes(), CHARSET); + + Task task; + try { + task = gson.fromJson(body, Task.class); + } catch (Exception e) { + sendServerError(exchange, Endpoint.POST_UPDATE_TASK); + System.out.println(e.getMessage()); + return; + } + + if (manager.updateTask(task) == null) { + sendNotFound(exchange, Endpoint.POST_UPDATE_TASK); + } else { + sendText(exchange, Endpoint.POST_UPDATE_TASK,"task updated success"); + } + + } + + case DELETE_TASK -> { + + String pathId = path.replaceFirst("/tasks/", ""); + int id = parsePathId(pathId); + if (id == -1) { + sendFormatException(exchange, Endpoint.DELETE_TASK, path); + return; + } + + if (manager.removeTask(id)) { + sendText(exchange, Endpoint.DELETE_TASK, "task removed success"); + } else { + sendServerError(exchange, Endpoint.DELETE_TASK); + } + + } + + case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + } + + + } + +} diff --git a/test/manager/TaskManagerTest.java b/test/manager/TaskManagerTest.java index fa2186e..5f51124 100644 --- a/test/manager/TaskManagerTest.java +++ b/test/manager/TaskManagerTest.java @@ -40,7 +40,7 @@ void updateTaskHistory(List history) { @BeforeEach void beforeEach() { // Создать новый менеджер задач - manager = Managers.getTaskManager(TaskManagerType.IN_MEMORY); + manager = new InMemoryTaskManager(); // Создать новую задачу task = new Task( diff --git a/test/server/HttpTaskServerTest.java b/test/server/HttpTaskServerTest.java new file mode 100644 index 0000000..102e4e3 --- /dev/null +++ b/test/server/HttpTaskServerTest.java @@ -0,0 +1,96 @@ +package server; + +import com.google.gson.Gson; +import data.Property; +import data.Task; +import manager.InMemoryTaskManager; +import manager.Managers; +import manager.TaskManager; +import manager.TaskManagerType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public abstract class HttpTaskServerTest implements Property { + protected TaskManager manager; + protected HttpTaskServer taskServer; + protected Gson gson; + protected final String host = "http://localhost:" + PORT + '/'; + + protected Task task1; + protected Task task2; + + @BeforeEach + void beforeEach() throws IOException { + manager = new InMemoryTaskManager(); + taskServer = new HttpTaskServer(manager); + gson = Managers.getGson(); + taskServer.start(); + + task1 = new Task(null, "FIRST TASK", "first task"); + task2 = new Task(null, "SECOND TASK", "second task"); + manager.addTask(task1); + manager.addTask(task2); + } + + @AfterEach + void afterEach() { + taskServer.stop(); + } + + /// Отправляет GET-запрос и возвращает ответ сервера + protected HttpResponse responseOfNewGetRequest(String stringUri) { + URI uri = URI.create(host + stringUri); + + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder().GET().uri(uri).build(); + HttpResponse.BodyHandler handler = HttpResponse.BodyHandlers.ofString(); + return client.send(request, handler); + + } catch (Exception exception) { + exception.printStackTrace(); + return null; + } + } + + /// Отправляет POST-запрос и возвращает ответ сервера + protected HttpResponse responseOfNewPostRequest(String stringUri, T object) { + URI uri = URI.create(host + stringUri); + + try (HttpClient client = HttpClient.newHttpClient()) { + String objectToJson = gson.toJson(object); + + HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(objectToJson)) + .uri(uri) + .build(); + HttpResponse.BodyHandler handler = HttpResponse.BodyHandlers.ofString(); + return client.send(request, handler); + + } catch (Exception exception) { + exception.printStackTrace(); + return null; + } + } + + /// Отправляет DELETE-запрос и возвращает ответ сервера + protected HttpResponse responseOfNewDeleteRequest(String stringUri) { + URI uri = URI.create(host + stringUri); + + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder().DELETE().uri(uri).build(); + HttpResponse.BodyHandler handler = HttpResponse.BodyHandlers.ofString(); + return client.send(request, handler); + + } catch (Exception exception) { + exception.printStackTrace(); + return null; + } + } + +} diff --git a/test/server/handlers/EpicsHandlerTest.java b/test/server/handlers/EpicsHandlerTest.java new file mode 100644 index 0000000..2028e0b --- /dev/null +++ b/test/server/handlers/EpicsHandlerTest.java @@ -0,0 +1,150 @@ +package server.handlers; + +import data.Epic; +import data.Status; +import data.Subtask; +import data.Task; +import org.junit.jupiter.api.Test; +import server.HttpTaskServerTest; +import server.tokens.TaskListTypeToken; + +import java.net.http.HttpResponse; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EpicsHandlerTest extends HttpTaskServerTest { + + @Test + void GET_EPIC() { + shouldReturnStatus200AndBodyOfEpic3AfterGetEpic3(); + shouldReturnStatus404AndBodyOfNotFoundAfterGetEpic1(); + } + + @Test + void GET_ALL_EPICS() { + shouldReturnStatus200AndListOfTwoElementsAfterGetAllEpics(); + } + + @Test + void POST_NEW_EPIC() { + shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewEpic(); + } + + @Test + void POST_UPDATE_EPIC() { + shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask(); + shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask(); + } + + @Test + void DELETE_EPIC() { + shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteEpic(); + } + + @Test + void GET_ALL_SUBTASKS_BY_EPIC() { + shouldReturnStatus200AndListOfTwoElementsAfterGetAllSubtasksByEpic(); + } + + + // ---------------------------------HOOD---------------------------------------- + + + /// Создает новый эпик, запрашивает его и сверяет поля + private void shouldReturnStatus200AndBodyOfEpic3AfterGetEpic3() { + manager.addEpic(new Epic(null, "FIRST EPIC", "first epic")); + + HttpResponse response = responseOfNewGetRequest("epics/3"); + + assertEquals(200, response.statusCode()); + + Epic requestEpic = gson.fromJson(response.body(), Epic.class); + assertEquals("FIRST EPIC", requestEpic.getTitle()); + assertEquals("first epic", requestEpic.getDescription()); + assertEquals(Status.NEW, requestEpic.getStatus()); + } + + /// Запрашивает эпик, но в id указан id задачи + private void shouldReturnStatus404AndBodyOfNotFoundAfterGetEpic1() { + HttpResponse response = responseOfNewGetRequest("epics/1"); + + assertEquals(404, response.statusCode()); + assertEquals("\tGET_EPIC 404 not found", response.body()); + } + + /// Создает два эпика, запрашивает все эпики, проверяет код ответа и количество + private void shouldReturnStatus200AndListOfTwoElementsAfterGetAllEpics() { + manager.addEpic(new Epic(null, "FIRST EPIC", "first epic")); + manager.addEpic(new Epic(null, "SECOND EPIC", "second epic")); + + HttpResponse response = responseOfNewGetRequest("epics"); + + assertEquals(200, response.statusCode()); + + List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); + assertEquals(2, requestList.size()); + } + + /// Постит новый эпик, проверяет код и тело ответа + private void shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewEpic() { + Epic epic = new Epic(null, "ADDED EPIC", "added epic"); + + HttpResponse response = responseOfNewPostRequest("epics", epic); + + assertEquals(200, response.statusCode()); + assertEquals("epic adding success", response.body()); + } + + /// Создает новый эпик, постит обновление этого эпика, проверяет статус и тело ответа + private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask() { + manager.addEpic(new Epic(null, "FIRST EPIC", "first epic")); + + Epic newEpic = new Epic(3, "FIRST EPIC UPDATED", null); + + HttpResponse response = responseOfNewPostRequest("epics/3", newEpic); + assertEquals(200, response.statusCode()); + + assertEquals("epic updated success", response.body()); + + } + + /// Запрашивает обновленный эпик, сверяет его поля, а также статус ответа + private void shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask() { + HttpResponse response = responseOfNewGetRequest("epics/3"); + assertEquals(200, response.statusCode()); + + Task requestTask = gson.fromJson(response.body(), Task.class); + assertEquals("FIRST EPIC UPDATED", requestTask.getTitle()); + assertEquals("first epic", requestTask.getDescription()); + } + + /// Удаляет эпик и проверяет ответ от сервера + private void shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteEpic() { + manager.addEpic(new Epic(null, "FIRST EPIC", "first epic")); + + HttpResponse response = responseOfNewDeleteRequest("epics/3"); + + assertEquals(200, response.statusCode()); + assertEquals("epic removed success", response.body()); + } + + /** + * Создает эпик с двумя подзадачами, запрашивает все подзадачи этого эпика, + * проверяет статус ответа и количество подзадач в ответе + */ + private void shouldReturnStatus200AndListOfTwoElementsAfterGetAllSubtasksByEpic() { + manager.addEpic(new Epic(null, "FIRST EPIC", "first epic")); + manager.addSubtask(new Subtask(null, "FIRST SUB", "first sub", + null, 3, null, null)); + manager.addSubtask(new Subtask(null, "SECOND SUB", "second sub", + null, 3, null, null)); + + HttpResponse response = responseOfNewGetRequest("epics/3/subtasks"); + + assertEquals(200, response.statusCode()); + + List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); + assertEquals(2, requestList.size()); + } +} diff --git a/test/server/handlers/HistoryHandlerTest.java b/test/server/handlers/HistoryHandlerTest.java new file mode 100644 index 0000000..1f44ea6 --- /dev/null +++ b/test/server/handlers/HistoryHandlerTest.java @@ -0,0 +1,28 @@ +package server.handlers; + +import data.Task; +import org.junit.jupiter.api.Test; +import server.HttpTaskServerTest; +import server.tokens.TaskListTypeToken; + +import java.net.http.HttpResponse; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HistoryHandlerTest extends HttpTaskServerTest { + + @Test + void GET_HISTORY() { + shouldReturnStatus200AndBodyOfHistoryTasks(); + } + + /// Добавляет задачу в историю, запрашивает список истории и проверяет количество + private void shouldReturnStatus200AndBodyOfHistoryTasks() { + manager.getTask(1); + + HttpResponse response = responseOfNewGetRequest("history"); + List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); + assertEquals(1, requestList.size()); + } +} diff --git a/test/server/handlers/PrioritizedHandlerTest.java b/test/server/handlers/PrioritizedHandlerTest.java new file mode 100644 index 0000000..c01a5ae --- /dev/null +++ b/test/server/handlers/PrioritizedHandlerTest.java @@ -0,0 +1,38 @@ +package server.handlers; + +import data.Task; +import org.junit.jupiter.api.Test; +import server.HttpTaskServerTest; +import server.tokens.TaskListTypeToken; + +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; + +public class PrioritizedHandlerTest extends HttpTaskServerTest { + + @Test + void GET_PRIORITIZED() { + shouldReturnStatus200AndBodyOfPrioritizedTasks(); + } + + /// Приоритизирует задачи, запрашивает их список и проверяет их количество + private void shouldReturnStatus200AndBodyOfPrioritizedTasks() { + manager.updateTask(new Task(1, null, null, null, + LocalDateTime.parse("2025-11-20 20:00", DATE_TIME_FORMATTER), + Duration.ofMinutes(30) + )); + + manager.updateTask(new Task(2, null, null, null, + LocalDateTime.parse("2025-11-20 20:30", DATE_TIME_FORMATTER), + Duration.ofMinutes(30) + )); + + HttpResponse response = responseOfNewGetRequest("prioritized"); + List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); + assertEquals(2, requestList.size()); + } +} diff --git a/test/server/handlers/SubtasksHandlerTest.java b/test/server/handlers/SubtasksHandlerTest.java new file mode 100644 index 0000000..a3eef60 --- /dev/null +++ b/test/server/handlers/SubtasksHandlerTest.java @@ -0,0 +1,117 @@ +package server.handlers; + +import data.Epic; +import data.Subtask; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import server.HttpTaskServerTest; +import server.tokens.TaskListTypeToken; + +import java.net.http.HttpResponse; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SubtasksHandlerTest extends HttpTaskServerTest { + + @BeforeEach + void subtaskBeforeEach() { + manager.addEpic(new Epic(3, "FIRST EPIC", "first epic")); + manager.addSubtask(new Subtask(4, "FIRST SUB", "first sub", + null, 3, null, null)); + manager.addSubtask(new Subtask(5, "SECOND SUB", "second sub", + null, 3, null, null)); + } + + @Test + void GET_SUBTASK() { + shouldReturnStatus200AndBodyOfSubtask4AfterGetSubtask4(); + } + + @Test + void GET_ALL_SUBTASKS() { + shouldReturnStatus200AndListOfTwoElementsAfterGetAllSubtasks(); + } + + @Test + void POST_NEW_SUBTASK() { + shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewSubtask(); + } + + @Test + void POST_UPDATE_SUBTASK() { + shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateSubtask(); + shouldReturnStatus200AndBodyOfUpdatedSubtaskAfterGetUpdatedSubtask(); + } + + @Test + void DELETE_SUBTASK() { + shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteSubtask(); + } + + + // ---------------------------------HOOD---------------------------------------- + + + /// Получает задачу из базы и сверяет все поля с оригиналом + private void shouldReturnStatus200AndBodyOfSubtask4AfterGetSubtask4() { + HttpResponse response = responseOfNewGetRequest("subtasks/4"); + + assertEquals(200, response.statusCode()); + + Subtask requestSubtask = gson.fromJson(response.body(), Subtask.class); + assertEquals("FIRST SUB", requestSubtask.getTitle()); + assertEquals("first sub", requestSubtask.getDescription()); + } + + /// Получает список всех подзадач и проверяет их количество + private void shouldReturnStatus200AndListOfTwoElementsAfterGetAllSubtasks() { + HttpResponse response = responseOfNewGetRequest("subtasks"); + + assertEquals(200, response.statusCode()); + + List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); + assertEquals(2, requestList.size()); + } + + /// Добавляет новую подзадачу и проверяет ответ сервера + private void shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewSubtask() { + Subtask subtask = new Subtask(null, "ADDED SUB", "added SUB", + null, 3, null, null); + + HttpResponse response = responseOfNewPostRequest("subtasks", subtask); + + assertEquals(200, response.statusCode()); + assertEquals("subtask adding success", response.body()); + } + + /// Обновляет существующую подзадачу и проверяет ответ от сервера + private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateSubtask() { + Subtask newSubtask = new Subtask(5, "SECOND SUB UPDATED", + null, null, null, null, null); + + HttpResponse response = responseOfNewPostRequest("subtasks/5", newSubtask); + assertEquals(200, response.statusCode()); + + assertEquals("subtask updated success", response.body()); + } + + /// Получает обновленную задачу с сервера и сверяет все поля + private void shouldReturnStatus200AndBodyOfUpdatedSubtaskAfterGetUpdatedSubtask() { + HttpResponse response = responseOfNewGetRequest("subtasks/5"); + assertEquals(200, response.statusCode()); + + Subtask requestTask = gson.fromJson(response.body(), Subtask.class); + assertEquals("SECOND SUB UPDATED", requestTask.getTitle()); + assertEquals("second sub", requestTask.getDescription()); + } + + /// Удаляет подзадачу и проверяет ответ от сервера + private void shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteSubtask() { + HttpResponse response = responseOfNewDeleteRequest("subtasks/4"); + + assertEquals(200, response.statusCode()); + assertEquals("subtask removed success", response.body()); + } + +} diff --git a/test/server/handlers/TasksHandlerTest.java b/test/server/handlers/TasksHandlerTest.java new file mode 100644 index 0000000..9489490 --- /dev/null +++ b/test/server/handlers/TasksHandlerTest.java @@ -0,0 +1,209 @@ +package server.handlers; + +import data.Endpoint; +import data.Status; +import data.Task; +import org.junit.jupiter.api.Test; +import server.HttpTaskServerTest; +import server.tokens.TaskListTypeToken; + +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; + +public class TasksHandlerTest extends HttpTaskServerTest { + + @Test + void BASE() { + shouldReturnStatus405AndBodyOfFormatExceptionAfterGetTaskWithStringId(); + } + + @Test + void GET_TASK() { + shouldReturnStatus200AndBodyOfTask1AfterGetTask1(); + shouldReturnStatus404AndBodyOfNotFoundAfterGetTask3(); + shouldReturnStatus405AndBodyOfFormatExceptionAfterGetTaskWithLongId(); + shouldReturnStatus406AndBodyOfTimeOverlapsAfterTryAddNewIntersectionTask(); + } + + @Test + void GET_ALL_TASK() { + shouldReturnStatus200AndListOfTwoElementsAfterGetAllTasks(); + } + + @Test + void POST_NEW_TASK() { + shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewTask(); + shouldReturnStatus500AndBodyOfServerErrorAfterTryAddingBrokenTask(); + } + + @Test + void POST_UPDATE_TASK() { + shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask(); + shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask(); + } + + @Test + void DELETE_TASK() { + shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteTask(); + } + + + // ---------------------------------HOOD---------------------------------------- + + + /// Пытается получить задачу, но вместо id указана буква + private void shouldReturnStatus405AndBodyOfFormatExceptionAfterGetTaskWithStringId() { + HttpResponse response = responseOfNewGetRequest("tasks/l"); + + assertEquals(405, response.statusCode()); + assertEquals("\tUNKNOWN 405 path format exception: /tasks/l", response.body()); + } + + /// Пытается получить задачу, но указан слишком большой id + private void shouldReturnStatus405AndBodyOfFormatExceptionAfterGetTaskWithLongId() { + HttpResponse response = responseOfNewGetRequest("tasks/9999999999"); + + assertEquals(405, response.statusCode()); + assertEquals("\tGET_TASK 405 path format exception: /tasks/9999999999", response.body()); + } + + /// Пытается получить задачу, но задачи с таким id нет в базе + private void shouldReturnStatus404AndBodyOfNotFoundAfterGetTask3() { + HttpResponse response = responseOfNewGetRequest("tasks/3"); + + assertEquals(404, response.statusCode()); + assertEquals("\tGET_TASK 404 not found", response.body()); + } + + /// Пытается добавить задачу, но она пересекается по срокам с существующей + private void shouldReturnStatus406AndBodyOfTimeOverlapsAfterTryAddNewIntersectionTask() { + manager.updateTask(new Task(2, null, null, null, + LocalDateTime.parse("2025-11-20 08:00", DATE_TIME_FORMATTER), + Duration.ofMinutes(30) + )); + + Task newTask = new Task(null, "NEW TASK", "new task", null, + LocalDateTime.parse("2025-11-20 08:20", DATE_TIME_FORMATTER), + Duration.ofMinutes(30) + ); + + HttpResponse response = responseOfNewPostRequest("tasks", newTask); + + assertEquals(406, response.statusCode()); + assertEquals("\tPOST_NEW_TASK 406 time overlaps: 2025-11-20 08:20 - 2025-11-20 08:50", + response.body() + ); + } + + /// Получает задачу из базы и сверяет все поля с оригиналом + private void shouldReturnStatus200AndBodyOfTask1AfterGetTask1() { + HttpResponse response = responseOfNewGetRequest("tasks/1"); + + assertEquals(200, response.statusCode()); + + Task requestTask = gson.fromJson(response.body(), Task.class); + assertEquals(task1.getTitle(), requestTask.getTitle()); + assertEquals(task1.getDescription(), requestTask.getDescription()); + assertEquals(task1.getStatus(), requestTask.getStatus()); + assertEquals(task1.getStartTime(), requestTask.getStartTime()); + assertEquals(task1.getDuration(), requestTask.getDuration()); + } + + /// Получает список всех задач и проверяет их количество + private void shouldReturnStatus200AndListOfTwoElementsAfterGetAllTasks() { + HttpResponse response = responseOfNewGetRequest("tasks"); + + assertEquals(200, response.statusCode()); + + List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); + assertEquals(2, requestList.size()); + } + + /// Добавляет новую задачу и проверяет ответ сервера + private void shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewTask() { + Task task3 = new Task(null, "ADDED TASK", "added task"); + + HttpResponse response = responseOfNewPostRequest("tasks", task3); + + assertEquals(200, response.statusCode()); + assertEquals("task adding success", response.body()); + } + + /// Пытается добавить новую задачу через неправильно заполненный json + private void shouldReturnStatus500AndBodyOfServerErrorAfterTryAddingBrokenTask() { + String brokenString = """ + { + "id": null, + "title": "BROKEN TASK", + "description": "broken task", + "status": null, + "startTime": "THIS IS FAIL", + "duration": 32 + }"""; + + URI uri = URI.create(host + "tasks"); + + HttpResponse response; + try (HttpClient client = HttpClient.newHttpClient()) { + + HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(brokenString)) + .uri(uri) + .build(); + HttpResponse.BodyHandler handler = HttpResponse.BodyHandlers.ofString(); + response = client.send(request, handler); + + } catch (Exception exception) { + exception.printStackTrace(); + return; + } + + assertEquals(500, response.statusCode()); + assertEquals("\t" + Endpoint.POST_NEW_TASK + " 500 Internal Server Error", response.body()); + } + + /// Обновляет существующую задачу и проверяет ответ от сервера + private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask() { + Task task3 = new Task( + 2, + "UPDATED TASK", + null, + Status.IN_PROGRESS, + LocalDateTime.parse("2025-11-20 10:00", DATE_TIME_FORMATTER), + Duration.ofMinutes(15) + ); + + HttpResponse response = responseOfNewPostRequest("tasks/2", task3); + assertEquals(200, response.statusCode()); + + assertEquals("task updated success", response.body()); + } + + /// Получает обновленную задачу с сервера и сверяет все поля + private void shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask() { + HttpResponse response1 = responseOfNewGetRequest("tasks/2"); + assertEquals(200, response1.statusCode()); + + Task requestTask = gson.fromJson(response1.body(), Task.class); + assertEquals("UPDATED TASK", requestTask.getTitle()); + assertEquals("second task", requestTask.getDescription()); + assertEquals(Status.IN_PROGRESS, requestTask.getStatus()); + assertEquals(LocalDateTime.parse("2025-11-20 10:00", DATE_TIME_FORMATTER), requestTask.getStartTime()); + assertEquals(LocalDateTime.parse("2025-11-20 10:15", DATE_TIME_FORMATTER), requestTask.getEndTime()); + } + + /// Удаляет задачу и проверяет ответ от сервера + private void shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteTask() { + HttpResponse response = responseOfNewDeleteRequest("tasks/1"); + + assertEquals(200, response.statusCode()); + assertEquals("task removed success", response.body()); + } +} diff --git a/test/server/tokens/TaskListTypeToken.java b/test/server/tokens/TaskListTypeToken.java new file mode 100644 index 0000000..fee7e12 --- /dev/null +++ b/test/server/tokens/TaskListTypeToken.java @@ -0,0 +1,9 @@ +package server.tokens; + +import com.google.gson.reflect.TypeToken; +import data.Task; + +import java.util.List; + +public class TaskListTypeToken extends TypeToken> { +} From d952ed795be94d8daab1164c2cfca9d0b3034f44 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Fri, 21 Nov 2025 17:15:27 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=20=D0=BA=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=BC=D1=83=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=B4=D0=B5=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D0=B0=20(?= =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20unused=20import)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/server/HttpTaskServerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/server/HttpTaskServerTest.java b/test/server/HttpTaskServerTest.java index 102e4e3..a12853f 100644 --- a/test/server/HttpTaskServerTest.java +++ b/test/server/HttpTaskServerTest.java @@ -6,7 +6,6 @@ import manager.InMemoryTaskManager; import manager.Managers; import manager.TaskManager; -import manager.TaskManagerType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; From c7589511d22891d5433d8552c432599d4d6f56f9 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 22 Nov 2025 21:26:26 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=20=D0=BA=D0=BE=20=D0=B2=D1=82=D0=BE=D1=80=D0=BE=D0=BC=D1=83=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=B4=D0=B5=D0=B2=D1=8F=D1=82?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/handlers/BaseHttpHandler.java | 35 ++++++-- src/server/handlers/EpicsHandler.java | 65 +++++++------- src/server/handlers/HistoryHandler.java | 8 +- src/server/handlers/PrioritizedHandler.java | 8 +- src/server/handlers/SubtasksHandler.java | 57 ++++++++----- src/server/handlers/TasksHandler.java | 49 ++++++----- test/server/handlers/EpicsHandlerTest.java | 82 ++++++++++++++---- .../handlers/PrioritizedHandlerTest.java | 2 +- test/server/handlers/SubtasksHandlerTest.java | 50 ++++++++--- test/server/handlers/TasksHandlerTest.java | 85 +++++++++++++++---- 10 files changed, 316 insertions(+), 125 deletions(-) diff --git a/src/server/handlers/BaseHttpHandler.java b/src/server/handlers/BaseHttpHandler.java index bcc3c53..063efc4 100644 --- a/src/server/handlers/BaseHttpHandler.java +++ b/src/server/handlers/BaseHttpHandler.java @@ -15,11 +15,10 @@ public class BaseHttpHandler implements HttpHandler, Property { protected final TaskManager manager; - protected static Gson gson; + protected static Gson gson = Managers.getGson(); public BaseHttpHandler(TaskManager manager) { this.manager = manager; - gson = Managers.getGson(); } @Override @@ -57,6 +56,17 @@ protected void sendText(HttpExchange exchange, Endpoint endpoint, String text) t exchange.close(); } + ///Сервер обнаружил в запросе клиента синтаксическую ошибку. + protected void sendBadRequestBoby(HttpExchange exchange, Endpoint endpoint) throws IOException { + String text = ("\t" + endpoint + " 400 bad request body"); + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(400, resp.length); + exchange.getResponseBody().write(resp); + System.out.println(text); + exchange.close(); + } + /// Для отправки ответа в случае, если объект не был найден protected void sendNotFound(HttpExchange exchange, Endpoint endpoint) throws IOException { String text = ("\t" + endpoint + " 404 not found"); @@ -68,7 +78,7 @@ protected void sendNotFound(HttpExchange exchange, Endpoint endpoint) throws IOE exchange.close(); } - /// Для отправки ответа в случае, если объект не был найден + /// Сервер понял запрос, но метод, использованный клиентом (например, GET или POST), не разрешен для данного ресурса protected void sendFormatException(HttpExchange exchange, Endpoint endpoint, String path) throws IOException { String text = ("\t" + endpoint + " 405 path format exception: " + path); byte[] resp = text.getBytes(CHARSET); @@ -79,7 +89,7 @@ protected void sendFormatException(HttpExchange exchange, Endpoint endpoint, Str exchange.close(); } - /// Для отправки ответа, если при создании или обновлении задача пересекается с уже существующими + /// Запрошенный URI не может удовлетворить переданным в заголовке характеристикам. protected void sendHasOverlaps(HttpExchange exchange, Endpoint endpoint, Task taskObject) throws IOException { String text = ("\t" + endpoint + " 406 time overlaps: " + taskObject.getStartTime().format(DATE_TIME_FORMATTER) + " - " + @@ -93,6 +103,20 @@ protected void sendHasOverlaps(HttpExchange exchange, Endpoint endpoint, Task ta exchange.close(); } + /** + * Сервер успешно принял запрос, может работать с указанным видом данных, + * однако имеется какая-то логическая ошибка, из-за которой невозможно произвести операцию над ресурсом. + */ + protected void sendUnprocessableEntity(HttpExchange exchange, Endpoint endpoint) throws IOException { + String text = ("\t" + endpoint + " 422 unprocessable entity"); + byte[] resp = text.getBytes(CHARSET); + exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); + exchange.sendResponseHeaders(422, resp.length); + exchange.getResponseBody().write(resp); + System.out.println(text); + exchange.close(); + } + /// Для отправки ответа, если произошла ошибка на сервере protected void sendServerError(HttpExchange exchange, Endpoint endpoint) throws IOException { String text = ("\t" + endpoint + " 500 Internal Server Error"); @@ -167,9 +191,6 @@ protected Endpoint getEndpoint(String method, String path) { } break; } - default: { - return Endpoint.UNKNOWN; - } } return Endpoint.UNKNOWN; diff --git a/src/server/handlers/EpicsHandler.java b/src/server/handlers/EpicsHandler.java index 9b35395..d7aeb4d 100644 --- a/src/server/handlers/EpicsHandler.java +++ b/src/server/handlers/EpicsHandler.java @@ -30,13 +30,13 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/epics/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.GET_EPIC, path); + sendFormatException(exchange, endpoint, path); return; } Epic epic = manager.getEpic(id); if (epic == null) { - sendNotFound(exchange, Endpoint.GET_EPIC); + sendNotFound(exchange, endpoint); return; } @@ -44,12 +44,12 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonEpic = gson.toJson(epic); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_EPIC); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_EPIC, jsonEpic); + sendText(exchange, endpoint, jsonEpic); } @@ -57,7 +57,7 @@ public void handle(HttpExchange exchange) throws IOException { List epicList = manager.getAllEpics(); if (epicList.isEmpty()) { - sendNotFound(exchange, Endpoint.GET_ALL_EPICS); + sendNotFound(exchange, endpoint); return; } @@ -65,12 +65,12 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonEpicList = gson.toJson(epicList); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_ALL_EPICS); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_ALL_EPICS, jsonEpicList); + sendText(exchange, endpoint, jsonEpicList); } @@ -83,17 +83,22 @@ public void handle(HttpExchange exchange) throws IOException { try { epic = gson.fromJson(body, Epic.class); } catch (Exception e) { - sendServerError(exchange, Endpoint.POST_NEW_EPIC); + sendBadRequestBoby(exchange, endpoint); System.out.println(e.getMessage()); return; } - if (manager.addEpic(epic) == null) { - sendHasOverlaps(exchange, Endpoint.POST_NEW_EPIC, epic); - } else { - sendText(exchange, Endpoint.POST_NEW_EPIC,"epic adding success"); + Integer epicId; + try { + epicId = manager.addEpic(epic).getId(); + } catch (Exception exception) { + sendServerError(exchange, endpoint); + System.out.println(exception.getMessage()); + return; } + sendText(exchange, endpoint, "epic adding success, epicId=" + epicId); + } case POST_UPDATE_EPIC -> { @@ -101,7 +106,7 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/epics/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.POST_UPDATE_EPIC, path); + sendFormatException(exchange, endpoint, path); return; } @@ -112,15 +117,21 @@ public void handle(HttpExchange exchange) throws IOException { try { epic = gson.fromJson(body, Epic.class); } catch (Exception e) { - sendServerError(exchange, Endpoint.POST_UPDATE_EPIC); + sendBadRequestBoby(exchange, endpoint); System.out.println(e.getMessage()); return; } + Integer epicId = epic.getId(); + if (epicId == null || id != epicId) { + sendUnprocessableEntity(exchange, endpoint); + return; + } + if (manager.updateEpic(epic) == null) { - sendNotFound(exchange, Endpoint.POST_UPDATE_EPIC); + sendNotFound(exchange, endpoint); } else { - sendText(exchange, Endpoint.POST_UPDATE_EPIC,"epic updated success"); + sendText(exchange, endpoint, "epic updated success"); } } @@ -130,14 +141,14 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/epics/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.DELETE_EPIC, path); + sendFormatException(exchange, endpoint, path); return; } if (manager.removeEpic(id)) { - sendText(exchange, Endpoint.DELETE_EPIC, "epic removed success"); + sendText(exchange, endpoint, "epic removed success"); } else { - sendServerError(exchange, Endpoint.DELETE_EPIC); + sendNotFound(exchange, endpoint); } } @@ -148,19 +159,13 @@ public void handle(HttpExchange exchange) throws IOException { pathId = pathId.replaceFirst("/subtasks", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC, path); - return; - } - - Epic epic = manager.getEpic(id); - if (epic == null) { - sendNotFound(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC); + sendFormatException(exchange, endpoint, path); return; } List subtaskList = manager.getAllSubTasksByEpic(id); if (subtaskList.isEmpty()) { - sendNotFound(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC); + sendNotFound(exchange, endpoint); return; } @@ -168,16 +173,16 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonSubtaskList = gson.toJson(subtaskList); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_ALL_SUBTASKS_BY_EPIC, jsonSubtaskList); + sendText(exchange, endpoint, jsonSubtaskList); } - case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + case UNKNOWN -> sendFormatException(exchange, endpoint, path); } } } diff --git a/src/server/handlers/HistoryHandler.java b/src/server/handlers/HistoryHandler.java index c8883af..2089bf2 100644 --- a/src/server/handlers/HistoryHandler.java +++ b/src/server/handlers/HistoryHandler.java @@ -27,7 +27,7 @@ public void handle(HttpExchange exchange) throws IOException { List historyList = manager.getHistory(); if (historyList.isEmpty()) { - sendNotFound(exchange, Endpoint.GET_HISTORY); + sendNotFound(exchange, endpoint); return; } @@ -35,16 +35,16 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonHistoryList = gson.toJson(historyList); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_HISTORY); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_HISTORY, jsonHistoryList); + sendText(exchange, endpoint, jsonHistoryList); } - case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + case UNKNOWN -> sendFormatException(exchange, endpoint, path); } } } diff --git a/src/server/handlers/PrioritizedHandler.java b/src/server/handlers/PrioritizedHandler.java index 759fc79..37820c4 100644 --- a/src/server/handlers/PrioritizedHandler.java +++ b/src/server/handlers/PrioritizedHandler.java @@ -27,7 +27,7 @@ public void handle(HttpExchange exchange) throws IOException { List prioritizedList = manager.getPrioritizedTasks(); if (prioritizedList.isEmpty()) { - sendNotFound(exchange, Endpoint.GET_PRIORITIZED); + sendNotFound(exchange, endpoint); return; } @@ -35,16 +35,16 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonPrioritizedList = gson.toJson(prioritizedList); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_PRIORITIZED); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_PRIORITIZED, jsonPrioritizedList); + sendText(exchange, endpoint, jsonPrioritizedList); } - case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + case UNKNOWN -> sendFormatException(exchange, endpoint, path); } } } diff --git a/src/server/handlers/SubtasksHandler.java b/src/server/handlers/SubtasksHandler.java index 4da73af..70addc8 100644 --- a/src/server/handlers/SubtasksHandler.java +++ b/src/server/handlers/SubtasksHandler.java @@ -29,13 +29,13 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/subtasks/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.GET_SUBTASK, path); + sendFormatException(exchange, endpoint, path); return; } Subtask subtask = manager.getSubtask(id); if (subtask == null) { - sendNotFound(exchange, Endpoint.GET_SUBTASK); + sendNotFound(exchange, endpoint); return; } @@ -43,12 +43,12 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonSubtask = gson.toJson(subtask); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_SUBTASK); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_SUBTASK, jsonSubtask); + sendText(exchange, endpoint, jsonSubtask); } @@ -56,7 +56,7 @@ public void handle(HttpExchange exchange) throws IOException { List subtaskList = manager.getAllSubtasks(); if (subtaskList.isEmpty()) { - sendNotFound(exchange, Endpoint.GET_ALL_SUBTASKS); + sendNotFound(exchange, endpoint); return; } @@ -64,11 +64,12 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonSubtaskList = gson.toJson(subtaskList); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_ALL_SUBTASKS); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_ALL_SUBTASKS, jsonSubtaskList); + + sendText(exchange, endpoint, jsonSubtaskList); } @@ -81,15 +82,25 @@ public void handle(HttpExchange exchange) throws IOException { try { subtask = gson.fromJson(body, Subtask.class); } catch (Exception e) { - sendServerError(exchange, Endpoint.POST_NEW_SUBTASK); + sendBadRequestBoby(exchange, endpoint); System.out.println(e.getMessage()); return; } - if (manager.addSubtask(subtask) == null) { - sendHasOverlaps(exchange, Endpoint.POST_NEW_SUBTASK, subtask); + Integer epicId = subtask.getEpicId(); + if (epicId == null) { + sendUnprocessableEntity(exchange, endpoint); + } else if (manager.getWithoutHistory(epicId) == null) { + sendNotFound(exchange, endpoint); + return; + } + + Subtask newSubtask = manager.addSubtask(subtask); + + if (newSubtask == null) { + sendHasOverlaps(exchange, endpoint, subtask); } else { - sendText(exchange, Endpoint.POST_NEW_SUBTASK,"subtask adding success"); + sendText(exchange, endpoint, "subtask adding success, subtaskId=" + newSubtask.getId()); } } @@ -99,7 +110,7 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/subtasks/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.POST_UPDATE_SUBTASK, path); + sendFormatException(exchange, endpoint, path); return; } @@ -110,15 +121,23 @@ public void handle(HttpExchange exchange) throws IOException { try { subtask = gson.fromJson(body, Subtask.class); } catch (Exception e) { - sendServerError(exchange, Endpoint.POST_UPDATE_SUBTASK); + sendBadRequestBoby(exchange, endpoint); System.out.println(e.getMessage()); return; } + Integer subtaskId = subtask.getId(); + if (subtaskId == null || id != subtaskId) { + sendUnprocessableEntity(exchange, endpoint); + } else if (manager.getWithoutHistory(subtaskId) == null) { + sendNotFound(exchange, endpoint); + return; + } + if (manager.updateSubtask(subtask) == null) { - sendNotFound(exchange, Endpoint.POST_UPDATE_SUBTASK); + sendHasOverlaps(exchange, endpoint, subtask); } else { - sendText(exchange, Endpoint.POST_UPDATE_SUBTASK,"subtask updated success"); + sendText(exchange, endpoint, "subtask updated success"); } } @@ -128,19 +147,19 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/subtasks/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.DELETE_SUBTASK, path); + sendFormatException(exchange, endpoint, path); return; } if (manager.removeSubtask(id)) { - sendText(exchange, Endpoint.DELETE_SUBTASK, "subtask removed success"); + sendText(exchange, endpoint, "subtask removed success"); } else { - sendServerError(exchange, Endpoint.DELETE_SUBTASK); + sendNotFound(exchange, endpoint); } } - case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + case UNKNOWN -> sendFormatException(exchange, endpoint, path); } } } diff --git a/src/server/handlers/TasksHandler.java b/src/server/handlers/TasksHandler.java index 37f804c..0614e50 100644 --- a/src/server/handlers/TasksHandler.java +++ b/src/server/handlers/TasksHandler.java @@ -29,13 +29,13 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/tasks/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.GET_TASK, path); + sendFormatException(exchange, endpoint, path); return; } Task task = manager.getTask(id); if (task == null) { - sendNotFound(exchange, Endpoint.GET_TASK); + sendNotFound(exchange, endpoint); return; } @@ -43,12 +43,12 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonTask = gson.toJson(task); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_TASK); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_TASK, jsonTask); + sendText(exchange, endpoint, jsonTask); } @@ -56,7 +56,7 @@ public void handle(HttpExchange exchange) throws IOException { List taskList = manager.getAllTasks(); if (taskList.isEmpty()) { - sendNotFound(exchange, Endpoint.GET_ALL_TASKS); + sendNotFound(exchange, endpoint); return; } @@ -64,11 +64,11 @@ public void handle(HttpExchange exchange) throws IOException { try { jsonTaskList = gson.toJson(taskList); } catch (Exception exception) { - sendServerError(exchange, Endpoint.GET_ALL_TASKS); + sendServerError(exchange, endpoint); System.out.println(exception.getMessage()); return; } - sendText(exchange, Endpoint.GET_ALL_TASKS, jsonTaskList); + sendText(exchange, endpoint, jsonTaskList); } @@ -81,15 +81,17 @@ public void handle(HttpExchange exchange) throws IOException { try { task = gson.fromJson(body, Task.class); } catch (Exception e) { - sendServerError(exchange, Endpoint.POST_NEW_TASK); + sendBadRequestBoby(exchange, endpoint); System.out.println(e.getMessage()); return; } - if (manager.addTask(task) == null) { - sendHasOverlaps(exchange, Endpoint.POST_NEW_TASK, task); + Task newTask = manager.addTask(task); + + if (newTask == null) { + sendHasOverlaps(exchange, endpoint, task); } else { - sendText(exchange, Endpoint.POST_NEW_TASK,"task adding success"); + sendText(exchange, endpoint,"task adding success, taskId=" + newTask.getId()); } } @@ -99,7 +101,7 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/tasks/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.POST_UPDATE_TASK, path); + sendFormatException(exchange, endpoint, path); return; } @@ -110,15 +112,24 @@ public void handle(HttpExchange exchange) throws IOException { try { task = gson.fromJson(body, Task.class); } catch (Exception e) { - sendServerError(exchange, Endpoint.POST_UPDATE_TASK); + sendBadRequestBoby(exchange, endpoint); System.out.println(e.getMessage()); return; } + Integer taskId = task.getId(); + if (taskId == null || id != taskId) { + sendUnprocessableEntity(exchange, endpoint); + return; + } else if (manager.getWithoutHistory(taskId) == null) { + sendNotFound(exchange, endpoint); + return; + } + if (manager.updateTask(task) == null) { - sendNotFound(exchange, Endpoint.POST_UPDATE_TASK); + sendHasOverlaps(exchange, endpoint, task); } else { - sendText(exchange, Endpoint.POST_UPDATE_TASK,"task updated success"); + sendText(exchange, endpoint,"task updated success"); } } @@ -128,19 +139,19 @@ public void handle(HttpExchange exchange) throws IOException { String pathId = path.replaceFirst("/tasks/", ""); int id = parsePathId(pathId); if (id == -1) { - sendFormatException(exchange, Endpoint.DELETE_TASK, path); + sendFormatException(exchange, endpoint, path); return; } if (manager.removeTask(id)) { - sendText(exchange, Endpoint.DELETE_TASK, "task removed success"); + sendText(exchange, endpoint, "task removed success"); } else { - sendServerError(exchange, Endpoint.DELETE_TASK); + sendNotFound(exchange, endpoint); } } - case UNKNOWN -> sendFormatException(exchange, Endpoint.UNKNOWN, path); + case UNKNOWN -> sendFormatException(exchange, endpoint, path); } diff --git a/test/server/handlers/EpicsHandlerTest.java b/test/server/handlers/EpicsHandlerTest.java index 2028e0b..ec557f0 100644 --- a/test/server/handlers/EpicsHandlerTest.java +++ b/test/server/handlers/EpicsHandlerTest.java @@ -1,19 +1,19 @@ package server.handlers; -import data.Epic; -import data.Status; -import data.Subtask; -import data.Task; +import data.*; import org.junit.jupiter.api.Test; import server.HttpTaskServerTest; import server.tokens.TaskListTypeToken; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; -public class EpicsHandlerTest extends HttpTaskServerTest { +class EpicsHandlerTest extends HttpTaskServerTest { @Test void GET_EPIC() { @@ -29,12 +29,15 @@ void GET_ALL_EPICS() { @Test void POST_NEW_EPIC() { shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewEpic(); + shouldReturnStatus400AndBodyOfBadRequestAfterTryAddBrokenEpic(); } @Test void POST_UPDATE_EPIC() { - shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask(); - shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask(); + shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateEpic(); + shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateEpic(); + shouldReturnStatus404AndBodyOfNotFoundAfterTryUpdateLostEpic(); + } @Test @@ -93,11 +96,11 @@ private void shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewEpic() { HttpResponse response = responseOfNewPostRequest("epics", epic); assertEquals(200, response.statusCode()); - assertEquals("epic adding success", response.body()); + assertEquals("epic adding success, epicId=3", response.body()); } /// Создает новый эпик, постит обновление этого эпика, проверяет статус и тело ответа - private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask() { + private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateEpic() { manager.addEpic(new Epic(null, "FIRST EPIC", "first epic")); Epic newEpic = new Epic(3, "FIRST EPIC UPDATED", null); @@ -107,16 +110,30 @@ private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask() { assertEquals("epic updated success", response.body()); + Epic requestTask = (Epic) manager.getWithoutHistory(3); + assertEquals("FIRST EPIC UPDATED", requestTask.getTitle()); + assertEquals("first epic", requestTask.getDescription()); + } - /// Запрашивает обновленный эпик, сверяет его поля, а также статус ответа - private void shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask() { - HttpResponse response = responseOfNewGetRequest("epics/3"); - assertEquals(200, response.statusCode()); + /// Пытается обновить эпик, но в теле эпика не указан ID + private void shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateEpic() { + Epic epic = new Epic(null, "TASK WITHOUT ID", null); - Task requestTask = gson.fromJson(response.body(), Task.class); - assertEquals("FIRST EPIC UPDATED", requestTask.getTitle()); - assertEquals("first epic", requestTask.getDescription()); + HttpResponse response = responseOfNewPostRequest("epics/3", epic); + + assertEquals(422, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_EPIC + " 422 unprocessable entity", response.body()); + } + + /// Пытается обновить эпик с несуществующим id + private void shouldReturnStatus404AndBodyOfNotFoundAfterTryUpdateLostEpic() { + Epic epic = new Epic(4, "TASK WITH LOST ID", null); + + HttpResponse response = responseOfNewPostRequest("epics/4", epic); + + assertEquals(404, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_EPIC + " 404 not found", response.body()); } /// Удаляет эпик и проверяет ответ от сервера @@ -147,4 +164,37 @@ private void shouldReturnStatus200AndListOfTwoElementsAfterGetAllSubtasksByEpic( List requestList = gson.fromJson(response.body(), new TaskListTypeToken().getType()); assertEquals(2, requestList.size()); } + + /// Пытается добавить новый эпик, но отправляет сломанный json в теле запроса + private void shouldReturnStatus400AndBodyOfBadRequestAfterTryAddBrokenEpic() { + String brokenEpic = """ + { + "id" : "THIS IS FAIL", + "title" : "BROKEN EPIC", + "description" : "broken epic", + "status" : null, + "startTime" : null, + "duration" : 0 + }"""; + + URI uri = URI.create(host + "epics"); + + HttpResponse response; + try (HttpClient client = HttpClient.newHttpClient()) { + + HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(brokenEpic)) + .uri(uri) + .build(); + HttpResponse.BodyHandler handler = HttpResponse.BodyHandlers.ofString(); + response = client.send(request, handler); + + } catch (Exception exception) { + exception.printStackTrace(); + return; + } + + assertEquals(400, response.statusCode()); + assertEquals("\t" + Endpoint.POST_NEW_EPIC + " 400 bad request body", response.body()); + } } diff --git a/test/server/handlers/PrioritizedHandlerTest.java b/test/server/handlers/PrioritizedHandlerTest.java index c01a5ae..ed74f08 100644 --- a/test/server/handlers/PrioritizedHandlerTest.java +++ b/test/server/handlers/PrioritizedHandlerTest.java @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class PrioritizedHandlerTest extends HttpTaskServerTest { +class PrioritizedHandlerTest extends HttpTaskServerTest { @Test void GET_PRIORITIZED() { diff --git a/test/server/handlers/SubtasksHandlerTest.java b/test/server/handlers/SubtasksHandlerTest.java index a3eef60..2e0ca84 100644 --- a/test/server/handlers/SubtasksHandlerTest.java +++ b/test/server/handlers/SubtasksHandlerTest.java @@ -1,5 +1,6 @@ package server.handlers; +import data.Endpoint; import data.Epic; import data.Subtask; import org.junit.jupiter.api.BeforeEach; @@ -12,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class SubtasksHandlerTest extends HttpTaskServerTest { +class SubtasksHandlerTest extends HttpTaskServerTest { @BeforeEach void subtaskBeforeEach() { @@ -36,12 +37,14 @@ void GET_ALL_SUBTASKS() { @Test void POST_NEW_SUBTASK() { shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewSubtask(); + shouldReturnStatus422AndBodyOfUnsupportedAfterTryAddSubtaskWithoutEpicId(); } @Test void POST_UPDATE_SUBTASK() { shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateSubtask(); - shouldReturnStatus200AndBodyOfUpdatedSubtaskAfterGetUpdatedSubtask(); + shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateSubtaskWithoutId(); + shouldReturnStatus404AndBodyOfNotFoundAfterTryUpdateLostSubtask(); } @Test @@ -82,7 +85,18 @@ private void shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewSubtask() { HttpResponse response = responseOfNewPostRequest("subtasks", subtask); assertEquals(200, response.statusCode()); - assertEquals("subtask adding success", response.body()); + assertEquals("subtask adding success, subtaskId=6", response.body()); + } + + /// Пытается добавить подзадачу, но в теле подзадачи не указан ID эпика + private void shouldReturnStatus422AndBodyOfUnsupportedAfterTryAddSubtaskWithoutEpicId() { + Subtask subtask = new Subtask(null, "TASK WITHOUT EPIC-ID", + null, null, null, null, null); + + HttpResponse response = responseOfNewPostRequest("subtasks", subtask); + + assertEquals(422, response.statusCode()); + assertEquals("\t" + Endpoint.POST_NEW_SUBTASK + " 422 unprocessable entity", response.body()); } /// Обновляет существующую подзадачу и проверяет ответ от сервера @@ -94,18 +108,34 @@ private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateSubtask() { assertEquals(200, response.statusCode()); assertEquals("subtask updated success", response.body()); - } - - /// Получает обновленную задачу с сервера и сверяет все поля - private void shouldReturnStatus200AndBodyOfUpdatedSubtaskAfterGetUpdatedSubtask() { - HttpResponse response = responseOfNewGetRequest("subtasks/5"); - assertEquals(200, response.statusCode()); - Subtask requestTask = gson.fromJson(response.body(), Subtask.class); + Subtask requestTask = (Subtask) manager.getWithoutHistory(5); assertEquals("SECOND SUB UPDATED", requestTask.getTitle()); assertEquals("second sub", requestTask.getDescription()); } + /// Пытается обновить подзадачу, но в теле подзадачи не указан ID подзадачи + private void shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateSubtaskWithoutId() { + Subtask subtask = new Subtask(null, "TASK WITHOUT ID", + null, null, 3, null, null); + + HttpResponse response = responseOfNewPostRequest("tasks/1", subtask); + + assertEquals(422, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_TASK + " 422 unprocessable entity", response.body()); + } + + /// Пытается обновить подзадачу с несуществующим id + private void shouldReturnStatus404AndBodyOfNotFoundAfterTryUpdateLostSubtask() { + Subtask subtask = new Subtask(7, "TASK WITH LOST ID", + null, null, null, null, null); + + HttpResponse response = responseOfNewPostRequest("subtasks/7", subtask); + + assertEquals(404, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_SUBTASK + " 404 not found", response.body()); + } + /// Удаляет подзадачу и проверяет ответ от сервера private void shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteSubtask() { HttpResponse response = responseOfNewDeleteRequest("subtasks/4"); diff --git a/test/server/handlers/TasksHandlerTest.java b/test/server/handlers/TasksHandlerTest.java index 9489490..0d4ace2 100644 --- a/test/server/handlers/TasksHandlerTest.java +++ b/test/server/handlers/TasksHandlerTest.java @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class TasksHandlerTest extends HttpTaskServerTest { +class TasksHandlerTest extends HttpTaskServerTest { @Test void BASE() { @@ -40,13 +40,17 @@ void GET_ALL_TASK() { @Test void POST_NEW_TASK() { shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewTask(); - shouldReturnStatus500AndBodyOfServerErrorAfterTryAddingBrokenTask(); + shouldReturnStatus400AndBodyOfBadRequestAfterTryAddingBrokenTask(); } @Test void POST_UPDATE_TASK() { shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask(); - shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask(); + shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateTask(); + shouldReturnStatus404AndBodyOfNotFoundAfterTryUpdateLostTask(); + shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateWrongTask(); + shouldReturnStatus406AndBodyOfOverlapsAfterTryUpdateIntersectionTask(); + shouldReturnStatus200AndBodyOfTask2AfterTryGetHistory(); } @Test @@ -133,11 +137,11 @@ private void shouldReturnStatus200AndBodyOfSuccessAddingAfterAddNewTask() { HttpResponse response = responseOfNewPostRequest("tasks", task3); assertEquals(200, response.statusCode()); - assertEquals("task adding success", response.body()); + assertEquals("task adding success, taskId=3", response.body()); } /// Пытается добавить новую задачу через неправильно заполненный json - private void shouldReturnStatus500AndBodyOfServerErrorAfterTryAddingBrokenTask() { + private void shouldReturnStatus400AndBodyOfBadRequestAfterTryAddingBrokenTask() { String brokenString = """ { "id": null, @@ -165,11 +169,14 @@ private void shouldReturnStatus500AndBodyOfServerErrorAfterTryAddingBrokenTask() return; } - assertEquals(500, response.statusCode()); - assertEquals("\t" + Endpoint.POST_NEW_TASK + " 500 Internal Server Error", response.body()); + assertEquals(400, response.statusCode()); + assertEquals("\t" + Endpoint.POST_NEW_TASK + " 400 bad request body", response.body()); } - /// Обновляет существующую задачу и проверяет ответ от сервера + /** + * Обновляет существующую задачу и проверяет ответ от сервера, + * затем получает ее через менеджер и сверяет все поля + */ private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask() { Task task3 = new Task( 2, @@ -184,14 +191,8 @@ private void shouldReturnStatus200AndBodyOfSuccessUpdatingAfterUpdateTask() { assertEquals(200, response.statusCode()); assertEquals("task updated success", response.body()); - } - - /// Получает обновленную задачу с сервера и сверяет все поля - private void shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask() { - HttpResponse response1 = responseOfNewGetRequest("tasks/2"); - assertEquals(200, response1.statusCode()); - Task requestTask = gson.fromJson(response1.body(), Task.class); + Task requestTask = manager.getWithoutHistory(2); assertEquals("UPDATED TASK", requestTask.getTitle()); assertEquals("second task", requestTask.getDescription()); assertEquals(Status.IN_PROGRESS, requestTask.getStatus()); @@ -199,6 +200,60 @@ private void shouldReturnStatus200AndBodyOfUpdatedTaskAfterGetUpdatedTask() { assertEquals(LocalDateTime.parse("2025-11-20 10:15", DATE_TIME_FORMATTER), requestTask.getEndTime()); } + /// Пытается обновить задачу, но в теле задачи не указан ID + private void shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateTask() { + Task task = new Task(null, "TASK WITHOUT ID", null); + + HttpResponse response = responseOfNewPostRequest("tasks/1", task); + + assertEquals(422, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_TASK + " 422 unprocessable entity", response.body()); + } + + /// Пытается обновить задачу с несуществующим id + private void shouldReturnStatus404AndBodyOfNotFoundAfterTryUpdateLostTask() { + Task task = new Task(3, "TASK WITH LOST ID", null); + + HttpResponse response = responseOfNewPostRequest("tasks/3", task); + + assertEquals(404, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_TASK + " 404 not found", response.body()); + } + + /// Пытается обновить задачу, но в эндпоинте и теле указаны разные существующие id + private void shouldReturnStatus422AndBodyOfUnsupportedAfterTryUpdateWrongTask() { + Task task = new Task(2, "TASK WITH LOST ID", null); + + HttpResponse response = responseOfNewPostRequest("tasks/1", task); + + assertEquals(422, response.statusCode()); + assertEquals("\t" + Endpoint.POST_UPDATE_TASK + " 422 unprocessable entity", response.body()); + } + + /// Пытается обновить задачу, но время пересекается с уже существующей + private void shouldReturnStatus406AndBodyOfOverlapsAfterTryUpdateIntersectionTask() { + Task task = new Task( + 1, + "UPDATED TASK", + null, + Status.IN_PROGRESS, + LocalDateTime.parse("2025-11-20 10:10", DATE_TIME_FORMATTER), + Duration.ofMinutes(10) + ); + + HttpResponse response = responseOfNewPostRequest("tasks/1", task); + assertEquals(406, response.statusCode()); + + assertEquals("\tPOST_UPDATE_TASK 406 time overlaps: 2025-11-20 10:10 - 2025-11-20 10:20", + response.body()); + } + + /// Проверяет историю после множества действий по обновлению задач + private void shouldReturnStatus200AndBodyOfTask2AfterTryGetHistory() { + HttpResponse response = responseOfNewGetRequest("history"); + assertEquals(404, response.statusCode()); + } + /// Удаляет задачу и проверяет ответ от сервера private void shouldReturnStatus200AndBodyOfSuccessRemovingAfterDeleteTask() { HttpResponse response = responseOfNewDeleteRequest("tasks/1");