diff --git a/java-kanban.iml b/java-kanban.iml
index 2642f22..a11e959 100644
--- a/java-kanban.iml
+++ b/java-kanban.iml
@@ -56,5 +56,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/managers/InMemoryTaskManager.java b/src/managers/InMemoryTaskManager.java
index 85680a2..997413a 100644
--- a/src/managers/InMemoryTaskManager.java
+++ b/src/managers/InMemoryTaskManager.java
@@ -233,4 +233,4 @@ public List getHistory() {
public List getPrioritizedTasks() {
return new ArrayList<>(prioritizedTasks);
}
-}
+}
\ No newline at end of file
diff --git a/src/managers/NotFoundException.java b/src/managers/NotFoundException.java
new file mode 100644
index 0000000..69f1418
--- /dev/null
+++ b/src/managers/NotFoundException.java
@@ -0,0 +1,7 @@
+package managers;
+
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/server/BaseHttpHandler.java b/src/server/BaseHttpHandler.java
new file mode 100644
index 0000000..7e547db
--- /dev/null
+++ b/src/server/BaseHttpHandler.java
@@ -0,0 +1,38 @@
+package server;
+
+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.nio.charset.StandardCharsets;
+import java.time.Duration;
+
+public abstract class BaseHttpHandler implements HttpHandler {
+ protected final Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Duration.class, new DurationAdapter())
+ .create();
+
+ protected void sendText(HttpExchange exchange, String text, int code) throws IOException {
+ byte[] response = text.getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8");
+ exchange.sendResponseHeaders(code, response.length);
+ exchange.getResponseBody().write(response);
+ exchange.close();
+ }
+
+ protected void sendNotFound(HttpExchange exchange) throws IOException {
+ sendText(exchange, "Not Found", 404);
+ }
+
+ protected void sendHasInteractions(HttpExchange exchange) throws IOException {
+ sendText(exchange, "Задача пересекается с существующими", 406);
+ }
+
+ protected void sendInternalError(HttpExchange exchange) throws IOException {
+ sendText(exchange, "Internal Server Error", 500);
+ }
+
+ @Override
+ public abstract void handle(HttpExchange exchange) throws IOException;
+}
\ No newline at end of file
diff --git a/src/server/DurationAdapter.java b/src/server/DurationAdapter.java
new file mode 100644
index 0000000..e0962f7
--- /dev/null
+++ b/src/server/DurationAdapter.java
@@ -0,0 +1,20 @@
+package server;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.time.Duration;
+
+public class DurationAdapter extends TypeAdapter {
+ @Override
+ public void write(JsonWriter out, Duration duration) throws IOException {
+ out.value(duration != null ? duration.toString() : null);
+ }
+
+ @Override
+ public Duration read(JsonReader in) throws IOException {
+ String value = in.nextString();
+ return value != null ? Duration.parse(value) : null;
+ }
+}
\ No newline at end of file
diff --git a/src/server/EpicsHandler.java b/src/server/EpicsHandler.java
new file mode 100644
index 0000000..eef9b47
--- /dev/null
+++ b/src/server/EpicsHandler.java
@@ -0,0 +1,113 @@
+package server;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.sun.net.httpserver.HttpExchange;
+import managers.NotFoundException;
+import managers.TaskManager;
+import tasks.Epic;
+import tasks.Status;
+import tasks.Subtask;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+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":
+ handleGet(exchange, path);
+ break;
+ case "POST":
+ handlePost(exchange);
+ break;
+ case "DELETE":
+ handleDelete(exchange, path);
+ break;
+ default:
+ sendNotFound(exchange);
+ }
+ } catch (Exception e) {
+ sendInternalError(exchange);
+ }
+ }
+
+ private void handleGet(HttpExchange exchange, String path) throws IOException {
+ if (path.matches("/epics/\\d+/subtasks")) {
+ try {
+ int id = Integer.parseInt(path.split("/")[2]);
+ List subtasks = taskManager.getEpicSubtasks(id);
+ sendText(exchange, gson.toJson(subtasks), 200);
+ } catch (NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ } else if (path.equals("/epics")) {
+ List epics = taskManager.getAllEpic();
+ sendText(exchange, gson.toJson(epics), 200);
+ } else if (path.matches("/epics/\\d+")) {
+ try {
+ int id = Integer.parseInt(path.split("/")[2]);
+ Epic epic = taskManager.getAllEpic().stream()
+ .filter(e -> e.getId() == id)
+ .findFirst()
+ .orElseThrow(() -> new NotFoundException("Эпик с ID " + id + " не найден"));
+ sendText(exchange, gson.toJson(epic), 200);
+ } catch (NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+
+ private void handlePost(HttpExchange exchange) throws IOException {
+ try {
+ String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
+ JsonObject json = JsonParser.parseString(body).getAsJsonObject();
+
+ String name = json.get("name").getAsString();
+ String description = json.get("description").getAsString();
+ Status status = Status.valueOf(json.get("status").getAsString());
+
+ Epic epic = new Epic(name, description, status);
+
+ if (json.has("id") && json.get("id").getAsInt() != 0) {
+ epic.setId(json.get("id").getAsInt());
+ taskManager.updateEpic(epic);
+ sendText(exchange, "Эпик обновлён", 200);
+ } else {
+ taskManager.addEpic(epic);
+ sendText(exchange, "Эпик создан", 201);
+ }
+ } catch (NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ }
+
+ private void handleDelete(HttpExchange exchange, String path) throws IOException {
+ if (path.matches("/epics/\\d+")) {
+ try {
+ int id = Integer.parseInt(path.split("/")[2]);
+ taskManager.removeEpicById(id);
+ sendText(exchange, "Эпик удалён", 200);
+ } catch (NumberFormatException | NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ } else if (path.equals("/epics")) {
+ taskManager.removeAllEpics();
+ sendText(exchange, "Все эпики удалены", 200);
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/server/HistoryHandler.java b/src/server/HistoryHandler.java
new file mode 100644
index 0000000..2266d63
--- /dev/null
+++ b/src/server/HistoryHandler.java
@@ -0,0 +1,29 @@
+package server;
+
+import com.sun.net.httpserver.HttpExchange;
+import managers.TaskManager;
+import tasks.Task;
+import java.io.IOException;
+import java.util.List;
+
+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 (!exchange.getRequestMethod().equals("GET")) {
+ sendNotFound(exchange);
+ return;
+ }
+ List history = taskManager.getHistory();
+ sendText(exchange, gson.toJson(history), 200);
+ } catch (Exception e) {
+ sendInternalError(exchange);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/server/HttpTaskServer.java b/src/server/HttpTaskServer.java
new file mode 100644
index 0000000..480ff1f
--- /dev/null
+++ b/src/server/HttpTaskServer.java
@@ -0,0 +1,43 @@
+package server;
+
+import com.sun.net.httpserver.HttpServer;
+import managers.Managers;
+import managers.TaskManager;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+public class HttpTaskServer {
+ private static final int PORT = 8080;
+ private final HttpServer server;
+ private final TaskManager taskManager;
+
+ public HttpTaskServer(TaskManager taskManager) throws IOException {
+ this.taskManager = taskManager;
+ server = HttpServer.create(new InetSocketAddress(PORT), 0);
+ registerHandlers();
+ }
+
+ private void registerHandlers() {
+ 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 void start() {
+ server.start();
+ System.out.println("HTTP-сервер запущен на порту " + PORT);
+ }
+
+ public void stop() {
+ server.stop(0);
+ System.out.println("HTTP-сервер остановлен");
+ }
+
+ public static void main(String[] args) throws IOException {
+ TaskManager manager = Managers.getDefault();
+ HttpTaskServer server = new HttpTaskServer(manager);
+ server.start();
+ }
+}
\ No newline at end of file
diff --git a/src/server/PrioritizedHandler.java b/src/server/PrioritizedHandler.java
new file mode 100644
index 0000000..c34186b
--- /dev/null
+++ b/src/server/PrioritizedHandler.java
@@ -0,0 +1,31 @@
+package server;
+
+import com.sun.net.httpserver.HttpExchange;
+import managers.InMemoryTaskManager;
+import managers.TaskManager;
+import tasks.Task;
+import java.io.IOException;
+import java.util.List;
+
+public class PrioritizedHandler extends BaseHttpHandler {
+ private final TaskManager taskManager;
+
+ public PrioritizedHandler(TaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ if (!exchange.getRequestMethod().equals("GET")) {
+ sendNotFound(exchange);
+ return;
+ }
+
+ if (taskManager instanceof InMemoryTaskManager) {
+ List prioritizedTasks = ((InMemoryTaskManager) taskManager).getPrioritizedTasks();
+ sendText(exchange, gson.toJson(prioritizedTasks), 200);
+ } else {
+ sendInternalError(exchange);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/server/SubtasksHandler.java b/src/server/SubtasksHandler.java
new file mode 100644
index 0000000..d1e6aba
--- /dev/null
+++ b/src/server/SubtasksHandler.java
@@ -0,0 +1,119 @@
+package server;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.sun.net.httpserver.HttpExchange;
+import managers.NotFoundException;
+import managers.TaskManager;
+import tasks.Status;
+import tasks.Subtask;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+
+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":
+ handleGet(exchange, path);
+ break;
+ case "POST":
+ handlePost(exchange);
+ break;
+ case "DELETE":
+ handleDelete(exchange, path);
+ break;
+ default:
+ sendNotFound(exchange);
+ }
+ } catch (Exception e) {
+ sendInternalError(exchange);
+ }
+ }
+
+ private void handleGet(HttpExchange exchange, String path) throws IOException {
+ if (path.matches("/subtasks/\\d+")) {
+ try {
+ int id = Integer.parseInt(path.split("/")[2]);
+ Subtask subtask = taskManager.getSubtaskById(id);
+ sendText(exchange, gson.toJson(subtask), 200);
+ } catch (NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ } else if (path.equals("/subtasks")) {
+ List subtasks = taskManager.getAllSubtask();
+ sendText(exchange, gson.toJson(subtasks), 200);
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+
+ private void handlePost(HttpExchange exchange) throws IOException {
+ try {
+ String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
+ JsonObject json = JsonParser.parseString(body).getAsJsonObject();
+
+ String name = json.get("name").getAsString();
+ String description = json.get("description").getAsString();
+ Status status = Status.valueOf(json.get("status").getAsString());
+ int epicId = json.get("epicId").getAsInt();
+
+ taskManager.getEpicSubtasks(epicId);
+
+ Duration duration = json.has("duration") && !json.get("duration").isJsonNull()
+ ? Duration.parse(json.get("duration").getAsString())
+ : null;
+ LocalDateTime startTime = json.has("startTime") && !json.get("startTime").isJsonNull()
+ ? LocalDateTime.parse(json.get("startTime").getAsString())
+ : null;
+
+ Subtask subtask = new Subtask(name, description, status, epicId, duration, startTime);
+
+ if (json.has("id") && json.get("id").getAsInt() != 0) {
+ subtask.setId(json.get("id").getAsInt());
+ taskManager.updateSubtask(subtask);
+ sendText(exchange, "Подзадача обновлена", 200);
+ } else {
+ taskManager.addSubtask(subtask);
+ sendText(exchange, "Подзадача создана", 201);
+ }
+ } catch (IllegalArgumentException e) {
+ if (e.getMessage().contains("пересекается")) {
+ sendHasInteractions(exchange);
+ } else {
+ sendNotFound(exchange);
+ }
+ } catch (NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ }
+
+ private void handleDelete(HttpExchange exchange, String path) throws IOException {
+ if (path.matches("/subtasks/\\d+")) {
+ try {
+ int id = Integer.parseInt(path.split("/")[2]);
+ taskManager.removeSubtaskId(id);
+ sendText(exchange, "Подзадача удалена", 200);
+ } catch (NumberFormatException | NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ } else if (path.equals("/subtasks")) {
+ taskManager.removeAllSubtasks();
+ sendText(exchange, "Все подзадачи удалены", 200);
+ } else {
+ sendNotFound(exchange);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/server/TasksHandler.java b/src/server/TasksHandler.java
new file mode 100644
index 0000000..63f7390
--- /dev/null
+++ b/src/server/TasksHandler.java
@@ -0,0 +1,101 @@
+package server;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.sun.net.httpserver.HttpExchange;
+import managers.NotFoundException;
+import managers.TaskManager;
+import tasks.Status;
+import tasks.Task;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+
+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 {
+ switch (exchange.getRequestMethod()) {
+ case "GET":
+ handleGet(exchange);
+ break;
+ case "POST":
+ handlePost(exchange);
+ break;
+ case "DELETE":
+ handleDelete(exchange);
+ break;
+ default:
+ sendNotFound(exchange);
+ }
+ } catch (Exception e) {
+ sendInternalError(exchange);
+ }
+ }
+
+ private void handleGet(HttpExchange exchange) throws IOException {
+ List tasks = taskManager.getAllTasks();
+ sendText(exchange, gson.toJson(tasks), 200);
+ }
+
+ private void handlePost(HttpExchange exchange) throws IOException {
+ try {
+ String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
+ JsonObject json = JsonParser.parseString(body).getAsJsonObject();
+
+ String name = json.get("name").getAsString();
+ String description = json.get("description").getAsString();
+ Status status = Status.valueOf(json.get("status").getAsString());
+
+ Duration duration = json.has("duration") && !json.get("duration").isJsonNull()
+ ? Duration.parse(json.get("duration").getAsString())
+ : null;
+ LocalDateTime startTime = json.has("startTime") && !json.get("startTime").isJsonNull()
+ ? LocalDateTime.parse(json.get("startTime").getAsString())
+ : null;
+
+ Task task = new Task(name, description, status, duration, startTime);
+
+ if (json.has("id") && json.get("id").getAsInt() != 0) {
+ task.setId(json.get("id").getAsInt());
+ taskManager.updateTask(task);
+ sendText(exchange, "Задача обновлена", 200);
+ } else {
+ taskManager.addTask(task);
+ sendText(exchange, "Задача создана", 201);
+ }
+ } catch (IllegalArgumentException e) {
+ if (e.getMessage().contains("пересекается")) {
+ sendHasInteractions(exchange);
+ } else {
+ sendNotFound(exchange);
+ }
+ } catch (NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ }
+
+ private void handleDelete(HttpExchange exchange) throws IOException {
+ String query = exchange.getRequestURI().getQuery();
+ if (query != null && query.startsWith("id=")) {
+ try {
+ int id = Integer.parseInt(query.substring(3));
+ taskManager.removeTaskById(id);
+ sendText(exchange, "Задача удалена", 200);
+ } catch (NumberFormatException | NotFoundException e) {
+ sendNotFound(exchange);
+ }
+ } else {
+ taskManager.removeAllTasks();
+ sendText(exchange, "Все задачи удалены", 200);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/server/HttpTaskServerTests.java b/test/server/HttpTaskServerTests.java
new file mode 100644
index 0000000..2bdedd0
--- /dev/null
+++ b/test/server/HttpTaskServerTests.java
@@ -0,0 +1,234 @@
+package server;
+
+import com.google.gson.*;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import managers.Managers;
+import managers.TaskManager;
+import org.junit.jupiter.api.*;
+import tasks.Epic;
+import tasks.Status;
+import tasks.Subtask;
+import tasks.Task;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class HttpTaskServerTest {
+ private static final String BASE = "http://localhost:8080";
+
+ private TaskManager manager;
+ private HttpTaskServer server;
+ private HttpClient client;
+ private Gson gson;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ manager = Managers.getDefault();
+ manager.removeAllTasks();
+ manager.removeAllSubtasks();
+ manager.removeAllEpics();
+
+ server = new HttpTaskServer(manager);
+ server.start();
+
+ client = HttpClient.newHttpClient();
+ gson = new GsonBuilder()
+ // 1. адаптеры для Duration и LocalDateTime
+ .registerTypeAdapter(Duration.class, new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, Duration value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ @Override
+ public Duration read(JsonReader in) throws IOException {
+ String s = in.nextString();
+ return s == null ? null : Duration.parse(s);
+ }
+ })
+ .registerTypeAdapter(LocalDateTime.class, new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, LocalDateTime value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ @Override
+ public LocalDateTime read(JsonReader in) throws IOException {
+ String s = in.nextString();
+ return s == null ? null : LocalDateTime.parse(s);
+ }
+ })
+ // 2. пропускаем поля duration и startTime из суперкласса Task, чтобы не было дублирования
+ .addSerializationExclusionStrategy(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes f) {
+ return f.getDeclaringClass().equals(tasks.Task.class)
+ && (f.getName().equals("duration") || f.getName().equals("startTime"));
+ }
+ @Override
+ public boolean shouldSkipClass(Class> clazz) {
+ return false;
+ }
+ })
+ .addDeserializationExclusionStrategy(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes f) {
+ return f.getDeclaringClass().equals(tasks.Task.class)
+ && (f.getName().equals("duration") || f.getName().equals("startTime"));
+ }
+ @Override
+ public boolean shouldSkipClass(Class> clazz) {
+ return false;
+ }
+ })
+ .create();
+ }
+
+ @AfterEach
+ void tearDown() {
+ server.stop();
+ }
+
+ // /tasks
+
+ @Test
+ void createTaskSuccess() throws IOException, InterruptedException {
+ Task t = new Task("T1","D1", Status.NEW, Duration.ofMinutes(5), LocalDateTime.now());
+ String body = gson.toJson(t);
+
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/tasks"))
+ .header("Content-Type","application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(201, resp.statusCode());
+ List all = manager.getAllTasks();
+ assertEquals(1, all.size());
+ assertEquals("T1", all.get(0).getName());
+ }
+
+ @Test
+ void invalidMethodTasks() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/tasks"))
+ .method("PUT", HttpRequest.BodyPublishers.noBody())
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(404, resp.statusCode());
+ }
+
+ // /subtasks
+
+ @Test
+ void createSubtaskSuccess() throws IOException, InterruptedException {
+ Epic epic = new Epic("E1","ED", Status.NEW);
+ manager.addEpic(epic);
+
+ Subtask s = new Subtask("S1","SD", Status.NEW,
+ epic.getId(), Duration.ofMinutes(3), LocalDateTime.now());
+ String body = gson.toJson(s);
+
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/subtasks"))
+ .header("Content-Type","application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(201, resp.statusCode());
+ assertEquals(1, manager.getAllSubtask().size());
+ }
+
+ @Test
+ void invalidMethodSubtasks() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/subtasks"))
+ .method("PATCH", HttpRequest.BodyPublishers.noBody())
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(404, resp.statusCode());
+ }
+
+ // /epics
+
+ @Test
+ void getNonExistingEpic() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/epics/999"))
+ .GET()
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(404, resp.statusCode());
+ }
+
+ // /history
+
+ @Test
+ void getEmptyHistory() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/history"))
+ .GET()
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(200, resp.statusCode());
+ assertEquals("[]", resp.body());
+ }
+
+ @Test
+ void invalidMethodHistory() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/history"))
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(404, resp.statusCode());
+ }
+
+ // /prioritized
+
+ @Test
+ void getEmptyPrioritized() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/prioritized"))
+ .GET()
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(200, resp.statusCode());
+ assertEquals("[]", resp.body());
+ }
+
+ @Test
+ void invalidMethodPrioritized() throws IOException, InterruptedException {
+ HttpResponse resp = client.send(
+ HttpRequest.newBuilder()
+ .uri(URI.create(BASE + "/prioritized"))
+ .method("DELETE", HttpRequest.BodyPublishers.noBody())
+ .build(),
+ HttpResponse.BodyHandlers.ofString()
+ );
+ assertEquals(404, resp.statusCode());
+ }
+}
diff --git a/test/server/TasksHandlerTest.java b/test/server/TasksHandlerTest.java
new file mode 100644
index 0000000..688a775
--- /dev/null
+++ b/test/server/TasksHandlerTest.java
@@ -0,0 +1,145 @@
+package server;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import managers.InMemoryHistoryManager;
+import managers.InMemoryTaskManager;
+import managers.TaskManager;
+import managers.HistoryManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tasks.Task;
+import tasks.Status;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TasksHandlerTest {
+ private HttpTaskServer taskServer;
+ private TaskManager taskManager;
+ private Gson gson;
+ private HttpClient client;
+
+ public TasksHandlerTest() throws IOException {
+ HistoryManager historyManager = new InMemoryHistoryManager();
+ taskManager = new InMemoryTaskManager(historyManager);
+ taskServer = new HttpTaskServer(taskManager);
+ client = HttpClient.newHttpClient();
+ gson = new GsonBuilder()
+ .registerTypeAdapter(Duration.class, new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, Duration value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ @Override
+ public Duration read(JsonReader in) throws IOException {
+ String s = in.nextString();
+ return s == null ? null : Duration.parse(s);
+ }
+ })
+ .registerTypeAdapter(LocalDateTime.class, new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, LocalDateTime value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ @Override
+ public LocalDateTime read(JsonReader in) throws IOException {
+ String s = in.nextString();
+ return s == null ? null : LocalDateTime.parse(s);
+ }
+ })
+ .create();
+ }
+
+ @BeforeEach
+ public void setUp() {
+ taskManager.removeAllTasks();
+ taskManager.removeAllSubtasks();
+ taskManager.removeAllEpics();
+ taskServer.start();
+ }
+
+ @AfterEach
+ public void shutDown() {
+ taskServer.stop();
+ }
+
+ @Test
+ public void testAddTaskSuccess() throws IOException, InterruptedException {
+ Task task = new Task("Test Task", "Description", Status.NEW, Duration.ofMinutes(30), LocalDateTime.of(2025, 5, 5, 10, 0));
+ String taskJson = gson.toJson(task);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks"))
+ .POST(HttpRequest.BodyPublishers.ofString(taskJson))
+ .header("Content-Type", "application/json")
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(201, response.statusCode());
+ assertEquals("Задача создана", response.body());
+
+ List tasks = taskManager.getAllTasks();
+ assertEquals(1, tasks.size());
+ assertEquals("Test Task", tasks.get(0).getName());
+ }
+
+ @Test
+ public void testDeleteTaskByIdSuccess() throws IOException, InterruptedException {
+ Task task = new Task("Test Task", "Description", Status.NEW, Duration.ofMinutes(30), LocalDateTime.of(2025, 5, 5, 10, 0));
+ taskManager.addTask(task);
+ int taskId = task.getId();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks?id=" + taskId))
+ .DELETE()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertEquals("Задача удалена", response.body());
+
+ assertEquals(0, taskManager.getAllTasks().size());
+ }
+
+ @Test
+ public void testDeleteAllTasksSuccess() throws IOException, InterruptedException {
+ Task task = new Task("Test Task", "Description", Status.NEW, Duration.ofMinutes(30), LocalDateTime.of(2025, 5, 5, 10, 0));
+ taskManager.addTask(task);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks"))
+ .DELETE()
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, response.statusCode());
+ assertEquals("Все задачи удалены", response.body());
+
+ assertEquals(0, taskManager.getAllTasks().size());
+ }
+
+ @Test
+ public void testInvalidMethodReturnsNotFound() throws IOException, InterruptedException {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:8080/tasks"))
+ .PUT(HttpRequest.BodyPublishers.noBody())
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ assertEquals(404, response.statusCode());
+ assertEquals("Not Found", response.body());
+ }
+}
\ No newline at end of file