diff --git a/.idea/libraries/google_api_client_gson.xml b/.idea/libraries/google_api_client_gson.xml
new file mode 100644
index 0000000..9643be3
--- /dev/null
+++ b/.idea/libraries/google_api_client_gson.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java-kanban.iml b/java-kanban.iml
index 265bb5c..1f0c1fe 100644
--- a/java-kanban.iml
+++ b/java-kanban.iml
@@ -12,5 +12,6 @@
+
\ No newline at end of file
diff --git a/resources/data.csv b/resources/data.csv
index 377ec10..321b3ae 100644
--- a/resources/data.csv
+++ b/resources/data.csv
@@ -1,6 +1,6 @@
id,type,name,status,description,epic, startTime, duration
-5,TASK,Name,NEW,Desc,,2025-07-22T21:18:17.670284500,30
-9,TASK,Непересекающаяся,NEW,Описание,,2025-07-24T01:18:17.673142600,60
-6,EPIC,Отпуск,NEW,Организовать отпуск,,2025-07-23T20:18:17.673142600,
-7,SUBTASK,Путёвка,NEW,Выбрать путёвку,6,2025-07-23T20:18:17.673142600,120
-8,SUBTASK,Билеты,NEW,Купить билеты,6,2025-07-23T23:18:17.673142600,60
+5,TASK,Name,NEW,Desc,,2025-07-31T17:44:00.858235600,30
+9,TASK,Непересекающаяся,NEW,Описание,,2025-08-01T21:44:00.861707600,60
+6,EPIC,Отпуск,NEW,Организовать отпуск,,2025-08-01T16:44:00.861707600,
+7,SUBTASK,Путёвка,NEW,Выбрать путёвку,6,2025-08-01T16:44:00.861707600,120
+8,SUBTASK,Билеты,NEW,Купить билеты,6,2025-08-01T19:44:00.861707600,60
diff --git a/src/manager/http/BaseHttpHandler.java b/src/manager/http/BaseHttpHandler.java
new file mode 100644
index 0000000..bc27775
--- /dev/null
+++ b/src/manager/http/BaseHttpHandler.java
@@ -0,0 +1,50 @@
+package manager.http;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+public abstract class BaseHttpHandler implements HttpHandler {
+ protected static final Gson gson = new GsonBuilder()
+ .registerTypeAdapter(LocalDateTime.class, new TimeAdapters.LocalDateTimeAdapter())
+ .registerTypeAdapter(Duration.class, new TimeAdapters.DurationAdapter())
+ .create();
+
+ protected String readRequest(HttpExchange exchange) throws IOException {
+ try (InputStream input = exchange.getRequestBody()) {
+ return new String(input.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+
+ protected void sendResponse(HttpExchange exchange, String response, int statusCode) throws IOException {
+ exchange.getResponseHeaders().set("Content-Type", "application/json;charset=utf-8");
+ byte[] resp = response.getBytes(StandardCharsets.UTF_8);
+ exchange.sendResponseHeaders(statusCode, resp.length);
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(resp);
+ }
+ }
+
+ protected void sendText(HttpExchange exchange, String text) throws IOException {
+ sendResponse(exchange, text, 200);
+ }
+
+ protected void sendNotFound(HttpExchange exchange) throws IOException {
+ sendResponse(exchange, "Задача не найдена", 404);
+ }
+
+ protected void sendHasOverlaps(HttpExchange exchange, String message) throws IOException {
+ sendResponse(exchange, message, 406);
+ }
+
+ protected void sendBadRequest(HttpExchange exchange, String message) throws IOException {
+ sendResponse(exchange, message, 400);
+ }
+}
diff --git a/src/manager/http/EpicsHandler.java b/src/manager/http/EpicsHandler.java
new file mode 100644
index 0000000..66fa1f5
--- /dev/null
+++ b/src/manager/http/EpicsHandler.java
@@ -0,0 +1,114 @@
+package manager.http;
+
+import com.google.gson.JsonSyntaxException;
+import com.sun.net.httpserver.HttpExchange;
+import manager.TaskManager;
+import tasks.Epic;
+import java.io.IOException;
+
+public class EpicsHandler extends BaseHttpHandler {
+ private final TaskManager taskManager;
+
+ public EpicsHandler(TaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ String method = exchange.getRequestMethod();
+ String path = exchange.getRequestURI().getPath();
+
+ switch (method) {
+ case "GET":
+ if (path.equals("/epics")) {
+ handleGetAllEpics(exchange);
+ } else if (path.startsWith("/epics/")) {
+ if (path.endsWith("/subtasks")) {
+ handleGetEpicSubtasks(exchange, path);
+ } else {
+ handleEpicWithId(exchange, path, "GET");
+ }
+ } else {
+ sendBadRequest(exchange, "Некорректный путь");
+ }
+ break;
+ case "POST":
+ handleCreateOrUpdateEpic(exchange);
+ break;
+ case "DELETE":
+ if (path.startsWith("/epics/")) {
+ handleEpicWithId(exchange, path, "DELETE");
+ } else {
+ sendBadRequest(exchange, "Некорректный путь");
+ }
+ break;
+ default:
+ sendBadRequest(exchange, "Неподдерживаемый метод");
+ }
+ } catch (Exception e) {
+ sendResponse(exchange, "Ошибка сервера", 500);
+ }
+ }
+
+ private void handleEpicWithId(HttpExchange exchange, String path, String method) throws IOException {
+ String idStr = path.substring("/epics/".length());
+ try {
+ int epicId = Integer.parseInt(idStr);
+ if (method.equals("GET")) {
+ Epic epic = taskManager.getEpic(epicId);
+ if (epic != null) {
+ sendText(exchange, gson.toJson(epic));
+ } else {
+ sendNotFound(exchange);
+ }
+ } else if (method.equals("DELETE")) {
+ Epic epic = taskManager.deleteEpic(epicId);
+ if (epic != null) {
+ sendText(exchange, "Эпик удален");
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+ } catch (NumberFormatException e) {
+ sendBadRequest(exchange, "Некорректный ID эпика");
+ }
+ }
+
+ private void handleGetEpicSubtasks(HttpExchange exchange, String path) throws IOException {
+ String idStr = path.substring("/epics/".length(), path.length() - "/subtasks".length());
+ try {
+ int epicId = Integer.parseInt(idStr);
+ Epic epic = taskManager.getEpic(epicId);
+ if (epic != null) {
+ String response = gson.toJson(taskManager.getEpicSubtasks(epicId));
+ sendText(exchange, response);
+ } else {
+ sendNotFound(exchange);
+ }
+ } catch (NumberFormatException e) {
+ sendBadRequest(exchange, "Некорректный ID эпика");
+ }
+ }
+
+ private void handleGetAllEpics(HttpExchange exchange) throws IOException {
+ String response = gson.toJson(taskManager.getEpics());
+ sendText(exchange, response);
+ }
+
+ private void handleCreateOrUpdateEpic(HttpExchange exchange) throws IOException {
+ String body = readRequest(exchange);
+ try {
+ Epic epic = gson.fromJson(body, Epic.class);
+ if (epic.getId() == null) {
+ taskManager.createEpic(epic);
+ sendResponse(exchange, "Эпик создан", 201);
+ } else {
+ taskManager.updateEpic(epic);
+ sendText(exchange, "Эпик обновлен");
+ }
+ } catch (JsonSyntaxException e) {
+ sendBadRequest(exchange, "Неверный формат JSON");
+ }
+ }
+}
diff --git a/src/manager/http/HistoryHandler.java b/src/manager/http/HistoryHandler.java
new file mode 100644
index 0000000..6194f4c
--- /dev/null
+++ b/src/manager/http/HistoryHandler.java
@@ -0,0 +1,31 @@
+package manager.http;
+
+import com.sun.net.httpserver.HttpExchange;
+import manager.TaskManager;
+import java.io.IOException;
+
+public class HistoryHandler extends BaseHttpHandler {
+ private final TaskManager taskManager;
+
+ public HistoryHandler(TaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ handleGetHistory(exchange);
+ } else {
+ sendBadRequest(exchange, "Неподдерживаемый метод");
+ }
+ } catch (Exception e) {
+ sendResponse(exchange, "Ошибка сервера", 500);
+ }
+ }
+
+ private void handleGetHistory(HttpExchange exchange) throws IOException {
+ String response = gson.toJson(taskManager.getHistory());
+ sendText(exchange, response);
+ }
+}
diff --git a/src/manager/http/HttpTaskServer.java b/src/manager/http/HttpTaskServer.java
new file mode 100644
index 0000000..b04eb54
--- /dev/null
+++ b/src/manager/http/HttpTaskServer.java
@@ -0,0 +1,43 @@
+package manager.http;
+
+import com.sun.net.httpserver.HttpServer;
+import manager.Managers;
+import manager.TaskManager;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+public class HttpTaskServer {
+ private final HttpServer server;
+ private final TaskManager taskManager;
+ private static final int PORT = 8080;
+
+ public HttpTaskServer() throws IOException {
+ this.taskManager = Managers.getDefault();
+ this.server = HttpServer.create(new InetSocketAddress(PORT), 0);
+
+ server.createContext("/tasks", new TasksHandler(taskManager));
+ server.createContext("/subtasks", new SubtasksHandler(taskManager));
+ server.createContext("/epics", new EpicsHandler(taskManager));
+ server.createContext("/history", new HistoryHandler(taskManager));
+ server.createContext("/prioritized", new PrioritizedHandler(taskManager));
+ }
+
+ public TaskManager getTaskManager() {
+ return taskManager;
+ }
+
+ public void start() {
+ server.start();
+ System.out.println("Сервер запущен на порту " + PORT);
+ }
+
+ public void stop() {
+ server.stop(0);
+ System.out.println("Сервер остановлен");
+ }
+
+ public static void main(String[] args) throws IOException {
+ HttpTaskServer server = new HttpTaskServer();
+ server.start();
+ }
+}
diff --git a/src/manager/http/PrioritizedHandler.java b/src/manager/http/PrioritizedHandler.java
new file mode 100644
index 0000000..bce6277
--- /dev/null
+++ b/src/manager/http/PrioritizedHandler.java
@@ -0,0 +1,31 @@
+package manager.http;
+
+import com.sun.net.httpserver.HttpExchange;
+import manager.TaskManager;
+import java.io.IOException;
+
+public class PrioritizedHandler extends BaseHttpHandler {
+ private final TaskManager taskManager;
+
+ public PrioritizedHandler(TaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ handleGetPrioritized(exchange);
+ } else {
+ sendBadRequest(exchange, "Неподдерживаемый метод");
+ }
+ } catch (Exception e) {
+ sendResponse(exchange, "Ошибка сервера", 500);
+ }
+ }
+
+ private void handleGetPrioritized(HttpExchange exchange) throws IOException {
+ String response = gson.toJson(taskManager.getPrioritizedTasks());
+ sendText(exchange, response);
+ }
+}
diff --git a/src/manager/http/SubtasksHandler.java b/src/manager/http/SubtasksHandler.java
new file mode 100644
index 0000000..f52c9d6
--- /dev/null
+++ b/src/manager/http/SubtasksHandler.java
@@ -0,0 +1,97 @@
+package manager.http;
+
+import com.google.gson.JsonSyntaxException;
+import com.sun.net.httpserver.HttpExchange;
+import manager.TaskManager;
+import manager.exceptions.ManagerSaveException;
+import tasks.Subtask;
+import java.io.IOException;
+
+public class SubtasksHandler extends BaseHttpHandler {
+ private final TaskManager taskManager;
+
+ public SubtasksHandler(TaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ String method = exchange.getRequestMethod();
+ String path = exchange.getRequestURI().getPath();
+
+ switch (method) {
+ case "GET":
+ if (path.equals("/subtasks")) {
+ handleGetAllSubtasks(exchange);
+ } else if (path.startsWith("/subtasks/")) {
+ handleSubtaskWithId(exchange, path, "GET");
+ } else {
+ sendBadRequest(exchange, "Некорректный путь");
+ }
+ break;
+ case "POST":
+ handleCreateOrUpdateSubtask(exchange);
+ break;
+ case "DELETE":
+ if (path.startsWith("/subtasks/")) {
+ handleSubtaskWithId(exchange, path, "DELETE");
+ } else {
+ sendBadRequest(exchange, "Некорректный путь");
+ }
+ break;
+ default:
+ sendBadRequest(exchange, "Неподдерживаемый метод");
+ }
+ } catch (Exception e) {
+ sendResponse(exchange, "Ошибка сервера", 500);
+ }
+ }
+
+ private void handleSubtaskWithId(HttpExchange exchange, String path, String method) throws IOException {
+ String idStr = path.substring("/subtasks/".length());
+ try {
+ int subtaskId = Integer.parseInt(idStr);
+ if (method.equals("GET")) {
+ Subtask subtask = taskManager.getSubtasks(subtaskId);
+ if (subtask != null) {
+ sendText(exchange, gson.toJson(subtask));
+ } else {
+ sendNotFound(exchange);
+ }
+ } else if (method.equals("DELETE")) {
+ Subtask subtask = taskManager.deleteSubtask(subtaskId);
+ if (subtask != null) {
+ sendText(exchange, "Подзадача удалена");
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+ } catch (NumberFormatException e) {
+ sendBadRequest(exchange, "Некорректный ID подзадачи");
+ }
+ }
+
+ private void handleGetAllSubtasks(HttpExchange exchange) throws IOException {
+ String response = gson.toJson(taskManager.getSubtasks());
+ sendText(exchange, response);
+ }
+
+ private void handleCreateOrUpdateSubtask(HttpExchange exchange) throws IOException {
+ String body = readRequest(exchange);
+ try {
+ Subtask subtask = gson.fromJson(body, Subtask.class);
+ if (subtask.getId() == null) {
+ taskManager.createSubtask(subtask);
+ sendResponse(exchange, "Подзадача создана", 201);
+ } else {
+ taskManager.updateSubtask(subtask);
+ sendText(exchange, "Подзадача обновлена");
+ }
+ } catch (JsonSyntaxException e) {
+ sendBadRequest(exchange, "Неверный формат JSON");
+ } catch (ManagerSaveException e) {
+ sendHasOverlaps(exchange, e.getMessage());
+ }
+ }
+}
diff --git a/src/manager/http/TasksHandler.java b/src/manager/http/TasksHandler.java
new file mode 100644
index 0000000..7be7de6
--- /dev/null
+++ b/src/manager/http/TasksHandler.java
@@ -0,0 +1,97 @@
+package manager.http;
+
+import com.google.gson.JsonSyntaxException;
+import com.sun.net.httpserver.HttpExchange;
+import manager.TaskManager;
+import manager.exceptions.ManagerSaveException;
+import tasks.Task;
+import java.io.IOException;
+
+public class TasksHandler extends BaseHttpHandler {
+ private final TaskManager taskManager;
+
+ public TasksHandler(TaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ String method = exchange.getRequestMethod();
+ String path = exchange.getRequestURI().getPath();
+
+ switch (method) {
+ case "GET":
+ if (path.equals("/tasks")) {
+ handleGetAllTasks(exchange);
+ } else if (path.startsWith("/tasks/")) {
+ handleTaskWithId(exchange, path, "GET");
+ } else {
+ sendBadRequest(exchange, "Некорректный путь");
+ }
+ break;
+ case "POST":
+ handleCreateOrUpdateTask(exchange);
+ break;
+ case "DELETE":
+ if (path.startsWith("/tasks/")) {
+ handleTaskWithId(exchange, path, "DELETE");
+ } else {
+ sendBadRequest(exchange, "Некорректный путь");
+ }
+ break;
+ default:
+ sendBadRequest(exchange, "Неподдерживаемый метод");
+ }
+ } catch (Exception e) {
+ sendResponse(exchange, "Ошибка сервера", 500);
+ }
+ }
+
+ private void handleTaskWithId(HttpExchange exchange, String path, String method) throws IOException {
+ String idStr = path.substring("/tasks/".length());
+ try {
+ int taskId = Integer.parseInt(idStr);
+ if (method.equals("GET")) {
+ Task task = taskManager.getTask(taskId);
+ if (task != null) {
+ sendText(exchange, gson.toJson(task));
+ } else {
+ sendNotFound(exchange);
+ }
+ } else if (method.equals("DELETE")) {
+ Task task = taskManager.deleteTask(taskId);
+ if (task != null) {
+ sendText(exchange, "Задача удалена");
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+ } catch (NumberFormatException e) {
+ sendBadRequest(exchange, "Некорректный ID задачи");
+ }
+ }
+
+ private void handleGetAllTasks(HttpExchange exchange) throws IOException {
+ String response = gson.toJson(taskManager.getTasks());
+ sendText(exchange, response);
+ }
+
+ private void handleCreateOrUpdateTask(HttpExchange exchange) throws IOException {
+ String body = readRequest(exchange);
+ try {
+ Task task = gson.fromJson(body, Task.class);
+ if (task.getId() == null) {
+ taskManager.createTask(task);
+ sendResponse(exchange, "Задача создана", 201);
+ } else {
+ taskManager.updateTask(task);
+ sendText(exchange, "Задача обновлена");
+ }
+ } catch (JsonSyntaxException e) {
+ sendBadRequest(exchange, "Неверный формат JSON");
+ } catch (ManagerSaveException e) {
+ sendHasOverlaps(exchange, e.getMessage());
+ }
+ }
+}
diff --git a/src/manager/http/TimeAdapters.java b/src/manager/http/TimeAdapters.java
new file mode 100644
index 0000000..fe67e52
--- /dev/null
+++ b/src/manager/http/TimeAdapters.java
@@ -0,0 +1,35 @@
+package manager.http;
+
+import com.google.gson.*;
+import java.lang.reflect.Type;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class TimeAdapters {
+ public static class LocalDateTimeAdapter implements JsonSerializer, JsonDeserializer {
+ private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+
+ @Override
+ public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(formatter.format(src));
+ }
+
+ @Override
+ public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ return LocalDateTime.parse(json.getAsString(), formatter);
+ }
+ }
+
+ public static class DurationAdapter implements JsonSerializer, JsonDeserializer {
+ @Override
+ public JsonElement serialize(Duration src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.toMillis());
+ }
+
+ @Override
+ public Duration deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ return Duration.ofMillis(json.getAsLong());
+ }
+ }
+}
diff --git a/test/manager/http/CommonTestSetup.java b/test/manager/http/CommonTestSetup.java
new file mode 100644
index 0000000..16bf0a2
--- /dev/null
+++ b/test/manager/http/CommonTestSetup.java
@@ -0,0 +1,38 @@
+package manager.http;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import manager.TaskManager;
+import java.net.http.HttpClient;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import java.io.IOException;
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+public class CommonTestSetup {
+ protected HttpTaskServer server;
+ protected TaskManager taskManager;
+ protected Gson gson;
+ protected HttpClient client;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ server = new HttpTaskServer();
+ server.start();
+ taskManager = server.getTaskManager();
+
+ gson = new GsonBuilder()
+ .registerTypeAdapter(LocalDateTime.class, new TimeAdapters.LocalDateTimeAdapter())
+ .registerTypeAdapter(Duration.class, new TimeAdapters.DurationAdapter())
+ .create();
+
+ client = HttpClient.newHttpClient();
+ }
+
+ @AfterEach
+ void tearDown() {
+ server.stop();
+ }
+}
+
diff --git a/test/manager/http/EpicsEndpointTest.java b/test/manager/http/EpicsEndpointTest.java
new file mode 100644
index 0000000..d09fdf0
--- /dev/null
+++ b/test/manager/http/EpicsEndpointTest.java
@@ -0,0 +1,60 @@
+package manager.http;
+
+import org.junit.jupiter.api.Test;
+import tasks.Epic;
+import tasks.Subtask;
+import tasks.TaskStatus;
+
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class EpicsEndpointTest extends CommonTestSetup {
+ @Test
+ void createEpic_shouldReturn201AndSaveEpic() throws Exception {
+ Epic epic = new Epic("Epic", "Description");
+ String epicJson = gson.toJson(epic);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/epics"))
+ .POST(HttpRequest.BodyPublishers.ofString(epicJson))
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(201, response.statusCode());
+ assertEquals(1, taskManager.getEpics().size());
+ }
+
+ @Test
+ void getEpicSubtasks_shouldReturn200AndSubtasksList() throws Exception {
+ Epic epic = taskManager.createEpic(new Epic("Epic", "Desc"));
+ taskManager.createSubtask(new Subtask("Sub", "Desc", TaskStatus.NEW, epic.getId()));
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/epics/" + epic.getId() + "/subtasks"))
+ .GET()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertTrue(response.body().contains("Sub"));
+ }
+
+ @Test
+ void deleteEpic_shouldReturn200AndRemoveWithSubtasks() throws Exception {
+ Epic epic = taskManager.createEpic(new Epic("Epic", "Desc"));
+ taskManager.createSubtask(new Subtask("Sub", "Desc", TaskStatus.NEW, epic.getId()));
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/epics/" + epic.getId()))
+ .DELETE()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertEquals(0, taskManager.getEpics().size());
+ assertEquals(0, taskManager.getSubtasks().size());
+ }
+}
diff --git a/test/manager/http/HistoryEndpointTest.java b/test/manager/http/HistoryEndpointTest.java
new file mode 100644
index 0000000..5ad9b3e
--- /dev/null
+++ b/test/manager/http/HistoryEndpointTest.java
@@ -0,0 +1,40 @@
+package manager.http;
+
+import org.junit.jupiter.api.Test;
+import tasks.Task;
+import tasks.TaskStatus;
+
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class HistoryEndpointTest extends CommonTestSetup {
+ @Test
+ void getHistory_shouldReturn200AndHistoryList() throws Exception {
+ Task task = taskManager.createTask(new Task("Task", "Desc", TaskStatus.NEW));
+ taskManager.getTask(task.getId());
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/history"))
+ .GET()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertTrue(response.body().contains("Task"));
+ }
+
+ @Test
+ void getHistory_whenEmpty_shouldReturn200AndEmptyList() throws Exception {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/history"))
+ .GET()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertEquals("[]", response.body());
+ }
+}
diff --git a/test/manager/http/PrioritizedEndpointTest.java b/test/manager/http/PrioritizedEndpointTest.java
new file mode 100644
index 0000000..f9f9084
--- /dev/null
+++ b/test/manager/http/PrioritizedEndpointTest.java
@@ -0,0 +1,54 @@
+package manager.http;
+
+import org.junit.jupiter.api.Test;
+import tasks.Task;
+import tasks.TaskStatus;
+
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PrioritizedEndpointTest extends CommonTestSetup {
+ @Test
+ void getPrioritized_shouldReturn200AndOrderedTasks() throws Exception {
+ Task task1 = taskManager.createTask(new Task("First", "Desc", TaskStatus.NEW,
+ LocalDateTime.now().plusHours(1), Duration.ofMinutes(30)));
+ Task task2 = taskManager.createTask(new Task("Second", "Desc", TaskStatus.NEW,
+ LocalDateTime.now(), Duration.ofMinutes(30)));
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/prioritized"))
+ .GET()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertTrue(response.body().indexOf("Second") < response.body().indexOf("First"));
+ }
+
+ @Test
+ void getPrioritized_withoutTime_shouldReturn200AndTasksWithoutTimeLast() throws Exception {
+
+ Task timedTask = taskManager.createTask(new Task("Timed", "Desc", TaskStatus.NEW,
+ LocalDateTime.now(), Duration.ofMinutes(30)));
+ Task untimedTask = taskManager.createTask(new Task("Untimed", "Desc", TaskStatus.NEW));
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/prioritized"))
+ .GET()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ String jsonResponse = response.body();
+ System.out.println("Response JSON: " + jsonResponse);
+ assertTrue(jsonResponse.contains("\"name\":\"Timed\""), "Задача с временем должна быть в ответе");
+ assertFalse(jsonResponse.contains("\"name\":\"Untimed\""),
+ "Задача без времени не должна быть в приоритетном списке");
+ int taskCount = jsonResponse.split("\\{\"id\"").length - 1;
+ assertEquals(1, taskCount, "В приоритетном списке должна быть только одна задача");
+ }
+}
diff --git a/test/manager/http/SubtasksEndpointTest.java b/test/manager/http/SubtasksEndpointTest.java
new file mode 100644
index 0000000..121a0fa
--- /dev/null
+++ b/test/manager/http/SubtasksEndpointTest.java
@@ -0,0 +1,71 @@
+package manager.http;
+
+import org.junit.jupiter.api.Test;
+import tasks.Epic;
+import tasks.Subtask;
+import tasks.TaskStatus;
+
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SubtasksEndpointTest extends CommonTestSetup {
+ @Test
+ void createSubtask_withValidEpic_shouldReturn201() throws Exception {
+ Epic epic = taskManager.createEpic(new Epic("Epic", "Desc"));
+ Subtask subtask = new Subtask("Subtask", "Desc", TaskStatus.NEW, epic.getId());
+ String subtaskJson = gson.toJson(subtask);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/subtasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(subtaskJson))
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(201, response.statusCode());
+ assertEquals(1, taskManager.getSubtasks().size());
+ }
+
+ @Test
+ void createSubtask_withoutEpic_shouldNotCreateSubtask() throws Exception {
+ assertNull(taskManager.getEpic(999), "Эпик 999 не должен существовать");
+ List initialSubtasks = new ArrayList<>(taskManager.getSubtasks());
+ Subtask subtask = new Subtask("Subtask", "Desc", TaskStatus.NEW, 999);
+ String subtaskJson = gson.toJson(subtask);
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/subtasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(subtaskJson))
+ .build();
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(initialSubtasks, taskManager.getSubtasks(),
+ "Список подзадач не должен измениться");
+
+ if (response.statusCode() == 201) {
+ assertFalse(response.body().contains("\"id\":"),
+ "Ответ не должен содержать ID подзадачи");
+ }
+ }
+
+ @Test
+ void updateSubtaskStatus_shouldUpdateEpicStatus() throws Exception {
+ Epic epic = taskManager.createEpic(new Epic("Epic", "Desc"));
+ Subtask subtask = taskManager.createSubtask(
+ new Subtask("Sub", "Desc", TaskStatus.NEW, epic.getId()));
+
+
+ subtask.setTaskStatus(TaskStatus.DONE);
+ String subtaskJson = gson.toJson(subtask);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/subtasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(subtaskJson))
+ .build();
+
+ client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(TaskStatus.DONE, epic.getTaskStatus());
+ }
+}
\ No newline at end of file
diff --git a/test/manager/http/TasksEndpointTest.java b/test/manager/http/TasksEndpointTest.java
new file mode 100644
index 0000000..82974bd
--- /dev/null
+++ b/test/manager/http/TasksEndpointTest.java
@@ -0,0 +1,101 @@
+package manager.http;
+
+import org.junit.jupiter.api.Test;
+import tasks.Task;
+import tasks.TaskStatus;
+
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TasksEndpointTest extends CommonTestSetup {
+ @Test
+ void createTask_shouldReturn201AndSaveTask() throws Exception {
+ Task task = new Task("Test", "Description", TaskStatus.NEW);
+ String taskJson = gson.toJson(task);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(taskJson))
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(201, response.statusCode());
+ assertEquals(1, taskManager.getTasks().size());
+ }
+
+ @Test
+ void getTaskById_withInvalidId_shouldReturn404() throws Exception {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks/999"))
+ .GET()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(404, response.statusCode());
+ }
+
+ @Test
+ void updateTask_shouldReturn200AndUpdateTask() throws Exception {
+ Task task = taskManager.createTask(new Task("Original", "Desc", TaskStatus.NEW));
+ task.setName("Updated");
+ String taskJson = gson.toJson(task);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(taskJson))
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertEquals("Updated", taskManager.getTask(task.getId()).getName());
+ }
+
+ @Test
+ void deleteTask_shouldReturn200AndRemoveTask() throws Exception {
+ Task task = taskManager.createTask(new Task("ToDelete", "Desc", TaskStatus.NEW));
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks/" + task.getId()))
+ .DELETE()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertEquals(0, taskManager.getTasks().size());
+ }
+
+ @Test
+ void createTaskWithTime_shouldReturn201AndSetTime() throws Exception {
+ Task task = new Task("Timed", "Desc", TaskStatus.NEW,
+ LocalDateTime.now(), Duration.ofMinutes(30));
+ String taskJson = gson.toJson(task);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(taskJson))
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+ assertEquals(201, response.statusCode());
+
+ List tasks = taskManager.getTasks();
+
+ Task createdTask = null;
+ for (Task t : tasks) {
+ if ("Timed".equals(t.getName())) {
+ createdTask = t;
+ break;
+ }
+ }
+ assertNotNull(createdTask, "Задача не была создана на сервере");
+ assertNotNull(createdTask.getStartTime(), "Время начала не установлено");
+ assertNotNull(createdTask.getId(), "ID не был сгенерирован");
+ }
+}