From e995dcd3eb732b380e8ebb053d7867bbc7f9578a Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Sat, 29 Nov 2025 10:19:43 +0000 Subject: [PATCH 1/2] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4a80115..19aec56 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/TvkQWWs6) # Features of modern Java # Цели и задачи л/р: From 4fed19dd00b10a4620c05d121fc97e4762b8ee54 Mon Sep 17 00:00:00 2001 From: Maria Barkovskaya Date: Sun, 14 Dec 2025 18:15:56 +0300 Subject: [PATCH 2/2] implement task --- .gitignore | 5 +- build.gradle.kts | 20 +- src/main/java/org/lab/Main.java | 7 +- src/main/java/org/lab/demo/DemoRunner.java | 75 +++++++ src/main/java/org/lab/model/Bug.java | 26 +++ src/main/java/org/lab/model/BugStatus.java | 5 + src/main/java/org/lab/model/Milestone.java | 48 +++++ .../java/org/lab/model/MilestoneStatus.java | 5 + src/main/java/org/lab/model/Project.java | 138 ++++++++++++ src/main/java/org/lab/model/Ticket.java | 49 +++++ src/main/java/org/lab/model/TicketStatus.java | 5 + src/main/java/org/lab/model/User.java | 5 + src/main/java/org/lab/model/ids/BugId.java | 11 + .../java/org/lab/model/ids/ProjectId.java | 11 + src/main/java/org/lab/model/ids/TicketId.java | 11 + src/main/java/org/lab/model/ids/UserId.java | 11 + .../org/lab/model/role/DeveloperRole.java | 35 +++ .../java/org/lab/model/role/ManagerRole.java | 11 + src/main/java/org/lab/model/role/Role.java | 16 ++ .../java/org/lab/model/role/TeamLeadRole.java | 19 ++ .../java/org/lab/model/role/TesterRole.java | 30 +++ .../org/lab/service/DashboardService.java | 38 ++++ .../java/org/lab/service/UserService.java | 31 +++ src/test/java/org/lab/model/ProjectTest.java | 202 ++++++++++++++++++ 24 files changed, 807 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/lab/demo/DemoRunner.java create mode 100644 src/main/java/org/lab/model/Bug.java create mode 100644 src/main/java/org/lab/model/BugStatus.java create mode 100644 src/main/java/org/lab/model/Milestone.java create mode 100644 src/main/java/org/lab/model/MilestoneStatus.java create mode 100644 src/main/java/org/lab/model/Project.java create mode 100644 src/main/java/org/lab/model/Ticket.java create mode 100644 src/main/java/org/lab/model/TicketStatus.java create mode 100644 src/main/java/org/lab/model/User.java create mode 100644 src/main/java/org/lab/model/ids/BugId.java create mode 100644 src/main/java/org/lab/model/ids/ProjectId.java create mode 100644 src/main/java/org/lab/model/ids/TicketId.java create mode 100644 src/main/java/org/lab/model/ids/UserId.java create mode 100644 src/main/java/org/lab/model/role/DeveloperRole.java create mode 100644 src/main/java/org/lab/model/role/ManagerRole.java create mode 100644 src/main/java/org/lab/model/role/Role.java create mode 100644 src/main/java/org/lab/model/role/TeamLeadRole.java create mode 100644 src/main/java/org/lab/model/role/TesterRole.java create mode 100644 src/main/java/org/lab/service/DashboardService.java create mode 100644 src/main/java/org/lab/service/UserService.java create mode 100644 src/test/java/org/lab/model/ProjectTest.java diff --git a/.gitignore b/.gitignore index b63da45..fd00d92 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,7 @@ build/ !**/src/test/**/build/ ### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ +.idea *.iws *.iml *.ipr diff --git a/build.gradle.kts b/build.gradle.kts index 79bf52a..baad2a5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,12 @@ plugins { group = "org.lab" version = "1.0-SNAPSHOT" +java { + toolchain { + languageVersion = JavaLanguageVersion.of(26) + } +} + repositories { mavenCentral() } @@ -17,4 +23,16 @@ dependencies { tasks.test { useJUnitPlatform() -} \ No newline at end of file +} + +tasks.withType().configureEach { + options.compilerArgs.add("--enable-preview") +} + +tasks.withType().configureEach { + jvmArgs("--enable-preview") +} + +tasks.withType().configureEach { + jvmArgs("--enable-preview") +} diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 22028ef..c46a8cd 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,4 +1,7 @@ -void main() { - IO.println("Hello and welcome!"); +import org.lab.demo.DemoRunner; + +void main() throws Exception { + IO.println("Starting lab demo"); + DemoRunner.runDemo(); } diff --git a/src/main/java/org/lab/demo/DemoRunner.java b/src/main/java/org/lab/demo/DemoRunner.java new file mode 100644 index 0000000..1fb90c5 --- /dev/null +++ b/src/main/java/org/lab/demo/DemoRunner.java @@ -0,0 +1,75 @@ +package org.lab.demo; + +import org.lab.model.*; +import org.lab.model.ids.ProjectId; +import org.lab.model.ids.TicketId; +import org.lab.model.role.ManagerRole; +import org.lab.service.DashboardService; +import org.lab.model.role.DeveloperRole; +import org.lab.model.role.TesterRole; +import org.lab.service.UserService; + +import java.time.LocalDate; + +public class DemoRunner { + public static void runDemo() throws Exception { + UserService users = new UserService(); + + var managerUser = users.register("Manager"); + var devUser = users.register("Developer"); + var testerUser = users.register("Tester"); + + var manager = new ManagerRole(managerUser); + var developer = new DeveloperRole(devUser); + var tester = new TesterRole(testerUser); + + var project = new Project( + new ProjectId(1), + "Modern Java", + manager, + users + ); + + project.addDeveloper(manager, developer); + project.addTester(manager, tester); + + var milestone = project.createMilestone( + manager, + new Milestone( + "MVP", + LocalDate.now(), + LocalDate.now().plusDays(30) + ) + ); + + project.activateMilestone(manager, milestone); + + var ticket = project.createTicket( + manager, + new Ticket( + new TicketId(100), + project, + milestone + ) + ); + + project.assignDeveloperToTicket(manager, ticket, developer); + + developer.startTicket(ticket); + developer.completeTicket(ticket); + + var bug = tester.createBug(project, "UI glitch"); + var fixed = developer.fixBug(bug); + var tested = tester.testBug(fixed); + var closed = tester.closeBug(tested); + + IO.println("Bug final status: " + closed.status()); + + var dashboard = new DashboardService(); + var snapshot = dashboard.snapshot(project); + + IO.println("Open tickets: " + snapshot.openTickets()); + IO.println("Open bugs: " + snapshot.openBugs()); + } +} + diff --git a/src/main/java/org/lab/model/Bug.java b/src/main/java/org/lab/model/Bug.java new file mode 100644 index 0000000..0fb99f2 --- /dev/null +++ b/src/main/java/org/lab/model/Bug.java @@ -0,0 +1,26 @@ +package org.lab.model; + +import org.lab.model.ids.BugId; + +public class Bug { + private final BugId id; + private final Project project; + private final String description; + private BugStatus status; + + public Bug(BugId id, Project project, String description) { + this.id = id; + this.project = project; + this.description = description; + this.status = BugStatus.NEW; + } + + public Bug withStatus(BugStatus newStatus) { + this.status = newStatus; + return this; + } + + public BugStatus status() { + return status; + } +} diff --git a/src/main/java/org/lab/model/BugStatus.java b/src/main/java/org/lab/model/BugStatus.java new file mode 100644 index 0000000..9da332e --- /dev/null +++ b/src/main/java/org/lab/model/BugStatus.java @@ -0,0 +1,5 @@ +package org.lab.model; + +public enum BugStatus { + NEW, FIXED, TESTED, CLOSED +} diff --git a/src/main/java/org/lab/model/Milestone.java b/src/main/java/org/lab/model/Milestone.java new file mode 100644 index 0000000..64d3d5b --- /dev/null +++ b/src/main/java/org/lab/model/Milestone.java @@ -0,0 +1,48 @@ +package org.lab.model; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +public final class Milestone { + + private final String name; + private final LocalDate start; + private final LocalDate end; + + private MilestoneStatus status = MilestoneStatus.OPEN; + private final List tickets = new ArrayList<>(); + + public Milestone(String name, LocalDate start, LocalDate end) { + this.name = name; + this.start = start; + this.end = end; + } + + public void activate() { + status = MilestoneStatus.ACTIVE; + } + + public void close() { + boolean allDone = tickets.stream() + .allMatch(t -> t.status() == TicketStatus.DONE); + + if (!allDone) { + throw new IllegalStateException("Not all tickets are done"); + } + + status = MilestoneStatus.CLOSED; + } + + public void addTicket(Ticket ticket) { + tickets.add(ticket); + } + + public List tickets() { + return List.copyOf(tickets); + } + + public MilestoneStatus status() { + return status; + } +} \ No newline at end of file diff --git a/src/main/java/org/lab/model/MilestoneStatus.java b/src/main/java/org/lab/model/MilestoneStatus.java new file mode 100644 index 0000000..48a0334 --- /dev/null +++ b/src/main/java/org/lab/model/MilestoneStatus.java @@ -0,0 +1,5 @@ +package org.lab.model; + +public enum MilestoneStatus { + OPEN, ACTIVE, CLOSED +} diff --git a/src/main/java/org/lab/model/Project.java b/src/main/java/org/lab/model/Project.java new file mode 100644 index 0000000..e40d55b --- /dev/null +++ b/src/main/java/org/lab/model/Project.java @@ -0,0 +1,138 @@ +package org.lab.model; + +import org.lab.model.ids.ProjectId; +import org.lab.model.role.*; +import org.lab.service.UserService; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class Project { + + private final ProjectId id; + private final String name; + + private final ManagerRole manager; + private TeamLeadRole teamLead; + + private final Set developers = new HashSet<>(); + private final Set testers = new HashSet<>(); + + private final List milestones = new ArrayList<>(); + Milestone activeMilestone; + + private final List bugs = new ArrayList<>(); + + private final UserService userService; + + public Project(ProjectId id, String name, ManagerRole manager, UserService userService) { + this.id = id; + this.name = name; + this.manager = manager; + this.userService = userService; + userService.addToProject(manager.user().id(), this); + } + + public void assignTeamLead(Role actor, TeamLeadRole lead) { + switch (actor) { + case ManagerRole m -> { + this.teamLead = lead; + userService.addToProject(lead.user().id(), this); + IO.println("Manager add new teamLead to project " + id.id()); + } + default -> throw new IllegalStateException("Only manager can assign team lead"); + } + } + + public void addDeveloper(Role actor, DeveloperRole dev) { + switch (actor) { + case ManagerRole m -> { + developers.add(dev); + userService.addToProject(dev.user().id(), this); + IO.println("Manager add new developer to project " + id.id()); + } + default -> throw new IllegalStateException("Only manager can add developer"); + } + } + + public void addTester(Role actor, TesterRole tester) { + switch (actor) { + case ManagerRole m -> { + testers.add(tester); + userService.addToProject(tester.user().id(), this); + IO.println("Manager add new tester to project " + id.id()); + } + default -> throw new IllegalStateException("Only manager can add tester"); + } + } + + public Milestone createMilestone(Role actor, Milestone milestone) { + switch (actor) { + case ManagerRole m -> { + milestones.add(milestone); + IO.println("Manager add new milestone to project " + id.id()); + return milestone; + } + default -> throw new IllegalStateException("Only manager can create milestone"); + } + } + + public void activateMilestone(Role actor, Milestone milestone) { + switch (actor) { + case ManagerRole m -> { + if (activeMilestone != null) { + throw new IllegalStateException("Only one active milestone allowed"); + } + milestone.activate(); + activeMilestone = milestone; + IO.println("Manager change activateMilestone for project " + id.id()); + } + default -> throw new IllegalStateException("Only manager can activate milestone"); + } + } + + public void closeMilestone(Role actor, Milestone milestone) { + switch (actor) { + case ManagerRole _ -> { + milestone.close(); + activeMilestone = null; + } + default -> throw new IllegalStateException("Only manager can close milestone"); + } + } + + public Ticket createTicket(Role actor, Ticket ticket) { + switch (actor) { + case ManagerRole _, TeamLeadRole _ -> { + activeMilestone.addTicket(ticket); + IO.println("New ticket was added for active milestone in project " + id.id()); + return ticket; + } + default -> throw new IllegalStateException("No rights to create ticket"); + } + } + + public void assignDeveloperToTicket(Role actor, Ticket ticket, DeveloperRole dev) { + switch (actor) { + case ManagerRole _, TeamLeadRole _ -> { + ticket.assignDeveloper(dev); + IO.println("Ticket was assigned to developer " + dev.user().name() + " in project " + id.id()); + } + default -> throw new IllegalStateException("No rights"); + } + } + + public void addBug(Bug bug) { + bugs.add(bug); + } + + public List bugs() { + return List.copyOf(bugs); + } + + public List milestones() { + return List.copyOf(milestones); + } +} diff --git a/src/main/java/org/lab/model/Ticket.java b/src/main/java/org/lab/model/Ticket.java new file mode 100644 index 0000000..73d6e5b --- /dev/null +++ b/src/main/java/org/lab/model/Ticket.java @@ -0,0 +1,49 @@ +package org.lab.model; + +import org.lab.model.ids.TicketId; +import org.lab.model.role.DeveloperRole; + +public final class Ticket { + + private final TicketId id; + private final Project project; + private final Milestone milestone; + + private DeveloperRole assignee; + private TicketStatus status = TicketStatus.NEW; + + public Ticket(TicketId id, Project project, Milestone milestone) { + this.id = id; + this.project = project; + this.milestone = milestone; + } + + public DeveloperRole assignedDeveloper() { + return assignee; + } + + public void assignDeveloper(DeveloperRole dev) { + this.assignee = dev; + status = TicketStatus.ACCEPTED; + } + + public void start() { + status = TicketStatus.IN_PROGRESS; + } + + public void complete() { + status = TicketStatus.DONE; + } + + public TicketStatus status() { + return status; + } + + public DeveloperRole assignee() { + return assignee; + } + + public TicketId id() { + return id; + } +} \ No newline at end of file diff --git a/src/main/java/org/lab/model/TicketStatus.java b/src/main/java/org/lab/model/TicketStatus.java new file mode 100644 index 0000000..fc853de --- /dev/null +++ b/src/main/java/org/lab/model/TicketStatus.java @@ -0,0 +1,5 @@ +package org.lab.model; + +public enum TicketStatus { + NEW, ACCEPTED, IN_PROGRESS, DONE +} diff --git a/src/main/java/org/lab/model/User.java b/src/main/java/org/lab/model/User.java new file mode 100644 index 0000000..6552623 --- /dev/null +++ b/src/main/java/org/lab/model/User.java @@ -0,0 +1,5 @@ +package org.lab.model; + +import org.lab.model.ids.UserId; + +public record User(UserId id, String name) {} diff --git a/src/main/java/org/lab/model/ids/BugId.java b/src/main/java/org/lab/model/ids/BugId.java new file mode 100644 index 0000000..98c31dd --- /dev/null +++ b/src/main/java/org/lab/model/ids/BugId.java @@ -0,0 +1,11 @@ +package org.lab.model.ids; + +public value class BugId { + private int value; + + public BugId(int value) { + this.value = value; + } + + public int id() { return value; } +} diff --git a/src/main/java/org/lab/model/ids/ProjectId.java b/src/main/java/org/lab/model/ids/ProjectId.java new file mode 100644 index 0000000..1eca7e0 --- /dev/null +++ b/src/main/java/org/lab/model/ids/ProjectId.java @@ -0,0 +1,11 @@ +package org.lab.model.ids; + +public value class ProjectId { + private int value; + + public ProjectId(int value) { + this.value = value; + } + + public int id() { return value; } +} diff --git a/src/main/java/org/lab/model/ids/TicketId.java b/src/main/java/org/lab/model/ids/TicketId.java new file mode 100644 index 0000000..ebb85ae --- /dev/null +++ b/src/main/java/org/lab/model/ids/TicketId.java @@ -0,0 +1,11 @@ +package org.lab.model.ids; + +public value class TicketId { + private int value; + + public TicketId(int value) { + this.value = value; + } + + public int id() { return value; } +} diff --git a/src/main/java/org/lab/model/ids/UserId.java b/src/main/java/org/lab/model/ids/UserId.java new file mode 100644 index 0000000..46227b8 --- /dev/null +++ b/src/main/java/org/lab/model/ids/UserId.java @@ -0,0 +1,11 @@ +package org.lab.model.ids; + +public value class UserId { + private int value; + + public UserId(int value) { + this.value = value; + } + + public int id() { return value; } +} diff --git a/src/main/java/org/lab/model/role/DeveloperRole.java b/src/main/java/org/lab/model/role/DeveloperRole.java new file mode 100644 index 0000000..787809d --- /dev/null +++ b/src/main/java/org/lab/model/role/DeveloperRole.java @@ -0,0 +1,35 @@ +package org.lab.model.role; + +import org.lab.model.*; +import org.lab.model.ids.BugId; + +public non-sealed class DeveloperRole extends Role { + + public DeveloperRole(User user) { + IO.println("Assigning DEVELOPER role"); + super(user); + } + + public void startTicket(Ticket ticket) { + ticket.start(); + } + + public void completeTicket(Ticket ticket) { + ticket.complete(); + } + + public Bug createBug(Project project, String desc) { + var bug = new Bug( + new BugId(desc.hashCode()), + project, + desc + ); + project.addBug(bug); + return bug; + } + + public Bug fixBug(Bug bug) { + bug = bug.withStatus(BugStatus.FIXED); + return bug; + } +} diff --git a/src/main/java/org/lab/model/role/ManagerRole.java b/src/main/java/org/lab/model/role/ManagerRole.java new file mode 100644 index 0000000..20ab18d --- /dev/null +++ b/src/main/java/org/lab/model/role/ManagerRole.java @@ -0,0 +1,11 @@ +package org.lab.model.role; + +import org.lab.model.User; + +public final class ManagerRole extends Role { + + public ManagerRole(User user) { + IO.println("Assigning MANAGER role"); + super(user); + } +} diff --git a/src/main/java/org/lab/model/role/Role.java b/src/main/java/org/lab/model/role/Role.java new file mode 100644 index 0000000..0a959fb --- /dev/null +++ b/src/main/java/org/lab/model/role/Role.java @@ -0,0 +1,16 @@ +package org.lab.model.role; + +import org.lab.model.User; + +public sealed abstract class Role permits ManagerRole, TeamLeadRole, DeveloperRole, TesterRole { + + protected final User user; + + protected Role(User user) { + this.user = user; + } + + public User user() { + return user; + } +} \ No newline at end of file diff --git a/src/main/java/org/lab/model/role/TeamLeadRole.java b/src/main/java/org/lab/model/role/TeamLeadRole.java new file mode 100644 index 0000000..da6639f --- /dev/null +++ b/src/main/java/org/lab/model/role/TeamLeadRole.java @@ -0,0 +1,19 @@ +package org.lab.model.role; + +import org.lab.model.Ticket; +import org.lab.model.TicketStatus; +import org.lab.model.User; + +public final class TeamLeadRole extends Role { + + public TeamLeadRole(User user) { + IO.println("Assigning TEAM LEAD role"); + super(user); + } + + public void verifyTicket(Ticket ticket) { + if (ticket.status() == TicketStatus.DONE) { + IO.println("End working on ticket " + ticket.id()); + } + } +} diff --git a/src/main/java/org/lab/model/role/TesterRole.java b/src/main/java/org/lab/model/role/TesterRole.java new file mode 100644 index 0000000..0a043aa --- /dev/null +++ b/src/main/java/org/lab/model/role/TesterRole.java @@ -0,0 +1,30 @@ +package org.lab.model.role; + +import org.lab.model.*; +import org.lab.model.ids.BugId; + +public final class TesterRole extends Role { + + public TesterRole(User user) { + IO.println("Assigning TESTER role"); + super(user); + } + + public Bug createBug(Project project, String desc) { + var bug = new Bug( + new BugId(desc.hashCode()), + project, + desc + ); + project.addBug(bug); + return bug; + } + + public Bug testBug(Bug bug) { + return bug.withStatus(BugStatus.TESTED); + } + + public Bug closeBug(Bug bug) { + return bug.withStatus(BugStatus.CLOSED); + } +} diff --git a/src/main/java/org/lab/service/DashboardService.java b/src/main/java/org/lab/service/DashboardService.java new file mode 100644 index 0000000..5a81c12 --- /dev/null +++ b/src/main/java/org/lab/service/DashboardService.java @@ -0,0 +1,38 @@ +package org.lab.service; + +import org.lab.model.*; + +import java.util.stream.Gatherers; + +public final class DashboardService { + + public record Snapshot(int openTickets, int openBugs) {} + + public Snapshot snapshot(Project project) { + + int openTickets = project.milestones().stream() + .flatMap(m -> m.tickets().stream()) + .gather(Gatherers.fold( + () -> 0, + (count, t) -> switch (t) { + case Ticket tt when tt.status() != TicketStatus.DONE -> count + 1; + case Ticket _ -> count; + } + )) + .findFirst() + .orElse(0); + + int openBugs = project.bugs().stream() + .gather(Gatherers.fold( + () -> 0, + (count, b) -> switch (b) { + case Bug bb when bb.status() != BugStatus.CLOSED -> count + 1; + case Bug _ -> count; + } + )) + .findFirst() + .orElse(0); + + return new Snapshot(openTickets, openBugs); + } +} diff --git a/src/main/java/org/lab/service/UserService.java b/src/main/java/org/lab/service/UserService.java new file mode 100644 index 0000000..4c5efad --- /dev/null +++ b/src/main/java/org/lab/service/UserService.java @@ -0,0 +1,31 @@ +package org.lab.service; + +import org.lab.model.Project; +import org.lab.model.User; +import org.lab.model.ids.UserId; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class UserService { + + private final List users = new ArrayList<>(); + private final Map> memberships = new HashMap<>(); + + public User register(String name) { + var user = new User(new UserId(users.size()), name); + users.add(user); + memberships.put(user.id(), new ArrayList<>()); + return user; + } + + public void addToProject(UserId userId, Project project) { + memberships.get(userId).add(project); + } + + public List projectsOf(UserId userId) { + return List.copyOf(memberships.get(userId)); + } +} diff --git a/src/test/java/org/lab/model/ProjectTest.java b/src/test/java/org/lab/model/ProjectTest.java new file mode 100644 index 0000000..a23876d --- /dev/null +++ b/src/test/java/org/lab/model/ProjectTest.java @@ -0,0 +1,202 @@ +package org.lab.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.model.ids.BugId; +import org.lab.model.ids.ProjectId; +import org.lab.model.ids.TicketId; +import org.lab.model.ids.UserId; +import org.lab.model.role.*; +import org.lab.service.UserService; +import org.lab.service.DashboardService; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProjectTest { + + private UserService userService; + private ManagerRole managerRole; + private Project project; + + @BeforeEach + void setup() { + userService = new UserService(); + var managerUser = userService.register("Alice"); + managerRole = new ManagerRole(managerUser); + project = new Project(new ProjectId(1), "Test Project", managerRole, userService); + } + + @Test + void testAddDeveloperAndMembership() { + var devUser = userService.register("Bob"); + var devRole = new DeveloperRole(devUser); + + project.addDeveloper(managerRole, devRole); + + assertTrue(project.milestones().isEmpty()); + assertTrue(userService.projectsOf(devUser.id()).contains(project)); + } + + @Test + void testAddTeamLeadAndMembership() { + var tlUser = userService.register("Charlie"); + var tlRole = new TeamLeadRole(tlUser); + + project.assignTeamLead(managerRole, tlRole); + + assertTrue(userService.projectsOf(tlUser.id()).contains(project)); + } + + @Test + void testAddTesterMembership() { + var testerUser = userService.register("Dana"); + var testerRole = new TesterRole(testerUser); + project.addTester(managerRole, testerRole); + + assertTrue(userService.projectsOf(testerUser.id()).contains(project)); + assertThrows(IllegalStateException.class, () -> project.addTester(new DeveloperRole(testerUser), testerRole)); + } + + @Test + void testCreateAndActivateMilestone() { + var milestone1 = new Milestone("MVP", LocalDate.now(), LocalDate.now().plusDays(30)); + var milestone2 = new Milestone("Beta", LocalDate.now().plusDays(31), LocalDate.now().plusDays(60)); + + project.createMilestone(managerRole, milestone1); + project.activateMilestone(managerRole, milestone1); + + assertEquals(milestone1, project.activeMilestone); + + project.createMilestone(managerRole, milestone2); + assertThrows(IllegalStateException.class, () -> project.activateMilestone(managerRole, milestone2)); + + project.closeMilestone(managerRole, milestone1); + project.activateMilestone(managerRole, milestone2); + + assertEquals(milestone2, project.activeMilestone); + } + + @Test + void testMilestoneCannotCloseIfTicketsOpen() { + var milestone = new Milestone("MVP", LocalDate.now(), LocalDate.now().plusDays(30)); + project.createMilestone(managerRole, milestone); + project.activateMilestone(managerRole, milestone); + + var devUser = userService.register("Dev"); + var devRole = new DeveloperRole(devUser); + project.addDeveloper(managerRole, devRole); + + var ticket = new Ticket(new TicketId(1), project, milestone); + project.createTicket(managerRole, ticket); + ticket.assignDeveloper(devRole); + + assertThrows(IllegalStateException.class, milestone::close); + } + + @Test + void testCreateTicketAndAssignDeveloper() { + var milestone = new Milestone("MVP", LocalDate.now(), LocalDate.now().plusDays(30)); + project.createMilestone(managerRole, milestone); + project.activateMilestone(managerRole, milestone); + + var tlUser = userService.register("TeamLead"); + var tlRole = new TeamLeadRole(tlUser); + project.assignTeamLead(managerRole, tlRole); + + var devUser = userService.register("Dev"); + var devRole = new DeveloperRole(devUser); + project.addDeveloper(managerRole, devRole); + + var ticket = new Ticket(new TicketId(1), project, milestone); + + project.createTicket(managerRole, ticket); + var ticket2 = new Ticket(new TicketId(2), project, milestone); + project.createTicket(tlRole, ticket2); + + project.assignDeveloperToTicket(managerRole, ticket, devRole); + project.assignDeveloperToTicket(tlRole, ticket2, devRole); + + assertEquals(devRole, ticket.assignedDeveloper()); + assertEquals(devRole, ticket2.assignedDeveloper()); + + assertThrows(IllegalStateException.class, () -> project.createTicket(devRole, new Ticket(new TicketId(3), project, milestone))); + assertThrows(IllegalStateException.class, () -> project.assignDeveloperToTicket(devRole, ticket, devRole)); + } + + @Test + void testDeveloperCompletesTicket() { + var milestone = new Milestone("MVP", LocalDate.now(), LocalDate.now().plusDays(30)); + project.createMilestone(managerRole, milestone); + project.activateMilestone(managerRole, milestone); + + var devUser = userService.register("Dev"); + var devRole = new DeveloperRole(devUser); + project.addDeveloper(managerRole, devRole); + + var ticket = new Ticket(new TicketId(1), project, milestone); + project.createTicket(managerRole, ticket); + project.assignDeveloperToTicket(managerRole, ticket, devRole); + + devRole.completeTicket(ticket); + + assertEquals(TicketStatus.DONE, ticket.status()); + } + + @Test + void testBugCreationAndFixing() { + var milestone = new Milestone("MVP", LocalDate.now(), LocalDate.now().plusDays(30)); + project.createMilestone(managerRole, milestone); + project.activateMilestone(managerRole, milestone); + + var devUser = userService.register("Dev"); + var devRole = new DeveloperRole(devUser); + project.addDeveloper(managerRole, devRole); + + var testerUser = userService.register("Tester"); + var testerRole = new TesterRole(testerUser); + project.addTester(managerRole, testerRole); + + var bug1 = new Bug(new BugId(1), project, "Bug 1"); + var bug2 = new Bug(new BugId(2), project, "Bug 2"); + + project.addBug(bug1); + project.addBug(bug2); + + devRole.fixBug(bug1); + assertEquals(BugStatus.FIXED, bug1.status()); + + testerRole.testBug(bug1); + assertEquals(BugStatus.TESTED, bug1.status()); + + testerRole.closeBug(bug1); + testerRole.closeBug(bug2); + assertEquals(BugStatus.CLOSED, bug1.status()); + assertEquals(BugStatus.CLOSED, bug2.status()); + } + + @Test + void testDashboardServiceGather() { + var devUser = userService.register("Dev"); + var devRole = new DeveloperRole(devUser); + project.addDeveloper(managerRole, devRole); + + var milestone = new Milestone("MVP", LocalDate.now(), LocalDate.now().plusDays(30)); + project.createMilestone(managerRole, milestone); + project.activateMilestone(managerRole, milestone); + + var ticket = new Ticket(new TicketId(1), project, milestone); + project.createTicket(managerRole, ticket); + ticket.assignDeveloper(devRole); + + var bug = new Bug(new BugId(1), project, "Bug 1"); + project.addBug(bug); + + var dashboardService = new DashboardService(); + var snapshot = dashboardService.snapshot(project); + + assertEquals(1, snapshot.openTickets()); + assertEquals(1, snapshot.openBugs()); + } +}