diff --git a/config/WootecoStyle.xml b/config/WootecoStyle.xml index cac33a8..eccfcb4 100644 --- a/config/WootecoStyle.xml +++ b/config/WootecoStyle.xml @@ -1,56 +1,95 @@ - - \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index e69de29..d31ebc8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,8 @@ +## 도메인 요구사항 + +## 상태별 요구사항 + +- 매칭 초기화 초기상태 + - [ ] 매칭의 초기상태로 다음 상태는 사용자 입력 선택 상태입니다. +- 사용장 명령어 선택 상태 + - [ ] 사용자로부터 입력을 기능선택의 입력을 받아옵니다. diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index c619a21..bf20ad3 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,8 +1,10 @@ package pairmatching; +import pairmatching.launcher.PairmatchingLauncher; + public class Application { public static void main(String[] args) { // TODO 구현 진행 - + new PairmatchingLauncher().execute(); } } diff --git a/src/main/java/pairmatching/domain/code/Course.java b/src/main/java/pairmatching/domain/code/Course.java new file mode 100644 index 0000000..f95d087 --- /dev/null +++ b/src/main/java/pairmatching/domain/code/Course.java @@ -0,0 +1,34 @@ +package pairmatching.domain.code; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public enum Course { + BACKEND("백엔드", "backend-crew.md"), + FRONTEND("프론트엔드", "frontend-crew.md"); + + private final String name; + private final String fileName; + + Course(final String name, final String fileName) { + this.name = name; + this.fileName = fileName; + } + + public static String messages() { + return Arrays.stream(values()) + .map(course -> course.name) + .collect(Collectors.joining(" | ")); + } + + public static Course from(String input) { + return Arrays.stream(values()) + .filter(course -> course.name.equals(input.trim())) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } + + public String getFileName() { + return this.fileName; + } +} diff --git a/src/main/java/pairmatching/domain/code/Level.java b/src/main/java/pairmatching/domain/code/Level.java new file mode 100644 index 0000000..2ed4b91 --- /dev/null +++ b/src/main/java/pairmatching/domain/code/Level.java @@ -0,0 +1,47 @@ +package pairmatching.domain.code; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum Level { + LEVEL1("레벨1", List.of(Subject.RACING, Subject.LOTTO, Subject.BASEBALL)), + LEVEL2("레벨2", List.of(Subject.SHOPPING, Subject.PAYMENT, Subject.SUBWAY)), + LEVEL3("레벨3"), + LEVEL4("레벨4", List.of(Subject.IMPROVE_PERFORMANCE, Subject.DEPLOY)), LEVEL5("레벨5"); + + private final String name; + private final List subjects; + + Level(final String name) { + this.name = name; + this.subjects = new ArrayList<>(); + } + + Level(final String name, final List subjects) { + this.name = name; + this.subjects = subjects; + } + + public static Level from(final String name, final Subject subject) { + return Arrays.stream(values()) + .filter(level -> level.name.equals(name.trim())) + .filter(level -> level.subjects.contains(subject)) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } + + public static String messages() { + return Arrays.stream(values()) + .map(Level::message) + .collect(Collectors.joining("\n")); + } + + private String message() { + var subjectNames = subjects.stream() + .map(Subject::getName) + .collect(Collectors.joining(" | ")); + return String.format(" - %s: %s", name, subjectNames); + } +} diff --git a/src/main/java/pairmatching/domain/code/Subject.java b/src/main/java/pairmatching/domain/code/Subject.java new file mode 100644 index 0000000..fef071a --- /dev/null +++ b/src/main/java/pairmatching/domain/code/Subject.java @@ -0,0 +1,32 @@ +package pairmatching.domain.code; + +import java.util.Arrays; + +public enum Subject { + RACING("자동차경주"), + LOTTO("로또"), + BASEBALL("숫자야구게임"), + SHOPPING("장바구니"), + PAYMENT("결제"), + SUBWAY("지하철노선도"), + IMPROVE_PERFORMANCE("성능개선"), + DEPLOY("배포"); + + + private final String name; + + Subject(final String input) { + this.name = input; + } + + public static Subject from(final String input) { + return Arrays.stream(values()) + .filter(subject -> subject.name.equals(input.trim())) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/count/RetryCount.java b/src/main/java/pairmatching/domain/count/RetryCount.java new file mode 100644 index 0000000..bad72ee --- /dev/null +++ b/src/main/java/pairmatching/domain/count/RetryCount.java @@ -0,0 +1,24 @@ +package pairmatching.domain.count; + +public class RetryCount { + private static final int INITIALIZE_COUNT = 0; + private static final int PLUS_COUNT = 1; + private static final int MAX_COUNT = 3; + private final int count; + + private RetryCount(final int count) { + if (count > MAX_COUNT) { + throw new IllegalStateException("3회이상 매칭을 시도할 수 없습니다."); + } + this.count = count; + } + + public static RetryCount generateInitializeCount() { + return new RetryCount(INITIALIZE_COUNT); + } + + public RetryCount plusRetry() { + return new RetryCount(this.count + PLUS_COUNT); + } + +} diff --git a/src/main/java/pairmatching/domain/generator/CrewGenerator.java b/src/main/java/pairmatching/domain/generator/CrewGenerator.java new file mode 100644 index 0000000..4266469 --- /dev/null +++ b/src/main/java/pairmatching/domain/generator/CrewGenerator.java @@ -0,0 +1,48 @@ +package pairmatching.domain.generator; + +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.code.Course; +import pairmatching.domain.matching.Crew; + +public class CrewGenerator { + private final ReadFile readFile; + private final ShuffleGenerator shuffleGenerator; + + + public CrewGenerator(ReadFile readFile, ShuffleGenerator shuffleGenerator) { + this.readFile = readFile; + this.shuffleGenerator = shuffleGenerator; + } + + public List generateCrews(Course course) { + var crewNames = this.readFileByCrews(course.getFileName()); + + var generatedCrews = this.shuffleGenerator.shuffle(crewNames) + .stream() + .map(name -> new Crew(course, name)) + .collect(Collectors.toList()); + + validateCrewSizeIsGreaterThanMinSize(generatedCrews.size()); + validateDuplicatedCrew(generatedCrews); + + return generatedCrews; + } + + private void validateDuplicatedCrew(List generatedCrews) { + if (generatedCrews.size() != new HashSet<>(generatedCrews).size()) { + throw new IllegalStateException("크루의 이름이 중복됩니다."); + } + } + + private void validateCrewSizeIsGreaterThanMinSize(int size) { + if (size < 2) { + throw new IllegalStateException("매칭할 수 없습니다."); + } + } + + private List readFileByCrews(final String fileName) { + return readFile.readFile(fileName); + } +} diff --git a/src/main/java/pairmatching/domain/generator/ReadFile.java b/src/main/java/pairmatching/domain/generator/ReadFile.java new file mode 100644 index 0000000..7cca35c --- /dev/null +++ b/src/main/java/pairmatching/domain/generator/ReadFile.java @@ -0,0 +1,7 @@ +package pairmatching.domain.generator; + +import java.util.List; + +public interface ReadFile { + List readFile(String fileName); +} diff --git a/src/main/java/pairmatching/domain/generator/ReadFileImpl.java b/src/main/java/pairmatching/domain/generator/ReadFileImpl.java new file mode 100644 index 0000000..60d6a77 --- /dev/null +++ b/src/main/java/pairmatching/domain/generator/ReadFileImpl.java @@ -0,0 +1,23 @@ +package pairmatching.domain.generator; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class ReadFileImpl implements ReadFile { + private static final String RESOURCES = "src/main/resources/"; + + @Override + public List readFile(String fileName) { + return this.read(fileName); + } + + private List read(String fileName) { + try { + return Files.readAllLines(Paths.get(RESOURCES + fileName)); + } catch (IOException exception) { + throw new IllegalStateException("잘못된 파일이거나 읽을 수 없습니다."); + } + } +} diff --git a/src/main/java/pairmatching/domain/generator/ShuffleGenerator.java b/src/main/java/pairmatching/domain/generator/ShuffleGenerator.java new file mode 100644 index 0000000..ac9f9f6 --- /dev/null +++ b/src/main/java/pairmatching/domain/generator/ShuffleGenerator.java @@ -0,0 +1,8 @@ +package pairmatching.domain.generator; + +import java.util.List; + +public interface ShuffleGenerator { + + List shuffle(List input); +} diff --git a/src/main/java/pairmatching/domain/generator/ShuffleGeneratorImpl.java b/src/main/java/pairmatching/domain/generator/ShuffleGeneratorImpl.java new file mode 100644 index 0000000..b85822d --- /dev/null +++ b/src/main/java/pairmatching/domain/generator/ShuffleGeneratorImpl.java @@ -0,0 +1,12 @@ +package pairmatching.domain.generator; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; + +public class ShuffleGeneratorImpl implements ShuffleGenerator { + + @Override + public List shuffle(List input) { + return Randoms.shuffle(input); + } +} diff --git a/src/main/java/pairmatching/domain/matching/Crew.java b/src/main/java/pairmatching/domain/matching/Crew.java new file mode 100644 index 0000000..54677f0 --- /dev/null +++ b/src/main/java/pairmatching/domain/matching/Crew.java @@ -0,0 +1,45 @@ +package pairmatching.domain.matching; + +import java.util.Objects; +import pairmatching.domain.code.Course; + +public class Crew { + private static final int MIN_SIZE = 1; + private final Course course; + private final String name; + + public Crew(Course course, String name) { + var tempName = name.trim(); + validateNameSizeIsGreaterThanMinSize(tempName.length()); + this.course = course; + this.name = name; + } + + private void validateNameSizeIsGreaterThanMinSize(int length) { + if (length < MIN_SIZE) { + throw new IllegalArgumentException(); + } + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Crew crew = (Crew) o; + return course == crew.course && Objects.equals(name, crew.name); + } + + @Override + public int hashCode() { + return Objects.hash(course, name); + } + + public String name() { + return this.name; + } +} diff --git a/src/main/java/pairmatching/domain/matching/MatchedCrew.java b/src/main/java/pairmatching/domain/matching/MatchedCrew.java new file mode 100644 index 0000000..89a5695 --- /dev/null +++ b/src/main/java/pairmatching/domain/matching/MatchedCrew.java @@ -0,0 +1,40 @@ +package pairmatching.domain.matching; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class MatchedCrew { + private final Set matchedCrew; + + public MatchedCrew() { + this.matchedCrew = new LinkedHashSet<>(); + } + + public void add(Crew crew) { + this.matchedCrew.add(crew); + } + + public void addAll(List crews) { + this.matchedCrew.addAll(crews); + } + + public String result() { + return matchedCrew.stream() + .map(Crew::name) + .collect(Collectors.joining(" : ")); + } + + public boolean hasMatchedAtBefore(List targetMatchedCrews) { + return targetMatchedCrews.stream() + .anyMatch(targetMatchedCrew -> targetMatchedCrew.hasMatchedAtBefore(this)); + } + + private boolean hasMatchedAtBefore(MatchedCrew matchedCrew) { + var union = new HashSet<>(matchedCrew.matchedCrew); + union.retainAll(this.matchedCrew); + return union.size() >= 2; + } +} diff --git a/src/main/java/pairmatching/domain/matching/MatchedCrews.java b/src/main/java/pairmatching/domain/matching/MatchedCrews.java new file mode 100644 index 0000000..304d45c --- /dev/null +++ b/src/main/java/pairmatching/domain/matching/MatchedCrews.java @@ -0,0 +1,28 @@ +package pairmatching.domain.matching; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class MatchedCrews { + private final List matchedCrewList; + + public MatchedCrews() { + this.matchedCrewList = new ArrayList<>(); + } + + public void add(MatchedCrew matchedCrew) { + this.matchedCrewList.add(matchedCrew); + } + + public String result() { + return this.matchedCrewList.stream() + .map(MatchedCrew::result) + .collect(Collectors.joining("\n")); + } + + public boolean hasMatchedAtBefore(MatchedCrews targetMatchedCrews) { + return matchedCrewList.stream() + .anyMatch(matchedCrew -> matchedCrew.hasMatchedAtBefore(targetMatchedCrews.matchedCrewList)); + } +} diff --git a/src/main/java/pairmatching/domain/matching/MatchingDivision.java b/src/main/java/pairmatching/domain/matching/MatchingDivision.java new file mode 100644 index 0000000..1f111dc --- /dev/null +++ b/src/main/java/pairmatching/domain/matching/MatchingDivision.java @@ -0,0 +1,54 @@ +package pairmatching.domain.matching; + +import java.util.Objects; +import pairmatching.domain.code.Course; +import pairmatching.domain.code.Level; +import pairmatching.domain.code.Subject; + +public class MatchingDivision { + private static final int FIX_SIZE = 3; + + private final Course course; + private final Level level; + private final Subject subject; + + public MatchingDivision(String input) { + var splitStr = input.split(","); + validateSplitSize(splitStr.length); + + this.course = Course.from(splitStr[0]); + this.subject = Subject.from(splitStr[2]); + this.level = Level.from(splitStr[1], this.subject); + } + + private void validateSplitSize(int length) { + if (length != FIX_SIZE) { + throw new IllegalArgumentException(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MatchingDivision that = (MatchingDivision) o; + return course == that.course && level == that.level && subject == that.subject; + } + + @Override + public int hashCode() { + return Objects.hash(course, level, subject); + } + + public Course getCourse() { + return this.course; + } + + public boolean isSameLevel(MatchingDivision matchingDivision) { + return this.level.equals(matchingDivision.level); + } +} diff --git a/src/main/java/pairmatching/domain/matching/MatchingResult.java b/src/main/java/pairmatching/domain/matching/MatchingResult.java new file mode 100644 index 0000000..5d3b003 --- /dev/null +++ b/src/main/java/pairmatching/domain/matching/MatchingResult.java @@ -0,0 +1,86 @@ +package pairmatching.domain.matching; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import pairmatching.exception.BeforeMatchedCrewException; +import pairmatching.exception.DuplicatedMatchingDivisionException; +import pairmatching.exception.MatchingDivisionDidNotExists; + +public class MatchingResult { + private final Map matchingResult; + + public MatchingResult() { + this.matchingResult = new HashMap<>(); + } + + public void matchPair(MatchingDivision matchingDivision, List crews) { + this.validateDuplicatedKey(matchingDivision); + + var matchedResult = generateMatchedCrews(new LinkedList<>(crews)); + + this.validateSameLevelExistsMatchedCrew(matchingDivision, matchedResult); + this.matchingResult.put(matchingDivision, matchedResult); + } + + public void rematchPair(MatchingDivision matchingDivision, List crews) { + var matchedResult = generateMatchedCrews(new LinkedList<>(crews)); + + this.validateSameLevelExistsMatchedCrew(matchingDivision, matchedResult); + this.matchingResult.put(matchingDivision, matchedResult); + } + + /** + * 같은 레벨에서 매칭된 크루가 잇는지 검증합니다. + * + * @param matchedResult + */ + private void validateSameLevelExistsMatchedCrew(MatchingDivision matchingDivision, MatchedCrews matchedResult) { + + var hasSameLevelMatchedCrews = this.matchingResult.entrySet().stream() + .filter(entry -> entry.getKey().isSameLevel(matchingDivision)) + .filter(entry -> !entry.getKey().equals(matchingDivision)) // 등록된 구분값을 제외함 + .anyMatch(entry -> entry.getValue().hasMatchedAtBefore(matchedResult)); + + if (hasSameLevelMatchedCrews) { + throw new BeforeMatchedCrewException(); + } + } + + private void validateDuplicatedKey(MatchingDivision matchingDivision) { + if (this.matchingResult.containsKey(matchingDivision)) { + throw new DuplicatedMatchingDivisionException(); + } + } + + private MatchedCrews generateMatchedCrews(LinkedList crews) { + var matchedCrews = new MatchedCrews(); + + while (crews.size() > 3) { + matchedCrews.add(generateMatchedCrew(crews, 2)); + } + + matchedCrews.add(generateMatchedCrew(crews, crews.size())); + + return matchedCrews; + } + + private MatchedCrew generateMatchedCrew(Queue crewQueue, final int size) { + var matchedCrew = new MatchedCrew(); + for (int i = 0; i < size; i++) { + matchedCrew.add(crewQueue.poll()); + } + return matchedCrew; + } + + public String findByMatchingDivision(MatchingDivision matchingDivision) { + if (!this.matchingResult.containsKey(matchingDivision)) { + throw new MatchingDivisionDidNotExists(); + } + return this.matchingResult.get(matchingDivision).result(); + } + + +} diff --git a/src/main/java/pairmatching/exception/BeforeMatchedCrewException.java b/src/main/java/pairmatching/exception/BeforeMatchedCrewException.java new file mode 100644 index 0000000..c6bf274 --- /dev/null +++ b/src/main/java/pairmatching/exception/BeforeMatchedCrewException.java @@ -0,0 +1,11 @@ +package pairmatching.exception; + +public class BeforeMatchedCrewException extends RuntimeException { + public BeforeMatchedCrewException(final String message) { + super(message); + } + + public BeforeMatchedCrewException() { + super("이전에 매칭된 경험이 있는 크루가 존재합니다."); + } +} diff --git a/src/main/java/pairmatching/exception/DuplicatedMatchingDivisionException.java b/src/main/java/pairmatching/exception/DuplicatedMatchingDivisionException.java new file mode 100644 index 0000000..1b87e1f --- /dev/null +++ b/src/main/java/pairmatching/exception/DuplicatedMatchingDivisionException.java @@ -0,0 +1,12 @@ +package pairmatching.exception; + +public class DuplicatedMatchingDivisionException extends RuntimeException { + + public DuplicatedMatchingDivisionException(final String message) { + super(message); + } + + public DuplicatedMatchingDivisionException() { + super(""); + } +} diff --git a/src/main/java/pairmatching/exception/MatchingDivisionDidNotExists.java b/src/main/java/pairmatching/exception/MatchingDivisionDidNotExists.java new file mode 100644 index 0000000..816d947 --- /dev/null +++ b/src/main/java/pairmatching/exception/MatchingDivisionDidNotExists.java @@ -0,0 +1,5 @@ +package pairmatching.exception; + +public class MatchingDivisionDidNotExists extends RuntimeException { + +} diff --git a/src/main/java/pairmatching/launcher/PairmatchingLauncher.java b/src/main/java/pairmatching/launcher/PairmatchingLauncher.java new file mode 100644 index 0000000..e5bb269 --- /dev/null +++ b/src/main/java/pairmatching/launcher/PairmatchingLauncher.java @@ -0,0 +1,41 @@ +package pairmatching.launcher; + +import pairmatching.domain.generator.CrewGenerator; +import pairmatching.domain.generator.ReadFileImpl; +import pairmatching.domain.generator.ShuffleGeneratorImpl; +import pairmatching.launcher.context.PairmatchingContext; +import pairmatching.launcher.context.PairmatchingContextImpl; +import pairmatching.launcher.status.InitStatus; +import pairmatching.launcher.status.PairmatchingStatus; +import pairmatching.launcher.status.QuitStatus; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; +import pairmatching.view.PairmatchingView; + +public class PairmatchingLauncher { + private final PairmatchingContext pairmatchingContext; + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + + private PairmatchingStatus pairmatchingStatus = new InitStatus(); + + public PairmatchingLauncher() { + var pairMatchingView = new PairmatchingView(inputView, outputView); + var crewGenerator = new CrewGenerator(new ReadFileImpl(), new ShuffleGeneratorImpl()); + + this.pairmatchingContext = new PairmatchingContextImpl(pairMatchingView, crewGenerator); + } + + public void execute() { + while (pairmatchingStatus.runnable()) { + try { + pairmatchingStatus = pairmatchingStatus.next(pairmatchingContext); + } catch (IllegalArgumentException exception) { + outputView.printError(exception.getMessage()); + } catch (IllegalStateException exception) { + outputView.printError(exception.getMessage()); + pairmatchingStatus = new QuitStatus(); + } + } + } +} diff --git a/src/main/java/pairmatching/launcher/code/CommandToStatusConvertor.java b/src/main/java/pairmatching/launcher/code/CommandToStatusConvertor.java new file mode 100644 index 0000000..fdba4e3 --- /dev/null +++ b/src/main/java/pairmatching/launcher/code/CommandToStatusConvertor.java @@ -0,0 +1,44 @@ +package pairmatching.launcher.code; + +import java.util.Arrays; +import pairmatching.launcher.status.FindMatchingResultStatus; +import pairmatching.launcher.status.InputStatus; +import pairmatching.launcher.status.MatchingInitializeStatus; +import pairmatching.launcher.status.MatchingProcessStatus; +import pairmatching.launcher.status.PairmatchingStatus; +import pairmatching.launcher.status.QuitStatus; + +public enum CommandToStatusConvertor { + MATCHING(FeatureCommand.MATCHING, new InputStatus(new MatchingProcessStatus())), + FIND(FeatureCommand.FIND, new InputStatus(new FindMatchingResultStatus())), + INITIALIZE(FeatureCommand.INITIALIZE, new MatchingInitializeStatus()), + QUIT(FeatureCommand.QUIT, new QuitStatus()), + UNKNOWN(); + + + private final FeatureCommand command; + private final PairmatchingStatus status; + + CommandToStatusConvertor(FeatureCommand command, PairmatchingStatus status) { + this.command = command; + this.status = status; + } + + CommandToStatusConvertor(FeatureCommand command) { + this.command = command; + this.status = new QuitStatus(); + } + + CommandToStatusConvertor() { + this.command = null; + this.status = new QuitStatus(); + } + + public static PairmatchingStatus getNextStatus(FeatureCommand featureCommand) { + return Arrays.stream(values()) + .filter(convertor -> convertor.command.equals(featureCommand)) + .findAny() + .orElse(UNKNOWN) + .status; + } +} diff --git a/src/main/java/pairmatching/launcher/code/FeatureCommand.java b/src/main/java/pairmatching/launcher/code/FeatureCommand.java new file mode 100644 index 0000000..b08a305 --- /dev/null +++ b/src/main/java/pairmatching/launcher/code/FeatureCommand.java @@ -0,0 +1,33 @@ +package pairmatching.launcher.code; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public enum FeatureCommand { + MATCHING("1", "페어 매칭"), + FIND("2", "페어 조회"), + INITIALIZE("3", "페어 초기화"), + QUIT("Q", "종료"); + + + private static final String MESSAGE_FORMAT = "%s. %s"; + private final String command; + private final String message; + + FeatureCommand(final String command, final String message) { + this.command = command; + this.message = message; + } + + public static FeatureCommand from(String input) { + return Arrays.stream(values()) + .filter(featureCommand -> featureCommand.command.equals(input)).findAny() + .orElseThrow(IllegalArgumentException::new); + } + + public static String messages() { + return Arrays.stream(values()) + .map(featureCommand -> String.format(MESSAGE_FORMAT, featureCommand.command, featureCommand.message)) + .collect(Collectors.joining("\n")); + } +} diff --git a/src/main/java/pairmatching/launcher/code/RematchingCommand.java b/src/main/java/pairmatching/launcher/code/RematchingCommand.java new file mode 100644 index 0000000..8e76709 --- /dev/null +++ b/src/main/java/pairmatching/launcher/code/RematchingCommand.java @@ -0,0 +1,30 @@ +package pairmatching.launcher.code; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public enum RematchingCommand { + YES("네"), NO("아니오"); + + private final String command; + + RematchingCommand(final String command) { + this.command = command; + } + + public static RematchingCommand from(final String input) { + return Arrays.stream(values()) + .filter(rematchingCommand -> rematchingCommand.command.equals(input.trim())) + .findAny().orElseThrow(IllegalArgumentException::new); + } + + public static String message() { + return Arrays.stream(values()) + .map(rematchingCommand -> rematchingCommand.command) + .collect(Collectors.joining(" | ")); + } + + public boolean isRematching() { + return this == YES; + } +} \ No newline at end of file diff --git a/src/main/java/pairmatching/launcher/context/PairmatchingContext.java b/src/main/java/pairmatching/launcher/context/PairmatchingContext.java new file mode 100644 index 0000000..9f676a9 --- /dev/null +++ b/src/main/java/pairmatching/launcher/context/PairmatchingContext.java @@ -0,0 +1,24 @@ +package pairmatching.launcher.context; + +import pairmatching.domain.matching.MatchingDivision; +import pairmatching.view.PairmatchingView; + +public interface PairmatchingContext { + + + PairmatchingView getPairmatchingView(); + + void inputMatchingDivision(MatchingDivision matchingDivision); + + String matchPair(); + + String rematchPair(); + + String findMatchedCrewsByMatchingDivision(); + + void plusTryCount(); + + void initializeRetryCount(); + + void initializeMatchingResult(); +} diff --git a/src/main/java/pairmatching/launcher/context/PairmatchingContextImpl.java b/src/main/java/pairmatching/launcher/context/PairmatchingContextImpl.java new file mode 100644 index 0000000..1dca7ae --- /dev/null +++ b/src/main/java/pairmatching/launcher/context/PairmatchingContextImpl.java @@ -0,0 +1,69 @@ +package pairmatching.launcher.context; + +import pairmatching.domain.count.RetryCount; +import pairmatching.domain.generator.CrewGenerator; +import pairmatching.domain.matching.MatchingDivision; +import pairmatching.domain.matching.MatchingResult; +import pairmatching.view.PairmatchingView; + +public class PairmatchingContextImpl implements PairmatchingContext { + private final PairmatchingView pairmatchingView; + private final CrewGenerator crewGenerator; + private MatchingResult matchingResult; + private MatchingDivision matchingDivision; + private RetryCount retryCount; + + public PairmatchingContextImpl(PairmatchingView pairmatchingView, CrewGenerator crewGenerator) { + this.pairmatchingView = pairmatchingView; + this.crewGenerator = crewGenerator; + this.matchingResult = new MatchingResult(); + } + + public PairmatchingView getPairmatchingView() { + return this.pairmatchingView; + } + + @Override + public void inputMatchingDivision(MatchingDivision matchingDivision) { + this.matchingDivision = matchingDivision; + } + + @Override + public String matchPair() { + var generatedCrews = crewGenerator.generateCrews(this.matchingDivision.getCourse()); + + this.matchingResult.matchPair(this.matchingDivision, generatedCrews); + + return this.matchingResult.findByMatchingDivision(matchingDivision); + } + + @Override + public String rematchPair() { + var generatedCrews = crewGenerator.generateCrews(this.matchingDivision.getCourse()); + + this.matchingResult.rematchPair(this.matchingDivision, generatedCrews); + + return this.matchingResult.findByMatchingDivision(matchingDivision); + } + + @Override + public String findMatchedCrewsByMatchingDivision() { + return this.matchingResult.findByMatchingDivision(matchingDivision); + } + + @Override + public void plusTryCount() { + this.retryCount = this.retryCount.plusRetry(); + + } + + public void initializeRetryCount() { + this.retryCount = RetryCount.generateInitializeCount(); + } + + @Override + public void initializeMatchingResult() { + this.matchingResult = new MatchingResult(); + } + +} diff --git a/src/main/java/pairmatching/launcher/status/AskRematchingStatus.java b/src/main/java/pairmatching/launcher/status/AskRematchingStatus.java new file mode 100644 index 0000000..52cc509 --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/AskRematchingStatus.java @@ -0,0 +1,24 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.context.PairmatchingContext; + +public class AskRematchingStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var pairMatchingView = pairmatchingContext.getPairmatchingView(); + var inputView = pairMatchingView.getInputView(); + + var rematchingCommand = inputView.readReMatchingCommand(); + if (rematchingCommand.isRematching()) { + return new BeforeMatchingIgnoreProcessStatus(); + } + + pairmatchingContext.inputMatchingDivision(inputView.readPairmatchingDivision()); + return new MatchingProcessStatus(); + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/BeforeMatchingIgnoreProcessStatus.java b/src/main/java/pairmatching/launcher/status/BeforeMatchingIgnoreProcessStatus.java new file mode 100644 index 0000000..1c2e79e --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/BeforeMatchingIgnoreProcessStatus.java @@ -0,0 +1,23 @@ +package pairmatching.launcher.status; + +import pairmatching.exception.BeforeMatchedCrewException; +import pairmatching.launcher.context.PairmatchingContext; + +public class BeforeMatchingIgnoreProcessStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var outputView = pairmatchingContext.getPairmatchingView().getOutputView(); + pairmatchingContext.plusTryCount(); + try { + outputView.printMatchingResultCrews(pairmatchingContext.rematchPair()); + return new SelectFeatureStatus(); + } catch (BeforeMatchedCrewException | IllegalStateException exception) { + return this; + } + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/FindMatchingResultStatus.java b/src/main/java/pairmatching/launcher/status/FindMatchingResultStatus.java new file mode 100644 index 0000000..bcb7002 --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/FindMatchingResultStatus.java @@ -0,0 +1,26 @@ +package pairmatching.launcher.status; + +import pairmatching.exception.MatchingDivisionDidNotExists; +import pairmatching.launcher.context.PairmatchingContext; + +public class FindMatchingResultStatus implements PairmatchingStatus { + + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var pairmatchingView = pairmatchingContext.getPairmatchingView(); + var outputView = pairmatchingView.getOutputView(); + + try { + outputView.printMatchingResultCrews(pairmatchingContext.findMatchedCrewsByMatchingDivision()); + } catch (MatchingDivisionDidNotExists matchingDivisionDidNotExists) { + throw new IllegalArgumentException("매칭 이력이 없습니다."); + } + + return new SelectFeatureStatus(); + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/InitStatus.java b/src/main/java/pairmatching/launcher/status/InitStatus.java new file mode 100644 index 0000000..c1bd3d1 --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/InitStatus.java @@ -0,0 +1,15 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.context.PairmatchingContext; + +public class InitStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + return new SelectFeatureStatus(); + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/InputStatus.java b/src/main/java/pairmatching/launcher/status/InputStatus.java new file mode 100644 index 0000000..ea7178d --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/InputStatus.java @@ -0,0 +1,31 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.context.PairmatchingContext; + +public class InputStatus implements PairmatchingStatus { + + private final PairmatchingStatus nextStatus; + + public InputStatus(PairmatchingStatus nextStatus) { + this.nextStatus = nextStatus; + } + + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var pairmatchingView = pairmatchingContext.getPairmatchingView(); + var inputView = pairmatchingView.getInputView(); + var outputView = pairmatchingView.getOutputView(); + + pairmatchingContext.initializeRetryCount(); + + outputView.printMatchingMenu(); + pairmatchingContext.inputMatchingDivision(inputView.readPairmatchingDivision()); + + return nextStatus; + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/MatchingInitializeStatus.java b/src/main/java/pairmatching/launcher/status/MatchingInitializeStatus.java new file mode 100644 index 0000000..372f7b4 --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/MatchingInitializeStatus.java @@ -0,0 +1,20 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.context.PairmatchingContext; + +public class MatchingInitializeStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var pairmatchingView = pairmatchingContext.getPairmatchingView(); + + pairmatchingContext.initializeMatchingResult(); + pairmatchingView.getOutputView().printInitializeMessage(); + + return new SelectFeatureStatus(); + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/MatchingProcessStatus.java b/src/main/java/pairmatching/launcher/status/MatchingProcessStatus.java new file mode 100644 index 0000000..06c5d0a --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/MatchingProcessStatus.java @@ -0,0 +1,33 @@ +package pairmatching.launcher.status; + +import pairmatching.exception.BeforeMatchedCrewException; +import pairmatching.exception.DuplicatedMatchingDivisionException; +import pairmatching.launcher.context.PairmatchingContext; +import pairmatching.view.OutputView; + +public class MatchingProcessStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var pairmatchingView = pairmatchingContext.getPairmatchingView(); + var outputView = pairmatchingView.getOutputView(); + + pairmatchingContext.plusTryCount(); + return this.getNextStatus(pairmatchingContext, outputView); + } + + private PairmatchingStatus getNextStatus(PairmatchingContext pairmatchingContext, OutputView outputView) { + try { + outputView.printMatchingResultCrews(pairmatchingContext.matchPair()); + return new SelectFeatureStatus(); + } catch (DuplicatedMatchingDivisionException duplicatedMatchingDivisionException) { + return new AskRematchingStatus(); + } catch (BeforeMatchedCrewException | IllegalStateException exception) { + return this; + } + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/launcher/status/PairmatchingStatus.java b/src/main/java/pairmatching/launcher/status/PairmatchingStatus.java new file mode 100644 index 0000000..6d9a066 --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/PairmatchingStatus.java @@ -0,0 +1,9 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.context.PairmatchingContext; + +public interface PairmatchingStatus { + PairmatchingStatus next(PairmatchingContext pairmatchingContext); + + boolean runnable(); +} diff --git a/src/main/java/pairmatching/launcher/status/QuitStatus.java b/src/main/java/pairmatching/launcher/status/QuitStatus.java new file mode 100644 index 0000000..8ea6d5d --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/QuitStatus.java @@ -0,0 +1,15 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.context.PairmatchingContext; + +public class QuitStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + return null; + } + + @Override + public boolean runnable() { + return false; + } +} diff --git a/src/main/java/pairmatching/launcher/status/SelectFeatureStatus.java b/src/main/java/pairmatching/launcher/status/SelectFeatureStatus.java new file mode 100644 index 0000000..cbcd80b --- /dev/null +++ b/src/main/java/pairmatching/launcher/status/SelectFeatureStatus.java @@ -0,0 +1,20 @@ +package pairmatching.launcher.status; + +import pairmatching.launcher.code.CommandToStatusConvertor; +import pairmatching.launcher.context.PairmatchingContext; + +public class SelectFeatureStatus implements PairmatchingStatus { + @Override + public PairmatchingStatus next(PairmatchingContext pairmatchingContext) { + var pairmatchingView = pairmatchingContext.getPairmatchingView(); + var featureCommand = pairmatchingView.getInputView().readFeatureCommand(); + + pairmatchingContext.initializeRetryCount(); + return CommandToStatusConvertor.getNextStatus(featureCommand); + } + + @Override + public boolean runnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 0000000..b243e72 --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,43 @@ +package pairmatching.view; + +import camp.nextstep.edu.missionutils.Console; +import java.util.NoSuchElementException; +import pairmatching.domain.matching.MatchingDivision; +import pairmatching.launcher.code.FeatureCommand; +import pairmatching.launcher.code.RematchingCommand; + +public class InputView extends IoPrinter { + + public FeatureCommand readFeatureCommand() { + this.println("기능을 선택하세요."); + this.println(FeatureCommand.messages()); + return FeatureCommand.from(this.readLineAfterPrintNewLine()); + } + + public MatchingDivision readPairmatchingDivision() { + + this.println("과정, 레벨, 미션을 선택하세요."); + this.println("ex) 백엔드, 레벨1, 자동차경주"); + return new MatchingDivision(this.readLineAfterPrintNewLine()); + } + + private String readLine() { + try { + return Console.readLine(); + } catch (NoSuchElementException exception) { + throw new IllegalStateException("더 이상 입력할 수 없습니다."); + } + } + + public RematchingCommand readReMatchingCommand() { + this.println("매칭 정보가 있습니다. 다시 매칭하시겠습니까?"); + this.println(RematchingCommand.message()); + return RematchingCommand.from(this.readLineAfterPrintNewLine()); + } + + private String readLineAfterPrintNewLine() { + var input = this.readLine(); + this.println(); + return input; + } +} diff --git a/src/main/java/pairmatching/view/IoPrinter.java b/src/main/java/pairmatching/view/IoPrinter.java new file mode 100644 index 0000000..4905b42 --- /dev/null +++ b/src/main/java/pairmatching/view/IoPrinter.java @@ -0,0 +1,19 @@ +package pairmatching.view; + +public class IoPrinter { + protected static final String DIVISION_HASH = "#############################################"; + + protected void print(final Object message) { + System.out.print(message); + } + + protected void println(final Object message) { + System.out.println(message); + } + + protected void println() { + this.println(""); + } + + +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 0000000..4537ce9 --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,27 @@ +package pairmatching.view; + +import pairmatching.domain.code.Course; +import pairmatching.domain.code.Level; + +public class OutputView extends IoPrinter { + + public void printMatchingResultCrews(String matchedCrewNames) { + this.println("페어 매칭 결과입니다."); + this.println(matchedCrewNames); + } + + public void printInitializeMessage() { + this.println("초기화 되었습니다. "); + } + + public void printError(final Object message) { + this.println("[ERROR]" + message); + } + + public void printMatchingMenu() { + this.println(DIVISION_HASH); + this.println(String.format("과정 : %s", Course.messages())); + this.println(Level.messages()); + this.println(DIVISION_HASH); + } +} diff --git a/src/main/java/pairmatching/view/PairmatchingView.java b/src/main/java/pairmatching/view/PairmatchingView.java new file mode 100644 index 0000000..3d681c3 --- /dev/null +++ b/src/main/java/pairmatching/view/PairmatchingView.java @@ -0,0 +1,19 @@ +package pairmatching.view; + +public class PairmatchingView { + private final InputView inputView; + private final OutputView outputView; + + public PairmatchingView(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public InputView getInputView() { + return inputView; + } + + public OutputView getOutputView() { + return outputView; + } +} diff --git a/src/test/java/pairmatching/ApplicationTest.java b/src/test/java/pairmatching/ApplicationTest.java index 289af26..d2d7701 100644 --- a/src/test/java/pairmatching/ApplicationTest.java +++ b/src/test/java/pairmatching/ApplicationTest.java @@ -11,6 +11,7 @@ class ApplicationTest extends NsTest { private static final String ERROR_MESSAGE = "[ERROR]"; + @Test void 짝수_인원_페어_매칭() { assertShuffleTest( @@ -31,6 +32,22 @@ class ApplicationTest extends NsTest { } ); } + + @Test + void _3회_실패에_대한_에러처리() { + assertShuffleTest( + () -> { + var command = new String[]{"1", "백엔드,레벨1,자동차경주", "1", "백엔드,레벨1,로또"}; + runException(command); + assertThat(output()).contains(ERROR_MESSAGE); + }, + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("태웅", "백호", "치수", "태섭") + ); + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/pairmatching/domain/code/CourseTest.java b/src/test/java/pairmatching/domain/code/CourseTest.java new file mode 100644 index 0000000..7df577e --- /dev/null +++ b/src/test/java/pairmatching/domain/code/CourseTest.java @@ -0,0 +1,28 @@ +package pairmatching.domain.code; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CourseTest { + + @ParameterizedTest + @ValueSource(strings = {"풀스택", " ", ""}) + void 잘못된_입력을_할_경우_오류를_반환합니다(final String input) { + Assertions.assertThatIllegalArgumentException().isThrownBy(() -> Course.from(input)); + } + + @Test + void 과정에_대한_메시지를_반환할_수_있습니다() { + var actual = Course.messages(); + var expected = "백엔드 | 프론트엔드"; + + Assertions.assertThat(actual).isEqualTo(expected); + } + + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/code/LevelTest.java b/src/test/java/pairmatching/domain/code/LevelTest.java new file mode 100644 index 0000000..1a0536f --- /dev/null +++ b/src/test/java/pairmatching/domain/code/LevelTest.java @@ -0,0 +1,39 @@ +package pairmatching.domain.code; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class LevelTest { + + @ParameterizedTest + @ValueSource(strings = {"레벨0", "레벨", "레벨6"}) + void 등록되지_않는_레벨에_대한_입력은_오류를_발생시킵니다(final String input) { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> Level.from(input, Subject.RACING)); + } + + @ParameterizedTest + @CsvSource(value = {"레벨1:장바구니", "레벨2:배포"}, delimiterString = ":") + void 레벨에_맞지_않는_과목이면_오류를_발생시킵니다(final String levelName, final String subjectName) { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> Level.from(levelName, Subject.from(subjectName))); + } + + @Test + void 레벨에_대한_메시지를_확인할_수_있습니다() { + var actual = Level.messages(); + var expected = " - 레벨1: 자동차경주 | 로또 | 숫자야구게임\n" + + " - 레벨2: 장바구니 | 결제 | 지하철노선도\n" + + " - 레벨3: \n" + + " - 레벨4: 성능개선 | 배포\n" + + " - 레벨5: "; + + Assertions.assertThat(actual).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/code/SubjectTest.java b/src/test/java/pairmatching/domain/code/SubjectTest.java new file mode 100644 index 0000000..45fda0d --- /dev/null +++ b/src/test/java/pairmatching/domain/code/SubjectTest.java @@ -0,0 +1,19 @@ +package pairmatching.domain.code; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class SubjectTest { + + + @ParameterizedTest + @ValueSource(strings = {"자동차", "로", " ", ""}) + void 잘못된_과제를_입력하면_오류를_발생시킵니다(final String input) { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> Subject.from(input)); + } +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/generator/CrewGeneratorTest.java b/src/test/java/pairmatching/domain/generator/CrewGeneratorTest.java new file mode 100644 index 0000000..6e12fd0 --- /dev/null +++ b/src/test/java/pairmatching/domain/generator/CrewGeneratorTest.java @@ -0,0 +1,44 @@ +package pairmatching.domain.generator; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import pairmatching.domain.code.Course; +import pairmatching.domain.matching.Crew; + + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CrewGeneratorTest { + + @Test + void 임의의_값을_넣을때_크루가_만들어지는가() { + var crewGenerator = new CrewGenerator(new FakeFileReader(List.of("가나", "가나다")), new FakeShuffle()); + + var crews = crewGenerator.generateCrews(Course.BACKEND); + + Assertions.assertThat(crews) + .isEqualTo(List.of(new Crew(Course.BACKEND, "가나"), new Crew(Course.BACKEND, "가나다"))); + } + + + @Test + void 입력받은_파일에서_크기가_2보다_작은경우_에러를_발생시킵니다() { + Assertions.assertThatIllegalStateException() + .isThrownBy(() -> { + var generator = new CrewGenerator(new FakeFileReader(List.of("가나")), new FakeShuffle()); + generator.generateCrews(Course.BACKEND); + }); + } + + @Test + void 입력받은_파일에서_중복되는_이름을_가지는_크루가_있으면_에러를_발생시킵니다() { + Assertions.assertThatIllegalStateException() + .isThrownBy(() -> { + var generator = new CrewGenerator(new FakeFileReader(List.of("가나", "가나")), new FakeShuffle()); + generator.generateCrews(Course.BACKEND); + }); + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/generator/FakeFileReader.java b/src/test/java/pairmatching/domain/generator/FakeFileReader.java new file mode 100644 index 0000000..9f7dda8 --- /dev/null +++ b/src/test/java/pairmatching/domain/generator/FakeFileReader.java @@ -0,0 +1,17 @@ +package pairmatching.domain.generator; + +import java.util.List; + +public class FakeFileReader implements ReadFile { + + private final List data; + + public FakeFileReader(List data) { + this.data = data; + } + + @Override + public List readFile(String fileName) { + return data; + } +} diff --git a/src/test/java/pairmatching/domain/generator/FakeShuffle.java b/src/test/java/pairmatching/domain/generator/FakeShuffle.java new file mode 100644 index 0000000..8e95ad8 --- /dev/null +++ b/src/test/java/pairmatching/domain/generator/FakeShuffle.java @@ -0,0 +1,10 @@ +package pairmatching.domain.generator; + +import java.util.List; + +public class FakeShuffle implements ShuffleGenerator { + @Override + public List shuffle(List input) { + return input; + } +} diff --git a/src/test/java/pairmatching/domain/matching/CrewTest.java b/src/test/java/pairmatching/domain/matching/CrewTest.java new file mode 100644 index 0000000..9743832 --- /dev/null +++ b/src/test/java/pairmatching/domain/matching/CrewTest.java @@ -0,0 +1,39 @@ +package pairmatching.domain.matching; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import pairmatching.domain.code.Course; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CrewTest { + + + @ParameterizedTest + @ValueSource(strings = {" ", " ", " "}) + void 크루의_이름은_비어있을_수_없습니다(final String input) { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> new Crew(Course.BACKEND, input)); + } + + @ParameterizedTest + @ValueSource(strings = {"가", "나다"}) + void 크루의_이름과_강좌가_같으면_같은_크루입니다(final String input) { + var actual = new Crew(Course.BACKEND, input); + var expected = new Crew(Course.BACKEND, input); + + Assertions.assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @ValueSource(strings = {"가", "나"}) + void 크루의_이름이_같고_강좌가_다르면_다른_크루입니다(final String input) { + var target = new Crew(Course.BACKEND, input); + var original = new Crew(Course.FRONTEND, input); + + Assertions.assertThat(original).isNotEqualTo(target); + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/matching/MatchedCrewsTest.java b/src/test/java/pairmatching/domain/matching/MatchedCrewsTest.java new file mode 100644 index 0000000..6c1e0f3 --- /dev/null +++ b/src/test/java/pairmatching/domain/matching/MatchedCrewsTest.java @@ -0,0 +1,33 @@ +package pairmatching.domain.matching; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import pairmatching.domain.code.Course; +import pairmatching.utils.TestUtils; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class MatchedCrewsTest { + + @Test + void 매칭된_크루_목록의_결과를_반환할_수_있습니다() { + var matchedCrews = new MatchedCrews(); + + matchedCrews.add(generateMatchedCrew(Course.BACKEND, "마,바")); + matchedCrews.add(generateMatchedCrew(Course.BACKEND, "가,나,다")); + + var actual = matchedCrews.result().split("\n"); + + Assertions.assertThat(actual[0]).isEqualTo("마 : 바"); + Assertions.assertThat(actual[1]).isEqualTo("가 : 나 : 다"); + } + + + private MatchedCrew generateMatchedCrew(Course course, final String name) { + var matchedCrew = new MatchedCrew(); + matchedCrew.addAll(TestUtils.convertStringToCrews(course, name)); + return matchedCrew; + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/matching/MatchingDivisionTest.java b/src/test/java/pairmatching/domain/matching/MatchingDivisionTest.java new file mode 100644 index 0000000..766df94 --- /dev/null +++ b/src/test/java/pairmatching/domain/matching/MatchingDivisionTest.java @@ -0,0 +1,37 @@ +package pairmatching.domain.matching; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class MatchingDivisionTest { + + @ParameterizedTest + @ValueSource(strings = {"가,", "", " ", " , , , "}) + void 구분자를_통해서_매칭_구분값을_입력받을_수_있습니다(final String input) { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> new MatchingDivision(input)); + } + + @ParameterizedTest + @ValueSource(strings = {"백엔드,레벨1,자동차경주"}) + void 구분과정이_같으면_같은_과정입니다(final String input) { + var expected = new MatchingDivision(input); + var actual = new MatchingDivision(input); + Assertions.assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"백엔드,레벨1,자동차경주:백엔드,레벨1,로또"}, delimiterString = ":") + void 구분값이_다르면_다른_과정입니다(final String originalInput, final String targetInput) { + var original = new MatchingDivision(originalInput); + var target = new MatchingDivision(targetInput); + + Assertions.assertThat(original).isNotEqualTo(target); + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/matching/MatchingResultTest.java b/src/test/java/pairmatching/domain/matching/MatchingResultTest.java new file mode 100644 index 0000000..02aee88 --- /dev/null +++ b/src/test/java/pairmatching/domain/matching/MatchingResultTest.java @@ -0,0 +1,53 @@ +package pairmatching.domain.matching; + + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static pairmatching.utils.TestUtils.convertStringToCrews; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import pairmatching.domain.code.Course; +import pairmatching.exception.BeforeMatchedCrewException; +import pairmatching.exception.DuplicatedMatchingDivisionException; +import pairmatching.exception.MatchingDivisionDidNotExists; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class MatchingResultTest { + + @Test + void 중복되는_키가_있는_경우_에러를_발생시킵니다() { + var matchingResult = new MatchingResult(); + var matchingDivision = new MatchingDivision("백엔드,레벨1,자동차경주"); + + assertThatThrownBy(() -> { + matchingResult.matchPair(matchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다")); + matchingResult.matchPair(matchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다")); + }).isInstanceOf(DuplicatedMatchingDivisionException.class); + } + + @Test + void 키를_통해서_매칭_결과를_조회할때_데이터가_없는_경우_에러를_발생시킵니다() { + var matchingResult = new MatchingResult(); + var matchingDivision = new MatchingDivision("백엔드,레벨1,자동차경주"); + + assertThatThrownBy(() -> { + matchingResult.findByMatchingDivision(matchingDivision); + }).isInstanceOf(MatchingDivisionDidNotExists.class); + } + + @Test + void 같은_레벨에서_페어를_맺은적이_있는_크루가_있는_경우_오류를_발생시킵니다() { + var matchingResult = new MatchingResult(); + var matchingDivision = new MatchingDivision("백엔드,레벨1,자동차경주"); + + var newMatchingDivision = new MatchingDivision("백엔드,레벨1,로또"); + assertThatThrownBy(() -> { + matchingResult.matchPair(matchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다")); + + matchingResult.matchPair(newMatchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다")); + }).isInstanceOf(BeforeMatchedCrewException.class); + + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/launcher/code/FeatureCommandTest.java b/src/test/java/pairmatching/launcher/code/FeatureCommandTest.java new file mode 100644 index 0000000..90eff96 --- /dev/null +++ b/src/test/java/pairmatching/launcher/code/FeatureCommandTest.java @@ -0,0 +1,28 @@ +package pairmatching.launcher.code; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class FeatureCommandTest { + + @ParameterizedTest + @ValueSource(strings = {" ", "4", "q"}) + void 잘못된_입력어를_입력하면_오류를_반환합니다(final String input) { + Assertions.assertThatIllegalArgumentException().isThrownBy(() -> FeatureCommand.from(input)); + } + + @Test + void 기능명령어는_메시지를_가집니다() { + var actual = FeatureCommand.messages(); + var expected = "1. 페어 매칭\n" + + "2. 페어 조회\n" + + "3. 페어 초기화\n" + + "Q. 종료"; + Assertions.assertThat(actual).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/pairmatching/launcher/context/FakePairmatchingContext.java b/src/test/java/pairmatching/launcher/context/FakePairmatchingContext.java new file mode 100644 index 0000000..f1d4e80 --- /dev/null +++ b/src/test/java/pairmatching/launcher/context/FakePairmatchingContext.java @@ -0,0 +1,49 @@ +package pairmatching.launcher.context; + +import pairmatching.domain.matching.MatchingDivision; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; +import pairmatching.view.PairmatchingView; + +public class FakePairmatchingContext implements PairmatchingContext { + + @Override + public PairmatchingView getPairmatchingView() { + return new PairmatchingView(new InputView(), new OutputView()); + } + + @Override + public void inputMatchingDivision(MatchingDivision matchingDivision) { + + } + + @Override + public String matchPair() { + return null; + } + + @Override + public String rematchPair() { + return null; + } + + @Override + public String findMatchedCrewsByMatchingDivision() { + return null; + } + + @Override + public void plusTryCount() { + + } + + @Override + public void initializeRetryCount() { + + } + + @Override + public void initializeMatchingResult() { + + } +} diff --git a/src/test/java/pairmatching/launcher/status/InitStatusTest.java b/src/test/java/pairmatching/launcher/status/InitStatusTest.java new file mode 100644 index 0000000..4a14326 --- /dev/null +++ b/src/test/java/pairmatching/launcher/status/InitStatusTest.java @@ -0,0 +1,21 @@ +package pairmatching.launcher.status; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import pairmatching.launcher.context.FakePairmatchingContext; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class InitStatusTest { + + @Test + void 초기화_상태_다음은_사용자_기능선택_상태입니다() { + var actualStatus = new InitStatus().next(new FakePairmatchingContext()); + var expectedStatus = new SelectFeatureStatus(); + Assertions.assertThat(actualStatus).isInstanceOf(expectedStatus.getClass()); + + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/launcher/status/MatchingProcessStatusTest.java b/src/test/java/pairmatching/launcher/status/MatchingProcessStatusTest.java new file mode 100644 index 0000000..776921e --- /dev/null +++ b/src/test/java/pairmatching/launcher/status/MatchingProcessStatusTest.java @@ -0,0 +1,18 @@ +package pairmatching.launcher.status; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import pairmatching.launcher.context.FakePairmatchingContext; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class MatchingProcessStatusTest { + + + @Test + void 매칭_처리상태에서_중복된_매칭이_일어난_경우_다시매칭한다() { + + new MatchingProcessStatus().next(new FakePairmatchingContext()); + } + +} \ No newline at end of file diff --git a/src/test/java/pairmatching/launcher/status/SelectFeatureStatusTest.java b/src/test/java/pairmatching/launcher/status/SelectFeatureStatusTest.java new file mode 100644 index 0000000..d76a6f6 --- /dev/null +++ b/src/test/java/pairmatching/launcher/status/SelectFeatureStatusTest.java @@ -0,0 +1,45 @@ +package pairmatching.launcher.status; + +import camp.nextstep.edu.missionutils.test.NsTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import pairmatching.launcher.context.FakePairmatchingContext; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class SelectFeatureStatusTest extends NsTest { + + PairmatchingStatus getNextStatus() { + return new SelectFeatureStatus().next(new FakePairmatchingContext()); + } + + @ParameterizedTest + @ValueSource(strings = {"1", "2"}) + void 기능선택하는상태에서_1_2를_입력하면_다음은_입력상태로_넘어간다(final String input) { + run(input); + Assertions.assertThat(this.getNextStatus()) + .isInstanceOf(InputStatus.class); + } + + @Test + void 기능선택상태에서_3을_입력하면_매칭_초기화_상태가_된다() { + run("3"); + Assertions.assertThat(this.getNextStatus()) + .isInstanceOf(MatchingInitializeStatus.class); + } + + @Test + void 기능선택상태에서_Q_를_입력하면_종료상태가_된다() { + run("Q"); + Assertions.assertThat(this.getNextStatus()) + .isInstanceOf(QuitStatus.class); + } + + @Override + protected void runMain() { + + } +} \ No newline at end of file diff --git a/src/test/java/pairmatching/utils/TestUtils.java b/src/test/java/pairmatching/utils/TestUtils.java new file mode 100644 index 0000000..f8de626 --- /dev/null +++ b/src/test/java/pairmatching/utils/TestUtils.java @@ -0,0 +1,16 @@ +package pairmatching.utils; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.code.Course; +import pairmatching.domain.matching.Crew; + +public class TestUtils { + + public static List convertStringToCrews(Course course, final String input) { + return Arrays.stream(input.split(",")) + .map(name -> new Crew(course, name)) + .collect(Collectors.toList()); + } +}