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