getBudgets() {
+ return budgets;
+ }
+
+ /**
+ * Resets the state of the BudgetManager by clearing all budgets and
+ * setting the total number of budgets to zero.
+ *
+ * This method is used for unit testing, ensuring that each test
+ * starts with a clean slate and does not retain any state from
+ * previous tests.
+ *
+ */
+ public static void reset() {
+ numberOfBudgets = 0;
+ budgets.clear();
+ }
+
+ /**
+ * Provides a global point of access to the single instance of the BudgetManager class.
+ *
+ * This method ensures that only one instance of BudgetManager exists throughout the application's lifetime.
+ * If an instance does not exist, it creates a new one. Otherwise, it returns the existing instance.
+ * This method is used for unit testing for Storage to ensure that stored values are correct.
+ *
+ *
+ * @return The single instance of the BudgetManager class.
+ */
+ public static BudgetManager getInstance() {
+ if (instance == null) {
+ instance = new BudgetManager(budgets, numberOfBudgets); // Pass static fields to constructor
+ }
+ return instance;
+ }
+
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManager.java b/src/main/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManager.java
new file mode 100644
index 0000000000..914e2f5da7
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManager.java
@@ -0,0 +1,190 @@
+package seedu.budgetbuddy.transaction.budget;
+
+import seedu.budgetbuddy.Ui;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.util.ArrayList;
+import java.util.logging.Logger;
+
+/**
+ * Manages the remaining budgets after expenses have been deducted from the budgets.
+ * Initializes the remaining budgets based on the current expenses and provides functionality
+ * to retrieve and list remaining budgets.
+ */
+public class RemainingBudgetManager {
+
+ /** Logger to record events and operations for RemainingBudgetManager. */
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /** List of budgets after expenses have been deducted. */
+ private ArrayList remainingBudgets;
+
+ /**
+ * Constructs a new RemainingBudgetManager. Initializes the remaining budgets by copying
+ * the existing budgets and deducting the appropriate expenses.
+ */
+ public RemainingBudgetManager() {
+ remainingBudgets = new ArrayList<>();
+ copyBudgetManager();
+ ArrayList expenses = ExpenseManager.getExpenses();
+ deductExpensesFromBudget(expenses);
+ LOGGER.info("Remaining budgets initialized and updated after deductions.");
+ }
+
+ /**
+ * Deducts the expenses from the existing budgets based on the list of expenses.
+ * For each expense, it attempts to find a matching budget for the given date
+ * and category, and deducts the expense amount from the respective budget.
+ *
+ * @param expenses The list of expenses to be deducted.
+ */
+ private void deductExpensesFromBudget(ArrayList expenses) {
+ for (Expense expense : expenses) {
+ YearMonth expenseDate = YearMonth.from(expense.getDate());
+ Category expenseCategory = expense.getCategory();
+ double expenseAmount = expense.getAmount();
+
+ Budget matchingBudget = null;
+ matchingBudget = searchForGivenBudget(expenseDate, matchingBudget);
+ matchingBudget = createNewBudget(matchingBudget, expenseDate);
+ matchingBudget.deductExpense(expenseCategory, expenseAmount);
+
+ LOGGER.info("Deducted " + expenseAmount + " from budget for " + expenseDate
+ + " in category " + expenseCategory);
+ }
+ }
+
+ /**
+ * Creates a new budget for a given date if no matching budget is found.
+ *
+ * @param matchingBudget The existing matching budget, or null if no budget exists.
+ * @param expenseDate The date (YearMonth) of the expense.
+ * @return The new or existing budget for the specified date.
+ */
+ private Budget createNewBudget(Budget matchingBudget, YearMonth expenseDate) {
+ assert expenseDate != null : "Expense date cannot be null";
+ if (matchingBudget == null) {
+ matchingBudget = new Budget(expenseDate);
+ remainingBudgets.add(matchingBudget); // Add the new budget to the list
+ LOGGER.info("Created new budget for " + expenseDate + " with initial amount 0.0");
+ }
+ return matchingBudget;
+ }
+
+ /**
+ * Searches for an existing budget that matches the specified date.
+ *
+ * @param expenseDate The date (YearMonth) to search for.
+ * @param matchingBudget The budget to match, initially null.
+ * @return The matching budget, or null if none is found.
+ */
+ private Budget searchForGivenBudget(YearMonth expenseDate, Budget matchingBudget) {
+ for (Budget budget : remainingBudgets) {
+ if (budget.getDate().equals(expenseDate)) {
+ matchingBudget = budget;
+ LOGGER.info("Found existing budget for date: " + expenseDate);
+ break;
+ }
+ }
+ return matchingBudget;
+ }
+
+ /**
+ * Copies the existing budgets from the BudgetManager to initialize the remainingBudgets.
+ */
+ private void copyBudgetManager() {
+ ArrayList budgets = BudgetManager.getBudgets();
+ assert budgets != null : "BudgetManager's budgets cannot be null";
+ for (Budget budget : budgets) {
+ remainingBudgets.add(new Budget(budget));
+ LOGGER.info("Copied budget: " + budget);
+ }
+ }
+
+ /**
+ * Lists all remaining budgets after expenses have been deducted.
+ * Displays the result to the user through the UI.
+ */
+ public void listRemainingBudgets() {
+ if (remainingBudgets.size() == 0) {
+ Ui.displayToUser("No budgets found");
+ LOGGER.info("No budgets found");
+ return;
+ }
+ String result = "All budgets after expense deductions:";
+ for (Budget budget : remainingBudgets) {
+ result += "\n" + budget;
+ }
+ Ui.displayToUser(result);
+ LOGGER.info("Displayed all remaining budgets to user.");
+ }
+
+ /**
+ * Retrieves the remaining budget for a given date and category.
+ *
+ * @param date The date (LocalDate) of the expense.
+ * @param category The category of the expense.
+ * @return A message indicating the remaining budget, or that no budget was found.
+ */
+ public String getRemainingBudgets(LocalDate date, Category category) {
+ YearMonth expenseMonth = YearMonth.from(date);
+ assert date != null : "Date cannot be null";
+ assert category != null : "Category cannot be null";
+
+ String result = findRemainingAmount(category, expenseMonth);
+ if (result != null) {
+ return result;
+ }
+
+ // If no budget is found for the specified date
+ LOGGER.warning("No budget found for " + expenseMonth + ".");
+ return "No budget found for " + expenseMonth + ".";
+ }
+
+ /**
+ * Searches for the remaining budget for the specified date and category.
+ * This method checks if a budget exists for the specified month and retrieves the
+ * remaining amount for the specified category.
+ *
+ * @param category The category of the expense.
+ * @param expenseMonth The month and year of the expense.
+ * @return A string representing the remaining budget for the specified category, or null if no budget is found.
+ */
+ private String findRemainingAmount(Category category, YearMonth expenseMonth) {
+ for (Budget budget : remainingBudgets) {
+ if (budget.getDate().equals(expenseMonth)) {
+ return processAmount(category, expenseMonth, budget);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Processes the remaining amount for the specified category and budget.
+ * If the category does not have a budget, it assumes the remaining amount is 0.0.
+ *
+ * @param category The category for which the remaining budget is being processed.
+ * @param expenseMonth The month and year of the expense.
+ * @param budget The budget from which the remaining amount will be retrieved.
+ * @return A formatted string showing the remaining budget for the category and any warnings if exceeded.
+ */
+ private static String processAmount(Category category, YearMonth expenseMonth, Budget budget) {
+ Double remainingAmount = budget.getCategoryBudgets().get(category);
+ if (remainingAmount == null) {
+ remainingAmount = 0.00; // If the category does not exist, assume remaining amount is 0
+ }
+ LOGGER.info("Retrieved remaining budget for " + expenseMonth + " in category " + category
+ + ": " + remainingAmount);
+ String result = "The remaining budget for " + expenseMonth + " in the " + category
+ + " category is: " + String.format("%.2f", remainingAmount);
+ if (remainingAmount < 0) {
+ result += "\nCaution! You have exceeded your budget!";
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/expense/Expense.java b/src/main/java/seedu/budgetbuddy/transaction/expense/Expense.java
new file mode 100644
index 0000000000..47c0a5db03
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/expense/Expense.java
@@ -0,0 +1,85 @@
+package seedu.budgetbuddy.transaction.expense;
+
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.Transaction;
+
+import java.time.LocalDate;
+
+/**
+ * Represents an expense transaction in the budget management system.
+ * An expense has a description, amount, date, and category.
+ */
+public class Expense extends Transaction {
+ /** The category of the expense. */
+ private Category category;
+
+ /**
+ * Constructs an Expense object with the specified details.
+ *
+ * @param description the description of the expense
+ * @param amount the amount of the expense
+ * @param date the date of the expense
+ * @param category the category of the expense
+ */
+ public Expense(String description, double amount, LocalDate date, Category category) {
+ super(description, amount, date);
+ this.category = category;
+ }
+
+ /**
+ * Returns a string representation of the expense, including its details.
+ *
+ * @return a string describing the expense
+ */
+ @Override
+ public String toString() {
+ return String.format("Description: %s Amount: %.2f Date: %s Category: %s",
+ description, amount, date, category);
+ }
+
+ /**
+ * Retrieves the description of the transaction.
+ *
+ * @return A string representing the description of the transaction.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Retrieves the amount of the transaction.
+ *
+ * @return A double representing the amount of the transaction.
+ */
+ public double getAmount() {
+ return amount;
+ }
+
+ /**
+ * Retrieves the date of the transaction.
+ *
+ * @return A LocalDate representing the date of the transaction.
+ */
+ public LocalDate getDate() {
+ return date;
+ }
+
+ /**
+ * Retrieves the category of the expense.
+ *
+ * @return The {@code Category} of the expense.
+ */
+ public Category getCategory() {
+ return category;
+ }
+
+
+ /**
+ * Updates the category of the expense.
+ *
+ * @param changeCategory The new category for the expense.
+ */
+ public void editCategory(Category changeCategory) {
+ this.category = changeCategory;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/expense/ExpenseManager.java b/src/main/java/seedu/budgetbuddy/transaction/expense/ExpenseManager.java
new file mode 100644
index 0000000000..f4da3589dd
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/expense/ExpenseManager.java
@@ -0,0 +1,383 @@
+package seedu.budgetbuddy.transaction.expense;
+
+import seedu.budgetbuddy.Ui;
+import seedu.budgetbuddy.graphs.ExpensesCategoryPieChart;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.RemainingBudgetManager;
+import seedu.budgetbuddy.util.LoggerSetup;
+import seedu.budgetbuddy.graphs.ExpensesOverMonthGraph;
+
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+
+import java.time.LocalDate;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * Manages a list of expenses, providing functionalities to add, delete,
+ * and list expenses, as well as tracking the total number of expenses.
+ */
+public class ExpenseManager {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+ private static int numberOfExpenses = 0;
+ private static ArrayList expenses = new ArrayList<>();
+ private static ExpenseManager instance;
+
+ /**
+ * Construct a ExpenseManager of array content expenses
+ *
+ * @param expenses is the content to be instantiated
+ */
+ public ExpenseManager(ArrayList expenses, int numberOfExpenses) {
+ assert numberOfExpenses >= 0 : "numberOfExpenses should be greater than or equal to 0";
+ ExpenseManager.expenses = expenses;
+ ExpenseManager.numberOfExpenses = numberOfExpenses;
+ }
+
+ /**
+ * Construct a ExpenseManager of array content expenses
+ */
+ public ExpenseManager() {
+
+ }
+
+ /**
+ * Adds a new expense to the manager.
+ *
+ * @param expense The expense to be added.
+ */
+ public static void addExpense(Expense expense) {
+ expenses.add(expense);
+ numberOfExpenses++;
+ String budgetRemaining = new RemainingBudgetManager().getRemainingBudgets(expense.getDate()
+ , expense.getCategory());
+ String result = "The following expense transaction has been added:\n"
+ + expense + '\n'
+ + "You have " + numberOfExpenses + " expense transaction(s) in total.\n" + budgetRemaining;
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * Load a new expense from storage to the manager.
+ *
+ * @param expense The expense to be added.
+ */
+ public static void loadExpense(Expense expense) {
+ expenses.add(expense);
+ numberOfExpenses++;
+ }
+
+ /**
+ * Deletes an expense from the manager at the specified index.
+ *
+ * @param index The index of the expense to be deleted.
+ */
+ public static void deleteExpense(int index) {
+ numberOfExpenses--;
+ String result = "The following expense transaction has been deleted:\n"
+ + expenses.get(index) + '\n'
+ + "You have " + numberOfExpenses + " expense transaction(s) in total.\n";
+ LocalDate date = expenses.get(index).getDate();
+ Category category = expenses.get(index).getCategory();
+ expenses.remove(index);
+ String budgetRemaining = new RemainingBudgetManager().getRemainingBudgets(date, category);
+ result += budgetRemaining;
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * Returns the current number of expenses.
+ *
+ * @return The total number of expenses.
+ */
+ public static int getNumberOfExpenses() {
+ return numberOfExpenses;
+ }
+
+ /**
+ * Calculates the total expenses for a specified month.
+ *
+ * @param month The month to calculate expenses for.
+ * @return The total expenses for the month; returns 0.0 if no expense is found.
+ */
+ public static double getMonthlyExpense(YearMonth month) {
+ double sum = 0;
+ for (Expense expense : expenses) {
+ if (month.equals(getYearMonthFromDate(expense.getDate()))) {
+ sum += expense.getAmount();
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Lists all the expenses managed by the manager.
+ * Displays each expense with its corresponding number.
+ */
+ public static void listExpenses() {
+ if (numberOfExpenses == 0) {
+ Ui.displayToUser("There are currently no expense entries. Try again after adding an expense entry.");
+ return;
+ }
+ String result = "";
+ int counter = 0;
+ double sumOfExpenses = 0;
+ for (Expense expense : expenses) {
+ counter++;
+ result += counter + ". " + expense.toString() + "\n";
+ sumOfExpenses += expense.getAmount();
+ }
+ result += "There are " + counter + " expense(s) in total" +
+ ", with a sum of $" + String.format("%.2f", sumOfExpenses) + ".";
+ LOGGER.log(Level.INFO, "Listing {0} expenses", numberOfExpenses);
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * Display all expense that matches with month & category field
+ * Displays each expense with its corresponding number.
+ * @param category
+ * @param month
+ * @return result String to be displayed to user
+ */
+ public static String listExpensesWithCategoryAndDate(Category category, YearMonth month) {
+ assert category != null : "category cannot be null";
+ assert month != null : "month cannot be null";
+ String result = "";
+ int counter = 0;
+ double amount = 0;
+ String monthInString = month.format(DateTimeFormatter.ofPattern("MMMM yyyy"));
+ for (Expense expense : expenses) {
+ if (category.equals(expense.getCategory()) && month.equals(getYearMonthFromDate(expense.getDate()))) {
+ counter++;
+ result += counter + ". " + expense.toString() + "\n";
+ amount += expense.getAmount();
+ }
+ }
+ if (result.equals("")) {
+ result = getEmptyDisplayMessage();
+ } else {
+ result += "Your total expense(s) for " + category + " in "
+ + monthInString + " are $" + String.format("%.2f", amount);
+ }
+ return result;
+ }
+
+ /**
+ * Display all expense that matches with category field
+ * Displays each expense with its corresponding number.
+ * @param category
+ * @return result String to be displayed to user
+ */
+ public static String listExpensesWithCategory(Category category) {
+ assert category != null : "category cannot be null";
+ String result = "";
+ int counter = 0;
+ double amount = 0;
+ for (Expense expense : expenses) {
+ if (category.equals(expense.getCategory())) {
+ counter++;
+ result += counter + ". " + expense.toString() + "\n";
+ amount += expense.getAmount();
+ }
+ }
+ if (result.equals("")) {
+ result = getEmptyDisplayMessage();
+ } else {
+ result += "Your total expense(s) for " + category + " are $" + String.format("%.2f", amount);
+ }
+ return result;
+ }
+
+ /**
+ * Display all expense that matches with month field
+ * Displays each expense with its corresponding number.
+ * @param month
+ * @return result String to be displayed to user
+ */
+ public static String listExpensesWithDate(YearMonth month) {
+ assert month != null : "month cannot be null";
+ String result = "";
+ int counter = 0;
+ double amountInMonth = 0;
+ String monthInString = month.format(DateTimeFormatter.ofPattern("MMMM yyyy"));
+ for (Expense expense : expenses) {
+ if (month.equals(getYearMonthFromDate(expense.getDate()))) {
+ counter++;
+ result += counter + ". " + expense.toString() + "\n";
+ amountInMonth += expense.getAmount();
+ }
+ }
+ if (result.equals("")) {
+ result = getEmptyDisplayMessage();
+ } else {
+ result += "Your total expense(s) for " + monthInString + " are $" + String.format("%.2f", amountInMonth);
+ }
+ return result;
+ }
+
+ /**
+ * Filters expenses with descriptions that contain the keyword(s) provided by user.
+ * @param keyword
+ * @return result String displayed to user
+ */
+ public static String searchExpenses(String keyword){
+ assert keyword != null: "Keyword should not be null";
+ String result = "";
+ if (keyword.equals("")) {
+ result = getEmptyDisplayMessage();
+ return result;
+ }
+ int counter = 1;
+ for (Expense expense : expenses) {
+ if (expense.getDescription().toLowerCase().contains(keyword.toLowerCase())){
+ result += counter + ". " + expense.toString() + "\n";
+ counter++;
+ }
+ }
+ if (result.equals("")) {
+ result = getEmptyDisplayMessage();
+ }
+ return result;
+ }
+
+ /**
+ * Displays a graph of expenses over the given year.
+ *
+ * @param year The year for which the expenses graph is to be displayed.
+ */
+ public static void displayExpensesOverMonthGraph(int year) {
+ ArrayList expensesOverMonthArray = getExpenses();
+ Map monthlyExpensesMap = ExpensesOverMonthGraph.monthMapBuilder(expensesOverMonthArray);
+ ExpensesOverMonthGraph.chartPrinter(monthlyExpensesMap, year);
+ }
+
+ /**
+ * Breaks down all expenses of the user by category.
+ * @return String that displays total expenses of the user and the amount and percentage of total expenses spent
+ * per category.
+ */
+ public static String breakdownExpensesByCategory() {
+ String result = "";
+ double totalExpensesFood = 0;
+ double totalExpensesOthers = 0;
+ double totalExpensesTransport = 0;
+ double totalExpensesEntertainment = 0;
+ double totalExpensesUtilities = 0;
+ double totalExpensesEducation = 0;
+ for (Expense expense : expenses) {
+ switch (expense.getCategory()) {
+ case FOOD -> totalExpensesFood += expense.getAmount();
+ case EDUCATION -> totalExpensesEducation += expense.getAmount();
+ case TRANSPORT -> totalExpensesTransport += expense.getAmount();
+ case UTILITIES -> totalExpensesUtilities += expense.getAmount();
+ case ENTERTAINMENT -> totalExpensesEntertainment += expense.getAmount();
+ case OTHERS -> totalExpensesOthers += expense.getAmount();
+ default -> LOGGER.warning("Invalid category type detected.");
+ }
+ }
+ assert totalExpensesFood >= 0 : "Total expense for food cannot be negative";
+ assert totalExpensesOthers >= 0 : "Total expense for others cannot be negative";
+ assert totalExpensesTransport >= 0 : "Total expense for transport cannot be negative";
+ assert totalExpensesEntertainment >= 0 : "Total expense for entertainment cannot be negative";
+ assert totalExpensesUtilities >= 0 : "Total expense for utilities cannot be negative";
+ assert totalExpensesEducation >= 0 : "Total expense for education cannot be negative";
+ double totalExpenses = totalExpensesEducation + totalExpensesFood + totalExpensesEntertainment +
+ totalExpensesTransport + totalExpensesUtilities + totalExpensesOthers;
+ if (totalExpenses == 0) {
+ result += "Total expenses: 0. You have not indicated any expense yet.";
+ } else {
+ result = "Total expenses: " + totalExpenses + "\n" + "Food: " + totalExpensesFood + "(" +
+ String.format("%.2f", totalExpensesFood / totalExpenses * 100) + "%)\n" + "Transport: " +
+ totalExpensesTransport + "(" + String.format("%.2f", totalExpensesTransport / totalExpenses * 100) +
+ "%)\n" + "Utilities: " + totalExpensesUtilities + "(" +
+ String.format("%.2f", totalExpensesUtilities / totalExpenses * 100) + "%)\n" + "Entertainment: " +
+ totalExpensesEntertainment + "(" +
+ String.format("%.2f", totalExpensesEntertainment / totalExpenses * 100) + "%)\n" + "Education: " +
+ totalExpensesEducation + "(" +
+ String.format("%.2f", totalExpensesEducation / totalExpenses * 100) + "%)\n" + "Others: " +
+ totalExpensesOthers + "(" + String.format("%.2f", totalExpensesOthers / totalExpenses * 100) +
+ "%)\n";
+ }
+ return result;
+ }
+
+ /**
+ * Displays the expenses for a specific month on a PieChart divided by different categories
+ *
+ * @param yearMonth The YearMonth object representing the month for which the total expenses are to be displayed.
+ */
+ public static void displayExpensesForMonthWithCategoriesGraph(YearMonth yearMonth) {
+ Map expensesByCategoryMap = ExpensesCategoryPieChart.expensesByCategoryMapBuilder(yearMonth);
+ ExpensesCategoryPieChart.displayExpenseByCategoryPieChart(yearMonth, expensesByCategoryMap);
+ }
+
+ /**
+ * Extract YearMonth value from date
+ * @param date
+ * @return
+ */
+ public static YearMonth getYearMonthFromDate(LocalDate date) {
+ assert date != null: "Date should not be null";
+ return YearMonth.from(date);
+ }
+
+ /**
+ * Generates a custom empty Display Expense message
+ * @return custom display Expense message
+ */
+ public static String getEmptyDisplayMessage() {
+ return "No expense entry with given parameters found, try again with a different parameter.";
+ }
+
+ /**
+ * A get-function to obtain the information in the current Expense List.
+ *
+ * @return return the expense ArrayList
+ */
+ public static ArrayList getExpenses() {
+ return expenses;
+ }
+
+ public static Expense getExpenseByIndex(int index) {
+ if (index > numberOfExpenses) {
+ return null;
+ }
+ return expenses.get(index-1);
+ }
+
+ /**
+ * Resets the state of the ExpenseManager by clearing all expenses and
+ * setting the total number of expenses to zero.
+ *
+ * This method is used for unit testing, ensuring that each test
+ * starts with a clean slate and does not retain any state from
+ * previous tests.
+ *
+ */
+ public static void reset() {
+ numberOfExpenses = 0;
+ expenses.clear();
+ }
+
+ /**
+ * Provides a global point of access to the single instance of the ExpenseManager class.
+ *
+ * This method ensures that only one instance of ExpenseManager exists throughout the application's lifetime.
+ * If an instance does not exist, it creates a new one. Otherwise, it returns the existing instance.
+ * This method is used for unit testing for Storage to ensure that stored values are correct.
+ *
+ *
+ * @return The single instance of the ExpenseManager class.
+ */
+ public static ExpenseManager getInstance() {
+ if (instance == null) {
+ instance = new ExpenseManager();
+ }
+ return instance;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/income/Income.java b/src/main/java/seedu/budgetbuddy/transaction/income/Income.java
new file mode 100644
index 0000000000..92b946fab1
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/income/Income.java
@@ -0,0 +1,49 @@
+package seedu.budgetbuddy.transaction.income;
+
+import seedu.budgetbuddy.transaction.Transaction;
+
+import java.time.LocalDate;
+
+/**
+ * Represents an income transaction with a description, amount, and date.
+ * This class extends the Transaction class to manage income-specific details.
+ */
+public class Income extends Transaction {
+ /**
+ * Creates an Income object with the specified description, amount, and date.
+ *
+ * @param description The description of the income.
+ * @param amount The amount of income.
+ * @param date The date of the income transaction.
+ */
+ public Income(String description, double amount, LocalDate date) {
+ super(description, amount, date);
+ }
+
+ /**
+ * Retrieves the description of the transaction.
+ *
+ * @return A string representing the description of the transaction.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Retrieves the amount involved in the transaction.
+ *
+ * @return A double representing the amount of the transaction.
+ */
+ public double getAmount() {
+ return amount;
+ }
+
+ /**
+ * Retrieves the date of the transaction.
+ *
+ * @return A LocalDate representing the date when the transaction occurred.
+ */
+ public LocalDate getDate() {
+ return date;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/income/IncomeManager.java b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeManager.java
new file mode 100644
index 0000000000..46056c998d
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeManager.java
@@ -0,0 +1,227 @@
+package seedu.budgetbuddy.transaction.income;
+
+import seedu.budgetbuddy.Ui;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Manages a collection of income transactions.
+ * Provides functionality to add, delete, and list incomes.
+ */
+public class IncomeManager {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+ private static int numberOfIncomes = 0;
+ private static ArrayList incomes = new ArrayList<>();
+ private static IncomeManager instance;
+
+ /**
+ * Construct a IncomeManager of array content incomes
+ *
+ * @param incomes is the content to be instantiated
+ */
+ public IncomeManager(ArrayList incomes, int numberOfIncomes) {
+ assert numberOfIncomes >= 0 : "numberOfIncomes should be greater than 0";
+ IncomeManager.incomes = incomes;
+ IncomeManager.numberOfIncomes = numberOfIncomes;
+ }
+
+ /**
+ * Construct a IncomeManager of array content incomes
+ */
+ public IncomeManager() {
+
+ }
+
+ /**
+ * Adds a new income to the manager.
+ *
+ * @param income The income to be added.
+ */
+ public static void addIncome(Income income) {
+ incomes.add(income);
+ numberOfIncomes++;
+ String result = "The following income transaction has been added:\n"
+ + income + '\n'
+ + "You have " + numberOfIncomes + " income transaction(s) in total.";
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * Load a new income from storage to the manager.
+ *
+ * @param income The income to be added.
+ */
+ public static void loadIncome(Income income) {
+ incomes.add(income);
+ numberOfIncomes++;
+ }
+
+ /**
+ * Deletes an income from the manager at the specified index.
+ *
+ * @param index The index of the income to be deleted.
+ */
+ public static void deleteIncome(int index) {
+ numberOfIncomes--;
+ String result = "The following income transaction has been deleted:\n"
+ + incomes.get(index) + '\n'
+ + "You have " + numberOfIncomes + " income transaction(s) in total.";
+ incomes.remove(index);
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * Returns the current number of incomes.
+ *
+ * @return The total number of incomes.
+ */
+ public static int getNumberOfIncomes() {
+ return numberOfIncomes;
+ }
+
+ /**
+ * Calculates the total income for a specified month.
+ *
+ * @param month The month to calculate income for.
+ * @return The total income for the month; returns 0.0 if no income is found.
+ */
+ public static double getMonthlyIncome(YearMonth month) {
+ double sum = 0;
+ for (Income income : incomes) {
+ if(month.equals(getYearMonthFromDate(income.getDate()))) {
+ sum += income.getAmount();
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Lists all the incomes managed by the manager.
+ * Displays each income with its corresponding number.
+ */
+ public static void listIncomes() {
+ if (numberOfIncomes == 0) {
+ Ui.displayToUser("There are currently no income entries. Try again after adding an income entry.");
+ return;
+ }
+ String result = "";
+ int counter = 0;
+ double sumOfIncome = 0;
+ for (Income income : incomes) {
+ counter++;
+ result += counter + ". " + income.toString() + "\n";
+ sumOfIncome += income.getAmount();
+ }
+ result += "There are " + counter + " income(s) in total" +
+ ", with a sum of $" + String.format("%.2f", sumOfIncome) + ".";
+ LOGGER.log(Level.INFO, "Listing {0} incomes", numberOfIncomes);
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * List all income that matches with month field that are managed by the manager.
+ * Displays each income with its corresponding number.
+ * @param month
+ */
+ public static void listIncomeWithMonth(YearMonth month) {
+ String result = "";
+ int counter = 0;
+ double filteredIncomeSum = 0;
+ String monthInString;
+
+ for (Income income : incomes) {
+ if (month.equals(getYearMonthFromDate(income.getDate()))) {
+ counter++;
+ result += counter + ". " + income.toString() + "\n";
+ filteredIncomeSum += income.getAmount();
+ }
+ }
+ if (result.equals("")) {
+ result = getEmptyDisplayMessage();
+ Ui.displayToUser(result);
+ return;
+ }
+ monthInString = month.format(DateTimeFormatter.ofPattern("MMMM yyyy"));
+ result += "There are " + counter + " income(s) in total for " + monthInString +
+ ", with a sum of $" + filteredIncomeSum + ".";
+ LOGGER.log(Level.INFO, "Listing {0} incomes", numberOfIncomes);
+ Ui.displayToUser(result);
+ }
+
+ /**
+ * Gets an Income object based on user input index.
+ * Searches IncomeList for Income object with desired index and returns reference to object.
+ *
+ * @param index user input index to be extracted from IncomeList
+ * @return an income object for future reference
+ */
+ public static Income getIncomeByIndex(int index) {
+ if (index > numberOfIncomes) {
+ return null;
+ }
+ return incomes.get(index-1);
+ }
+
+ /**
+ * Extract YearMonth value from date
+ * @param date
+ * @return
+ */
+ public static YearMonth getYearMonthFromDate(LocalDate date) {
+ return YearMonth.from(date);
+ }
+
+ /**
+ * Generates a custom empty Display income message
+ * @return custom display income message
+ */
+ public static String getEmptyDisplayMessage() {
+ return "No income entry with given month found, try again with a different month.";
+ }
+
+ /**
+ * A get-function to obtain the information in the current Income List.
+ *
+ * @return return the income ArrayList
+ */
+ public static ArrayList getIncomes() {
+ return incomes;
+ }
+
+ /**
+ * Resets the state of the IncomeManager by clearing all incomes and
+ * setting the total number of incomes to zero.
+ *
+ * This method is used for unit testing, ensuring that each test
+ * starts with a clean slate and does not retain any state from
+ * previous tests.
+ *
+ */
+ public static void reset() {
+ numberOfIncomes = 0;
+ incomes.clear();
+ }
+
+ /**
+ * Provides a global point of access to the single instance of the IncomeManager class.
+ *
+ * This method ensures that only one instance of IncomeManager exists throughout the application's lifetime.
+ * If an instance does not exist, it creates a new one. Otherwise, it returns the existing instance.
+ * This method is used for unit testing for Storage to ensure that stored values are correct.
+ *
+ *
+ * @return The single instance of the IncomeManager class.
+ */
+ public static IncomeManager getInstance() {
+ if (instance == null) {
+ instance = new IncomeManager(incomes, numberOfIncomes); // Pass static fields to constructor
+ }
+ return instance;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/income/IncomeSpent.java b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeSpent.java
new file mode 100644
index 0000000000..e7b419a9c2
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeSpent.java
@@ -0,0 +1,46 @@
+package seedu.budgetbuddy.transaction.income;
+
+import seedu.budgetbuddy.Ui;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+
+import java.time.YearMonth;
+
+/**
+ * The IncomeSpent class provides functionality to calculate and display
+ * the percentage of income spent for a specified month.
+ */
+public class IncomeSpent {
+
+ /**
+ * Calculates the percentage of income spent for a specified month.
+ *
+ * @param month The month for which the spending percentage is calculated.
+ * @return The percentage of income spent for the month.
+ */
+ public static double calculateSpentPercentage(YearMonth month) {
+ double monthlyIncome = IncomeManager.getMonthlyIncome(month);
+ double monthlyExpense = ExpenseManager.getMonthlyExpense(month);
+ return (monthlyExpense / monthlyIncome) * 100;
+ }
+
+ /**
+ * Returns a formatted string representing the percentage of income spent
+ * for a specified month.
+ *
+ * @param month The month to format the spending percentage for.
+ * @return A string displaying the percentage of income spent for the month.
+ */
+ public static String toString(YearMonth month) {
+ String formattedPercentage = String.format("%.1f", calculateSpentPercentage(month));
+ return String.format("Percentage of income spent for %s: %s%%", month, formattedPercentage);
+ }
+
+ /**
+ * Displays the percentage of income spent for a specified month to the user.
+ *
+ * @param month The month for which to display the spending percentage.
+ */
+ public static void displaySpentPercentage(YearMonth month) {
+ Ui.displayToUser(toString(month));
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/saving/Saving.java b/src/main/java/seedu/budgetbuddy/transaction/saving/Saving.java
new file mode 100644
index 0000000000..73d62f13cd
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/saving/Saving.java
@@ -0,0 +1,54 @@
+package seedu.budgetbuddy.transaction.saving;
+
+import java.time.YearMonth;
+
+/**
+ * Represents a monthly saving with an amount and a YearMonth attribute.
+ * Monthly savings is calculated based on income - expense.
+ */
+public class Saving {
+ private YearMonth yearMonth;
+ private double savings;
+
+ /**
+ * Creates a Saving object with the specified YearMonth and savings.
+ * @param yearMonth YearMonth of the saving
+ * @param savings amount saved that month
+ */
+ public Saving(YearMonth yearMonth, double savings){
+ this.yearMonth = yearMonth;
+ this.savings = savings;
+ }
+
+ /**
+ * Retrieves YearMonth of the object.
+ * @return A YearMonth representing the month of the saving
+ */
+ public YearMonth getYearMonth() {
+ return yearMonth;
+ }
+
+ /**
+ * Retrieves the amount of savings for the month.
+ * @return A double representing the amount saved that month
+ */
+ public double getSavings() {
+ return savings;
+ }
+
+ /**
+ * Adds an earned income specified by the user to the savings of that month.
+ * @param income Earned income to be added.
+ */
+ public void addIncome(double income){
+ savings += income;
+ }
+
+ /**
+ * Deducts an expense specified by the user from the savings of that month.
+ * @param expense Expense to be deducted from savings.
+ */
+ public void deductExpense(double expense){
+ savings -= expense;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/transaction/saving/SavingsManager.java b/src/main/java/seedu/budgetbuddy/transaction/saving/SavingsManager.java
new file mode 100644
index 0000000000..371220695b
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/transaction/saving/SavingsManager.java
@@ -0,0 +1,113 @@
+package seedu.budgetbuddy.transaction.saving;
+
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.YearMonth;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.logging.Logger;
+
+/**
+ * Displays the savings of the user.
+ * The user can specify if they wish to see their total savings by month or in total.
+ */
+public class SavingsManager {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Displays the total savings of the user based on all their incomes and expenses.
+ * Total Savings = Total Income - Total Expenses
+ */
+ public static String displayTotalSavings(){
+ double savings = 0;
+ double totalIncome = 0;
+ double totalExpense = 0;
+
+ String result = "";
+ if (IncomeManager.getIncomes().size() > 0){
+ for (Income income: IncomeManager.getIncomes()){
+ totalIncome += income.getAmount();
+ }
+ }
+
+ if (ExpenseManager.getExpenses().size() > 0){
+ for (Expense expense: ExpenseManager.getExpenses()){
+ totalExpense += expense.getAmount();
+ }
+ }
+ savings = totalIncome - totalExpense;
+
+ result += "Total Savings: " + String.format("%.2f", savings);
+ result += "\n" + "Total Income: " + String.format("%.2f", totalIncome);
+ result += "\n" + "Total Expense: " + String.format("%.2f", totalExpense);
+
+ LOGGER.info("Listing total savings");
+ return result;
+ }
+
+ /**
+ * Displays the savings by month in chronological order based on their monthly expenses and incomes.
+ * Monthly savings = Monthly Income - Monthly Expense
+ */
+ public static String displayTotalSavingsByMonth(){
+ String result = "";
+ ArrayList listYearMonths = new ArrayList<>();
+ ArrayList savings = new ArrayList<>();
+ if (IncomeManager.getIncomes().size() <= 0 && ExpenseManager.getExpenses().size() <= 0){
+ result = "Total savings: 0.00";
+ return result;
+ }
+
+ for (Income income: IncomeManager.getIncomes()){
+ YearMonth incomeYearMonth = IncomeManager.getYearMonthFromDate(income.getDate());
+ int indexOfSaving = findYearMonthFromArray(listYearMonths, incomeYearMonth);
+ if (indexOfSaving == -1){
+ listYearMonths.add(incomeYearMonth);
+ savings.add(new Saving(incomeYearMonth, income.getAmount()));
+ } else{
+ savings.get(indexOfSaving).addIncome(income.getAmount());
+ }
+ }
+
+
+ for (Expense expense : ExpenseManager.getExpenses()) {
+ YearMonth expenseYearMonth = ExpenseManager.getYearMonthFromDate(expense.getDate());
+ int indexOfSaving = findYearMonthFromArray(listYearMonths, expenseYearMonth);
+ if (indexOfSaving == -1) {
+ listYearMonths.add(expenseYearMonth);
+ savings.add(new Saving(expenseYearMonth, -expense.getAmount()));
+ } else {
+ savings.get(indexOfSaving).deductExpense(expense.getAmount());
+ }
+ }
+
+
+ savings.sort(Comparator.comparing(Saving::getYearMonth));
+ for (Saving saving: savings){
+ result += "Savings in " + saving.getYearMonth() +": " + String.format("%.2f", saving.getSavings());
+ if (saving != savings.get(savings.size()-1)){
+ result += "\n";
+ }
+ }
+ LOGGER.info("Displaying savings by month");
+ return result;
+ }
+
+ /**
+ * Helper method to get the index of a specified YearMonth in the ArrayList to update savings.
+ * @param listYearMonth ArrayList containing all YearMonths of Saving objects.
+ * @param yearMonth Specific YearMonth to find in the ArrayList
+ * @return index of the YearMonth in the ArrayList, otherwise -1.
+ */
+ private static int findYearMonthFromArray(ArrayList listYearMonth, YearMonth yearMonth){
+ if (!listYearMonth.contains(yearMonth)){
+ return -1;
+ } else{
+ return listYearMonth.indexOf(yearMonth);
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/util/LoggerSetup.java b/src/main/java/seedu/budgetbuddy/util/LoggerSetup.java
new file mode 100644
index 0000000000..0e3522acd7
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/util/LoggerSetup.java
@@ -0,0 +1,35 @@
+package seedu.budgetbuddy.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.FileHandler;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+public class LoggerSetup {
+ private static final Logger logger = Logger.getLogger(LoggerSetup.class.getName());
+
+ static {
+ try {
+ //Ensure the Log Directory Exist
+ File logDirectory = new File("logs");
+ if (!logDirectory.exists()) {
+ logDirectory.mkdir();
+ }
+
+ // Create a file handler that writes logs messages to logs/app.log
+ FileHandler fileHandler = new FileHandler("logs/app.log", true);
+ fileHandler.setFormatter(new SimpleFormatter()); // Use a simple text format for the logs
+
+ logger.addHandler(fileHandler);
+ logger.setUseParentHandlers(false);
+
+ } catch (IOException e) {
+ logger.severe("Failed to initialize log file handler: " + e.getMessage());
+ }
+ }
+
+ public static Logger getLogger() {
+ return logger;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/AmountValidator.java b/src/main/java/seedu/budgetbuddy/validators/AmountValidator.java
new file mode 100644
index 0000000000..a0c036ac33
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/AmountValidator.java
@@ -0,0 +1,29 @@
+package seedu.budgetbuddy.validators;
+
+/**
+ * Validates and converts amount from String to double.
+ */
+public class AmountValidator {
+ /**
+ * Converts amount from String to double.
+ *
+ * @param part The String containing the amount.
+ * @return The parsed amount or -1 if invalid.
+ */
+ public static double validateAmount(String part) {
+ for (int i = 2; i < part.length(); i++) {
+ if (!(Character.isDigit(part.charAt(i)) || part.charAt(i) == '.'|| part.charAt(i) == '-')) {
+ return -1;
+ }
+ }
+ try {
+ double amount = Double.parseDouble(part.substring(2));
+ if (amount * 100 % 1 != 0) {
+ return -1;
+ }
+ return amount;
+ } catch (NumberFormatException e) {
+ return -1; // Indicates invalid amount
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/CategoryValidator.java b/src/main/java/seedu/budgetbuddy/validators/CategoryValidator.java
new file mode 100644
index 0000000000..2461c0da4e
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/CategoryValidator.java
@@ -0,0 +1,40 @@
+package seedu.budgetbuddy.validators;
+
+import seedu.budgetbuddy.transaction.Category;
+
+/**
+ * Validates category input for transactions.
+ */
+public class CategoryValidator {
+
+ /**
+ * Parses the category from the command part.
+ *
+ * @param part The command part containing the category.
+ * @return The parsed category or OTHERS if invalid.
+ */
+ public static Category validateCategory(String part) {
+ String categoryStr = part.substring(2).toUpperCase();
+ try {
+ return Category.valueOf(categoryStr);
+ } catch (IllegalArgumentException e) {
+ return Category.OTHERS; // Default category if invalid
+ }
+ }
+
+ /**
+ * Parses the category from user inputs. Used for any interaction with data extraction from existing list
+ * (example: edit operations)
+ *
+ * @param part The command part containing the category.
+ * @return The parsed category or OTHERS if invalid.
+ */
+ public static Category validateSearchCategory(String part) {
+ String categoryStr = part.substring(2).toUpperCase();
+ try {
+ return Category.valueOf(categoryStr);
+ } catch (IllegalArgumentException e) {
+ return null; // Default category if invalid
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/DateValidator.java b/src/main/java/seedu/budgetbuddy/validators/DateValidator.java
new file mode 100644
index 0000000000..d8090d4214
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/DateValidator.java
@@ -0,0 +1,41 @@
+package seedu.budgetbuddy.validators;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
+
+/**
+ * Validates and converts the date given.
+ */
+public class DateValidator {
+ /**
+ * Validates the date given.
+ *
+ * @param part The part containing the date.
+ * @return The parsed date or null if invalid.
+ */
+ public static LocalDate validateDate(String part) {
+ try {
+ return LocalDate.parse(part.substring(2),
+ DateTimeFormatter.ofPattern("d/M/uuuu").withResolverStyle(ResolverStyle.STRICT));
+ } catch (DateTimeParseException e) {
+ return null; // Indicates invalid date
+ }
+ }
+
+ /**
+ * Parses the date from the command part.
+ *
+ * @param part The command part containing the date.
+ * @return The parsed YearMonth or null if invalid.
+ */
+ public static YearMonth validateYearMonth(String part) {
+ try {
+ return YearMonth.parse(part.substring(2), DateTimeFormatter.ofPattern("M/yyyy"));
+ } catch (DateTimeParseException e) {
+ return null; // Indicates invalid date
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/IndexValidator.java b/src/main/java/seedu/budgetbuddy/validators/IndexValidator.java
new file mode 100644
index 0000000000..912f33bae5
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/IndexValidator.java
@@ -0,0 +1,47 @@
+package seedu.budgetbuddy.validators;
+
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+/**
+ * Validates the index input.
+ */
+public class IndexValidator {
+ /**
+ * Validates the given expense index.
+ *
+ * @param value The input string representing the expense index.
+ * @return The validated index if it is a valid number within the range, or -1 if invalid.
+ */
+ public static int validateExpenseIndex(String value) {
+ int index;
+ try {
+ index = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ if (index >= 1 && index <= ExpenseManager.getNumberOfExpenses()) {
+ return index;
+ }
+ return -1;
+ }
+
+ /**
+ * Validates the given income index.
+ *
+ * @param value The input string representing the income index.
+ * @return The validated index if it is a valid number within the range, or -1 if invalid.
+ */
+ public static int validateIncomeIndex(String value) {
+ int index;
+ try {
+ index = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ if (index >= 1 && index <= IncomeManager.getNumberOfIncomes()) {
+ return index;
+ }
+ return -1;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/budget/AddBudgetValidator.java b/src/main/java/seedu/budgetbuddy/validators/budget/AddBudgetValidator.java
new file mode 100644
index 0000000000..52a10d7b4c
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/budget/AddBudgetValidator.java
@@ -0,0 +1,67 @@
+package seedu.budgetbuddy.validators.budget;
+
+import seedu.budgetbuddy.commands.budget.AddBudgetCommand;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.YearMonth;
+import java.util.logging.Logger;
+
+import static seedu.budgetbuddy.validators.AmountValidator.validateAmount;
+import static seedu.budgetbuddy.validators.CategoryValidator.validateCategory;
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+/**
+ * Validates commands for adding budgets.
+ */
+public class AddBudgetValidator {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ public static Command processCommand(String command) throws BudgetBuddyException {
+ assert command != null : "Command cannot be null";
+
+ if (command.equals("add budget")) {
+ LOGGER.warning("Attempted to add budget without amount.");
+ throw new BudgetBuddyException("No amount provided.");
+ }
+
+ String trimmedCommand = command.substring("add budget ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ // Initialize default values
+ double amount = 0; // invalid amount initially
+ YearMonth date = YearMonth.now();
+ Category category = Category.OTHERS;
+
+ // Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("a/")) {
+ amount = validateAmount(part);
+ if (amount == -1) {
+ throw new BudgetBuddyException("Invalid amount format.");
+ }
+ } else if (part.startsWith("m/")) {
+ date = validateYearMonth(part);
+ } else if (part.startsWith("c/")) {
+ category = validateCategory(part);
+ } else {
+ throw new BudgetBuddyException("Unrecognised input: '" + part + "'. Please check the command format.");
+ }
+ }
+
+ // Validate amount
+ if (amount <= 0) {
+ throw new BudgetBuddyException("Invalid amount: " + amount + ". Amount must be a positive value.");
+ }
+
+ // Validate date
+ if (date == null) {
+ throw new BudgetBuddyException("Invalid date format. Use m/MM/yyyy.");
+ }
+
+ // All validations passed, return the command
+ return new AddBudgetCommand(amount, date, category);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidator.java b/src/main/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidator.java
new file mode 100644
index 0000000000..5a79ea5c24
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidator.java
@@ -0,0 +1,78 @@
+package seedu.budgetbuddy.validators.budget;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.budget.DeductBudgetCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.YearMonth;
+import java.util.logging.Logger;
+
+import static seedu.budgetbuddy.validators.AmountValidator.validateAmount;
+import static seedu.budgetbuddy.validators.CategoryValidator.validateCategory;
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+/**
+ * Validates commands for deducting budgets.
+ */
+public class DeductBudgetValidator {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ public static Command processCommand(String command) throws BudgetBuddyException {
+ assert command != null : "Command cannot be null";
+
+ if (command.equals("deduct budget")) {
+ LOGGER.warning("Attempted to deduct budget without amount.");
+ throw new BudgetBuddyException("No amount provided.");
+ }
+
+ String trimmedCommand = command.substring("deduct budget ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ // Initialize default values
+ double amount = 0; // invalid amount initially
+ YearMonth date = YearMonth.now();
+ Category category = Category.OTHERS;
+
+ // Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("a/")) {
+ amount = validateAmount(part);
+ if (amount == -1) {
+ throw new BudgetBuddyException("Invalid amount format.");
+ }
+ } else if (part.startsWith("m/")) {
+ date = validateYearMonth(part);
+ } else if (part.startsWith("c/")) {
+ category = validateCategory(part);
+ } else {
+ throw new BudgetBuddyException("Unrecognised input: '" + part + "'. Please check the command format.");
+ }
+ }
+
+ // Validate amount
+ if (amount <= 0) {
+ throw new BudgetBuddyException("Invalid amount: " + amount + ". Must be a positive value.");
+ }
+
+ // Validate date
+ if (date == null) {
+ throw new BudgetBuddyException("Invalid date format. Use m/MM/yyyy.");
+ }
+
+ // Check if budget exists for the date
+ if (BudgetManager.getBudget(date) == null) {
+ throw new BudgetBuddyException("Budget does not exist for the specified date: " + date);
+ }
+
+ if (BudgetManager.getBudget(date).getCategoryBudgetAmount(category) <= 0) {
+ throw new BudgetBuddyException("Budget's category does not exist for the specified date: " + category
+ + ", " + date + "\n" + "Try deducting from another category.");
+ }
+
+ // All validations passed, return the command
+ return new DeductBudgetCommand(amount, date, category);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/budget/ListBudgetValidator.java b/src/main/java/seedu/budgetbuddy/validators/budget/ListBudgetValidator.java
new file mode 100644
index 0000000000..8f007ed835
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/budget/ListBudgetValidator.java
@@ -0,0 +1,41 @@
+package seedu.budgetbuddy.validators.budget;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.budget.ListBudgetCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+
+import java.time.YearMonth;
+
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+/**
+ * Validates user commands for listing budgets.
+ */
+public class ListBudgetValidator {
+
+ /**
+ * Processes the command string to determine if it is valid for listing budgets.
+ * If valid, it returns a ListBudgetCommand with the parsed date.
+ *
+ * @param command The command string entered by the user.
+ * @return A ListBudgetCommand if valid; otherwise, an IncorrectCommand.
+ */
+ public static Command processCommand(String command) throws BudgetBuddyException {
+ if (command.equals("list budgets")) {
+ return new ListBudgetCommand(null); // No date provided, list all budgets
+ }
+
+ // Check for date in the command
+ String trimmedCommand = command.substring("list budgets ".length()).trim();
+ YearMonth date = null;
+
+ if (!trimmedCommand.isEmpty()) {
+ date = validateYearMonth(trimmedCommand);
+ if (date == null) {
+ throw new BudgetBuddyException("Invalid format. Use 'list budgets [m/MM/yyyy]'.");
+ }
+ }
+
+ return new ListBudgetCommand(date); // Return command with date
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/AddExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/AddExpenseValidator.java
new file mode 100644
index 0000000000..6ec8eaacd1
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/AddExpenseValidator.java
@@ -0,0 +1,82 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.commands.expense.AddExpenseCommand;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.transaction.Category;
+
+import java.time.LocalDate;
+
+import static seedu.budgetbuddy.validators.AmountValidator.validateAmount;
+import static seedu.budgetbuddy.validators.CategoryValidator.validateCategory;
+import static seedu.budgetbuddy.validators.DateValidator.validateDate;
+
+public class AddExpenseValidator {
+
+ /**
+ * Processes the given command to create a new expense entry.
+ *
+ * @param command The command string to be processed.
+ * @return A Command object representing the add expense command
+ * or an IncorrectCommand if validation fails.
+ */
+ public static Command processCommand(String command) {
+ if (command.equals("add expense")) {
+ return new IncorrectCommand("No description provided.");
+ }
+
+ String trimmedCommand = command.substring("add expense ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ // Initialize default values
+ String description = "";
+ double amount = 0; // invalid amount initially
+ LocalDate date = LocalDate.now();
+ Category category = Category.OTHERS;
+ boolean hasAmount = false;
+
+ // Process parts to extract details
+ for (String part : parts) {
+ String prefix = part.length() >= 2 ? part.substring(0, 2) : "";
+ switch (prefix) {
+
+ case "a/":
+ amount = validateAmount(part);
+ hasAmount = true;
+ if (amount <= 0) {
+ return new IncorrectCommand("Amount should be a positive number with up to 2 " +
+ "decimal places.");
+ }
+ break;
+
+ case "d/":
+ date = validateDate(part);
+ if (date == null) {
+ return new IncorrectCommand("Invalid date format. Use d/DD/MM/YYYY.");
+ }
+ break;
+
+ case "c/":
+ category = validateCategory(part);
+ break;
+
+ default:
+ description += part + " ";
+ }
+ }
+
+ description = description.trim();
+ // Validate description
+ if (description.isEmpty()) {
+ return new IncorrectCommand("Description cannot be empty.");
+ }
+
+ // Validate amount
+ if (!hasAmount) {
+ return new IncorrectCommand("Amount not entered.");
+ }
+
+ // All validations passed, return the command
+ return new AddExpenseCommand(description, amount, date, category);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidator.java
new file mode 100644
index 0000000000..2726acab26
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidator.java
@@ -0,0 +1,36 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.expense.DeleteExpenseCommand;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.validators.IndexValidator;
+
+/**
+ * Validates the command for deleting an expense.
+ */
+public class DeleteExpenseValidator {
+
+ /**
+ * Processes the delete expense command and returns the appropriate command object.
+ *
+ * @param command The command string entered by the user.
+ * @return A Command object representing the delete expense action or an IncorrectCommand if the index is invalid.
+ */
+ public static Command processCommand(String command) {
+ String trimmedCommand;
+
+ try {
+ trimmedCommand = command.substring("delete expense ".length());
+ } catch (IndexOutOfBoundsException e) {
+ return new IncorrectCommand("Index not given");
+ }
+
+ int index = IndexValidator.validateExpenseIndex(trimmedCommand);
+
+ if (index == -1) {
+ return new IncorrectCommand("Invalid Index");
+ }
+
+ return new DeleteExpenseCommand(index - 1);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidator.java
new file mode 100644
index 0000000000..ebbd7599e9
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidator.java
@@ -0,0 +1,49 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.DisplayExpensesForMonthWithCategoriesGraphCommand;
+import seedu.budgetbuddy.util.LoggerSetup;
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+import java.time.YearMonth;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Validates and processes commands related to displaying expenses for a specific month with categories.
+ */
+public class DisplayExpensesForMonthWithCategoriesValidator {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Processes the given command to create a command object for displaying expenses by month and category.
+ *
+ * @param command The command string input by the user.
+ * @return A Command object corresponding to the user's input, or an IncorrectCommand if the input is invalid.
+ */
+ public static Command processCommand(String command){
+ LOGGER.log(Level.INFO, "Processing command: " + command);
+ YearMonth yearMonth;
+
+ String trimmedCommand = command.substring("display expenses with categories".length()).trim();
+ if (trimmedCommand.isEmpty()) {
+ LOGGER.log(Level.WARNING, "Trimmed command is empty");
+ return new IncorrectCommand("Please provide a valid month");
+ }
+
+ if (trimmedCommand.startsWith("m/")) {
+ yearMonth = validateYearMonth(trimmedCommand);
+ } else {
+ LOGGER.log(Level.WARNING, "Invalid Command: " + trimmedCommand);
+ return new IncorrectCommand("Unknown command: " + trimmedCommand + " Please use m/MM/YYYY");
+ }
+
+ if (yearMonth == null) {
+ LOGGER.log(Level.WARNING, "Invalid YearMonth: " + yearMonth);
+ return new IncorrectCommand("Invalid Date");
+ } else {
+ return new DisplayExpensesForMonthWithCategoriesGraphCommand(yearMonth);
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidator.java
new file mode 100644
index 0000000000..3a3849369a
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidator.java
@@ -0,0 +1,71 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.DisplayTotalExpensesCommand;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A validator class for processing and validating the "display yearly expenses" command.
+ * It extracts and validates year information from the command.
+ */
+public class DisplayTotalExpensesValidator {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Processes the command input, validates the year, and returns a corresponding command.
+ *
+ * @param command The raw command string entered by the user.
+ * @return A {@link Command} object, either {@link DisplayTotalExpensesCommand} or {@link IncorrectCommand}.
+ */
+ public static Command processCommand(String command) {
+ LOGGER.log(Level.INFO, "Processing command: " + command);
+
+ // Extract the portion of the command after "display yearly expenses"
+ String trimmedCommand = command.substring("display monthly expenses".length()).trim();
+ if (trimmedCommand.isEmpty()) {
+ LOGGER.log(Level.WARNING, "Trimmed command is empty");
+ return new IncorrectCommand("Please provide a year.");
+ }
+
+ // Check if the command starts with "y/" and extract the year argument
+ String yearArg;
+ if (trimmedCommand.startsWith("y/")) {
+ yearArg = trimmedCommand.substring(2); // Remove the "y/" prefix
+ } else {
+ LOGGER.log(Level.WARNING, "Invalid Command: " + trimmedCommand);
+ return new IncorrectCommand("Unknown command '" + trimmedCommand + "'. " +
+ "Expected format: 'y/YYYY'");
+ }
+
+ // Validate and parse the year
+ if (!isValidYear(yearArg)) {
+ LOGGER.log(Level.WARNING, "Invalid year: " + yearArg);
+ return new IncorrectCommand("Invalid or missing year format.");
+ }
+
+ int year = Integer.parseInt(yearArg);
+ return new DisplayTotalExpensesCommand(year);
+ }
+
+ /**
+ * Validates the year input to ensure it consists of four digits and falls within the valid range (1900 to 2100).
+ *
+ * @param year The year string to validate.
+ * @return True if the year is valid, false otherwise.
+ */
+ private static boolean isValidYear(String year) {
+ if (year.length() != 4) {
+ return false;
+ }
+ try {
+ Integer.parseInt(year);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/EditExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/EditExpenseValidator.java
new file mode 100644
index 0000000000..560733bd95
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/EditExpenseValidator.java
@@ -0,0 +1,108 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.Ui;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.EditExpenseCommand;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.LocalDate;
+import java.util.logging.Logger;
+
+import static seedu.budgetbuddy.validators.AmountValidator.validateAmount;
+import static seedu.budgetbuddy.validators.CategoryValidator.validateSearchCategory;
+import static seedu.budgetbuddy.validators.DateValidator.validateDate;
+
+public class EditExpenseValidator {
+
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Processes the command string to determine if it is valid expense index.
+ * If valid, it creates an EditExpenseCommand object which will be used for subsequent execution.
+ * otherwise, it returns an IncorrectCommand object with the error.
+ *
+ * @param command first input given by user
+ * @return command object
+ */
+ public static Command processFirstCommand(String command) {
+ if (command.equals("edit expense")) {
+ return new IncorrectCommand("No index detected, try again with an index.");
+ }
+ if (command.startsWith("edit expenses")) {
+ return new IncorrectCommand("Invalid input");
+ }
+ try {
+ String trimmedCommand = command.substring("edit expense ".length());
+ String[] parts = trimmedCommand.split(" ");
+ int editIndex = Integer.parseInt(parts[0]);
+ if (editIndex < 0) {
+ return new IncorrectCommand("Edit index must be greater than 0.");
+ }
+ Expense expense = ExpenseManager.getExpenseByIndex(editIndex);
+ if (expense == null) {
+ return new IncorrectCommand("Input index is larger than the number of expenses. " +
+ "Try with a smaller index");
+ } else {
+ return new EditExpenseCommand(expense);
+ }
+ } catch (NumberFormatException e) {
+ return new IncorrectCommand("Index must be a valid number larger than 0.");
+ }
+
+ }
+
+ /**
+ * Processes the command string to determine if it is valid for editing.
+ * If valid, it updates the fields to be changed and returns a true for validity of command.
+ * otherwise, it will display error message and return a false validity of command.
+ * For any invalid category input, it will be set to others by default.
+ *
+ * @param command Input given by user
+ * @return Validity of command {@code Boolean}
+ */
+ public static boolean processSecondCommand(String command){
+
+ String[] parts = command.split(" ");
+
+ //Process Initial Value
+ LocalDate date = null;
+ Category category = null;
+ double amount = -1.0;
+
+ //Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("d/")) {
+ date = validateDate(part);
+ if (date == null) {
+ LOGGER.warning("Invalid date format. Date found: " + part);
+ Ui.displayToUser("Invalid month format. Use d/dd/mm/yyyy.");
+ return false;
+ }
+ } else if (part.startsWith("c/")) {
+ category = validateSearchCategory(part);
+ if (category == null) {
+ LOGGER.warning("Invalid category format. Category found: " + part);
+ Ui.displayToUser("Unknown category detected. Try again with a valid category.");
+ return false;
+ }
+ } else if (part.startsWith("a/")) {
+ amount = validateAmount(part);
+ if (amount <= 0.0) {
+ Ui.displayToUser("Amount should be a positive number with up to 2 decimal places.");
+ return false;
+ }
+ }
+ }
+
+ //set Fields to be changed
+ EditExpenseCommand.setDate(date);
+ EditExpenseCommand.setCategory(category);
+ EditExpenseCommand.setAmount(amount);
+
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/ListExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/ListExpenseValidator.java
new file mode 100644
index 0000000000..189ddacfa4
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/ListExpenseValidator.java
@@ -0,0 +1,78 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.ListExpenseCommand;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.YearMonth;
+import java.util.logging.Logger;
+
+import static seedu.budgetbuddy.validators.CategoryValidator.validateSearchCategory;
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+/**
+ * @author Kenneth
+ */
+public class ListExpenseValidator{
+
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Processes the command string to determine if it is valid for displaying expenses.
+ * If valid, it returns a DisplayExpenseCommand with the parsed date.
+ *
+ * @param command
+ * @return
+ */
+ public static Command processCommand(String command) {
+ if (command.equals("list expenses")){
+ return new ListExpenseCommand();
+ }
+
+ String trimmedCommand = command.substring("list expenses ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ //Process Initial Value
+ YearMonth month = null;
+ Category category = null;
+
+ //Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("m/")) {
+ month = validateYearMonth(part);
+ if (month == null) {
+ LOGGER.warning("Invalid month format. Month found: " + part);
+ return new IncorrectCommand("Invalid month format. Use m/MM/yyyy.");
+ }
+ } else if (part.startsWith("c/")) {
+ category = validateSearchCategory(part);
+ if (category == null) {
+ LOGGER.warning("Invalid Category. Category found: " + part);
+ return new IncorrectCommand("Unknown category. Use a valid category.");
+ }
+ }
+ }
+
+ //Check of Display Type
+ return checkListType(category, month);
+ }
+
+ /**
+ * Checks the value of category and date and returns the corresponding
+ * DisplayExpenseCommand type
+ * @param category
+ * @param date
+ * @return
+ */
+ public static Command checkListType(Category category, YearMonth date) {
+ if (category != null && date == null) {
+ return new ListExpenseCommand(category);
+ } else if (category == null && date != null) {
+ return new ListExpenseCommand(date);
+ } else {
+ return new ListExpenseCommand(category, date);
+ }
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/SearchExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/SearchExpenseValidator.java
new file mode 100644
index 0000000000..269c067bcc
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/expense/SearchExpenseValidator.java
@@ -0,0 +1,20 @@
+package seedu.budgetbuddy.validators.expense;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.expense.SearchExpenseCommand;
+
+public class SearchExpenseValidator {
+ /**
+ * Validates command input by user to see if string input has a keyword provided as a descriptor
+ * @param command Command input by user.
+ * @return new SearchExpenseCommand object.
+ */
+ public static Command processCommand(String command){
+ assert command != null: "Command cannot be null";
+ if (command.equals("search expenses")){
+ return new SearchExpenseCommand();
+ }
+ String trimmedCommand = command.substring("search expenses ".length());
+ return new SearchExpenseCommand(trimmedCommand);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/income/AddIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/AddIncomeValidator.java
new file mode 100644
index 0000000000..cbfed24170
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/income/AddIncomeValidator.java
@@ -0,0 +1,80 @@
+package seedu.budgetbuddy.validators.income;
+
+import seedu.budgetbuddy.commands.income.AddIncomeCommand;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+
+import java.time.LocalDate;
+
+import static seedu.budgetbuddy.validators.AmountValidator.validateAmount;
+import static seedu.budgetbuddy.validators.DateValidator.validateDate;
+
+
+/**
+ * Validates the command for adding an income.
+ */
+public class AddIncomeValidator {
+
+ /**
+ * Processes the given command to create a new income entry.
+ *
+ * @param command The command string to be processed.
+ * @return A Command object representing the add income command
+ * or an IncorrectCommand if validation fails.
+ */
+ public static Command processCommand(String command) {
+ if (command.equals("add income")) {
+ return new IncorrectCommand("No description provided.");
+ }
+
+ String trimmedCommand = command.substring("add income ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ // Initialize default values
+ String description = "";
+ double amount = 0; // invalid amount initially
+ LocalDate date = LocalDate.now();
+ boolean hasAmount = false;
+
+ // Process parts to extract details
+ for (String part : parts) {
+ String prefix = part.length() >= 2 ? part.substring(0, 2) : "";
+ switch (prefix) {
+
+ case "a/":
+ amount = validateAmount(part);
+ hasAmount = true;
+ if (amount <= 0) {
+ return new IncorrectCommand("Amount should be a positive number with up to 2 " +
+ "decimal places.");
+ }
+ break;
+
+ case "d/":
+ date = validateDate(part);
+ if (date == null) {
+ return new IncorrectCommand("Invalid date format. Use d/dd/MM/yyyy.");
+ }
+ break;
+
+ default:
+ description += part + " ";
+ }
+ }
+
+ description = description.trim();
+
+ // Validate description
+ if (description.isEmpty()) {
+ return new IncorrectCommand("Description cannot be empty.");
+ }
+
+ // Validate amount
+ if (hasAmount == false) {
+ return new IncorrectCommand("Amount not entered.");
+ }
+
+ // All validations passed, return the command
+ return new AddIncomeCommand(description, amount, date);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidator.java
new file mode 100644
index 0000000000..6b3496b31f
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidator.java
@@ -0,0 +1,36 @@
+package seedu.budgetbuddy.validators.income;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.income.DeleteIncomeCommand;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.validators.IndexValidator;
+
+/**
+ * Validates the command for deleting an income.
+ */
+public class DeleteIncomeValidator {
+
+ /**
+ * Processes the delete income command and returns the appropriate command object.
+ *
+ * @param command The command string entered by the user.
+ * @return A Command object representing the delete income action or an IncorrectCommand if the index is invalid.
+ */
+ public static Command processCommand(String command) {
+ String trimmedCommand;
+
+ try {
+ trimmedCommand = command.substring("delete income ".length());
+ } catch (IndexOutOfBoundsException e) {
+ return new IncorrectCommand("Index not given");
+ }
+
+ int index = IndexValidator.validateIncomeIndex(trimmedCommand);
+
+ if (index == -1) {
+ return new IncorrectCommand("Invalid Index");
+ }
+
+ return new DeleteIncomeCommand(index - 1);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidator.java
new file mode 100644
index 0000000000..90abc81789
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidator.java
@@ -0,0 +1,54 @@
+package seedu.budgetbuddy.validators.income;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.income.DisplayIncomeSpentCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+/**
+ * Validates and processes the command to display the percentage of income spent for a specified month.
+ */
+public class DisplayIncomeSpentValidator {
+
+ /**
+ * Processes the given command and returns a corresponding Command object.
+ *
+ * @param command The command string to process.
+ * @return Command representing the display income spent command for the specified month.
+ * @throws BudgetBuddyException If no income is recorded for the specified month.
+ */
+ public static Command processCommand(String command) throws BudgetBuddyException {
+ YearMonth month = null;
+
+ if (command.equals("display income spent")) {
+ month = YearMonth.from(LocalDate.now());
+ } else {
+ String trimmedCommand = command.substring("display income spent ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ //Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("m/")) {
+ month = validateYearMonth(part);
+ if (month == null) {
+ throw new BudgetBuddyException("Invalid month format. Use m/MM/yyyy.");
+ }
+ } else {
+ throw new BudgetBuddyException("Unrecognised input: '" + part + "'. " +
+ "Please check the command format.");
+ }
+ }
+ }
+
+ if (IncomeManager.getMonthlyIncome(month) <= 0) {
+ throw new BudgetBuddyException("No income recorded for the month: " + month);
+ }
+
+ return new DisplayIncomeSpentCommand(month);
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/income/EditIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/EditIncomeValidator.java
new file mode 100644
index 0000000000..eb65c35865
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/income/EditIncomeValidator.java
@@ -0,0 +1,101 @@
+package seedu.budgetbuddy.validators.income;
+
+import seedu.budgetbuddy.Ui;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.income.EditIncomeCommand;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.time.LocalDate;
+import java.util.logging.Logger;
+
+import static seedu.budgetbuddy.validators.AmountValidator.validateAmount;
+import static seedu.budgetbuddy.validators.DateValidator.validateDate;
+
+public class EditIncomeValidator {
+
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Processes the command string to determine if it is valid income index.
+ * If valid, it creates an EditIncomeCommand object which will be used for subsequent execution.
+ * otherwise, it returns an IncorrectCommand object with the error.
+ *
+ * @param command first input given by user
+ * @return command object
+ */
+ public static Command processFirstCommand(String command) {
+ if (command.equals("edit income")) {
+ return new IncorrectCommand("No index detected, try again with an index.");
+ }
+ if (command.startsWith("edit incomes")) {
+ return new IncorrectCommand("Invalid input");
+ }
+ try {
+ String trimmedCommand = command.substring("edit income ".length());
+ String[] parts = trimmedCommand.split(" ");
+ int editIndex = Integer.parseInt(parts[0]);
+ if (editIndex < 0) {
+ return new IncorrectCommand("Edit index must be greater than 0.");
+ }
+ Income income = IncomeManager.getIncomeByIndex(editIndex);
+ if (income == null) {
+ return new IncorrectCommand("Input index is larger than the number of incomes. " +
+ "Try with a smaller index");
+ } else {
+ return new EditIncomeCommand(income);
+ }
+ } catch (NumberFormatException e) {
+ return new IncorrectCommand("Index must be a valid number larger than 0.");
+ }
+
+ }
+
+ /**
+ * Processes the command string to determine if it is valid for editing.
+ * If valid, it updates the fields to be changed and returns a true for validity of command.
+ * otherwise, it will display error message and return a false validity of command.
+ *
+ * @param command Input given by user
+ * @return Validity of command {@code Boolean}
+ */
+ public static boolean processSecondCommand(String command) {
+
+ String[] parts = command.split(" ");
+
+ //Process Initial Value
+ LocalDate date = null;
+ double amount = -1.0;
+
+ //Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("d/")) {
+ date = validateDate(part);
+ if (date == null) {
+ LOGGER.warning("Invalid date format. Date found: " + part);
+ Ui.displayToUser("Invalid month format. Use d/dd/mm/yyyy.");
+ return false;
+ }
+ } else if (part.startsWith("a/")) {
+ amount = validateAmount(part);
+ if (amount < 0.0) {
+ Ui.displayToUser("Invalid amount format. Amount should be a positive number.");
+ return false;
+ }
+ }
+ }
+
+ if (date == null && amount == -1.0) {
+ Ui.displayToUser("No valid fields detected, Exiting change menu.");
+ return false;
+ }
+
+ //set Fields to be changed
+ EditIncomeCommand.setDate(date);
+ EditIncomeCommand.setAmount(amount);
+
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/income/ListIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/ListIncomeValidator.java
new file mode 100644
index 0000000000..9f879dbc2f
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/income/ListIncomeValidator.java
@@ -0,0 +1,44 @@
+package seedu.budgetbuddy.validators.income;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.income.ListIncomeCommand;
+
+import java.time.YearMonth;
+
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+public class ListIncomeValidator {
+
+ /**
+ * Processes the command string to determine if it is valid for displaying income.
+ * If valid, it returns a DisplayIncomeCommand with the parsed date.
+ *
+ * @param command
+ * @return
+ */
+ public static Command processCommand(String command) {
+ if (command.equals("list incomes")){
+ return new ListIncomeCommand();
+ }
+
+ String trimmedCommand = command.substring("list incomes ".length());
+ String[] parts = trimmedCommand.split(" ");
+
+ //Process Initial Value
+ YearMonth month = null;
+
+ //Process parts to extract details
+ for (String part : parts) {
+ if (part.startsWith("m/")) {
+ month = validateYearMonth(part);
+ if (month == null) {
+ return new IncorrectCommand("Invalid month format. Use m/MM/yyyy.");
+ }
+ }
+ }
+
+ return new ListIncomeCommand(month);
+ }
+
+}
diff --git a/src/main/java/seedu/budgetbuddy/validators/saving/DisplaySavingsValidator.java b/src/main/java/seedu/budgetbuddy/validators/saving/DisplaySavingsValidator.java
new file mode 100644
index 0000000000..1463f8b142
--- /dev/null
+++ b/src/main/java/seedu/budgetbuddy/validators/saving/DisplaySavingsValidator.java
@@ -0,0 +1,32 @@
+package seedu.budgetbuddy.validators.saving;
+
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.saving.DisplaySavingsCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.util.LoggerSetup;
+
+import java.util.logging.Logger;
+
+/**
+ * Validator class for validating user input and the "display savings" command.
+ */
+public class DisplaySavingsValidator {
+ private static final Logger LOGGER = LoggerSetup.getLogger();
+
+ /**
+ * Validates command input by user to see if string input has a keyword provided as a descriptor
+ * @param command Command input by user.
+ * @return new DisplaySavingsCommand object.
+ * @throws BudgetBuddyException if user input is not "display savings" or "display savings m/"
+ */
+ public static Command processCommand(String command) throws BudgetBuddyException{
+ assert command != null: "Command cannot be null";
+ if (command.equals("display savings m/")){
+ return new DisplaySavingsCommand(true);
+ } else if (command.equals("display savings")){
+ return new DisplaySavingsCommand(false);
+ }
+ LOGGER.info("Invalid display savings message by user.");
+ throw new BudgetBuddyException("Key in display savings m/ or display savings");
+ }
+}
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java
similarity index 77%
rename from src/test/java/seedu/duke/DukeTest.java
rename to src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java
index 2dda5fd651..fb8deccc50 100644
--- a/src/test/java/seedu/duke/DukeTest.java
+++ b/src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java
@@ -1,10 +1,10 @@
-package seedu.duke;
+package seedu.budgetbuddy;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
-class DukeTest {
+class BudgetBuddyTest {
@Test
public void sampleTest() {
assertTrue(true);
diff --git a/src/test/java/seedu/budgetbuddy/ParserTest.java b/src/test/java/seedu/budgetbuddy/ParserTest.java
new file mode 100644
index 0000000000..33c45b1a80
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/ParserTest.java
@@ -0,0 +1,101 @@
+package seedu.budgetbuddy;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.AddExpenseCommand;
+import seedu.budgetbuddy.commands.budget.AddBudgetCommand;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.Budget;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ParserTest {
+ private Parser parser;
+
+ @BeforeEach
+ public void setUp() {
+ ExpenseManager expenseManager = new ExpenseManager();
+ IncomeManager incomeManager = new IncomeManager();
+ BudgetManager budgetManager = new BudgetManager();
+ parser = new Parser(expenseManager, incomeManager, budgetManager);
+ }
+
+ @Test
+ public void parseCommand_validAddExpenseCommand_returnsAddExpenseCommand() throws BudgetBuddyException {
+ String command = "add expense KFC lunch a/10.50 d/28/10/2024 c/FOOD";
+
+ Command result = parser.parseCommand(command);
+
+ assertInstanceOf(AddExpenseCommand.class, result);
+ }
+
+ @Test
+ public void parseCommand_validAddBudgetCommand_returnsAddBudgetCommand() throws BudgetBuddyException {
+ String command = "add budget a/700 m/11/2024 c/TRANSPORT";
+
+ Command result = parser.parseCommand(command);
+
+ assertInstanceOf(AddBudgetCommand.class, result);
+ }
+
+ @Test
+ public void parseCommand_invalidCommand_returnsIncorrectCommand() throws BudgetBuddyException {
+ String command = "invalid command";
+
+ Command result = parser.parseCommand(command);
+
+ assertInstanceOf(IncorrectCommand.class, result);
+
+ IncorrectCommand incorrectCommand = (IncorrectCommand) result;
+ assertEquals("Invalid input", incorrectCommand.getFeedbackToUser());
+ }
+
+ @Test
+ public void parseFile_validExpenseLine_parsesExpenseCorrectly() {
+ // Given a valid expense input line
+ String input = "expense | Lunch | 15 | 10/10/2024 | OTHERS";
+
+ // Parse the file input (use the class name since parseFile is static)
+ Parser.parseFile(input);
+
+ Expense expense= ExpenseManager.getExpenses().get(0);
+ assertEquals(expense.getAmount(), 15);
+ assertEquals(expense.getDescription(), "Lunch");
+
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
+ LocalDate expectedDate = LocalDate.parse("10/10/2024", formatter);
+ assertEquals(expectedDate, expense.getDate());
+
+ assertEquals(Category.OTHERS, expense.getCategory());
+ }
+
+ @Test
+ public void parseFile_validBudgetLine_parsesBudgetCorrectly() {
+ // Given a valid budget input line
+ String input = "budget | Budget for October | 2024-10 | {food=100, entertainment=50}";
+
+ // Parse the file input (use the class name)
+ Parser.parseFile(input);
+
+ Budget budget = BudgetManager.getBudgets().get(0);
+ assertEquals(budget.getCategoryBudgetAmount(Category.FOOD), 100);
+ assertEquals(budget.getCategoryBudgetAmount(Category.ENTERTAINMENT), 50);
+
+ YearMonth expectedDate = YearMonth.parse("2024-10");
+ assertEquals(budget.getDate(), expectedDate);
+ }
+}
+
+
diff --git a/src/test/java/seedu/budgetbuddy/StorageTest.java b/src/test/java/seedu/budgetbuddy/StorageTest.java
new file mode 100644
index 0000000000..4adda25bb0
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/StorageTest.java
@@ -0,0 +1,134 @@
+package seedu.budgetbuddy;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.Budget;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.util.Scanner;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StorageTest {
+
+ private Storage storage;
+ private File tempFile;
+
+ @BeforeEach
+ public void setUp() throws IOException {
+ // Create a temporary file for testing
+ tempFile = File.createTempFile("testStorage", ".txt");
+ tempFile.deleteOnExit(); // Ensure the file is deleted after the test
+
+ // Initialize the Storage with the temporary file
+ storage = new Storage(tempFile.getAbsolutePath());
+
+ // Clear static managers before each test to ensure tests are isolated
+ ExpenseManager.reset();
+ IncomeManager.reset();
+ BudgetManager.reset();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ // Cleanup any resources after each test
+ tempFile.delete();
+ }
+
+ @Test
+ public void testCreateFileIfNotExists_createsFile() throws IOException {
+ // Ensure the file doesn't exist before creating it
+ File file = new File(tempFile.getAbsolutePath());
+ file.delete(); // Delete if it exists
+
+ // Ensure the file doesn't exist
+ assertFalse(file.exists());
+
+ // Call the method to create the file
+ storage.createFileIfNotExists();
+
+ // Verify the file has been created
+ assertTrue(file.exists());
+ }
+
+ @Test
+ public void testLoad_fileNotFound_throwsFileNotFoundException() {
+ // Use a non-existing file path
+ Storage invalidStorage = new Storage("non_existing_file.txt");
+
+ // Assert that load() throws FileNotFoundException
+ assertThrows(IOException.class, invalidStorage::load);
+ }
+
+ @Test
+ public void testLoad_successfulLoading() throws IOException {
+ try (FileWriter writer = new FileWriter(tempFile)) {
+ writer.write("expense | Lunch | 10.0 | 10/10/2024 | Food\n");
+ writer.write("income | Salary | 2000.0 | 01/10/2024\n");
+ writer.write("budget | 1500.0 | 2024-10 | {Food=500.0, Transport=300.0}\n");
+ }
+ storage.load();
+ assertDoesNotThrow(() -> storage.load());
+ }
+
+ @Test
+ public void testSave_savesCorrectly() throws IOException {
+ //Sample Data for Testing
+ Expense expense = new Expense("Lunch", 10.0, LocalDate.of(2024, 7, 10),
+ Category.FOOD);
+ Income income = new Income("Salary", 2000.0, LocalDate.of(2024, 7, 10));
+ Budget budget = new Budget(YearMonth.of(2024, 7));
+
+ // Add sample data to static managers
+ ExpenseManager.addExpense(expense);
+ IncomeManager.addIncome(income);
+ BudgetManager.addBudget(budget);
+
+ // Save data to the file
+ storage.save(ExpenseManager.getInstance(), IncomeManager.getInstance(), BudgetManager.getInstance());
+
+ // Read the content from the file and check if the data was saved correctly
+ File file = new File(tempFile.getAbsolutePath());
+ assertTrue(file.exists());
+
+ // Perform actual file reading and check if the saved data matches the expected format
+ Scanner scanner = new Scanner(file);
+ boolean expenseSaved = false;
+ boolean incomeSaved = false;
+ boolean budgetSaved = false;
+
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if (line.contains("Lunch")) {
+ expenseSaved = true;
+ }
+ if (line.contains("Salary")) {
+ incomeSaved = true;
+ }
+ if (line.contains("2024-07")) {
+ budgetSaved = true;
+ }
+
+ }
+
+ // Ensure all data is saved
+ assertTrue(expenseSaved);
+ assertTrue(incomeSaved);
+ assertTrue(budgetSaved);
+ }
+}
+
diff --git a/src/test/java/seedu/budgetbuddy/commands/budget/AddBudgetCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/budget/AddBudgetCommandTest.java
new file mode 100644
index 0000000000..37a7d93da7
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/budget/AddBudgetCommandTest.java
@@ -0,0 +1,93 @@
+package seedu.budgetbuddy.commands.budget;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.Budget;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AddBudgetCommandTest {
+
+ @BeforeEach
+ public void setUp() {
+ // Reset the static state of BudgetManager before each test
+ BudgetManager.reset();
+ }
+
+ @Test
+ public void constructor_validInputs_initializesCorrectly() {
+ AddBudgetCommand command = new AddBudgetCommand(100.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ assertNotNull(command);
+ }
+
+ @Test
+ public void constructor_inputIsNegativeAmount_throwsAssertionError() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ new AddBudgetCommand(-50.0, YearMonth.of(2024, 10), Category.FOOD);
+ });
+ assertEquals("Amount must be non-negative", exception.getMessage());
+ }
+
+ @Test
+ public void constructor_inputIsNullDate_throwsAssertionError() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ new AddBudgetCommand(100.0, null, Category.FOOD);
+ });
+ assertEquals("Date cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void constructor_inputIsNullCategory_throwsAssertionError() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ new AddBudgetCommand(100.0, YearMonth.of(2024, 10), null);
+ });
+ assertEquals("Category cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void isCommand_commandStartsWithAddBudget_returnsTrue() {
+ assertTrue(AddBudgetCommand.isCommand("add budget 100 for October 2024"));
+ }
+
+ @Test
+ public void isCommand_commandDoesNotStartWithAddBudget_returnsFalse() {
+ assertFalse(AddBudgetCommand.isCommand("remove budget 100 for October 2024"));
+ }
+
+ @Test
+ public void execute_noExistingBudget_addsNewBudget() {
+ AddBudgetCommand command = new AddBudgetCommand(150.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ command.execute();
+
+ Budget budget = BudgetManager.getBudget(YearMonth.of(2024, 10));
+ assertNotNull(budget);
+ assertEquals(150.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void execute_existingBudget_updatesBudget() {
+ // Add a budget
+ AddBudgetCommand addCommand = new AddBudgetCommand(100.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ addCommand.execute();
+
+ // Add another amount to the existing budget
+ AddBudgetCommand updateCommand = new AddBudgetCommand(50.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ updateCommand.execute();
+
+ Budget budget = BudgetManager.getBudget(YearMonth.of(2024, 10));
+ assertNotNull(budget);
+ assertEquals(150.0, budget.getTotalMonthlyBudget());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommandTest.java
new file mode 100644
index 0000000000..910915d472
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommandTest.java
@@ -0,0 +1,90 @@
+package seedu.budgetbuddy.commands.budget;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.Budget;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DeductBudgetCommandTest {
+
+ @BeforeEach
+ public void setUp() {
+ // Reset the static state of BudgetManager before each test
+ BudgetManager.reset();
+ }
+
+ @Test
+ public void constructor_validInputs_initializesCorrectly() {
+ DeductBudgetCommand command = new DeductBudgetCommand(50.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ assertNotNull(command);
+ }
+
+ @Test
+ public void constructor_inputISNegativeAmount_throwsAssertionError() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ new DeductBudgetCommand(-30.0, YearMonth.of(2024, 10), Category.FOOD);
+ });
+ assertEquals("Amount must be non-negative", exception.getMessage());
+ }
+
+ @Test
+ public void constructor_inputIsNullDate_throwsAssertionError() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ new DeductBudgetCommand(50.0, null, Category.FOOD);
+ });
+ assertEquals("Date cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void constructor_inputIsNullCategory_throwsAssertionError() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ new DeductBudgetCommand(50.0, YearMonth.of(2024, 10), null);
+ });
+ assertEquals("Category cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void isCommand_commandStartsWithDeductBudget_returnsTrue() {
+ assertTrue(DeductBudgetCommand.isCommand("deduct budget 50 for October 2024"));
+ }
+
+ @Test
+ public void isCommand_commandDoesNotStartWithDeductBudget_returnsFalse() {
+ assertFalse(DeductBudgetCommand.isCommand("add budget 50 for October 2024"));
+ }
+
+ @Test
+ public void execute_existingBudget_deductsAmount() {
+ // First, add a budget to deduct from
+ AddBudgetCommand addCommand = new AddBudgetCommand(100.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ addCommand.execute();
+
+ // Now deduct an amount from the existing budget
+ DeductBudgetCommand deductCommand = new DeductBudgetCommand(30.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ deductCommand.execute();
+
+ Budget budget = BudgetManager.getBudget(YearMonth.of(2024, 10));
+ assertNotNull(budget);
+ assertEquals(70.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void execute_noExistingBudget_doesNothing() {
+ DeductBudgetCommand deductCommand = new DeductBudgetCommand(50.0,
+ YearMonth.of(2024, 10), Category.FOOD);
+ assertNull(BudgetManager.getBudget(YearMonth.of(2024, 10))); // Ensure no budget was created
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/commands/budget/ListBudgetCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/budget/ListBudgetCommandTest.java
new file mode 100644
index 0000000000..7d2d489e94
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/budget/ListBudgetCommandTest.java
@@ -0,0 +1,72 @@
+package seedu.budgetbuddy.commands.budget;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+import seedu.budgetbuddy.transaction.budget.Budget;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ListBudgetCommandTest {
+
+ private BudgetManager budgetManager;
+ private ListBudgetCommand listBudgetCommand;
+
+ @BeforeEach
+ public void setUp() {
+ budgetManager = new BudgetManager();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ budgetManager.reset(); // Clear data after each test
+ }
+
+ @Test
+ public void isCommand_validCommand_true() {
+ assertTrue(ListBudgetCommand.isCommand("list budgets"));
+ }
+
+ @Test
+ public void isCommand_invalidCommand_false() {
+ assertFalse(ListBudgetCommand.isCommand("add budget"));
+ }
+
+ @Test
+ public void constructor_withDate_setsDateCorrectly() {
+ YearMonth date = YearMonth.of(2024, 11);
+ listBudgetCommand = new ListBudgetCommand(date);
+ assertEquals(date, listBudgetCommand.getDate());
+ }
+
+ @Test
+ public void execute_withSpecificDate_listsBudgetsForThatDate() {
+ YearMonth date = YearMonth.of(2024, 11);
+ Budget budget = new Budget(date);
+ budgetManager.addBudget(budget);
+
+ listBudgetCommand = new ListBudgetCommand(date);
+
+ listBudgetCommand.execute();
+
+ assertEquals(1, budgetManager.getNumberOfBudgets());
+ assertEquals(budget, budgetManager.getBudget(date));
+ }
+
+ @Test
+ public void execute_withNullDate_listsAllBudgets() {
+ budgetManager.addBudget(new Budget(YearMonth.of(2024, 11)));
+ budgetManager.addBudget(new Budget(YearMonth.of(2024, 10)));
+
+ listBudgetCommand = new ListBudgetCommand(null);
+
+ listBudgetCommand.execute();
+
+ assertEquals(2, budgetManager.getNumberOfBudgets());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/commands/expense/AddExpenseCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/expense/AddExpenseCommandTest.java
new file mode 100644
index 0000000000..9b437cec94
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/expense/AddExpenseCommandTest.java
@@ -0,0 +1,66 @@
+package seedu.budgetbuddy.commands.expense;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AddExpenseCommandTest {
+
+ @BeforeEach
+ public void setUp() {
+ ExpenseManager.reset();
+ }
+
+ @Test
+ public void constructor_validInputs_initializesCorrectly() {
+ AddExpenseCommand command = new AddExpenseCommand("Lunch", 15.0, LocalDate.of(2024,
+ 10, 20), Category.FOOD);
+ assertNotNull(command);
+ assertEquals("Lunch", command.getDescription());
+ assertEquals(15.0, command.getAmount());
+ assertEquals(LocalDate.of(2024, 10, 20), command.getDate());
+ assertEquals(Category.FOOD, command.getCategory());
+ }
+
+ @Test
+ public void isCommand_commandStartsWithAddExpense_returnsTrue() {
+ assertTrue(AddExpenseCommand.isCommand("add expense Lunch 15 for FOOD"));
+ }
+
+ @Test
+ public void isCommand_commandDoesNotStartWithAddExpense_returnsFalse() {
+ assertFalse(AddExpenseCommand.isCommand("remove expense Lunch 15 for FOOD"));
+ }
+
+ @Test
+ public void execute_validExpense_addsExpense() {
+ AddExpenseCommand command = new AddExpenseCommand("Dinner", 20.0, LocalDate.of(2024,
+ 10, 20), Category.FOOD);
+ command.execute();
+
+ Expense expense = ExpenseManager.getExpenses().get(0); // Assume this is the first added expense
+ assertNotNull(expense);
+ assertEquals("Dinner", expense.getDescription());
+ assertEquals(20.0, expense.getAmount());
+ assertEquals(LocalDate.of(2024, 10, 20), expense.getDate());
+ assertEquals(Category.FOOD, expense.getCategory());
+ }
+
+ @Test
+ public void execute_nullDate_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> {
+ new AddExpenseCommand("Groceries", 10.0, null, Category.OTHERS).execute();
+ });
+ }
+}
+
diff --git a/src/test/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommandTest.java
new file mode 100644
index 0000000000..5a77a37ea1
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommandTest.java
@@ -0,0 +1,56 @@
+package seedu.budgetbuddy.commands.expense;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DeleteExpenseCommandTest {
+
+ @BeforeEach
+ public void setUp() {
+ ExpenseManager.reset();
+ //Add Sample Data to test with
+ ExpenseManager.addExpense(new Expense("Lunch", 10.0, LocalDate.now(), Category.FOOD));
+ ExpenseManager.addExpense(new Expense("Bus fare", 2.5, LocalDate.now(), Category.TRANSPORT));
+ }
+
+ @Test
+ public void execute_validIndex_deletesExpense() {
+ // Assert initial size of expense list
+ assertEquals(2, ExpenseManager.getExpenses().size());
+
+ // Delete the first expense
+ DeleteExpenseCommand deleteCommand = new DeleteExpenseCommand(0);
+ deleteCommand.execute();
+
+ // Verify the size of expense list after deletion
+ assertEquals(1, ExpenseManager.getExpenses().size());
+ assertEquals("Bus fare", ExpenseManager.getExpenses().get(0).getDescription());
+ }
+
+ @Test
+ public void execute_invalidIndex_throwsIndexOutOfBoundsException() {
+ DeleteExpenseCommand deleteCommand = new DeleteExpenseCommand(5);
+
+ assertThrows(IndexOutOfBoundsException.class, deleteCommand::execute);
+ }
+
+ @Test
+ public void isCommand_commandStartsWithDeleteExpense_returnsTrue() {
+ assertTrue(DeleteExpenseCommand.isCommand("delete expense 1"));
+ }
+
+ @Test
+ public void isCommand_commandDoesNotStartWithDeleteExpense_returnsFalse() {
+ assertFalse(DeleteExpenseCommand.isCommand("remove expense 1"));
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/commands/expense/EditExpenseCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/expense/EditExpenseCommandTest.java
new file mode 100644
index 0000000000..b7f10cc71d
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/expense/EditExpenseCommandTest.java
@@ -0,0 +1,62 @@
+package seedu.budgetbuddy.commands.expense;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class EditExpenseCommandTest {
+ private static Expense expense;
+ private static final double EMPTY_AMOUNT = -1.0;
+ private static final Category EMPTY_CATEGORY = null;
+ private static final LocalDate EMPTY_DATE = null;
+
+ @Test
+ void editExpensesWithAmountOnly_validAmount_expectChangedAmount(){
+ expense = new Expense(
+ "New Expense",
+ 500.0,
+ LocalDate.of(2024,2,12),
+ Category.FOOD);
+ double newAmount = 50;
+ EditExpenseCommand expenseCommand = new EditExpenseCommand(expense);
+ expenseCommand.setAmount(newAmount);
+ expenseCommand.processEdit();
+ assertEquals(newAmount,expense.getAmount());
+ }
+
+ @Test
+ void editExpensesWithCategoryOnly_validCategory_expectChangedCategory(){
+ expense = new Expense(
+ "New Expense",
+ 500.0,
+ LocalDate.of(2024,2,12),
+ Category.FOOD);
+ Category newCategory = Category.TRANSPORT;
+ EditExpenseCommand expenseCommand = new EditExpenseCommand(expense);
+ expenseCommand.setCategory(newCategory);
+ expenseCommand.processEdit();
+ assertEquals(newCategory,expense.getCategory());
+ }
+
+ @Test
+ void editExpensesWithEmptyFields_expectUnchangedExpense(){
+ expense = new Expense(
+ "New Expense",
+ 500.0,
+ LocalDate.of(2024,2,12),
+ Category.FOOD);
+ EditExpenseCommand expenseCommand = new EditExpenseCommand(expense);
+ expenseCommand.setCategory(EMPTY_CATEGORY);
+ expenseCommand.setDate(EMPTY_DATE);
+ expenseCommand.setAmount(EMPTY_AMOUNT);
+ expenseCommand.processEdit();
+ assertEquals(Category.FOOD,expense.getCategory());
+ assertEquals(LocalDate.of(2024,2,12),expense.getDate());
+ assertEquals(500.0,expense.getAmount());
+
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommandTest.java
new file mode 100644
index 0000000000..a751dff207
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommandTest.java
@@ -0,0 +1,84 @@
+package seedu.budgetbuddy.commands.income;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static seedu.budgetbuddy.transaction.Category.OTHERS;
+
+public class DisplayIncomeSpentCommandTest {
+
+ private DisplayIncomeSpentCommand commandForNovember;
+ private DisplayIncomeSpentCommand commandForOctober;
+ private ByteArrayOutputStream outputStreamCaptor;
+
+ @BeforeEach
+ public void setUp() {
+ // Set up a new ByteArrayOutputStream to capture output
+ outputStreamCaptor = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(outputStreamCaptor));
+
+ // Temporarily suppress system output when adding expenses and incomes
+ PrintStream originalSystemOut = System.out;
+ System.setOut(new PrintStream(new ByteArrayOutputStream())); // Suppress output
+
+ // Clear existing incomes and expenses if needed
+ IncomeManager.getIncomes().clear();
+ ExpenseManager.getExpenses().clear();
+
+ // Set up dummy incomes for specific months
+ IncomeManager.addIncome(new Income("Salary", 2000.0, LocalDate.of(2024, 11, 5)));
+ IncomeManager.addIncome(new Income("Freelance Work", 500.0, LocalDate.of(2024, 11, 10)));
+ IncomeManager.addIncome(new Income("Gift", 3000.0, LocalDate.of(2024, 10, 3)));
+
+ // Set up dummy expenses for specific months
+ ExpenseManager.addExpense(new Expense("Groceries", 1000.0, LocalDate.of(2024, 11, 6), OTHERS));
+ ExpenseManager.addExpense(new Expense("Utilities", 500.0, LocalDate.of(2024, 11, 15), OTHERS));
+ ExpenseManager.addExpense(new Expense("Dining Out", 900.0, LocalDate.of(2024, 10, 10), OTHERS));
+
+ // Restore original System.out
+ System.setOut(originalSystemOut);
+
+ // Initialize commands with YearMonth to test
+ commandForNovember = new DisplayIncomeSpentCommand(YearMonth.of(2024, 11));
+ commandForOctober = new DisplayIncomeSpentCommand(YearMonth.of(2024, 10));
+ }
+
+ @Test
+ public void isCommand_validCommand_returnsTrue() {
+ assertTrue(DisplayIncomeSpentCommand.isCommand("display income spent"));
+ }
+
+ @Test
+ public void isCommand_invalidCommand_returnsFalse() {
+ assertFalse(DisplayIncomeSpentCommand.isCommand("deduct income spent"));
+ }
+
+ @Test
+ public void execute_correctMonthPercentageDisplayed() {
+ // Execute command and check display output
+ commandForNovember.execute();
+
+ String output = outputStreamCaptor.toString().trim();
+ assertTrue(output.contains("Percentage of income spent for 2024-11: 60.0%"));
+ }
+
+ @Test
+ public void execute_differentMonthPercentageDisplayed() {
+ // Execute command and check display output
+ commandForOctober.execute();
+
+ String output = outputStreamCaptor.toString().trim();
+ assertTrue(output.contains("Percentage of income spent for 2024-10: 30.0%"));
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetManagerTest.java
new file mode 100644
index 0000000000..c1b04311eb
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetManagerTest.java
@@ -0,0 +1,99 @@
+package seedu.budgetbuddy.transaction.budget;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class BudgetManagerTest {
+
+ @BeforeEach
+ public void setUp() {
+ // Reset the static state of BudgetManager before each test
+ BudgetManager.reset();
+ }
+
+ @Test
+ public void testAddBudget_inputIsBudget_incrementsNumberOfBudgets() {
+ Budget budget = new Budget(YearMonth.of(2024, 1));
+ BudgetManager.addBudget(budget);
+ assertEquals(1, BudgetManager.getNumberOfBudgets());
+ }
+
+ @Test
+ public void testAddBudget_inputIsBudget_addsBudgetToList() {
+ Budget budget = new Budget(YearMonth.of(2024, 7));
+ BudgetManager.addBudget(budget);
+ assertEquals(budget, BudgetManager.getBudgets().get(0));
+ }
+
+ @Test
+ public void testDeleteBudget_inputIsBudget_decrementsNumberOfBudgets() {
+ Budget budget = new Budget(YearMonth.of(2024, 5));
+ BudgetManager.addBudget(budget);
+ BudgetManager.deleteBudget(budget);
+ assertEquals(0, BudgetManager.getNumberOfBudgets());
+ }
+
+ @Test
+ public void testDeleteBudget_inputIsBudget_deleteBudgetFromList() {
+ Budget budget = new Budget(YearMonth.of(2024, 3));
+ BudgetManager.addBudget(budget);
+ BudgetManager.deleteBudget(budget);
+ assertTrue(BudgetManager.getBudgets().isEmpty());
+ }
+
+ @Test
+ public void testGetBudget_returnsCorrectBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 8));
+ BudgetManager.addBudget(budget);
+ Budget result = BudgetManager.getBudget(YearMonth.of(2024, 8));
+ assertEquals(budget, result);
+ }
+
+ @Test
+ public void testGetBudget_returnsNullForNonExistentBudget() {
+ Budget result = BudgetManager.getBudget(YearMonth.of(2024, 12));
+ assertNull(result);
+ }
+
+ @Test
+ public void addBudget_inputIsNullBudget_assertionErrorThrown() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ BudgetManager.addBudget(null);
+ });
+ assertEquals("Budget to be added cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void deleteBudget_inputIsNullBudget_assertionErrorThrown() {
+ AssertionError exception = assertThrows(AssertionError.class, () -> {
+ BudgetManager.deleteBudget(null);
+ });
+ assertEquals("Budget to be deleted cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void getBudget_multipleBudgets_returnsCorrectBudget() {
+ Budget budget1 = new Budget(YearMonth.of(2024, 9));
+ Budget budget2 = new Budget(YearMonth.of(2024, 8));
+ BudgetManager.addBudget(budget1);
+ BudgetManager.addBudget(budget2);
+ Budget result = BudgetManager.getBudget(YearMonth.of(2024, 9));
+ assertEquals(budget1, result);
+ }
+
+ @Test
+ public void reset_clearsBudgets() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ BudgetManager.addBudget(budget);
+ BudgetManager.reset();
+ assertEquals(0, BudgetManager.getNumberOfBudgets());
+ assertTrue(BudgetManager.getBudgets().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetTest.java b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetTest.java
new file mode 100644
index 0000000000..54d334f062
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetTest.java
@@ -0,0 +1,143 @@
+package seedu.budgetbuddy.transaction.budget;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class BudgetTest {
+
+ @Test
+ public void testAddAmount_inputIsDouble_increasesBudgetTotalMonthlyBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 50.0);
+ assertEquals(50.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void testDeductAmount_inputIsDouble_decreasesBudgetTotalMonthlyBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.OTHERS, 100);
+ budget.deductAmount(Category.OTHERS, 30.0);
+ assertEquals(70.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void testAddAmount_inputIsInt_increasesBudgetTotalMonthlyBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 2));
+ budget.addAmount(Category.ENTERTAINMENT, 150);
+ assertEquals(150.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void testDeductAmount_inputIsInt_decreasesBudgetTotalMonthlyBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 2));
+ budget.addAmount(Category.TRANSPORT, 100);
+ budget.deductAmount(Category.TRANSPORT, 70);
+ assertEquals(30.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void testToString_returnsCorrectString() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 100);
+ String result = budget.toString();
+ assertEquals("Total Monthly Budget: 100.00 Date: 2024-10\n Category: {FOOD=100.00}", result);
+ }
+
+ @Test
+ public void testGetAmount_returnsCorrectTotalMonthlyBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 9));
+ budget.addAmount(Category.FOOD, 250);
+ assertEquals(250.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void testGetDate_returnsCorrectDate() {
+ Budget budget = new Budget(YearMonth.of(2024, 9));
+ assertEquals(YearMonth.of(2024, 9), budget.getDate());
+ }
+
+ @Test
+ public void addAmount_inputIsValidCategory_updatesTotalCorrectly() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 50.0);
+ assertEquals(50.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void deductAmount_inputIsValidCategory_decreasesTotalMonthlyBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.OTHERS, 100);
+ budget.deductAmount(Category.OTHERS, 30.0);
+ assertEquals(70.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void addMultipleCategories_updatesTotalCorrectly() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 50);
+ budget.addAmount(Category.ENTERTAINMENT, 150);
+ budget.addAmount(Category.TRANSPORT, 100);
+ assertEquals(300.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void deductBudget_inputIsExceedingAmount_removesCategory() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 50);
+ budget.deductAmount(Category.FOOD, 50);
+ assertTrue(budget.getCategoryBudgets().isEmpty());
+ }
+
+ @Test
+ public void deductBudget_inputIsNonExistentCategory_doesNotChangeBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.deductAmount(Category.FOOD, 10);
+ assertEquals(0.0, budget.getTotalMonthlyBudget());
+ assertTrue(budget.getCategoryBudgets().isEmpty());
+ }
+
+ @Test
+ public void addBudget_inputIsZeroAmount_doesNotChangeBudget() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 0);
+ assertEquals(0.0, budget.getTotalMonthlyBudget());
+ }
+
+ @Test
+ public void deepCopyConstructor_copiesBudgetCorrectly() {
+ Budget original = new Budget(YearMonth.of(2024, 10));
+ original.addAmount(Category.FOOD, 100);
+ Budget copy = new Budget(original);
+ original.deductAmount(Category.FOOD, 50); // Change the original
+
+ assertEquals(50.0, original.getTotalMonthlyBudget()); // Original should be updated
+ assertEquals(100.0, copy.getTotalMonthlyBudget()); // Copy should remain unchanged
+ }
+
+ @Test
+ public void toString_returnsCorrectStringRepresentation() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 100);
+ String result = budget.toString();
+ assertEquals("Total Monthly Budget: 100.00 Date: 2024-10\n Category: {FOOD=100.00}", result);
+ }
+
+ @Test
+ public void getDate_returnsCorrectDate() {
+ Budget budget = new Budget(YearMonth.of(2024, 9));
+ assertEquals(YearMonth.of(2024, 9), budget.getDate());
+ }
+
+ @Test
+ public void addAmount_inputIsExistingCategory_updatesAmount() {
+ Budget budget = new Budget(YearMonth.of(2024, 10));
+ budget.addAmount(Category.FOOD, 100);
+ budget.addAmount(Category.FOOD, 50); // Adding to existing category
+ assertEquals(150.0, budget.getTotalMonthlyBudget());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManagerTest.java
new file mode 100644
index 0000000000..6b064b76c0
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManagerTest.java
@@ -0,0 +1,62 @@
+package seedu.budgetbuddy.transaction.budget;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+
+/**
+ * Test class for RemainingBudgetManager.
+ */
+public class RemainingBudgetManagerTest {
+
+ private RemainingBudgetManager remainingBudgetManager;
+
+ @BeforeEach
+ public void setUp() {
+ // Set up budgets and expenses
+ BudgetManager.reset();
+ ExpenseManager.reset();
+ Budget budgetOct = new Budget(YearMonth.of(2024, 10));
+ budgetOct.addAmount(Category.EDUCATION, 500);
+ Budget budgetSep = new Budget(YearMonth.of(2024, 9));
+ budgetSep.addAmount(Category.OTHERS, 300);
+ BudgetManager.addBudget(budgetOct);
+ BudgetManager.addBudget(budgetSep);
+
+ ExpenseManager.addExpense(new Expense("Lunch", 50.0, LocalDate.of(2024, 11, 5), Category.FOOD));
+ ExpenseManager.addExpense(new Expense("Groceries", 30.0, LocalDate.of(2024, 9, 25), Category.OTHERS));
+
+ remainingBudgetManager = new RemainingBudgetManager(); // Initialize
+ }
+
+ @Test
+ public void getRemainingBudgets_expenseWithoutBudget_negativeBudget() {
+ String result = remainingBudgetManager.getRemainingBudgets(LocalDate.of(2024, 11, 5), Category.FOOD);
+ assertEquals("The remaining budget for 2024-11 in the FOOD category is: -50.00\n" +
+ "Caution! You have exceeded your budget!", result);
+ }
+
+ @Test
+ public void getRemainingBudgets_expenseWithBudget_positiveBudget() {
+ String result = remainingBudgetManager.getRemainingBudgets(LocalDate.of(2024, 9, 25), Category.OTHERS);
+ assertEquals("The remaining budget for 2024-09 in the OTHERS category is: 270.00", result);
+ }
+
+ @Test
+ public void getRemainingBudgets_addExpense_positiveBudget() {
+ ExpenseManager.addExpense(new Expense("Books", 20.0, LocalDate.of(2024, 10, 5), Category.EDUCATION));
+ remainingBudgetManager = new RemainingBudgetManager();
+
+ String result = remainingBudgetManager.getRemainingBudgets(LocalDate.of(2024, 10, 5), Category.EDUCATION);
+ assertEquals("The remaining budget for 2024-10 in the EDUCATION category is: 480.00", result);
+ }
+
+}
diff --git a/src/test/java/seedu/budgetbuddy/transaction/expense/ExpenseManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/expense/ExpenseManagerTest.java
new file mode 100644
index 0000000000..64eb4dbf33
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/transaction/expense/ExpenseManagerTest.java
@@ -0,0 +1,174 @@
+package seedu.budgetbuddy.transaction.expense;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ExpenseManagerTest {
+
+ private static int numberOfExpenses = 0;
+ private static ArrayList expenses = new ArrayList<>();
+ private static ExpenseManager expenseManager;
+ private static final String EMPTY_DISPLAY_STRING =
+ "No expense entry with given parameters found, try again with a different parameter.";
+
+ void initializeTestContent(){
+ expenseManager = new ExpenseManager(expenses, numberOfExpenses);
+ expenseManager.reset();
+ Expense newExpense = new Expense("New Food",
+ 12,
+ LocalDate.parse("2024-02-12"),
+ Category.FOOD);
+ expenseManager.addExpense(newExpense);
+ }
+ void initializeEmptyTestContent(){
+ expenseManager = new ExpenseManager(new ArrayList<>(), numberOfExpenses);
+ }
+
+ String getExpectedString(){
+ String result = "";
+ int counter = 1;
+ for (Expense expense : ExpenseManager.getExpenses()) {
+ result += counter + ". " + expense.toString() + "\n";
+ counter++;
+ }
+ result += "Your total expense(s) for FOOD in February 2024 are $12.00";
+ return result;
+ }
+
+ @Test
+ void displayExpensesWithCategoryAndDate_validMonthCategory_expectOneEntry() {
+ initializeTestContent();
+ Category category = Category.FOOD;
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals(getExpectedString(),
+ ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth));
+ }
+
+ @Test
+ void displayExpensesWithCategoryAndDate_incorrectCategory_expectEmptyDisplayString() {
+ Category category = Category.TRANSPORT;
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals(EMPTY_DISPLAY_STRING,
+ ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth));
+ }
+
+ @Test
+ void displayExpensesWithCategoryAndDate_incorrectMonth_expectEmptyDisplayString() {
+ Category category = Category.FOOD;
+ YearMonth yearMonth = YearMonth.of(2024, 1);
+ assertEquals(EMPTY_DISPLAY_STRING,
+ ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth));
+ }
+
+ @Test
+ void searchExpense_noDescriptor_expectEmptyDisplayString(){
+ initializeTestContent();
+ assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.searchExpenses(""));
+ }
+
+ @Test
+ void searchExpense_descriptorProvidedNoFoundExpense_expectEmptyDisplayString(){
+ initializeTestContent();
+ assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.searchExpenses("RANDOMMESSAGE"));
+ }
+
+ @Test
+ void searchExpense_foundExpense_expectOutput(){
+ initializeTestContent();
+ assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n",
+ ExpenseManager.searchExpenses("New"));
+ }
+
+ @Test
+ void breakdownExpensesByCategory_noExpenses_expectNoExpensesMessage(){
+ initializeEmptyTestContent();
+ assertEquals("Total expenses: 0. You have not indicated any expense yet.",
+ ExpenseManager.breakdownExpensesByCategory());
+ }
+
+ @Test
+ void breakdownExpensesByCategory_oneExpense_expectExpenseMessage(){
+ initializeTestContent();
+ assertEquals("Total expenses: 12.0\nFood: 12.0(100.00%)\nTransport: 0.0(0.00%)\nUtilities:" +
+ " 0.0(0.00%)\nEntertainment: 0.0(0.00%)\nEducation: 0.0(0.00%)\nOthers: 0.0(0.00%)\n",
+ ExpenseManager.breakdownExpensesByCategory());
+ }
+
+ @Test
+ void listExpensesWithCategoryAndDate_noExpenses_expectNoExpensesMessage(){
+ initializeEmptyTestContent();
+ Category category = Category.FOOD;
+ YearMonth yearMonth = YearMonth.now();
+ assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth));
+ }
+
+ @Test
+ void listExpensesWithCategoryAndDate_oneExpense_expectExpenseMessage(){
+ initializeTestContent();
+ Category category = Category.FOOD;
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n" +
+ "Your total expense(s) for FOOD in February 2024 are $12.00",
+ ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth));
+ }
+
+ @Test
+ void listExpensesWithCategoryAndDate_oneExpenseWrongCategory_expectNoExpensesFoundMessage(){
+ initializeTestContent();
+ Category category = Category.ENTERTAINMENT;
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth));
+ }
+
+ @Test
+ void listExpensesWithCategory_noExpense_expectNoExpensesFoundMessage(){
+ initializeEmptyTestContent();
+ Category category = Category.FOOD;
+ assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithCategory(category));
+ }
+
+ @Test
+ void listExpensesWithCategory_oneExpense_expectExpenseMessage(){
+ initializeTestContent();
+ Category category = Category.FOOD;
+ assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n" +
+ "Your total expense(s) for FOOD are $12.00",
+ ExpenseManager.listExpensesWithCategory(category));
+ }
+
+ @Test
+ void listExpensesWithDate_noExpense_expectNoExpensesFoundMessage(){
+ initializeEmptyTestContent();
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithDate(yearMonth));
+ }
+
+ @Test
+ void listExpensesWithDate_oneExpense_expectExpenseMessage(){
+ initializeTestContent();
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n" +
+ "Your total expense(s) for February 2024 are $12.00",
+ ExpenseManager.listExpensesWithDate(yearMonth));
+ }
+
+ @Test
+ void getMonthlyExpense_oneExpense_expectMonthlyExpense(){
+ initializeTestContent();
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals(12.0, ExpenseManager.getMonthlyExpense(yearMonth));
+ }
+
+ @Test
+ void getMonthlyExpense_noExpense_expectZeroMonthlyExpense(){
+ initializeEmptyTestContent();
+ YearMonth yearMonth = YearMonth.of(2024, 2);
+ assertEquals(0.0, ExpenseManager.getMonthlyExpense(yearMonth));
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/transaction/income/IncomeManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/income/IncomeManagerTest.java
new file mode 100644
index 0000000000..7893d2301c
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/transaction/income/IncomeManagerTest.java
@@ -0,0 +1,46 @@
+package seedu.budgetbuddy.transaction.income;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class IncomeManagerTest {
+
+ @BeforeEach
+ void resetIncomeManager() {
+ IncomeManager.reset(); // Ensure a clean state before each test
+ }
+
+ @Test
+ void addIncome_validIncome_incomeAddedSuccessfully() {
+ Income income = new Income("Salary", 5000, LocalDate.of(2024, 10, 1));
+ IncomeManager.addIncome(income);
+
+ assertEquals(1, IncomeManager.getNumberOfIncomes());
+ assertEquals(income, IncomeManager.getIncomes().get(0));
+ }
+
+ @Test
+ void deleteIncome_validIndex_incomeDeletedSuccessfully() {
+ Income income = new Income("Salary", 5000, LocalDate.of(2024, 10, 1));
+ IncomeManager.addIncome(income);
+
+ IncomeManager.deleteIncome(0);
+
+ assertEquals(0, IncomeManager.getNumberOfIncomes());
+ assertTrue(IncomeManager.getIncomes().isEmpty());
+ }
+
+ @Test
+ void getYearMonthFromDate_validDate_returnsCorrectYearMonth() {
+ LocalDate date = LocalDate.of(2024, 10, 1);
+ YearMonth expected = YearMonth.of(2024, 10);
+ assertEquals(expected, IncomeManager.getYearMonthFromDate(date));
+ }
+}
+
diff --git a/src/test/java/seedu/budgetbuddy/transaction/saving/SavingsManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/saving/SavingsManagerTest.java
new file mode 100644
index 0000000000..f1c87942c7
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/transaction/saving/SavingsManagerTest.java
@@ -0,0 +1,73 @@
+package seedu.budgetbuddy.transaction.saving;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SavingsManagerTest {
+ private static IncomeManager incomeManager;
+ private static ExpenseManager expenseManager;
+ private static ArrayList incomes;
+ private static ArrayList expenses;
+
+ void initializeTestContent(){
+ incomes = new ArrayList<>();
+ int numberOfIncomes = 0;
+ incomeManager = new IncomeManager(incomes, numberOfIncomes);
+ expenses = new ArrayList<>();
+ int numberOfExpenses = 0;
+ expenseManager = new ExpenseManager(expenses, numberOfExpenses);
+ }
+
+ void initializeTestContent(boolean emptyIncome, boolean emptyExpense){
+ incomes = new ArrayList<>();
+ int numberOfIncomes = 0;
+ incomeManager = new IncomeManager(incomes, numberOfIncomes);
+ if (!emptyIncome){
+ Income newIncome = new Income("tuition", 1000, LocalDate.parse("2024-10-10"));
+ incomeManager.addIncome(newIncome);
+ }
+ expenses = new ArrayList<>();
+ int numberOfExpenses = 0;
+ expenseManager = new ExpenseManager(expenses, numberOfExpenses);
+ if (!emptyExpense){
+ Expense newExpense = new Expense("Ticket", 100, LocalDate.parse("2024-10-10"),
+ Category.ENTERTAINMENT);
+ expenseManager.addExpense(newExpense);
+ }
+ }
+
+ @Test
+ void displayTotalSavings_noIncomeNoExpense_expectZeroSavings(){
+ initializeTestContent();
+ assertEquals("Total Savings: 0.00\nTotal Income: 0.00\nTotal Expense: 0.00",
+ SavingsManager.displayTotalSavings());
+ }
+
+ @Test
+ void displayTotalSavings_noIncomeOneExpense_expectNegativeSavings(){
+ initializeTestContent(true, false);
+ assertEquals("Total Savings: -100.00\nTotal Income: 0.00\nTotal Expense: 100.00",
+ SavingsManager.displayTotalSavings());
+ }
+
+ @Test
+ void displayTotalSavingsByMonth_noIncomeNoExpense_expectZeroSavings(){
+ initializeTestContent();
+ assertEquals("Total savings: 0.00", SavingsManager.displayTotalSavingsByMonth());
+ }
+
+ @Test
+ void displayTotalSavingsByMonth_oneIncomeOneExpense_expectSavings(){
+ initializeTestContent(false, false);
+ assertEquals("Savings in 2024-10: 900.00", SavingsManager.displayTotalSavingsByMonth());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/budget/AddBudgetValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/budget/AddBudgetValidatorTest.java
new file mode 100644
index 0000000000..a092ec57bf
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/budget/AddBudgetValidatorTest.java
@@ -0,0 +1,83 @@
+package seedu.budgetbuddy.validators.budget;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.budget.AddBudgetCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.transaction.Category;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AddBudgetValidatorTest {
+
+ @Test
+ public void processCommand_validCommand_returnsAddBudgetCommand() throws BudgetBuddyException {
+ String command = "add budget a/100.0 m/11/2024 c/Food"; // Valid command
+ Command result = AddBudgetValidator.processCommand(command);
+
+ assertNotNull(result);
+ assertTrue(result instanceof AddBudgetCommand);
+ AddBudgetCommand addBudgetCommand = (AddBudgetCommand) result;
+ assertEquals(100.0, addBudgetCommand.getAmount());
+ assertEquals(YearMonth.of(2024, 11), addBudgetCommand.getDate());
+ assertEquals(Category.FOOD, addBudgetCommand.getCategory());
+ }
+
+ @Test
+ public void processCommand_missingAmount_throwsBudgetBuddyException() {
+ String command = "add budget"; // Missing amount
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ AddBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("No amount provided."));
+ }
+
+ @Test
+ public void processCommand_invalidAmountFormat_throwsBudgetBuddyException() {
+ String command = "add budget a/invalid m/2024-11 c/Food"; // Invalid amount format
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ AddBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Invalid amount format."));
+ }
+
+ @Test
+ public void processCommand_negativeAmount_throwsBudgetBuddyException() {
+ String command = "add budget a/-100.0 m/2024-11 c/Food"; // Negative amount
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ AddBudgetValidator.processCommand(command));
+
+
+ assertTrue(exception.getMessage().contains("Invalid amount: -100.0. Amount must be a positive value."));
+ }
+
+ @Test
+ public void processCommand_invalidDateFormat_throwsBudgetBuddyException() {
+ String command = "add budget a/100.0 m/invalid-date c/Food"; // Invalid date format
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ AddBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Invalid date format. Use m/MM/yyyy."));
+ }
+
+ @Test
+ public void processCommand_noCategory_usesDefaultCategory() throws BudgetBuddyException {
+ String command = "add budget a/100.0 m/11/2024"; // No category provided
+
+ Command result = AddBudgetValidator.processCommand(command);
+
+ assertNotNull(result);
+ assertTrue(result instanceof AddBudgetCommand);
+ AddBudgetCommand addBudgetCommand = (AddBudgetCommand) result;
+ assertEquals(Category.OTHERS, addBudgetCommand.getCategory());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidatorTest.java
new file mode 100644
index 0000000000..23f2d7fd8e
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidatorTest.java
@@ -0,0 +1,111 @@
+package seedu.budgetbuddy.validators.budget;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.budget.DeductBudgetCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.budget.Budget;
+import seedu.budgetbuddy.transaction.budget.BudgetManager;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DeductBudgetValidatorTest {
+
+ @BeforeEach
+ public void setUp() {
+ // Reset the static state of BudgetManager before each test
+ BudgetManager.reset();
+ }
+
+ @Test
+ public void processCommand_validCommand_returnsDeductBudgetCommand() throws BudgetBuddyException {
+ // Assuming a budget exists for November 2024
+ Budget budget = new Budget(YearMonth.of(2024, 11));
+ budget.addAmount(Category.FOOD, 1000);
+ BudgetManager.addBudget(budget);
+
+ String command = "deduct budget a/100.0 m/11/2024 c/Food"; // Valid command
+ Command result = DeductBudgetValidator.processCommand(command);
+
+ assertNotNull(result);
+ assertTrue(result instanceof DeductBudgetCommand);
+ DeductBudgetCommand deductBudgetCommand = (DeductBudgetCommand) result;
+ assertEquals(100.0, deductBudgetCommand.getAmount());
+ assertEquals(YearMonth.of(2024, 11), deductBudgetCommand.getDate());
+ assertEquals(Category.FOOD, deductBudgetCommand.getCategory());
+ }
+
+ @Test
+ public void processCommand_missingAmount_throwsBudgetBuddyException() {
+ String command = "deduct budget"; // Missing amount
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ DeductBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("No amount provided."));
+ }
+
+ @Test
+ public void processCommand_invalidAmountFormat_throwsBudgetBuddyException() {
+ String command = "deduct budget a/invalid m/2024-11 c/Food"; // Invalid amount format
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ DeductBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Invalid amount format."));
+ }
+
+ @Test
+ public void processCommand_negativeAmount_throwsBudgetBuddyException() {
+ String command = "deduct budget a/-100.0 m/11/2024 c/Food"; // Negative amount
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ DeductBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Invalid amount: -100.0. Must be a positive value."));
+ }
+
+ @Test
+ public void processCommand_invalidDateFormat_throwsBudgetBuddyException() {
+ String command = "deduct budget a/100.0 m/invalid-date c/Food"; // Invalid date format
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ DeductBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Invalid date format. Use m/MM/yyyy."));
+ }
+
+ @Test
+ public void processCommand_budgetDoesNotExist_throwsBudgetBuddyException() {
+ String command = "deduct budget a/100.0 m/11/2024 c/Food"; // Budget does not exist for the date
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ DeductBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Budget does not exist for the specified date: 2024-11"));
+ }
+
+ @Test
+ public void processCommand_noCategory_usesDefaultCategory() throws BudgetBuddyException {
+ // Assuming a budget exists for November 2024
+ Budget budget = new Budget(YearMonth.of(2024, 11));
+ budget.addAmount(Category.OTHERS, 1000);
+ BudgetManager.addBudget(budget);
+
+ String command = "deduct budget a/100.0 m/11/2024"; // No category provided
+
+ Command result = DeductBudgetValidator.processCommand(command);
+
+ assertNotNull(result);
+ assertTrue(result instanceof DeductBudgetCommand);
+ DeductBudgetCommand deductBudgetCommand = (DeductBudgetCommand) result;
+ assertEquals(Category.OTHERS, deductBudgetCommand.getCategory());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/budget/ListBudgetValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/budget/ListBudgetValidatorTest.java
new file mode 100644
index 0000000000..13a8ea879d
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/budget/ListBudgetValidatorTest.java
@@ -0,0 +1,49 @@
+package seedu.budgetbuddy.validators.budget;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.budget.ListBudgetCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ListBudgetValidatorTest {
+
+ @Test
+ public void processCommand_noDate_returnsListBudgetCommand() throws BudgetBuddyException {
+ String command = "list budgets"; // No date provided
+ Command result = ListBudgetValidator.processCommand(command);
+
+ assertNotNull(result);
+ assertEquals(ListBudgetCommand.class, result.getClass());
+ ListBudgetCommand listBudgetCommand = (ListBudgetCommand) result;
+ assertNull(listBudgetCommand.getDate()); // Expecting null for date
+ }
+
+ @Test
+ public void processCommand_validDate_returnsListBudgetCommand() throws BudgetBuddyException {
+ String command = "list budgets m/11/2024"; // Valid date provided
+ Command result = ListBudgetValidator.processCommand(command);
+
+ assertNotNull(result);
+ assertEquals(ListBudgetCommand.class, result.getClass());
+ ListBudgetCommand listBudgetCommand = (ListBudgetCommand) result;
+ assertEquals(YearMonth.of(2024, 11), listBudgetCommand.getDate());
+ }
+
+ @Test
+ public void processCommand_invalidDateFormat_throwsBudgetBuddyException() {
+ String command = "list budgets m/invalid-date"; // Invalid date format
+
+ BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () ->
+ ListBudgetValidator.processCommand(command));
+
+ assertTrue(exception.getMessage().contains("Invalid format. Use 'list budgets [m/MM/yyyy]'."));
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/AddExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/AddExpenseValidatorTest.java
new file mode 100644
index 0000000000..05fa322675
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/expense/AddExpenseValidatorTest.java
@@ -0,0 +1,82 @@
+package seedu.budgetbuddy.validators.expense;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.expense.AddExpenseCommand;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.transaction.Category;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class AddExpenseValidatorTest {
+
+ @Test
+ void processCommand_invalidAmount_returnsIncorrectCommand() {
+ Command result = AddExpenseValidator.processCommand("add expense Lunch a/abc d/12/10/2024 c/FOOD");
+
+ // Check if result is an IncorrectCommand and compare the message
+ assertInstanceOf(IncorrectCommand.class, result);
+ String message = ((IncorrectCommand) result).getFeedbackToUser();
+ assertEquals("Amount should be a positive number with up to 2 decimal places.", message);
+ }
+
+ @Test
+ void processCommand_noDescription_returnsIncorrectCommand() {
+ Command result = AddExpenseValidator.processCommand("add expense a/100 d/12/10/2024 c/FOOD");
+
+ // Check if result is an IncorrectCommand and compare the message
+ assertInstanceOf(IncorrectCommand.class, result);
+ String message = ((IncorrectCommand) result).getFeedbackToUser();
+ assertEquals("Description cannot be empty.", message);
+ }
+
+ @Test
+ void processCommand_invalidDate_returnsIncorrectCommand() {
+ Command result = AddExpenseValidator.processCommand("add expense Lunch a/100 d/invalid_date c/FOOD");
+
+ // Check if result is an IncorrectCommand and compare the message
+ assertInstanceOf(IncorrectCommand.class, result);
+ String message = ((IncorrectCommand) result).getFeedbackToUser();
+ assertEquals("Invalid date format. Use d/DD/MM/YYYY.", message);
+ }
+
+ @Test
+ void processCommand_noAmount_returnsIncorrectCommand() {
+ Command result = AddExpenseValidator.processCommand("add expense Lunch d/12/10/2024 c/FOOD");
+
+ // Check if result is an IncorrectCommand and compare the message
+ assertInstanceOf(IncorrectCommand.class, result);
+ String message = ((IncorrectCommand) result).getFeedbackToUser();
+ assertEquals("Amount not entered.", message);
+ }
+
+ @Test
+ void processCommand_negativeAmount_returnsIncorrectCommand() {
+ Command result = AddExpenseValidator.processCommand("add expense Lunch a/-50 d/12/10/2024 c/FOOD");
+
+ // Check if result is an IncorrectCommand and compare the message
+ assertInstanceOf(IncorrectCommand.class, result);
+ String message = ((IncorrectCommand) result).getFeedbackToUser();
+ assertEquals("Amount should be a positive number with up to 2 decimal places.", message);
+ }
+
+
+ @Test
+ void processCommand_validCommand_returnsAddExpenseCommand() {
+ Command result = AddExpenseValidator.processCommand("add expense Lunch a/100 d/12/10/2024 c/FOOD");
+
+ // Check if the result is an AddExpenseCommand (a valid command)
+ assertInstanceOf(AddExpenseCommand.class, result);
+ AddExpenseCommand command = (AddExpenseCommand) result;
+
+ // Verify the details of the created command
+ assertEquals("Lunch", command.getDescription());
+ assertEquals(100, command.getAmount());
+ assertEquals(LocalDate.of(2024, 10, 12), command.getDate());
+ assertEquals(Category.FOOD, command.getCategory());
+ }
+}
+
diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidatorTest.java
new file mode 100644
index 0000000000..7b355c5581
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidatorTest.java
@@ -0,0 +1,48 @@
+package seedu.budgetbuddy.validators.expense;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class DeleteExpenseValidatorTest {
+
+ private String baseCommand;
+
+ @BeforeEach
+ void setUp() {
+ baseCommand = "delete expense ";
+ }
+
+ @Test
+ void testInvalidDeleteExpenseCommand() {
+ Command command = DeleteExpenseValidator.processCommand(baseCommand + "invalid");
+ assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to invalid input.");
+ assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser());
+ }
+
+ @Test
+ void testNegativeIndex() {
+ Command command = DeleteExpenseValidator.processCommand(baseCommand + "-1");
+ assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to negative index.");
+ assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser());
+ }
+
+ @Test
+ void testZeroIndex() {
+ Command command = DeleteExpenseValidator.processCommand(baseCommand + "0");
+ assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to zero index.");
+ assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser());
+ }
+
+ @Test
+ void testEmptyInput() {
+ Command command = DeleteExpenseValidator.processCommand(baseCommand);
+ assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to missing index.");
+ assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidatorTest.java
new file mode 100644
index 0000000000..ccfef6d4d3
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidatorTest.java
@@ -0,0 +1,82 @@
+package seedu.budgetbuddy.validators.expense;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.DisplayExpensesForMonthWithCategoriesGraphCommand;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DisplayExpensesForMonthWithCategoriesValidatorTest {
+
+ @Test
+ public void processCommand_validCommand_returnsDisplayExpensesCommand() {
+ // Given a valid command string
+ String command = "display expenses with categories m/10/2024";
+
+ // Process the command using the validator
+ Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command);
+
+ // Check if the result is of type DisplayExpensesForMonthWithCategoriesGraphCommand
+ assertInstanceOf(DisplayExpensesForMonthWithCategoriesGraphCommand.class, result);
+
+ // Check if the year and month were correctly parsed
+ YearMonth expectedYearMonth = YearMonth.of(2024, 10);
+ DisplayExpensesForMonthWithCategoriesGraphCommand displayCommand =
+ (DisplayExpensesForMonthWithCategoriesGraphCommand) result;
+ assertEquals(expectedYearMonth, displayCommand.getYearMonth(), "YearMonth should match the expected value.");
+ }
+
+ @Test
+ public void processCommand_invalidDate_returnsIncorrectCommand() {
+ // Given a command with an invalid date format
+ String command = "display expenses with categories m/invalid-date";
+
+ // Process the command using the validator
+ Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command);
+
+ // Check if the result is an IncorrectCommand
+ assertInstanceOf(IncorrectCommand.class, result);
+
+ // Check if the error message is correct
+ IncorrectCommand incorrectCommand = (IncorrectCommand) result;
+ assertEquals("Invalid Date",
+ incorrectCommand.getFeedbackToUser());
+ }
+
+ @Test
+ public void processCommand_emptyDate_returnsIncorrectCommand() {
+ // Given a command with no date provided
+ String command = "display expenses with categories";
+
+ // Process the command using the validator
+ Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command);
+
+ // Check if the result is an IncorrectCommand
+ assertInstanceOf(IncorrectCommand.class, result);
+
+ // Check if the error message is correct
+ IncorrectCommand incorrectCommand = (IncorrectCommand) result;
+ assertEquals("Please provide a valid month", incorrectCommand.getFeedbackToUser());
+ }
+
+ @Test
+ public void processCommand_invalidMonthFormat_returnsIncorrectCommand() {
+ // Given a command with an invalid month format
+ String command = "display expenses with categories m/13/2024";
+
+ // Process the command using the validator
+ Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command);
+
+ // Check if the result is an IncorrectCommand
+ assertInstanceOf(IncorrectCommand.class, result);
+
+ // Check if the error message is correct
+ IncorrectCommand incorrectCommand = (IncorrectCommand) result;
+ assertEquals("Invalid Date", incorrectCommand.getFeedbackToUser());
+ }
+}
+
diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidatorTest.java
new file mode 100644
index 0000000000..4008d8cbe1
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidatorTest.java
@@ -0,0 +1,51 @@
+package seedu.budgetbuddy.validators.expense;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.DisplayTotalExpensesCommand;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DisplayTotalExpensesValidatorTest {
+
+ private String baseCommand;
+
+ @BeforeEach
+ void setUp() {
+ baseCommand = "display monthly expenses";
+ }
+
+ @Test
+ void testValidYearInput() {
+ Command command = DisplayTotalExpensesValidator.processCommand(baseCommand + " y/2023");
+ assertInstanceOf(DisplayTotalExpensesCommand.class, command);
+ assertEquals("Displaying expense graph for 2023", ((DisplayTotalExpensesCommand) command).
+ getFeedbackToUser());
+
+ }
+
+ @Test
+ void testMissingYearInput() {
+ Command command = DisplayTotalExpensesValidator.processCommand(baseCommand);
+ assertInstanceOf(IncorrectCommand.class, command);
+ assertEquals("Please provide a year.", ((IncorrectCommand) command).getFeedbackToUser());
+ }
+
+ @Test
+ void testInvalidYearFormat() {
+ Command command = DisplayTotalExpensesValidator.processCommand(baseCommand + " y/abc");
+ assertInstanceOf(IncorrectCommand.class, command);
+ assertEquals("Invalid or missing year format.", ((IncorrectCommand) command).getFeedbackToUser());
+ }
+
+ @Test
+ void testInvalidCommandFormat() {
+ Command command = DisplayTotalExpensesValidator.processCommand(baseCommand + " y2023");
+ assertInstanceOf(IncorrectCommand.class, command);
+ assertEquals("Unknown command 'y2023'. Expected format: 'y/YYYY'", ((IncorrectCommand) command).
+ getFeedbackToUser());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/EditExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/EditExpenseValidatorTest.java
new file mode 100644
index 0000000000..fa3dffa235
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/expense/EditExpenseValidatorTest.java
@@ -0,0 +1,81 @@
+package seedu.budgetbuddy.validators.expense;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.EditExpenseCommand;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+class EditExpenseValidatorTest {
+
+ private ExpenseManager testExpenseManager;
+
+ void createTestCase (){
+ testExpenseManager = new ExpenseManager();
+ Expense testExpense = new Expense("testExpense", 12.00, LocalDate.now(), Category.OTHERS);
+ testExpenseManager.addExpense(testExpense);
+ }
+
+ @Test
+ void processFirstCommand_validIndexGiven_expectEditExpenseCommandType() {
+ createTestCase();
+ String userInput = "edit expense 1";
+ Command validCommand = EditExpenseValidator.processFirstCommand(userInput);
+ assertEquals(EditExpenseCommand.class, validCommand.getClass());
+ }
+
+ @Test
+ void processFirstCommand_invalidInputGiven_expectIncorrectCommandType() {
+ String userInput = "edit expense 50";
+ Command invalidCommand = EditExpenseValidator.processFirstCommand(userInput);
+ assertEquals(IncorrectCommand.class ,invalidCommand.getClass());
+ }
+
+ @Test
+ void processSecondCommand_validAmountGiven_expectTrue() {
+ String userInput = "a/500";
+ Boolean validResult = EditExpenseValidator.processSecondCommand(userInput);
+ assertEquals(true, validResult);
+ }
+
+ @Test
+ void processSecondCommand_validCategoryGiven_expectTrue() {
+ String userInput = "c/food";
+ Boolean validResult = EditExpenseValidator.processSecondCommand(userInput);
+ assertEquals(true, validResult);
+ }
+
+ @Test
+ void processSecondCommand_validDateGiven_expectTrue() {
+ String userInput = "d/12/10/2024";
+ Boolean validResult = EditExpenseValidator.processSecondCommand(userInput);
+ assertEquals(true, validResult);
+ }
+
+ @Test
+ void processSecondCommand_invalidAmountGiven_expectFalse() {
+ String userInput = "a/-500";
+ Boolean invalidResult = EditExpenseValidator.processSecondCommand(userInput);
+ assertEquals(false, invalidResult);
+ }
+ @Test
+ void processSecondCommand_invalidDateGiven_expectFalse() {
+ String userInput = "d/20202020";
+ Boolean invalidResult = EditExpenseValidator.processSecondCommand(userInput);
+ assertEquals(false, invalidResult);
+ }
+
+ @Test
+ void processSecondCommand_invalidCategoryGiven_expectFalse() {
+ String userInput = "c/invalid";
+ Boolean invalidResult = EditExpenseValidator.processSecondCommand(userInput);
+ assertEquals(false, invalidResult);
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/ListExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/ListExpenseValidatorTest.java
new file mode 100644
index 0000000000..0173c3f740
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/expense/ListExpenseValidatorTest.java
@@ -0,0 +1,80 @@
+package seedu.budgetbuddy.validators.expense;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.expense.ListExpenseCommand;
+import seedu.budgetbuddy.transaction.Category;
+
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth;
+
+class DisplayExpenseValidatorTest {
+
+ @Test
+ void checkListType_categoryIsEmpty_expectNullCategory(){
+ ListExpenseValidator expenseValidator = new ListExpenseValidator();
+ Category category = null;
+ YearMonth yearMonth = validateYearMonth("02/2024");
+ ListExpenseCommand command = (ListExpenseCommand) expenseValidator.checkListType(category, yearMonth);
+ assertEquals(null, command.getCategory());
+ assertEquals(yearMonth,command.getMonth());
+ }
+
+ @Test
+ void checkListType_monthIsEmpty_expectNullMonth(){
+ ListExpenseValidator expenseValidator = new ListExpenseValidator();
+ Category category = Category.FOOD;
+ YearMonth yearMonth = null;
+ ListExpenseCommand command = (ListExpenseCommand) expenseValidator.checkListType(category, yearMonth);
+ assertEquals(category, command.getCategory());
+ assertEquals(yearMonth,command.getMonth());
+ }
+
+ @Test
+ void checkListType_categoryMonthSpecified_expectSameValues(){
+ ListExpenseValidator expenseValidator = new ListExpenseValidator();
+ Category category = Category.FOOD;
+ YearMonth yearMonth = validateYearMonth("02/2024");
+ ListExpenseCommand command = (ListExpenseCommand) expenseValidator.checkListType(category, yearMonth);
+ assertEquals(category, command.getCategory());
+ assertEquals(yearMonth,command.getMonth());
+ }
+
+ @Test
+ void processCommand_validInput_expectListExpenseCommandType(){
+ String userInput = "list expenses";
+ Command validCommand = ListExpenseValidator.processCommand(userInput);
+ assertEquals(validCommand.getClass(), ListExpenseCommand.class);
+ }
+
+ @Test
+ void processCommand_validInputWithMonth_expectListExpenseCommandType(){
+ String userInput = "list expenses m/12/2024";
+ Command validCommand = ListExpenseValidator.processCommand(userInput);
+ assertEquals(validCommand.getClass(), ListExpenseCommand.class);
+ }
+
+ @Test
+ void processCommand_validInputWithCategory_expectListExpenseCommandType(){
+ String userInput = "list expenses c/food";
+ Command validCommand = ListExpenseValidator.processCommand(userInput);
+ assertEquals(validCommand.getClass(), ListExpenseCommand.class);
+ }
+
+ @Test
+ void processCommand_validInputWithMonthAndCategory_expectListExpenseCommandType(){
+ String userInput = "list expenses m/12/2024 c/food";
+ Command validCommand = ListExpenseValidator.processCommand(userInput);
+ assertEquals(validCommand.getClass(), ListExpenseCommand.class);
+ }
+
+ @Test
+ void processCommand_invalidInput_expectInvalidCommandType(){
+ String userInput = "list expenses c/invalid";
+ Command invalidCommand = ListExpenseValidator.processCommand(userInput);
+ assertEquals(invalidCommand.getClass(), IncorrectCommand.class);
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/income/AddIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/AddIncomeValidatorTest.java
new file mode 100644
index 0000000000..9ce492150c
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/income/AddIncomeValidatorTest.java
@@ -0,0 +1,97 @@
+package seedu.budgetbuddy.validators.income;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.income.AddIncomeCommand;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class AddIncomeValidatorTest {
+
+ @Test
+ void testNoDescriptionProvided() {
+ Command result = AddIncomeValidator.processCommand("add income");
+ assertInstanceOf(IncorrectCommand.class, result);
+ assertEquals("No description provided.", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void testValidCommand() {
+ String command = "add income Lunch income a/50.5 d/25/10/2024";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(AddIncomeCommand.class, result, "Expected AddIncomeCommand instance");
+ AddIncomeCommand addIncomeCommand = (AddIncomeCommand) result;
+
+ assertEquals("Lunch income", addIncomeCommand.getDescription(), "Expected description to " +
+ "match");
+ assertEquals(50.5, addIncomeCommand.getAmount(), "Expected amount to match");
+ assertEquals(LocalDate.of(2024, 10, 25), addIncomeCommand.getDate(),
+ "Expected date to match");
+ }
+
+ @Test
+ void testMissingAmount() {
+ String command = "add income Salary d/25/10/2024";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(IncorrectCommand.class, result);
+ assertEquals("Amount not entered.", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void testInvalidAmountFormat() {
+ String command = "add income Bonus a/abc d/25/10/2024";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(IncorrectCommand.class, result);
+ assertEquals("Amount should be a positive number with up to 2 decimal places.",
+ ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void testMissingDate() {
+ String command = "add income Freelance job a/100";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(AddIncomeCommand.class, result);
+ AddIncomeCommand addIncomeCommand = (AddIncomeCommand) result;
+
+ assertEquals("Freelance job", addIncomeCommand.getDescription());
+ assertEquals(100, addIncomeCommand.getAmount());
+ assertEquals(LocalDate.now(), addIncomeCommand.getDate(), "Expected default date to be today");
+ }
+
+ @Test
+ void testInvalidDateFormat() {
+ String command = "add income Part-time a/50 d/invalid-date";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(IncorrectCommand.class, result);
+ assertEquals("Invalid date format. Use d/dd/MM/yyyy.", ((IncorrectCommand) result).
+ getFeedbackToUser());
+ }
+
+ @Test
+ void testEmptyDescription() {
+ String command = "add income a/100 d/25/10/2024";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(IncorrectCommand.class, result);
+ assertEquals("Description cannot be empty.", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void testNegativeAmount() {
+ String command = "add income Refund a/-50 d/25/10/2024";
+ Command result = AddIncomeValidator.processCommand(command);
+
+ assertInstanceOf(IncorrectCommand.class, result);
+ assertEquals("Amount should be a positive number with up to 2 decimal places.",
+ ((IncorrectCommand) result).getFeedbackToUser());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidatorTest.java
new file mode 100644
index 0000000000..a05c78ef27
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidatorTest.java
@@ -0,0 +1,78 @@
+package seedu.budgetbuddy.validators.income;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.income.DeleteIncomeCommand;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DeleteIncomeValidatorTest {
+
+ @Test
+ void processCommand_outOfRangeIndex_returnsIncorrectIncomeCommand() {
+ String command = "delete income 10";
+ Command result = DeleteIncomeValidator.processCommand(command.trim());
+
+ assertTrue(result instanceof IncorrectCommand);
+ assertEquals("Invalid Index", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void processCommand_noIndexProvided_returnsIncorrectCommandIndexNotGiven() {
+ String command = "delete income";
+ Command result = DeleteIncomeValidator.processCommand(command);
+
+ assertTrue(result instanceof IncorrectCommand);
+ assertEquals("Index not given", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void processCommand_invalidIndex_returnsIncorrectCommandInvalidIndex() {
+ String command = "delete income abc";
+ Command result = DeleteIncomeValidator.processCommand(command);
+
+ assertTrue(result instanceof IncorrectCommand);
+ assertEquals("Invalid Index", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void processCommand_validIndex_returnsDeleteIncomeCommand() {
+ IncomeManager.addIncome(new Income("Salary", 5000.00, LocalDate.of(2024, 11, 1)));
+ IncomeManager.addIncome(new Income("Freelance", 1500.00, LocalDate.of(2024, 11, 5)));
+ IncomeManager.addIncome(new Income("Bonus", 2000.00, LocalDate.of(2024, 11, 10)));
+
+ String command = "delete income 3";
+ Command result = DeleteIncomeValidator.processCommand(command);
+
+ assertTrue(result instanceof DeleteIncomeCommand);
+ assertEquals(2, ((DeleteIncomeCommand) result).getIndex()); // Expecting index to be 2 due to 0-based indexing
+ }
+
+ @Test
+ void processCommand_negativeIndex_returnsIncorrectCommandInvalidIndex() {
+ String command = "delete income -1";
+ Command result = DeleteIncomeValidator.processCommand(command);
+
+ assertTrue(result instanceof IncorrectCommand);
+ assertEquals("Invalid Index", ((IncorrectCommand) result).getFeedbackToUser());
+ }
+
+ @Test
+ void processCommand_extraSpacesAfterIndex_returnsDeleteIncomeCommand() {
+ IncomeManager.addIncome(new Income("Salary", 5000.00, LocalDate.of(2024, 11, 1)));
+ IncomeManager.addIncome(new Income("Freelance", 1500.00, LocalDate.of(2024, 11, 5)));
+ IncomeManager.addIncome(new Income("Bonus", 2000.00, LocalDate.of(2024, 11, 10)));
+
+ String command = "delete income 3 ";
+ Command result = DeleteIncomeValidator.processCommand(command.trim());
+
+ assertTrue(result instanceof DeleteIncomeCommand);
+ assertEquals(2, ((DeleteIncomeCommand) result).getIndex());
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidatorTest.java
new file mode 100644
index 0000000000..2a4c62f172
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidatorTest.java
@@ -0,0 +1,92 @@
+package seedu.budgetbuddy.validators.income;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.income.DisplayIncomeSpentCommand;
+import seedu.budgetbuddy.exceptions.BudgetBuddyException;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DisplayIncomeSpentValidatorTest {
+
+ private static final YearMonth VALID_MONTH = YearMonth.of(2024, 11);
+ private static final YearMonth INVALID_MONTH = YearMonth.of(2023, 2);
+
+ @BeforeEach
+ public void setUp() {
+ // Reset IncomeManager to ensure no residual data from other tests
+ IncomeManager.getIncomes().clear();
+ }
+
+ @Test
+ public void processCommand_validCommandWithoutMonth_returnsCommandForCurrentMonth() throws BudgetBuddyException {
+ // Add mock income for current month
+ IncomeManager.addIncome(new Income("Salary", 2000.0,
+ LocalDate.of(2024, 11, 5)));
+
+ // Call method with valid command without month specification
+ Command command = DisplayIncomeSpentValidator.processCommand("display income spent");
+
+ assertNotNull(command);
+ assertTrue(command instanceof DisplayIncomeSpentCommand);
+ DisplayIncomeSpentCommand displayCommand = (DisplayIncomeSpentCommand) command;
+ assertEquals(VALID_MONTH, displayCommand.getMonth()); // Should match current month (November 2024)
+ }
+
+ @Test
+ public void processCommand_validCommandWithValidMonth_returnsCommandForSpecifiedMonth()
+ throws BudgetBuddyException {
+ // Add dummy income for the specified month (November 2024)
+ IncomeManager.addIncome(new Income("Salary", 2000.0,
+ LocalDate.of(2024, 11, 5)));
+
+ // Call method with valid command specifying a month
+ Command command = DisplayIncomeSpentValidator.processCommand("display income spent m/11/2024");
+
+ assertNotNull(command);
+ assertTrue(command instanceof DisplayIncomeSpentCommand);
+ DisplayIncomeSpentCommand displayCommand = (DisplayIncomeSpentCommand) command;
+ assertEquals(VALID_MONTH, displayCommand.getMonth()); // Should match November 2024
+ }
+
+ @Test
+ public void processCommand_invalidMonthFormat_throwsBudgetBuddyException() {
+ // Call method with an invalid month format
+ BudgetBuddyException thrown = assertThrows(BudgetBuddyException.class, () -> {
+ DisplayIncomeSpentValidator.processCommand("display income spent m/invalid-month");
+ });
+ assertTrue(thrown.getMessage().contains("Invalid month format. Use m/MM/yyyy."));
+ }
+
+ @Test
+ public void processCommand_noIncomeRecordedForMonth_throwsBudgetBuddyException() {
+ // Call method with valid command specifying a month but no income added for that month
+ BudgetBuddyException thrown = assertThrows(BudgetBuddyException.class, () -> {
+ DisplayIncomeSpentValidator.processCommand("display income spent m/11/2024");
+ });
+ assertTrue(thrown.getMessage().contains("No income recorded for the month: 2024-11"));
+ }
+
+ @Test
+ public void processCommand_validMonthNoIncome_throwsBudgetBuddyException() {
+ // Add dummy income with a value of 0 for the month of November
+ IncomeManager.addIncome(new Income("Salary", 0.0,
+ LocalDate.of(2024, 11, 5)));
+
+ // Call method with valid month and no income recorded for that month
+ BudgetBuddyException thrown = assertThrows(BudgetBuddyException.class, () -> {
+ DisplayIncomeSpentValidator.processCommand("display income spent m/11/2024");
+ });
+ assertTrue(thrown.getMessage().contains("No income recorded for the month: 2024-11"));
+ }
+}
+
diff --git a/src/test/java/seedu/budgetbuddy/validators/income/EditIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/EditIncomeValidatorTest.java
new file mode 100644
index 0000000000..568b9c1498
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/income/EditIncomeValidatorTest.java
@@ -0,0 +1,74 @@
+package seedu.budgetbuddy.validators.income;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.income.EditIncomeCommand;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+class EditIncomeValidatorTest {
+
+ private IncomeManager testIncomeManager;
+
+ void createTestCase (){
+ testIncomeManager = new IncomeManager();
+ Income testIncome = new Income("testExpense", 12.00, LocalDate.now());
+ testIncomeManager.addIncome(testIncome);
+ }
+
+ @Test
+ void processFirstCommand_validIndexGiven_expectEditExpenseCommandType() {
+ createTestCase();
+ String userInput = "edit income 1";
+ Command validCommand = EditIncomeValidator.processFirstCommand(userInput);
+ assertEquals(EditIncomeCommand.class, validCommand.getClass());
+ }
+
+ @Test
+ void processFirstCommand_invalidInputGiven_expectIncorrectCommandType() {
+ String userInput = "edit income 50";
+ Command invalidCommand = EditIncomeValidator.processFirstCommand(userInput);
+ assertEquals(IncorrectCommand.class ,invalidCommand.getClass());
+ }
+
+ @Test
+ void processSecondCommand_validAmountGiven_expectTrue() {
+ String userInput = "a/500";
+ Boolean validResult = EditIncomeValidator.processSecondCommand(userInput);
+ assertEquals(true, validResult);
+ }
+
+ @Test
+ void processSecondCommand_validDateGiven_expectTrue() {
+ String userInput = "d/12/10/2024";
+ Boolean validResult = EditIncomeValidator.processSecondCommand(userInput);
+ assertEquals(true, validResult);
+ }
+
+ @Test
+ void processSecondCommand_emptyInputGiven_expectFalse() {
+ String userInput = "";
+ Boolean invalidResult = EditIncomeValidator.processSecondCommand(userInput);
+ assertEquals(false, invalidResult);
+ }
+
+ @Test
+ void processSecondCommand_invalidAmountGiven_expectFalse() {
+ String userInput = "a/-500";
+ Boolean invalidResult = EditIncomeValidator.processSecondCommand(userInput);
+ assertEquals(false, invalidResult);
+ }
+
+ @Test
+ void processSecondCommand_invalidDateGiven_expectFalse() {
+ String userInput = "d/202020202";
+ Boolean invalidResult = EditIncomeValidator.processSecondCommand(userInput);
+ assertEquals(false, invalidResult);
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/income/ListIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/ListIncomeValidatorTest.java
new file mode 100644
index 0000000000..df7047054d
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/income/ListIncomeValidatorTest.java
@@ -0,0 +1,33 @@
+package seedu.budgetbuddy.validators.income;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.commands.Command;
+import seedu.budgetbuddy.commands.IncorrectCommand;
+import seedu.budgetbuddy.commands.income.ListIncomeCommand;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+class ListIncomeValidatorTest {
+
+ @Test
+ void processCommand_validInput_expectListExpenseCommandType(){
+ String userInput = "list incomes";
+ Command validCommand = ListIncomeValidator.processCommand(userInput);
+ assertEquals(validCommand.getClass(), ListIncomeCommand.class);
+ }
+
+ @Test
+ void processCommand_validInputWithMont_expectListExpenseCommandType(){
+ String userInput = "list incomes m/12/2024";
+ Command validCommand = ListIncomeValidator.processCommand(userInput);
+ assertEquals(validCommand.getClass(), ListIncomeCommand.class);
+ }
+
+ @Test
+ void processCommand_invalidInput_expectInvalidCommandType(){
+ String userInput = "list incomes m/20/2024";
+ Command invalidCommand = ListIncomeValidator.processCommand(userInput);
+ assertEquals(invalidCommand.getClass(), IncorrectCommand.class);
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/saving/AmountValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/saving/AmountValidatorTest.java
new file mode 100644
index 0000000000..9c652794d5
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/saving/AmountValidatorTest.java
@@ -0,0 +1,50 @@
+package seedu.budgetbuddy.validators.saving;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.validators.AmountValidator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+class AmountValidatorTest {
+
+ @Test
+ void validateAmount_inputIsDouble_expectDouble() {
+ AmountValidator amountValidator = new AmountValidator();
+ String input = "a/123.45";
+ Double result = amountValidator.validateAmount(input);
+ assertEquals(Double.valueOf(123.45), result);
+ }
+
+ @Test
+ void validateAmount_inputWith3DecimalPoints_expectNegativeOne() {
+ AmountValidator amountValidator = new AmountValidator();
+ String input = "a/123.4567";
+ Double result = amountValidator.validateAmount(input);
+ assertEquals(Double.valueOf(-1), result);
+ }
+
+ @Test
+ void validateAmount_inputIsNotDouble_expectNegativeOne() {
+ AmountValidator amountValidator = new AmountValidator();
+ String input = "a/invalid";
+ Double result = amountValidator.validateAmount(input);
+ assertEquals(Double.valueOf(-1), result);
+ }
+
+ @Test
+ void validateAmount_inputContainsAlphabet_expectNegativeOne() {
+ AmountValidator amountValidator = new AmountValidator();
+ String input = "a/123d";
+ Double result = amountValidator.validateAmount(input);
+ assertEquals(Double.valueOf(-1), result);
+ }
+
+ @Test
+ void validateAmount_inputContainsTwoDecimal_expectNegativeOne() {
+ AmountValidator amountValidator = new AmountValidator();
+ String input = "a/123.1.1";
+ Double result = amountValidator.validateAmount(input);
+ assertEquals(Double.valueOf(-1), result);
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/saving/DateValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/saving/DateValidatorTest.java
new file mode 100644
index 0000000000..e8ca40fcb6
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/saving/DateValidatorTest.java
@@ -0,0 +1,27 @@
+package seedu.budgetbuddy.validators.saving;
+
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.validators.DateValidator;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+
+class DateValidatorTest {
+ @Test
+ void validateDate_validDate_expectParsedDate() {
+ String input = "d/15/8/2023";
+ LocalDate result = DateValidator.validateDate(input);
+ LocalDate expected = LocalDate.of(2023, 8, 15);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void validateDate_invalidDate_expectNull() {
+ String input = "d/invalid";
+ LocalDate result = DateValidator.validateDate(input);
+ assertNull(result);
+ }
+}
diff --git a/src/test/java/seedu/budgetbuddy/validators/saving/IndexValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/saving/IndexValidatorTest.java
new file mode 100644
index 0000000000..0ac417c89b
--- /dev/null
+++ b/src/test/java/seedu/budgetbuddy/validators/saving/IndexValidatorTest.java
@@ -0,0 +1,82 @@
+package seedu.budgetbuddy.validators.saving;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.budgetbuddy.transaction.expense.Expense;
+import seedu.budgetbuddy.transaction.expense.ExpenseManager;
+import seedu.budgetbuddy.transaction.income.Income;
+import seedu.budgetbuddy.transaction.income.IncomeManager;
+import seedu.budgetbuddy.transaction.Category;
+import seedu.budgetbuddy.validators.IndexValidator;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+/**
+ * JUnit test class for IndexValidator.
+ */
+class IndexValidatorTest {
+
+ @BeforeEach
+ void setUp() {
+ // Initialize a list of expenses with Category, description, amount, and date
+ ArrayList expenses = new ArrayList<>();
+ expenses.add(new Expense("Test1", 10.50, LocalDate.of(2024, 1, 1), Category.OTHERS));
+ expenses.add(new Expense("Test2", 2.80, LocalDate.of(2024, 2, 1), Category.OTHERS));
+ expenses.add(new Expense("Test3", 12.00, LocalDate.of(2024, 3, 1), Category.OTHERS));
+
+ // Initialize a list of incomes
+ ArrayList incomes = new ArrayList<>();
+ incomes.add(new Income("Test1", 10.50, LocalDate.of(2024, 1, 1)));
+ incomes.add(new Income("Test2", 1.50, LocalDate.of(2024, 2, 1)));
+
+ // Initialize ExpenseManager and IncomeManager with the number of expenses and incomes
+ new ExpenseManager(expenses, expenses.size());
+ new IncomeManager(incomes, incomes.size());
+ }
+
+ @Test
+ void validateExpenseIndex_validIndexWithinRange_expectValidIndex() {
+ String input = "2";
+ int result = IndexValidator.validateExpenseIndex(input);
+ assertEquals(2, result, "Expected valid index 2 but got " + result);
+ }
+
+ @Test
+ void validateExpenseIndex_invalidIndex_expectNegativeOne() {
+ String input = "invalid";
+ int result = IndexValidator.validateExpenseIndex(input);
+ assertEquals(-1, result, "Expected -1 for invalid input but got " + result);
+ }
+
+ @Test
+ void validateExpenseIndex_indexOutOfRange_expectNegativeOne() {
+ String input = "5"; // Out of range since there are only 3 expenses
+ int result = IndexValidator.validateExpenseIndex(input);
+ assertEquals(-1, result, "Expected -1 for out-of-range index but got " + result);
+ }
+
+ @Test
+ void validateIncomeIndex_validIndexWithinRange_expectValidIndex() {
+ String input = "1";
+ int result = IndexValidator.validateIncomeIndex(input);
+ assertEquals(1, result, "Expected valid index 1 but got " + result);
+ }
+
+ @Test
+ void validateIncomeIndex_invalidIndex_expectNegativeOne() {
+ String input = "invalid";
+ int result = IndexValidator.validateIncomeIndex(input);
+ assertEquals(-1, result, "Expected -1 for invalid input but got " + result);
+ }
+
+ @Test
+ void validateIncomeIndex_indexOutOfRange_expectNegativeOne() {
+ String input = "3"; // Out of range since there are only 2 incomes
+ int result = IndexValidator.validateIncomeIndex(input);
+ assertEquals(-1, result, "Expected -1 for out-of-range index but got " + result);
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..83d0e38c4f 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,339 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
-What is your name?
-Hello James Gosling
+========================================================
+Welcome to Budget Buddy!
+========================================================
+Enter commands: ========================================================
+No description provided.
+========================================================
+Enter commands: ========================================================
+Amount not entered.
+========================================================
+Enter commands: ========================================================
+Amount should be a positive number with up to 2 decimal places.
+========================================================
+Enter commands: ========================================================
+Invalid date format. Use d/DD/MM/YYYY.
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been added:
+Description: Japan Flight Amount: 123.45 Date: 2024-12-30 Category: TRANSPORT
+You have 1 expense transaction(s) in total.
+The remaining budget for 2024-12 in the TRANSPORT category is: -123.45
+Caution! You have exceeded your budget!
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been added:
+Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS
+You have 2 expense transaction(s) in total.
+The remaining budget for 2024-10 in the OTHERS category is: -100.10
+Caution! You have exceeded your budget!
+========================================================
+Enter commands: ========================================================
+1. Description: Japan Flight Amount: 123.45 Date: 2024-12-30 Category: TRANSPORT
+2. Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS
+There are 2 expense(s) in total, with a sum of $223.55.
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been deleted:
+Description: Japan Flight Amount: 123.45 Date: 2024-12-30 Category: TRANSPORT
+You have 1 expense transaction(s) in total.
+No budget found for 2024-12.
+========================================================
+Enter commands: ========================================================
+Invalid input
+========================================================
+Enter commands: ========================================================
+1. Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS
+There are 1 expense(s) in total, with a sum of $100.10.
+========================================================
+Enter commands: ========================================================
+No description provided.
+========================================================
+Enter commands: ========================================================
+Description cannot be empty.
+========================================================
+Enter commands: ========================================================
+No description provided.
+========================================================
+Enter commands: ========================================================
+Description cannot be empty.
+========================================================
+Enter commands: ========================================================
+Amount should be a positive number with up to 2 decimal places.
+========================================================
+Enter commands: ========================================================
+Invalid date format. Use d/dd/MM/yyyy.
+========================================================
+Enter commands: ========================================================
+The following income transaction has been added:
+Description: Tuition Amount: 100.00 Date: 2024-12-15
+You have 1 income transaction(s) in total.
+========================================================
+Enter commands: ========================================================
+The following income transaction has been added:
+Description: Internship Amount: 123.00 Date: 2024-10-13
+You have 2 income transaction(s) in total.
+========================================================
+Enter commands: ========================================================
+No amount provided.
+========================================================
+Enter commands: ========================================================
+Invalid amount: -1000.0. Amount must be a positive value.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 1234.00 Date: 2024-11
+ Category: {OTHERS=1234.00}
+You have 1 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 800.00 Date: 2024-03
+ Category: {OTHERS=800.00}
+You have 2 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 3534.00 Date: 2024-11
+ Category: {OTHERS=3534.00}
+You have 2 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 3000.00 Date: 2024-07
+ Category: {OTHERS=3000.00}
+You have 3 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 5000.00 Date: 2024-02
+ Category: {OTHERS=5000.00}
+You have 4 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+No amount provided.
+========================================================
+Enter commands: ========================================================
+Invalid amount: -1000.0. Must be a positive value.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 2300.00 Date: 2024-11
+ Category: {OTHERS=2300.00}
+You have 4 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget has been deleted as its amount reached zero:
+Date: 2024-03
+You have 3 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget has been deleted as its amount reached zero:
+Date: 2024-11
+You have 2 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 3500.00 Date: 2024-02
+ Category: {OTHERS=3500.00}
+You have 2 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+Here is the budget for the specified month:
+Total Monthly Budget: 3500.00 Date: 2024-02
+ Category: {OTHERS=3500.00}
+========================================================
+Enter commands: ========================================================
+No budget found for date: 2022-07
+========================================================
+Enter commands: ========================================================
+Listing up to the 12 most recent budgets:
+1. Total Monthly Budget: 3000.00 Date: 2024-07
+ Category: {OTHERS=3000.00}
+2. Total Monthly Budget: 3500.00 Date: 2024-02
+ Category: {OTHERS=3500.00}
+========================================================
+Enter commands: ========================================================
+Invalid input
+========================================================
+Enter commands: ========================================================
+1. Description: Tuition Amount: 100.00 Date: 2024-12-15
+2. Description: Internship Amount: 123.00 Date: 2024-10-13
+There are 2 income(s) in total, with a sum of $223.00.
+========================================================
+Enter commands: ========================================================
+Invalid input
+========================================================
+Enter commands: ========================================================
+Invalid input
+========================================================
+Enter commands: ========================================================
+Invalid input
+========================================================
+Enter commands: ========================================================
+The following income transaction has been deleted:
+Description: Tuition Amount: 100.00 Date: 2024-12-15
+You have 1 income transaction(s) in total.
+========================================================
+Enter commands: ========================================================
+1. Description: Internship Amount: 123.00 Date: 2024-10-13
+There are 1 income(s) in total, with a sum of $123.00.
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been added:
+Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT
+You have 2 expense transaction(s) in total.
+The remaining budget for 2024-10 in the TRANSPORT category is: -123.45
+Caution! You have exceeded your budget!
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been added:
+Description: Ipad Amount: 123.45 Date: 2024-08-30 Category: EDUCATION
+You have 3 expense transaction(s) in total.
+The remaining budget for 2024-08 in the EDUCATION category is: -123.45
+Caution! You have exceeded your budget!
+========================================================
+Enter commands: ========================================================
+1. Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT
+Your total expense(s) for TRANSPORT are $123.45
+========================================================
+Enter commands: ========================================================
+1. Description: Ipad Amount: 123.45 Date: 2024-08-30 Category: EDUCATION
+Your total expense(s) for August 2024 are $123.45
+========================================================
+Enter commands: ========================================================
+1. Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT
+Your total expense(s) for TRANSPORT in October 2024 are $123.45
+========================================================
+Enter commands: ========================================================
+Unknown category. Use a valid category.
+========================================================
+Enter commands: ========================================================
+Invalid month format. Use m/MM/yyyy.
+========================================================
+Enter commands: ========================================================
+No expense entry with given parameters found, try again with a different parameter.
+========================================================
+Enter commands: ========================================================
+No expense entry with given parameters found, try again with a different parameter.
+========================================================
+Enter commands: ========================================================
+The following income transaction has been added:
+Description: Tuition Amount: 100.00 Date: 2024-12-15
+You have 2 income transaction(s) in total.
+========================================================
+Enter commands: ========================================================
+The following income transaction has been added:
+Description: Internship Amount: 123.00 Date: 2024-10-13
+You have 3 income transaction(s) in total.
+========================================================
+Enter commands: ========================================================
+1. Description: Internship Amount: 123.00 Date: 2024-10-13
+2. Description: Tuition Amount: 100.00 Date: 2024-12-15
+3. Description: Internship Amount: 123.00 Date: 2024-10-13
+There are 3 income(s) in total, with a sum of $346.00.
+========================================================
+Enter commands: ========================================================
+1. Description: Internship Amount: 123.00 Date: 2024-10-13
+2. Description: Internship Amount: 123.00 Date: 2024-10-13
+There are 2 income(s) in total for October 2024, with a sum of $246.0.
+========================================================
+Enter commands: ========================================================
+No income entry with given month found, try again with a different month.
+========================================================
+Enter commands: ========================================================
+Invalid month format. Use m/MM/yyyy.
+========================================================
+Enter commands: ========================================================
+The following income transaction has been added:
+Description: tutor Amount: 1500.00 Date: 2023-10-20
+You have 4 income transaction(s) in total.
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been added:
+Description: food Amount: 550.00 Date: 2023-10-30 Category: OTHERS
+You have 4 expense transaction(s) in total.
+The remaining budget for 2023-10 in the OTHERS category is: -550.00
+Caution! You have exceeded your budget!
+========================================================
+Enter commands: ========================================================
+Percentage of income spent for 2023-10: 36.7%
+========================================================
+Enter commands: ========================================================
+1. Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS
+2. Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT
+3. Description: Ipad Amount: 123.45 Date: 2024-08-30 Category: EDUCATION
+4. Description: food Amount: 550.00 Date: 2023-10-30 Category: OTHERS
+There are 4 expense(s) in total, with a sum of $897.00.
+========================================================
+Enter commands: Edit the following fields as follows: Amount: a/, Category: c/, Date: d/
+You may the exit change menu by pressing enter with no input.
+Currently Editing Entry:
+Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS
+Enter edit Field: ========================================================
+Edited Expense:
+Description: Buy Tesla Stock Amount: 50.00 Date: 2024-10-02 Category: OTHERS
+========================================================
+Enter commands: ========================================================
+The following expense transaction has been added:
+Description: Fridge Amount: 5000.00 Date: 2024-02-01 Category: OTHERS
+You have 5 expense transaction(s) in total.
+The remaining budget for 2024-02 in the OTHERS category is: -1500.00
+Caution! You have exceeded your budget!
+========================================================
+Enter commands: ========================================================
+The following budget amount has been updated:
+Total Monthly Budget: 5500.00 Date: 2024-02
+ Category: {OTHERS=5500.00}
+You have 2 budget(s) in total.
+========================================================
+Enter commands: ========================================================
+All budgets after expense deductions:
+Total Monthly Budget: 3000.00 Date: 2024-07
+ Category: {OTHERS=3000.00}
+Total Monthly Budget: 500.00 Date: 2024-02
+ Category: {OTHERS=500.00}
+Total Monthly Budget: -173.45 Date: 2024-10
+ Category: {TRANSPORT=-123.45, OTHERS=-50.00}
+Total Monthly Budget: -123.45 Date: 2024-08
+ Category: {EDUCATION=-123.45}
+Total Monthly Budget: -550.00 Date: 2023-10
+ Category: {OTHERS=-550.00}
+========================================================
+Enter commands: ========================================================
+1. Description: Internship Amount: 123.00 Date: 2024-10-13
+2. Description: Tuition Amount: 100.00 Date: 2024-12-15
+3. Description: Internship Amount: 123.00 Date: 2024-10-13
+4. Description: tutor Amount: 1500.00 Date: 2023-10-20
+There are 4 income(s) in total, with a sum of $1846.00.
+========================================================
+Enter commands: Edit the following fields as follows: Amount: a/, Date: d/
+You may the exit change menu by pressing enter with no input.
+Currently Editing Entry:
+Description: Internship Amount: 123.00 Date: 2024-10-13
+Enter edit Field: ========================================================
+Edited Income:
+Description: Internship Amount: 10000.00 Date: 2024-12-10
+========================================================
+Enter commands: ========================================================
+Edit index must be greater than 0.
+========================================================
+Enter commands: ========================================================
+Input index is larger than the number of incomes. Try with a smaller index
+========================================================
+Enter commands: Edit the following fields as follows: Amount: a/, Date: d/
+You may the exit change menu by pressing enter with no input.
+Currently Editing Entry:
+Description: Internship Amount: 123.00 Date: 2024-10-13
+Enter edit Field: ========================================================
+No valid fields detected, Exiting change menu.
+========================================================
+Enter commands: ========================================================
+1. Description: Internship Amount: 10000.00 Date: 2024-12-10
+2. Description: Tuition Amount: 100.00 Date: 2024-12-15
+3. Description: Internship Amount: 123.00 Date: 2024-10-13
+4. Description: tutor Amount: 1500.00 Date: 2023-10-20
+There are 4 income(s) in total, with a sum of $11723.00.
+========================================================
+Enter commands: ========================================================
+Bye!
+========================================================
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..9d462f8f0e 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1,74 @@
-James Gosling
\ No newline at end of file
+add expense
+add expense no amount
+add expense negative amount a/-1
+add expense wrong date format a/123.45 d/wrong
+add expense Japan Flight a/123.45 d/30/12/2024 c/transport
+add expense Buy Tesla Stock a/100.10 d/02/10/2024
+list expenses
+delete expense 1
+list expense
+list expenses
+add expense
+add expense a/122
+add income
+add income a/123
+add income Negative amount a/-1
+add income wrong date a/122 d/wrong
+add income Tuition a/100.00 d/15/12/2024
+add income Internship a/123 d/13/10/2024
+add budget
+add budget a/-1000
+add budget a/1234 m/11/2024
+add budget a/800 m/03/2024
+add budget a/2300 m/11/2024
+add budget a/3000 m/07/2024
+add budget a/5000 m/02/2024
+deduct budget
+deduct budget a/-1000
+deduct budget a/1234 m/11/2024
+deduct budget a/800 m/03/2024
+deduct budget a/2300 m/11/2024
+deduct budget a/1500 m/02/2024
+list budgets m/02/2024
+list budgets m/07/2022
+list budgets
+list budget
+list incomes
+remove income 1
+remove incomes 1
+list income
+delete income 1
+list incomes
+add expense Japan Flight a/123.45 d/30/10/2024 c/transport
+add expense Ipad a/123.45 d/30/08/2024 c/education
+list expenses c/transport
+list expenses m/08/2024
+list expenses c/transport m/10/2024
+list expenses c/incorrect category
+list expenses m/invalid month
+list expenses c/food
+list expenses c/education m/02/2024
+add income Tuition a/100.00 d/15/12/2024
+add income Internship a/123 d/13/10/2024
+list incomes
+list incomes m/10/2024
+list incomes m/08/2024
+list incomes m/20/2024
+add income tutor a/1500 d/20/10/2023
+add expense food a/550 d/30/10/2023
+display income spent m/10/2023
+list expenses
+edit expense 1
+a/50
+add expense Fridge a/5000 d/1/2/2024 c/others
+add budget a/2000 m/02/2024
+list remaining budget
+list incomes
+edit income 1
+a/10000 d/10/12/2024
+edit income -10
+edit income 50
+edit income 3
+c/food
+list incomes
+bye
\ No newline at end of file