choices) { this.choices = choices; }
+
+ public boolean isEnding() { return isEnding; }
+ public void setEnding(boolean ending) { isEnding = ending; }
+
+ public String getEndingText() { return endingText; }
+ public void setEndingText(String endingText) { this.endingText = endingText; }
+
+ public void addChoice(QuestChoice choice) {
+ if (choices == null) choices = new ArrayList<>();
+ choices.add(choice);
+ }
+
+ public static class QuestChoice {
+ private String text;
+ private String nextStepId;
+
+ public QuestChoice() {}
+
+ public QuestChoice(String text, String nextStepId) {
+ this.text = text;
+ this.nextStepId = nextStepId;
+ }
+
+ public String getText() { return text; }
+ public void setText(String text) { this.text = text; }
+
+ public String getNextStepId() { return nextStepId; }
+ public void setNextStepId(String nextStepId) { this.nextStepId = nextStepId; }
+ }
+}
diff --git a/src/main/java/com/javarush/wladimir/model/Role.java b/src/main/java/com/javarush/wladimir/model/Role.java
new file mode 100644
index 0000000..09e2d98
--- /dev/null
+++ b/src/main/java/com/javarush/wladimir/model/Role.java
@@ -0,0 +1,6 @@
+package com.javarush.wladimir.model;
+
+public enum Role {
+ USER,
+ ADMIN
+}
diff --git a/src/main/java/com/javarush/wladimir/model/User.java b/src/main/java/com/javarush/wladimir/model/User.java
new file mode 100644
index 0000000..8c04528
--- /dev/null
+++ b/src/main/java/com/javarush/wladimir/model/User.java
@@ -0,0 +1,50 @@
+package com.javarush.wladimir.model;
+
+import java.util.UUID;
+
+public class User {
+ private final String id = UUID.randomUUID().toString();
+ private String username;
+ private String password;
+ private Role role = Role.USER;
+ private String progress = "start";
+
+ public User() {
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public void setRole(Role role) {
+ this.role = role;
+ }
+
+ public String getProgress() {
+ return progress;
+ }
+
+ public void setProgress(String progress) {
+ this.progress = progress;
+ }
+}
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
new file mode 100644
index 0000000..692900b
--- /dev/null
+++ b/src/main/resources/messages.properties
@@ -0,0 +1,75 @@
+# ===== General =====
+app.title=Text Quest
+button.create=Create
+button.cancel=Cancel
+button.play=Play
+button.backHome=Back to Home
+button.login=Login
+button.register=Register
+button.logout=Logout
+link.home=Home
+
+
+# ===== Index =====
+index.welcome=Welcome to Text Quest
+index.start=Start Your Adventure
+index.loginHint=Login to create and play quests
+index.hello=Hello
+
+# ===== Login =====
+login.title=Login
+login.username=Username
+login.password=Password
+login.submit=Login
+login.noAccount=No account yet?
+login.registerLink=Register here
+
+# ===== Register =====
+register.title=Register
+register.submit=Create Account
+register.haveAccount=Already have an account?
+register.loginLink=Login here
+
+# ===== Create Quest =====
+quest.create.title=Create a New Quest
+quest.create.structure=Quest Structure (with branching)
+quest.create.format=Format:
+quest.create.format.desc=stepId|description|choice1->next_step|choice2->next_step
+quest.create.important=Important:
+quest.create.important.desc=Each line is one step. Use | as separator. Use -> for branching.
+quest.create.ending=Ending marker:
+quest.create.ending.desc=Use |END to mark an ending.
+quest.create.example=Example:
+quest.create.ttitle=Title
+quest.create.new=Create Quest
+
+# ===== My Quests =====
+quests.title=My Quests
+quests.createNew=Create New Quest
+quests.createdBy=Created by
+quests.steps=Steps
+quests.description=Quests with branching paths and player choices
+quests.none=You haven't created any quests yet.
+quests.first=Create your first quest!
+
+# ===== Play Quest =====
+play.choose=Choose your action
+play.end=The End
+play.restart=Restart Quest
+
+# ===== Game / Admin =====
+admin.title=Admin Panel
+admin.users=Users
+admin.quests=Quests
+admin.action=Action
+admin.role=Role
+admin.uname=Username
+admin.del=Delete
+button.role.admin=Admin
+
+# ===== Play Quest =====
+play.quest.playq=Play Quest
+play.quest.backq=Back to Quests
+play.quest.message=This step has no choices available.
+play.quest.back=Back
+play.quest.comp=Quest Completed!
\ No newline at end of file
diff --git a/src/main/resources/messages_de.properties b/src/main/resources/messages_de.properties
new file mode 100644
index 0000000..927472f
--- /dev/null
+++ b/src/main/resources/messages_de.properties
@@ -0,0 +1,72 @@
+# ===== Allgemein =====
+app.title=Text Quest
+button.create=Erstellen
+button.cancel=Abbrechen
+button.play=Spielen
+button.backHome=Zurück zur Startseite
+button.login=Anmelden
+button.register=Registrieren
+button.logout=Abmelden
+link.home=Startseite
+
+# ===== Index =====
+index.welcome=Willkommen bei Text Quest
+index.start=Starte dein Abenteuer
+index.loginHint=Melde dich an, um Quests zu erstellen und zu spielen
+index.hello=Hallo
+
+# ===== Anmeldung =====
+login.title=Anmeldung
+login.username=Benutzername
+login.password=Passwort
+login.submit=Anmelden
+login.noAccount=Noch kein Konto?
+login.registerLink=Hier registrieren
+
+# ===== Registrierung =====
+register.title=Registrierung
+register.submit=Konto erstellen
+register.haveAccount=Bereits ein Konto?
+register.loginLink=Hier anmelden
+
+# ===== Quest erstellen =====
+quest.create.title=Neue Quest erstellen
+quest.create.structure=Quest-Struktur (mit Verzweigungen)
+quest.create.format=Format:
+quest.create.format.desc=stepId|Beschreibung|Wahl1->nächster_Schritt|Wahl2->nächster_Schritt
+quest.create.important=Wichtig:
+quest.create.important.desc=Jede Zeile ist ein Schritt. Verwende | als Trennzeichen. Verwende -> für Verzweigungen.
+quest.create.ending=Endmarkierung:
+quest.create.ending.desc=Verwende |END, um ein Ende zu markieren.
+quest.create.example=Beispiel:
+
+# ===== Meine Quests =====
+quests.title=Meine Quests
+quests.createNew=Neue Quest erstellen
+quests.createdBy=Erstellt von
+quests.steps=Schritte
+quests.description=Quests mit Verzweigungen und Spielerentscheidungen
+quests.none=Du hast noch keine Quests erstellt.
+quests.first=Erstelle deine erste Quest!
+
+# ===== Quest spielen =====
+play.choose=Wähle deine Aktion
+play.end=Das Ende
+play.restart=Quest neu starten
+
+# ===== Spiel / Admin =====
+admin.title=Admin-Bereich
+admin.users=Benutzer
+admin.quests=Quests
+admin.action=Aktion
+admin.role=Rolle
+admin.uname=Benutzername
+admin.del=Löschen
+button.role.admin=Admin
+
+# ===== Play Quest =====
+play.quest.playq=Quest spielen
+play.quest.backq=Zurück zu den Quests
+play.quest.message=Bei diesem Schritt gibt es keine Auswahlmöglichkeiten.
+play.quest.back=Zurück
+play.quest.comp=Mission abgeschlossen!
\ No newline at end of file
diff --git a/src/main/resources/messages_ru.properties b/src/main/resources/messages_ru.properties
new file mode 100644
index 0000000..ef41ce1
--- /dev/null
+++ b/src/main/resources/messages_ru.properties
@@ -0,0 +1,74 @@
+# ===== General =====
+app.title=????? ?????
+button.create=???????
+button.cancel=??????
+button.play=??????
+button.backHome=?? ???????
+button.login=?????
+button.register=???????????
+button.logout=?????
+link.home=???????
+
+# ===== Index =====
+index.welcome=????? ?????????? ? ????? ?????
+index.start=??????? ???? ???????????
+index.loginHint=???????, ????? ????????? ? ?????? ? ??????
+index.hello=??????
+
+# ===== Login =====
+login.title=????
+login.username=??? ????????????
+login.password=??????
+login.submit=?????
+login.noAccount=??? ??? ?????????
+login.registerLink=????????????????? ?????
+
+# ===== Register =====
+register.title=???????????
+register.submit=??????? ???????
+register.haveAccount=??? ???? ????????
+register.loginLink=????? ?????
+
+# ===== Create Quest =====
+quest.create.title=??????? ????? ?????
+quest.create.structure=????????? ?????? (? ??????????)
+quest.create.format=??????:
+quest.create.format.desc=stepId|????????|?????1->?????????_???|?????2->?????????_???
+quest.create.important=?????:
+quest.create.important.desc=?????? ?????? - ???? ???. ??????????? | ??? ???????????. ??????????? -> ??? ?????????.
+quest.create.ending=?????? ?????????:
+quest.create.ending.desc=??????????? |END, ????? ???????? ?????.
+quest.create.example=??????:
+quest.create.ttitle=????????
+quest.create.new=??????? ?????
+
+# ===== My Quests =====
+quests.title=??? ??????
+quests.createNew=??????? ????? ?????
+quests.createdBy=???????
+quests.steps=????
+quests.description=?????? ? ??????????? ? ???????? ??????
+quests.none=?? ??? ?? ??????? ?? ?????? ??????.
+quests.first=???????? ???? ?????? ?????!
+
+# ===== Play Quest =====
+play.choose=???????? ????????
+play.end=?????
+play.restart=?????? ????? ??????
+
+# ===== Game / Admin =====
+admin.title=?????? ??????????????
+admin.users=????????????
+admin.quests=??????
+admin.action=????????
+admin.role=????
+admin.uname=??? ????????????
+admin.del=???????
+button.role.admin=?????????????
+
+# ===== Play Quest =====
+play.quest.playq=?????? ? ?????
+play.quest.backq=????? ? ???????
+play.quest.message=?? ???? ???? ??? ????????? ???????.
+play.quest.back=?????
+play.quest.comp=????? ????????!
\ No newline at end of file
diff --git a/src/main/webapp/admin.jsp b/src/main/webapp/admin.jsp
new file mode 100644
index 0000000..505f856
--- /dev/null
+++ b/src/main/webapp/admin.jsp
@@ -0,0 +1,50 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ java.util.List users = (java.util.List) request.getAttribute("users");
+%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ <% if (users != null) {
+ for (Object _o : users) {
+ com.javarush.wladimir.model.User u = (com.javarush.wladimir.model.User)_o;
+ %>
+
+ <%= u.getUsername() %>
+ <%= (u.getRole() != null ? u.getRole().name() : "") %>
+
+ <% if (!"admin".equals(u.getUsername())) { %>
+
+ <% } %>
+
+
+ <% }
+ }
+ %>
+
+
+
+
diff --git a/src/main/webapp/createQuest.jsp b/src/main/webapp/createQuest.jsp
new file mode 100644
index 0000000..400ea66
--- /dev/null
+++ b/src/main/webapp/createQuest.jsp
@@ -0,0 +1,63 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ String error = (String) request.getAttribute("error");
+%>
+
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/game.jsp b/src/main/webapp/game.jsp
new file mode 100644
index 0000000..0ab9fe3
--- /dev/null
+++ b/src/main/webapp/game.jsp
@@ -0,0 +1,65 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ com.javarush.wladimir.model.User u = (com.javarush.wladimir.model.User) session.getAttribute("currentUser");
+ String progress = (String) request.getAttribute("progress");
+ String message = "";
+ if (progress == null) progress = (u != null ? u.getProgress() : "start");
+ if ("start".equals(progress)) {
+ message = "Welcome, Stalker. You awaken in the Zone...";
+ } else if ("zone".equals(progress)) {
+ message = "You stand in the heart of the Zone. Ruins stretch before you. To the east, a shimmering anomaly pulses with strange energy. What do you do?";
+ } else if ("ruins".equals(progress)) {
+ message = "You explore the decaying ruins. Strange artifacts litter the ground. You hear growls in the distance. The air feels heavy.";
+ } else if ("anomaly".equals(progress)) {
+ message = "You approach the anomaly. The air crackles. Gravity seems bent here. Artifacts float nearby. One wrong move could be your last.";
+ } else if ("artifact_found".equals(progress)) {
+ message = "You carefully retrieve a glowing artifact! It hums with energy. This will fetch a good price. Quest Complete!";
+ } else if ("mutant_encounter".equals(progress)) {
+ message = "A growl freezes your blood. A mutant emerges from the shadows! Fight or flee?";
+ } else if ("teleported".equals(progress)) {
+ message = "You jump into the anomaly! Reality distorts. You are teleported far away. Disoriented but alive. Quest Complete!";
+ } else if ("escaped".equals(progress)) {
+ message = "You run from the anomaly zone. Your heart pounds. You made it out. Safe, but empty-handed. Quest Complete.";
+ } else if ("fought_mutant".equals(progress)) {
+ message = "You fought the mutant! You survived, barely. The Zone respects strength. Quest Complete!";
+ } else if ("fled".equals(progress)) {
+ message = "You fled the mutant and escaped the ruins. Alive is what matters. Quest Complete.";
+ } else {
+ message = "You wander the Zone, unsure of your path.";
+ }
+%>
+
+
+
+
+ Game - Text Quest
+
+
+
+ Text Quest
+ <%= message %>
+
+
+ <% if ("start".equals(progress)) { %>
+ Enter the Zone
+ <% } else if ("zone".equals(progress)) { %>
+ Explore Ruins
+ Enter Anomaly
+ <% } else if ("ruins".equals(progress)) { %>
+ Search for Artifacts
+ Venture Deeper
+ <% } else if ("anomaly".equals(progress)) { %>
+ Jump into Anomaly
+ Escape
+ <% } else if ("mutant_encounter".equals(progress)) { %>
+ Fight
+ Flee
+ <% } else if ("artifact_found".equals(progress) || "teleported".equals(progress) || "escaped".equals(progress) || "fought_mutant".equals(progress) || "fled".equals(progress)) { %>
+ Quest Completed
+ <% } %>
+ Reset Quest
+
+
+ Home
+
+
diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp
new file mode 100644
index 0000000..0c5222c
--- /dev/null
+++ b/src/main/webapp/index.jsp
@@ -0,0 +1,47 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ Object _user = session.getAttribute("currentUser");
+%>
+
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+
+ Text Quest
+
+
+
+
+
+
+
+ <% if (_user != null) {
+ com.javarush.wladimir.model.User u = (com.javarush.wladimir.model.User)_user;
+ %>
+
, <%= u.getUsername() %> !
+
+
+
+ <% if (u.getRole() != null && "ADMIN".equals(u.getRole().name())) { %>
+
+ <% } %>
+ <% } else { %>
+
+
+ <% } %>
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/login.jsp b/src/main/webapp/login.jsp
new file mode 100644
index 0000000..735fee7
--- /dev/null
+++ b/src/main/webapp/login.jsp
@@ -0,0 +1,39 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ String error = (String) request.getAttribute("error");
+%>
+
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ <% if (error != null) { %>
+ <%= error %>
+ <% } %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/playQuest.jsp b/src/main/webapp/playQuest.jsp
new file mode 100644
index 0000000..3980f8c
--- /dev/null
+++ b/src/main/webapp/playQuest.jsp
@@ -0,0 +1,86 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
+<%@ page import="com.javarush.wladimir.model.Quest" %>
+<%@ page import="com.javarush.wladimir.model.QuestStep" %>
+<%@ page import="com.javarush.wladimir.model.User" %>
+
+
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<%
+ Quest quest = (Quest) request.getAttribute("quest");
+ QuestStep currentStep = (QuestStep) request.getAttribute("currentStep");
+ String stepId = (String) request.getAttribute("stepId");
+ Boolean isEnding = (Boolean) request.getAttribute("isEnding");
+ User user = (User) session.getAttribute("currentUser");
+%>
+
+
+
<%= quest.getTitle() %>
+
+
+
<%= currentStep.getDescription() %>
+
+
+ <% if (isEnding != null && isEnding) { %>
+
+
+
<%= currentStep.getEndingText() %>
+
+
+ <% } else if (currentStep.getChoices() != null && !currentStep.getChoices().isEmpty()) { %>
+
+
+
+
+ <% for (int i = 0; i < currentStep.getChoices().size(); i++) {
+ QuestStep.QuestChoice choice = currentStep.getChoices().get(i);
+ %>
+
+
+ → <%= choice.getText() %>
+
+
+ <% } %>
+
+ <% } else { %>
+
+
+
+
+ <% } %>
+
+
+
+
+
diff --git a/src/main/webapp/register.jsp b/src/main/webapp/register.jsp
new file mode 100644
index 0000000..715bca1
--- /dev/null
+++ b/src/main/webapp/register.jsp
@@ -0,0 +1,39 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ String error = (String) request.getAttribute("error");
+%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ <% if (error != null) { %>
+ <%= error %>
+ <% } %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/userQuests.jsp b/src/main/webapp/userQuests.jsp
new file mode 100644
index 0000000..7d5fe5e
--- /dev/null
+++ b/src/main/webapp/userQuests.jsp
@@ -0,0 +1,68 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+ java.util.List quests = (java.util.List) request.getAttribute("quests");
+%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <% if (quests != null && !quests.isEmpty()) { %>
+ <% for (Object _o : quests) {
+ com.javarush.wladimir.model.Quest q = (com.javarush.wladimir.model.Quest)_o;
+ %>
+
+
<%= q.getTitle() %>
+
+ : <%= q.getCreator() %>
+ : <%= q.getSteps() != null ? q.getSteps().size() : 0 %>
+
+
+
+
+
+
+ <% } %>
+ <% } else { %>
+
+ <% } %>
+
+
+
+
+
diff --git a/src/test/java/pantera/textquest/AdminServletTest.java b/src/test/java/pantera/textquest/AdminServletTest.java
new file mode 100644
index 0000000..7f24c91
--- /dev/null
+++ b/src/test/java/pantera/textquest/AdminServletTest.java
@@ -0,0 +1,138 @@
+package pantera.textquest;
+
+import com.javarush.wladimir.AdminServlet;
+import com.javarush.wladimir.dao.InMemoryUserDao;
+import com.javarush.wladimir.model.Role;
+import com.javarush.wladimir.model.User;
+import jakarta.servlet.*;
+import jakarta.servlet.http.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class AdminServletTest {
+
+ @Mock
+ private HttpServletRequest request;
+ @Mock
+ private HttpServletResponse response;
+ @Mock
+ private HttpSession session;
+ @Mock
+ private ServletContext servletContext;
+ @Mock
+ private ServletConfig servletConfig;
+ @Mock
+ private RequestDispatcher dispatcher;
+
+ private AdminServlet servlet;
+ private InMemoryUserDao dao;
+
+ @BeforeEach
+ void setUp() throws ServletException {
+ servlet = new AdminServlet();
+ dao = new InMemoryUserDao();
+
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute("userDao")).thenReturn(dao);
+ when(request.getSession()).thenReturn(session);
+ when(request.getContextPath()).thenReturn("/text-quest");
+ when(request.getRequestDispatcher("/admin.jsp")).thenReturn(dispatcher);
+
+ servlet.init(servletConfig);
+ }
+
+
+ @Test
+ void doGet_adminAccess_allowed() throws ServletException, IOException {
+ User admin = dao.findByUsername("admin").get();
+ when(request.getMethod()).thenReturn("GET");
+ when(session.getAttribute("currentUser")).thenReturn(admin);
+
+ servlet.service(request, response);
+
+ verify(dispatcher).forward(request, response);
+ }
+
+ @Test
+ void doGet_nonAdmin_denied() throws ServletException, IOException {
+ User user = new User();
+ user.setUsername("user");
+ user.setRole(Role.USER);
+
+ when(request.getMethod()).thenReturn("GET");
+ when(session.getAttribute("currentUser")).thenReturn(user);
+
+ servlet.service(request, response);
+
+ verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+
+ @Test
+ void doGet_notLoggedIn_denied() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("GET");
+ when(session.getAttribute("currentUser")).thenReturn(null);
+
+ servlet.service(request, response);
+
+ verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+
+
+ @Test
+ void doPost_adminDeletesUser() throws ServletException, IOException {
+ User admin = dao.findByUsername("admin").get();
+ User toDelete = new User();
+ toDelete.setUsername("todelete");
+ dao.add(toDelete);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(session.getAttribute("currentUser")).thenReturn(admin);
+ when(request.getParameter("action")).thenReturn("delete");
+ when(request.getParameter("username")).thenReturn("todelete");
+
+ servlet.service(request, response);
+
+ assertFalse(dao.findByUsername("todelete").isPresent());
+ verify(response).sendRedirect("/text-quest/admin");
+ }
+
+ @Test
+ void doPost_nonAdmin_denied() throws ServletException, IOException {
+ User user = new User();
+ user.setUsername("user");
+ user.setRole(Role.USER);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(session.getAttribute("currentUser")).thenReturn(user);
+ when(request.getParameter("action")).thenReturn("delete");
+
+ servlet.service(request, response);
+
+ verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+
+ @Test
+ void doPost_invalidAction_ignored() throws ServletException, IOException {
+ User admin = dao.findByUsername("admin").get();
+ User target = new User();
+ target.setUsername("target");
+ dao.add(target);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(session.getAttribute("currentUser")).thenReturn(admin);
+ when(request.getParameter("action")).thenReturn("invalid");
+
+ servlet.service(request, response);
+
+ assertTrue(dao.findByUsername("target").isPresent());
+ }
+}
diff --git a/src/test/java/pantera/textquest/GameServletTest.java b/src/test/java/pantera/textquest/GameServletTest.java
new file mode 100644
index 0000000..50da9e6
--- /dev/null
+++ b/src/test/java/pantera/textquest/GameServletTest.java
@@ -0,0 +1,195 @@
+package pantera.textquest;
+
+import com.javarush.wladimir.GameServlet;
+import com.javarush.wladimir.dao.InMemoryUserDao;
+import com.javarush.wladimir.model.User;
+import jakarta.servlet.*;
+import jakarta.servlet.http.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class GameServletTest {
+
+ @Mock
+ private HttpServletRequest request;
+ @Mock
+ private HttpServletResponse response;
+ @Mock
+ private HttpSession session;
+ @Mock
+ private ServletContext servletContext;
+ @Mock
+ private ServletConfig servletConfig;
+ @Mock
+ private RequestDispatcher dispatcher;
+
+ private GameServlet servlet;
+ private InMemoryUserDao dao;
+
+ @BeforeEach
+ void setUp() throws ServletException {
+ servlet = new GameServlet();
+ dao = new InMemoryUserDao();
+
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute("userDao")).thenReturn(dao);
+ when(request.getRequestDispatcher("/game.jsp")).thenReturn(dispatcher);
+
+ servlet.init(servletConfig);
+ }
+
+
+ @Test
+ void doGet_withoutLogin_redirectsToLogin() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("GET");
+ when(request.getSession()).thenReturn(session);
+ when(request.getContextPath()).thenReturn("/text-quest");
+ when(session.getAttribute("currentUser")).thenReturn(null);
+
+ servlet.service(request, response);
+
+ verify(response).sendRedirect("/text-quest/login");
+ }
+
+ @Test
+ void doGet_withLogin_forwardsToGameJsp() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("start");
+
+ when(request.getMethod()).thenReturn("GET");
+ when(request.getSession()).thenReturn(session);
+ when(session.getAttribute("currentUser")).thenReturn(u);
+
+ servlet.service(request, response);
+
+ verify(dispatcher).forward(request, response);
+ }
+
+
+ @Test
+ void doPost_startAction() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("start");
+ dao.add(u);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(request.getContextPath()).thenReturn("/text-quest");
+ when(session.getAttribute("currentUser")).thenReturn(u);
+ when(request.getParameter("action")).thenReturn("start");
+
+ servlet.service(request, response);
+
+ assertEquals("zone", u.getProgress());
+ verify(response).sendRedirect("/text-quest/game");
+ }
+
+ @Test
+ void doPost_exploreRuinsAction() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("zone");
+ dao.add(u);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(session.getAttribute("currentUser")).thenReturn(u);
+ when(request.getParameter("action")).thenReturn("explore_ruins");
+
+ servlet.service(request, response);
+
+ assertEquals("ruins", u.getProgress());
+ }
+
+ @Test
+ void doPost_enterAnomalyAction() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("zone");
+ dao.add(u);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(session.getAttribute("currentUser")).thenReturn(u);
+ when(request.getParameter("action")).thenReturn("enter_anomaly");
+
+ servlet.service(request, response);
+
+ assertEquals("anomaly", u.getProgress());
+ }
+
+ @Test
+ void doPost_searchArtifactsAction() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("ruins");
+ dao.add(u);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(session.getAttribute("currentUser")).thenReturn(u);
+ when(request.getParameter("action")).thenReturn("search_artifacts");
+
+ servlet.service(request, response);
+
+ assertEquals("artifact_found", u.getProgress());
+ }
+
+ @Test
+ void doPost_resetAction() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("treasure");
+ dao.add(u);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(session.getAttribute("currentUser")).thenReturn(u);
+ when(request.getParameter("action")).thenReturn("reset");
+
+ servlet.service(request, response);
+
+ assertEquals("start", u.getProgress());
+ }
+
+ @Test
+ void doPost_withoutLogin_redirectsToLogin() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(request.getContextPath()).thenReturn("/text-quest");
+ when(session.getAttribute("currentUser")).thenReturn(null);
+ when(request.getParameter("action")).thenReturn("start");
+
+ servlet.service(request, response);
+
+ verify(response).sendRedirect("/text-quest/login");
+ }
+
+ @Test
+ void doPost_invalidAction_doesNothing() throws ServletException, IOException {
+ User u = new User();
+ u.setUsername("player");
+ u.setProgress("zone");
+ dao.add(u);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(session.getAttribute("currentUser")).thenReturn(u);
+ when(request.getParameter("action")).thenReturn("invalid_action");
+
+ servlet.service(request, response);
+
+ assertEquals("zone", u.getProgress());
+ }
+}
diff --git a/src/test/java/pantera/textquest/RegisterServletTest.java b/src/test/java/pantera/textquest/RegisterServletTest.java
new file mode 100644
index 0000000..5fccf2b
--- /dev/null
+++ b/src/test/java/pantera/textquest/RegisterServletTest.java
@@ -0,0 +1,126 @@
+package pantera.textquest;
+
+import com.javarush.wladimir.RegisterServlet;
+import com.javarush.wladimir.dao.InMemoryUserDao;
+import com.javarush.wladimir.model.User;
+import jakarta.servlet.*;
+import jakarta.servlet.http.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class RegisterServletTest {
+
+ @Mock
+ private HttpServletRequest request;
+ @Mock
+ private HttpServletResponse response;
+ @Mock
+ private HttpSession session;
+ @Mock
+ private ServletContext servletContext;
+ @Mock
+ private ServletConfig servletConfig;
+ @Mock
+ private RequestDispatcher dispatcher;
+
+ private RegisterServlet servlet;
+ private InMemoryUserDao dao;
+
+ @BeforeEach
+ void setUp() throws ServletException {
+ servlet = new RegisterServlet();
+ dao = new InMemoryUserDao();
+
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute("userDao")).thenReturn(dao);
+ when(request.getRequestDispatcher("/register.jsp")).thenReturn(dispatcher);
+
+ servlet.init(servletConfig);
+ }
+
+
+ @Test
+ void doGet_forwardsToRegisterJsp() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("GET");
+
+ servlet.service(request, response);
+
+ verify(dispatcher).forward(request, response);
+ }
+
+
+ @Test
+ void doPost_successfulRegistration() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(request.getContextPath()).thenReturn("/text-quest");
+ when(request.getParameter("username")).thenReturn("newuser");
+ when(request.getParameter("password")).thenReturn("password123");
+
+ servlet.service(request, response);
+
+ assertTrue(dao.findByUsername("newuser").isPresent());
+
+ User user = dao.findByUsername("newuser").get();
+ assertEquals("newuser", user.getUsername());
+ assertEquals("password123", user.getPassword());
+
+ verify(session).setAttribute(eq("currentUser"), any(User.class));
+ verify(response).sendRedirect("/text-quest/game");
+ }
+
+
+ @Test
+ void doPost_duplicateUsername_fails() throws ServletException, IOException {
+ User existing = new User();
+ existing.setUsername("existing");
+ existing.setPassword("pass");
+ dao.add(existing);
+
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(request.getParameter("username")).thenReturn("existing");
+ when(request.getParameter("password")).thenReturn("newpass");
+
+ servlet.service(request, response);
+
+ verify(request).setAttribute(eq("error"), contains("already exists"));
+ verify(dispatcher).forward(request, response);
+ }
+
+
+ @Test
+ void doPost_emptyUsername_fails() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(request.getParameter("username")).thenReturn("");
+ when(request.getParameter("password")).thenReturn("password");
+
+ servlet.service(request, response);
+
+ verify(request).setAttribute(eq("error"), contains("Invalid"));
+ verify(dispatcher).forward(request, response);
+ }
+
+ @Test
+ void doPost_nullUsername_fails() throws ServletException, IOException {
+ when(request.getMethod()).thenReturn("POST");
+ when(request.getSession()).thenReturn(session);
+ when(request.getParameter("username")).thenReturn(null);
+ when(request.getParameter("password")).thenReturn("password");
+
+ servlet.service(request, response);
+
+ verify(request).setAttribute(eq("error"), contains("Invalid"));
+ verify(dispatcher).forward(request, response);
+ }
+}
diff --git a/src/test/java/pantera/textquest/dao/InMemoryUserDaoExtendedTest.java b/src/test/java/pantera/textquest/dao/InMemoryUserDaoExtendedTest.java
new file mode 100644
index 0000000..367c9e2
--- /dev/null
+++ b/src/test/java/pantera/textquest/dao/InMemoryUserDaoExtendedTest.java
@@ -0,0 +1,146 @@
+package pantera.textquest.dao;
+
+import com.javarush.wladimir.dao.InMemoryUserDao;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import com.javarush.wladimir.model.Quest;
+import com.javarush.wladimir.model.Role;
+import com.javarush.wladimir.model.User;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InMemoryUserDaoExtendedTest {
+ private InMemoryUserDao dao;
+
+ @BeforeEach
+ public void setup() {
+ dao = new InMemoryUserDao();
+ }
+
+ @Test
+ public void testDefaultAdminExists() {
+ assertTrue(dao.findByUsername("admin").isPresent());
+ User admin = dao.findByUsername("admin").get();
+ assertEquals(Role.ADMIN, admin.getRole());
+ }
+
+ @Test
+ public void testAddMultipleUsers() {
+ User u1 = new User();
+ u1.setUsername("user1");
+ u1.setPassword("pass1");
+
+ User u2 = new User();
+ u2.setUsername("user2");
+ u2.setPassword("pass2");
+
+ assertTrue(dao.add(u1));
+ assertTrue(dao.add(u2));
+ assertEquals(3, dao.listAll().size()); // admin + 2 users
+ }
+
+ @Test
+ public void testAddDuplicateUserFails() {
+ User u1 = new User();
+ u1.setUsername("duplicate");
+ u1.setPassword("pass");
+
+ assertTrue(dao.add(u1));
+
+ User u2 = new User();
+ u2.setUsername("duplicate");
+ u2.setPassword("other");
+
+ assertFalse(dao.add(u2));
+ }
+
+ @Test
+ public void testAddNullUserFails() {
+ assertFalse(dao.add(null));
+ }
+
+ @Test
+ public void testDeleteUser() {
+ User u = new User();
+ u.setUsername("todelete");
+ u.setPassword("pass");
+ dao.add(u);
+
+ assertTrue(dao.findByUsername("todelete").isPresent());
+ assertTrue(dao.delete("todelete"));
+ assertFalse(dao.findByUsername("todelete").isPresent());
+ }
+
+ @Test
+ public void testDeleteNonexistentUserFails() {
+ assertFalse(dao.delete("nonexistent"));
+ }
+
+ @Test
+ public void testAuthenticateSuccess() {
+ User u = new User();
+ u.setUsername("user");
+ u.setPassword("secret");
+ dao.add(u);
+
+ assertTrue(dao.authenticate("user", "secret").isPresent());
+ }
+
+ @Test
+ public void testAuthenticateWrongPassword() {
+ User u = new User();
+ u.setUsername("user");
+ u.setPassword("secret");
+ dao.add(u);
+
+ assertFalse(dao.authenticate("user", "wrong").isPresent());
+ }
+
+ @Test
+ public void testListAllUsers() {
+ List users = dao.listAll();
+ assertEquals(1, users.size()); // admin
+
+ User u = new User();
+ u.setUsername("newuser");
+ dao.add(u);
+
+ assertEquals(2, dao.listAll().size());
+ }
+
+ @Test
+ public void testAddAndListQuests() {
+ User u = new User();
+ u.setUsername("questcreator");
+ dao.add(u);
+
+ Quest q1 = new Quest("Quest 1", List.of("Step 1"), "questcreator");
+ Quest q2 = new Quest("Quest 2", List.of("A", "B"), "questcreator");
+
+ assertTrue(dao.addQuest("questcreator", q1));
+ assertTrue(dao.addQuest("questcreator", q2));
+
+ List quests = dao.listQuests("questcreator");
+ assertEquals(2, quests.size());
+ }
+
+ @Test
+ public void testListQuestsEmptyUser() {
+ List quests = dao.listQuests("nonexistent");
+ assertNotNull(quests);
+ assertTrue(quests.isEmpty());
+ }
+
+ @Test
+ public void testAddQuestNullFails() {
+ assertFalse(dao.addQuest("user", null));
+ }
+
+ @Test
+ public void testAddQuestNullUsernameFails() {
+ Quest q = new Quest("Q", List.of(), "user");
+ assertFalse(dao.addQuest(null, q));
+ }
+}
diff --git a/src/test/java/pantera/textquest/dao/InMemoryUserDaoTest.java b/src/test/java/pantera/textquest/dao/InMemoryUserDaoTest.java
new file mode 100644
index 0000000..1f4f2dc
--- /dev/null
+++ b/src/test/java/pantera/textquest/dao/InMemoryUserDaoTest.java
@@ -0,0 +1,23 @@
+package pantera.textquest.dao;
+
+import com.javarush.wladimir.dao.InMemoryUserDao;
+import org.junit.jupiter.api.Test;
+import com.javarush.wladimir.model.User;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InMemoryUserDaoTest {
+ @Test
+ public void addAndFindUser() {
+ InMemoryUserDao dao = new InMemoryUserDao();
+ User u = new User();
+ u.setUsername("testuser");
+ u.setPassword("pwd");
+ assertTrue(dao.add(u));
+ assertTrue(dao.findByUsername("testuser").isPresent());
+ assertTrue(dao.authenticate("testuser", "pwd").isPresent());
+ assertFalse(dao.authenticate("testuser", "wrong").isPresent());
+ assertTrue(dao.delete("testuser"));
+ assertFalse(dao.findByUsername("testuser").isPresent());
+ }
+}
diff --git a/src/test/java/pantera/textquest/model/QuestTest.java b/src/test/java/pantera/textquest/model/QuestTest.java
new file mode 100644
index 0000000..75d2594
--- /dev/null
+++ b/src/test/java/pantera/textquest/model/QuestTest.java
@@ -0,0 +1,45 @@
+package pantera.textquest.model;
+
+import com.javarush.wladimir.model.Quest;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class QuestTest {
+ @Test
+ public void testQuestCreation() {
+ Quest q = new Quest("Test Quest", List.of("Step 1", "Step 2"), "creator");
+ assertEquals("Test Quest", q.getTitle());
+ assertEquals(2, q.getSteps().size());
+ assertEquals("creator", q.getCreator());
+ assertNotNull(q.getId());
+ }
+
+ @Test
+ public void testQuestEmptySteps() {
+ Quest q = new Quest("Quest", null, "user");
+ assertNotNull(q.getSteps());
+ assertTrue(q.getSteps().isEmpty());
+ }
+
+ @Test
+ public void testQuestSettersAndGetters() {
+ Quest q = new Quest();
+ q.setTitle("New Quest");
+ q.setSteps(List.of("A", "B", "C"));
+ q.setCreator("admin");
+
+ assertEquals("New Quest", q.getTitle());
+ assertEquals(3, q.getSteps().size());
+ assertEquals("admin", q.getCreator());
+ }
+
+ @Test
+ public void testQuestIdUniqueness() {
+ Quest q1 = new Quest("Q1", List.of(), "user");
+ Quest q2 = new Quest("Q2", List.of(), "user");
+ assertNotEquals(q1.getId(), q2.getId());
+ }
+}
diff --git a/src/test/java/pantera/textquest/model/UserTest.java b/src/test/java/pantera/textquest/model/UserTest.java
new file mode 100644
index 0000000..20670b1
--- /dev/null
+++ b/src/test/java/pantera/textquest/model/UserTest.java
@@ -0,0 +1,48 @@
+package pantera.textquest.model;
+
+import com.javarush.wladimir.model.Role;
+import com.javarush.wladimir.model.User;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UserTest {
+ @Test
+ public void testUserCreation() {
+ User u = new User();
+ u.setUsername("testuser");
+ u.setPassword("password");
+ u.setRole(Role.USER);
+
+ assertEquals("testuser", u.getUsername());
+ assertEquals("password", u.getPassword());
+ assertEquals(Role.USER, u.getRole());
+ assertNotNull(u.getId());
+ }
+
+ @Test
+ public void testUserProgress() {
+ User u = new User();
+ assertEquals("start", u.getProgress());
+
+ u.setProgress("zone");
+ assertEquals("zone", u.getProgress());
+
+ u.setProgress("artifact_found");
+ assertEquals("artifact_found", u.getProgress());
+ }
+
+ @Test
+ public void testUserAdmin() {
+ User u = new User();
+ u.setRole(Role.ADMIN);
+ assertEquals(Role.ADMIN, u.getRole());
+ }
+
+ @Test
+ public void testUserIdUniqueness() {
+ User u1 = new User();
+ User u2 = new User();
+ assertNotEquals(u1.getId(), u2.getId());
+ }
+}