diff --git a/src/main/java/seedu/hireme/logic/commands/StatusCommand.java b/src/main/java/seedu/hireme/logic/commands/StatusCommand.java new file mode 100644 index 00000000000..bc8d6297b8b --- /dev/null +++ b/src/main/java/seedu/hireme/logic/commands/StatusCommand.java @@ -0,0 +1,110 @@ +package seedu.hireme.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.hireme.commons.core.index.Index; +import seedu.hireme.logic.Messages; +import seedu.hireme.logic.commands.exceptions.CommandException; +import seedu.hireme.model.Model; +import seedu.hireme.model.internshipapplication.InternshipApplication; +import seedu.hireme.model.internshipapplication.Status; + +/** + * Changes the status of an internship application identified using its displayed index. + */ +public class StatusCommand extends Command { + + /** Command word for accepting an application. */ + public static final String COMMAND_WORD_ACCEPT = "/accept"; + + /** Command word for marking an application as pending. */ + public static final String COMMAND_WORD_PENDING = "/pending"; + + /** Command word for rejecting an application. */ + public static final String COMMAND_WORD_REJECT = "/reject"; + + /** + * Message to display for command usage instructions. + */ + public static final String MESSAGE_USAGE = COMMAND_WORD_ACCEPT + + " / " + COMMAND_WORD_PENDING + " / " + COMMAND_WORD_REJECT + + ": Changes the status of the internship application identified by " + + "the index number used in the displayed list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD_ACCEPT + " 1"; + + /** + * Message to display upon successful status update. + */ + public static final String MESSAGE_STATUS_CHANGE_SUCCESS = "Updated status of internship application: %1$s to %2$s"; + + private final Index targetIndex; + private final Status newStatus; + + /** + * Constructs a {@code StatusCommand}. + * + * @param targetIndex The index of the internship application to update. + * @param newStatus The new status to set for the internship application. + */ + public StatusCommand(Index targetIndex, Status newStatus) { + this.targetIndex = targetIndex; + this.newStatus = newStatus; + } + + /** + * Executes the command to update the status of an internship application. + * + * @param model The model containing the list of internship applications. + * @return A {@code CommandResult} indicating the result of the status update. + * @throws CommandException If the target index is invalid. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INTERNSHIP_APPLICATION_DISPLAYED_INDEX); + } + + InternshipApplication internshipApplicationToUpdate = lastShownList.get(targetIndex.getZeroBased()); + internshipApplicationToUpdate.setStatus(newStatus); + return new CommandResult(String.format(MESSAGE_STATUS_CHANGE_SUCCESS, + Messages.format(internshipApplicationToUpdate), newStatus.getValue())); + } + + /** + * Checks if this command is equal to another object. + * + * @param other The other object to compare. + * @return True if both objects are the same or have the same target index and new status, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof StatusCommand)) { + return false; + } + + StatusCommand otherStatusCommand = (StatusCommand) other; + return targetIndex.equals(otherStatusCommand.targetIndex) + && newStatus.equals(otherStatusCommand.newStatus); + } + + /** + * Returns the string representation of this command. + * + * @return A string representing this {@code StatusCommand}. + */ + @Override + public String toString() { + return StatusCommand.class.getCanonicalName() + + "{targetIndex=" + targetIndex + ", newStatus=" + newStatus.getValue() + "}"; + } +} diff --git a/src/main/java/seedu/hireme/logic/parser/AddressBookParser.java b/src/main/java/seedu/hireme/logic/parser/AddressBookParser.java index 2f446855cc0..dcb7d1c9c1b 100644 --- a/src/main/java/seedu/hireme/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/hireme/logic/parser/AddressBookParser.java @@ -16,8 +16,10 @@ import seedu.hireme.logic.commands.FindCommand; import seedu.hireme.logic.commands.HelpCommand; import seedu.hireme.logic.commands.ListCommand; +import seedu.hireme.logic.commands.StatusCommand; import seedu.hireme.logic.parser.exceptions.ParseException; import seedu.hireme.model.internshipapplication.InternshipApplication; +import seedu.hireme.model.internshipapplication.Status; /** * Parses user input. @@ -59,6 +61,15 @@ public Command parseCommand(String userInput) throws Pars case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case StatusCommand.COMMAND_WORD_ACCEPT: + return new StatusCommandParser(Status.ACCEPTED).parse(arguments); + + case StatusCommand.COMMAND_WORD_PENDING: + return new StatusCommandParser(Status.PENDING).parse(arguments); + + case StatusCommand.COMMAND_WORD_REJECT: + return new StatusCommandParser(Status.REJECTED).parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); diff --git a/src/main/java/seedu/hireme/logic/parser/StatusCommandParser.java b/src/main/java/seedu/hireme/logic/parser/StatusCommandParser.java new file mode 100644 index 00000000000..7ce1f769cb8 --- /dev/null +++ b/src/main/java/seedu/hireme/logic/parser/StatusCommandParser.java @@ -0,0 +1,43 @@ +package seedu.hireme.logic.parser; + +import static seedu.hireme.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.hireme.commons.core.index.Index; +import seedu.hireme.logic.commands.StatusCommand; +import seedu.hireme.logic.parser.exceptions.ParseException; +import seedu.hireme.model.internshipapplication.Status; + +/** + * Parses input arguments and creates a new StatusCommand object + */ +public class StatusCommandParser implements Parser { + + private final Status status; + + public StatusCommandParser(Status status) { + this.status = status; + } + + /** + * Parses the given {@code String} of arguments in the context of the StatusCommand + * and returns a StatusCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public StatusCommand parse(String args) throws ParseException { + try { + // Parse index from the arguments + Index index = ParserUtil.parseIndex(args.trim()); + + // Ensure the status is one of the allowed values (PENDING, ACCEPTED, REJECTED) + if (status == null || !(status == Status.PENDING || status == Status.ACCEPTED + || status == Status.REJECTED)) { + throw new ParseException(Status.MESSAGE_CONSTRAINTS); + } + + return new StatusCommand(index, status); + + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, StatusCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/hireme/model/ModelManager.java b/src/main/java/seedu/hireme/model/ModelManager.java index d1a7ea44608..a95e89583a9 100644 --- a/src/main/java/seedu/hireme/model/ModelManager.java +++ b/src/main/java/seedu/hireme/model/ModelManager.java @@ -131,16 +131,20 @@ public void updateFilteredList(Predicate predicate) { @Override public boolean equals(Object other) { - + // Check if the objects are the same instance if (other == this) { return true; } - // instanceof handles nulls - if (!(other instanceof ModelManager otherModelManager)) { + // Check if the other object is an instance of ModelManager and not null + if (!(other instanceof ModelManager)) { return false; } + // Cast the other object to ModelManager + ModelManager otherModelManager = (ModelManager) other; + + // Compare the state of addressBook, userPrefs, and filtered lists return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) && filtered.equals(otherModelManager.filtered); diff --git a/src/main/java/seedu/hireme/model/internshipapplication/InternshipApplication.java b/src/main/java/seedu/hireme/model/internshipapplication/InternshipApplication.java index 8b5805d1445..7ae5ab14edb 100644 --- a/src/main/java/seedu/hireme/model/internshipapplication/InternshipApplication.java +++ b/src/main/java/seedu/hireme/model/internshipapplication/InternshipApplication.java @@ -17,7 +17,7 @@ public class InternshipApplication implements HireMeComparable + * The deep copy ensures that the new {@code InternshipApplication} is a completely independent + * copy, meaning any changes to the new object will not affect the original object. + * This method creates a new {@code Company} instance to avoid sharing mutable state, but since + * {@code String} and {@code Status} are immutable, those are shared directly. + *

+ * + * @return A new {@code InternshipApplication} instance with the same values as this instance. + */ + public InternshipApplication deepCopy() { + return new InternshipApplication( + new Company(this.company.getEmail(), this.company.getName()), // Deep copy of mutable Company + this.dateOfApplication, // String is immutable, safe to share reference + this.role, // String is immutable, safe to share reference + this.status // Status is immutable (enum), safe to share reference + ); + } } diff --git a/src/test/java/seedu/hireme/logic/commands/StatusCommandTest.java b/src/test/java/seedu/hireme/logic/commands/StatusCommandTest.java new file mode 100644 index 00000000000..6126c2fd621 --- /dev/null +++ b/src/test/java/seedu/hireme/logic/commands/StatusCommandTest.java @@ -0,0 +1,101 @@ +package seedu.hireme.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.hireme.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.hireme.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.hireme.logic.commands.CommandTestUtil.showInternshipApplicationAtIndex; +import static seedu.hireme.testutil.TypicalIndexes.INDEX_FIRST_INTERNSHIP_APPLICATION; +import static seedu.hireme.testutil.TypicalIndexes.INDEX_SECOND_INTERNSHIP_APPLICATION; +import static seedu.hireme.testutil.TypicalInternshipApplications.getClonedAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.hireme.commons.core.index.Index; +import seedu.hireme.logic.Messages; +import seedu.hireme.model.Model; +import seedu.hireme.model.ModelManager; +import seedu.hireme.model.UserPrefs; +import seedu.hireme.model.internshipapplication.InternshipApplication; +import seedu.hireme.model.internshipapplication.Status; + +public class StatusCommandTest { + + private Model model = new ModelManager<>(getClonedAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Model clonedModel = new ModelManager<>(getClonedAddressBook(), new UserPrefs()); + + InternshipApplication internshipApplicationToUpdate = clonedModel + .getFilteredList().get(INDEX_FIRST_INTERNSHIP_APPLICATION.getZeroBased()); + + StatusCommand statusCommand = new StatusCommand(INDEX_FIRST_INTERNSHIP_APPLICATION, Status.ACCEPTED); + + String expectedMessage = String.format(StatusCommand.MESSAGE_STATUS_CHANGE_SUCCESS, + Messages.format(internshipApplicationToUpdate), Status.ACCEPTED.getValue()); + + ModelManager expectedModel = new ModelManager<>(getClonedAddressBook(), new UserPrefs()); + + InternshipApplication updatedApplication = new InternshipApplication( + internshipApplicationToUpdate.getCompany(), + internshipApplicationToUpdate.getDateOfApplication(), + internshipApplicationToUpdate.getRole(), + Status.ACCEPTED + ); + + expectedModel.setItem(internshipApplicationToUpdate, updatedApplication); + + assertCommandSuccess(statusCommand, clonedModel, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredList().size() + 1); + StatusCommand statusCommand = new StatusCommand(outOfBoundIndex, Status.ACCEPTED); + + assertCommandFailure(statusCommand, model, Messages.MESSAGE_INVALID_INTERNSHIP_APPLICATION_DISPLAYED_INDEX); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showInternshipApplicationAtIndex(model, INDEX_FIRST_INTERNSHIP_APPLICATION); + + Index outOfBoundIndex = INDEX_SECOND_INTERNSHIP_APPLICATION; + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getList().size()); + + StatusCommand statusCommand = new StatusCommand(outOfBoundIndex, Status.PENDING); + + assertCommandFailure(statusCommand, model, Messages.MESSAGE_INVALID_INTERNSHIP_APPLICATION_DISPLAYED_INDEX); + } + + @Test + public void equals() { + StatusCommand statusFirstCommand = new StatusCommand(INDEX_FIRST_INTERNSHIP_APPLICATION, Status.ACCEPTED); + StatusCommand statusSecondCommand = new StatusCommand(INDEX_SECOND_INTERNSHIP_APPLICATION, Status.PENDING); + + assertTrue(statusFirstCommand.equals(statusFirstCommand)); + + StatusCommand statusFirstCommandCopy = new StatusCommand(INDEX_FIRST_INTERNSHIP_APPLICATION, Status.ACCEPTED); + assertTrue(statusFirstCommand.equals(statusFirstCommandCopy)); + + assertFalse(statusFirstCommand.equals(1)); + assertFalse(statusFirstCommand.equals(null)); + assertFalse(statusFirstCommand.equals(statusSecondCommand)); + } + + @Test + public void toStringMethod() { + Index targetIndex = Index.fromOneBased(1); + StatusCommand statusCommand = new StatusCommand(targetIndex, Status.ACCEPTED); + String expected = StatusCommand.class.getCanonicalName() + + "{targetIndex=" + targetIndex + ", newStatus=" + Status.ACCEPTED + "}"; + assertEquals(expected, statusCommand.toString()); + } + + private void showNoInternshipApplication(Model model) { + model.updateFilteredList(p -> false); + assertTrue(model.getFilteredList().isEmpty()); + } +} diff --git a/src/test/java/seedu/hireme/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/hireme/logic/parser/AddressBookParserTest.java index ad33bf9e5be..8f2a13271b8 100644 --- a/src/test/java/seedu/hireme/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/hireme/logic/parser/AddressBookParserTest.java @@ -19,12 +19,15 @@ import seedu.hireme.logic.commands.FindCommand; import seedu.hireme.logic.commands.HelpCommand; import seedu.hireme.logic.commands.ListCommand; +import seedu.hireme.logic.commands.StatusCommand; import seedu.hireme.logic.parser.exceptions.ParseException; import seedu.hireme.model.internshipapplication.InternshipApplication; import seedu.hireme.model.internshipapplication.NameContainsKeywordsPredicate; +import seedu.hireme.model.internshipapplication.Status; import seedu.hireme.testutil.InternshipApplicationBuilder; import seedu.hireme.testutil.InternshipApplicationUtil; + public class AddressBookParserTest { private final AddressBookParser parser = new AddressBookParser(); @@ -87,4 +90,22 @@ public void parseCommand_unrecognisedInput_throwsParseException() { public void parseCommand_unknownCommand_throwsParseException() { assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); } + + @Test + public void parseCommand_statusAccept_success() throws Exception { + StatusCommand command = (StatusCommand) parser.parseCommand("/accept 1"); + assertEquals(new StatusCommand(INDEX_FIRST_INTERNSHIP_APPLICATION, Status.ACCEPTED), command); + } + + @Test + public void parseCommand_statusPending_success() throws Exception { + StatusCommand command = (StatusCommand) parser.parseCommand("/pending 1"); + assertEquals(new StatusCommand(INDEX_FIRST_INTERNSHIP_APPLICATION, Status.PENDING), command); + } + + @Test + public void parseCommand_statusReject_success() throws Exception { + StatusCommand command = (StatusCommand) parser.parseCommand("/reject 1"); + assertEquals(new StatusCommand(INDEX_FIRST_INTERNSHIP_APPLICATION, Status.REJECTED), command); + } } diff --git a/src/test/java/seedu/hireme/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/hireme/logic/parser/CommandParserTestUtil.java index c8299de1935..62ca4cde196 100644 --- a/src/test/java/seedu/hireme/logic/parser/CommandParserTestUtil.java +++ b/src/test/java/seedu/hireme/logic/parser/CommandParserTestUtil.java @@ -35,7 +35,10 @@ public static void assertParseFailure(Parser getTypicalAddressBook() { return ab; } + public static AddressBook getClonedAddressBook() { + AddressBook clonedAb = new AddressBook<>(); + for (InternshipApplication internshipApp : getTypicalInternshipApplications()) { + clonedAb.addItem(internshipApp.deepCopy()); + } + return clonedAb; + } + public static List getTypicalInternshipApplications() { return new ArrayList<>(Arrays.asList(APPLE, BOFA, CITIBANK, DELL, EY, FIGMA, YAHOO)); }