diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index 3caea4b..10ae9b9 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -3,8 +3,8 @@ import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange; import edu.ntnu.idi.idatt2003.g40.mappe.model.Player; import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; -import edu.ntnu.idi.idatt2003.g40.mappe.service.FileConverter; -import edu.ntnu.idi.idatt2003.g40.mappe.service.FileParser; +import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileParser; +import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileManager; import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; import edu.ntnu.idi.idatt2003.g40.mappe.utils.ConfigValues; @@ -86,9 +86,9 @@ public void start(final Stage stage) throws Exception { ViewManager viewManager = new ViewManager(stage, eventManager); List stocksInFile; - FileParser parser1 = new FileParser("/sp500.csv"); + StockFileManager parser1 = new StockFileManager("src/main/resources/sp500.csv"); - FileConverter converter1 = new FileConverter(); + StockFileParser converter1 = new StockFileParser(); stocksInFile = converter1.getStocksFromStrings(parser1.readFile()); Exchange exchange = new Exchange("Exchange", stocksInFile); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java index b73f634..9cf5230 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java @@ -1,38 +1,35 @@ package edu.ntnu.idi.idatt2003.g40.mappe.engine; import edu.ntnu.idi.idatt2003.g40.mappe.model.Player; -import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase; -import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale; import edu.ntnu.idi.idatt2003.g40.mappe.model.Share; import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction; -import edu.ntnu.idi.idatt2003.g40.mappe.service.*; -import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; -import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; -import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventPublisher; -import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleIntegerProperty; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import javafx.beans.property.ReadOnlyIntegerProperty; +import javafx.beans.property.ReadOnlyIntegerWrapper; /** * Represents a stock exchange where stocks can be traded. * - *

Holds a map of stocks where stock symbol is key and stock is value.

+ *

Holds a map of {@link Stock} objects where stock symbol is key and + * stock is value.

* *

Delegates buying and selling to player elements using calculators

* *

Advances week.

* * @see Player - * @see TransactionCalculator + * @see edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator + * + * @version 1.1.0 * */ public final class Exchange { @@ -44,7 +41,7 @@ public final class Exchange { /** * Current week (set to 1 in constructor). * */ - private final IntegerProperty week = new SimpleIntegerProperty(1); + private final ReadOnlyIntegerWrapper week = new ReadOnlyIntegerWrapper(1); /** * Map of {@link Stock} objects. Key is stock symbol. Value is stock. @@ -59,13 +56,16 @@ public final class Exchange { /** * Constructor. * - * @param name name of exchange. - * @param stocks list of {@link Stock} objects. + * @param name name of exchange. + * @param stocks list of {@link Stock} objects. * * @throws IllegalArgumentException if name or stocks are empty/null. * */ - public Exchange(final String name, final List stocks) throws IllegalArgumentException { - if (!Validator.NOT_EMPTY.isValid(name) || stocks == null || stocks.isEmpty()) { + public Exchange(final String name, final List stocks) + throws IllegalArgumentException { + if (!Validator.NOT_EMPTY.isValid(name) + || stocks == null + || stocks.isEmpty()) { throw new IllegalArgumentException("Invalid exchange parameters!"); } this.name = name; @@ -95,12 +95,12 @@ public int getWeek() { } /** - * Getter method for the {@link IntegerProperty} object of week. + * Getter method for the {@link ReadOnlyIntegerProperty} object of week. * * @return week. * */ - public IntegerProperty weekProperty() { - return week; + public ReadOnlyIntegerProperty weekProperty() { + return week.getReadOnlyProperty(); } /** @@ -108,7 +108,7 @@ public IntegerProperty weekProperty() { * * @param symbol the stock symbol. * - * @return true or false. + * @return true or false. * */ public boolean hasStock(final String symbol) { return stockMap.containsKey(symbol); @@ -117,16 +117,18 @@ public boolean hasStock(final String symbol) { /** * Getter method for stock element. * - * @param symbol the symbol of the stock to get. + * @param symbol the symbol of the stock to get. * - * @return {@link Stock} element gotten. + * @return {@link Stock} element gotten. * - * @throws IllegalArgumentException if symbol is invalid. + * @throws IllegalArgumentException if symbol is invalid or not in exchange. * */ - public Stock getStock(final String symbol) throws IllegalArgumentException { - if (!Validator.VALID_STOCK_NAME.isValid(symbol)) { + public Stock getStock(final String symbol) + throws IllegalArgumentException { + if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol) + || !stockMap.containsKey(symbol)) { throw new IllegalArgumentException( - Validator.VALID_STOCK_NAME.getErrorMessage()); + Validator.VALID_STOCK_SYMBOL.getErrorMessage()); } return stockMap.get(symbol); } @@ -136,7 +138,7 @@ public Stock getStock(final String symbol) throws IllegalArgumentException { * * @param searchTerm the term to search for. * - * @return a list of {@link Stock} objects. + * @return a list of {@link Stock} objects. * */ public List findStocks(final String searchTerm) { List result = new ArrayList<>(); @@ -154,24 +156,27 @@ public List findStocks(final String searchTerm) { /** * Method called when a player buys a stock. * - * @param symbol the stock this player buys. - * @param quantity the amount of stock to buy. - * @param player the player buying stock. + * @param symbol the stock this player buys. + * @param quantity the amount of stock to buy. + * @param player the player buying stock. * - * @return Transaction representing the transaction. + * @return Transaction representing the transaction. * * @throws IllegalArgumentException if symbol or player is invalid. * */ public Transaction buy(final String symbol, final BigDecimal quantity, final Player player) throws IllegalArgumentException { - if (!Validator.VALID_STOCK_NAME.isValid(symbol) || player == null) { + if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol) + || quantity == null + || player == null) { throw new IllegalArgumentException("Invalid purchase!"); } - Stock stock = stockMap.get(symbol); + Stock stock = getStock(symbol); Share share = new Share(stock, quantity, stock.getSalesPrice()); - TransactionCalculator calculator = new PurchaseCalculator(share); - Purchase purchase = new Purchase(share, week.get(), calculator); + Transaction purchase = TransactionFactory.createTransaction( + TransactionType.PURCHASE, share, getWeek() + ); player.handleTransaction(purchase); return purchase; } @@ -179,10 +184,10 @@ public Transaction buy(final String symbol, /** * Method called when a player sells share. * - * @param share the share to sell. - * @param player the player buying stock. + * @param share the share to sell. + * @param player the player buying stock. * - * @return Transaction representing the transaction. + * @return Transaction representing the transaction. * * @throws IllegalArgumentException if share or player is null. * */ @@ -191,75 +196,84 @@ public Transaction sell(final Share share, final Player player) if (share == null || player == null) { throw new IllegalArgumentException("Invalid sell!"); } - TransactionCalculator calculator = new SaleCalculator(share); - Sale sale = new Sale(share, week.get(), calculator); + Transaction sale = TransactionFactory.createTransaction( + TransactionType.SALE, share, getWeek() + ); player.handleTransaction(sale); return sale; } /** - * Method called when a player sells share. + * Method called when a player sells share, + * defined by an amount instead of specific {@link Share} object. + * + *

{@link edu.ntnu.idi.idatt2003.g40.mappe.model.Portfolio}

* - * @param amount the amount of "shares" to sell. + * @param amount the amount of "shares" to sell. * @param stockSymbol the stock to sell shares in. - * @param player the player buying stock. + * @param player the player buying stock. * - * @return Transaction representing the transaction. + * @return List of transactions commited to sell the shares. * - * @throws IllegalArgumentException if any parameter is null, or if player does not have enough shares. + * @throws IllegalArgumentException if any parameter is null, + * or if player does not have enough shares, + * or if player does not own any shares + * of the given stock. * */ - public List sell(BigDecimal amount, + public Transaction sell(BigDecimal amount, final String stockSymbol, final Player player) throws IllegalArgumentException { - if (amount == null || player == null || !Validator.NOT_EMPTY.isValid(stockSymbol)) { - throw new IllegalArgumentException("Invalid sell!"); - } else { + if (amount == null + || player == null + || !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) { + throw new IllegalArgumentException("Invalid sell parameters!"); + } - List sharesOfStock = player.getPortfolio().getShares().stream() - .filter(s -> s.getStock().getSymbol().equals(stockSymbol)) - .toList(); + List matchingShares = player.getPortfolio().getShares(stockSymbol); - BigDecimal totalOwned = player.getPortfolio().getTotalSharesBySymbol(stockSymbol); + if (matchingShares.isEmpty()) { + throw new IllegalArgumentException("Player does not own any shares of this stock!"); + } - if (amount.compareTo(totalOwned) > 0) { - amount = totalOwned; - } - ArrayList transactions = new ArrayList<>(); - BigDecimal remainingToSell = amount; + Share ownedPosition = matchingShares.getFirst(); + BigDecimal totalOwned = ownedPosition.getQuantity(); - for (Share share : sharesOfStock) { - if (remainingToSell.compareTo(BigDecimal.ZERO) <= 0) { - break; - } + if (amount.compareTo(totalOwned) > 0) { + amount = totalOwned; + } - BigDecimal shareQty = share.getQuantity(); + Stock stock = ownedPosition.getStock(); + Share shareToSell = new Share(stock, amount, stock.getSalesPrice()); - if (shareQty.compareTo(remainingToSell) <= 0) { - remainingToSell = remainingToSell.subtract(shareQty); - transactions.add(sell(share, player)); - } else { - Share newShare = player.getPortfolio().splitShare(share, remainingToSell); - remainingToSell = BigDecimal.ZERO; - transactions.add(sell(newShare, player)); - } - } - return transactions; - } + Transaction sale = TransactionFactory.createTransaction( + TransactionType.SALE, shareToSell, getWeek() + ); + player.handleTransaction(sale); + + return sale; } /** * Method for advancing time, increasing the amount of weeks. + * + *

Applies a random price change from -5% to 5% to every stock, + * plus a flat percent determined by their fortune, that can range from + * -10% to +10%.

+ * + * @see edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineView * */ public void advance() { for (Stock stock : stockMap.values()) { BigDecimal currentPrice = stock.getSalesPrice(); - double change = ((random.nextDouble() * 0.10) - 0.05) + stock.getFortune(); + BigDecimal change = BigDecimal.valueOf(random.nextDouble() * 0.10 - 0.05) + .add(BigDecimal.valueOf(stock.getFortune())); stock.setFortune(0); - BigDecimal factor = BigDecimal.valueOf(1 + change); + BigDecimal factor = BigDecimal.ONE.add(change); - BigDecimal newPrice = currentPrice.multiply(factor); + BigDecimal newPrice = currentPrice.multiply(factor) + .setScale(2, java.math.RoundingMode.HALF_UP); stock.addNewSalesPrice(newPrice); } week.set(week.get() + 1); @@ -269,11 +283,17 @@ public void advance() { * Method for getting the stocks with the most * amount of increase since last week. * - * @param limit the maximum amount of stocks returned + * @param limit the maximum amount of stocks returned * * @return list of {@link Stock} objects. + * + * @throws IllegalArgumentException if limit is invalid (negative or zero). * */ - public List getGainers(final int limit) { + public List getGainers(final int limit) + throws IllegalArgumentException { + if (limit < 1) { + throw new IllegalArgumentException("Invalid limit for getting gainers!"); + } return stockMap.entrySet().stream() // We only want the stocks with a positive price change. .filter(e -> @@ -294,11 +314,17 @@ public List getGainers(final int limit) { * Method for getting the stocks with the highest * loss of price since last week. * - * @param limit the maximum amount of stocks returned + * @param limit the maximum amount of stocks returned * * @return list of {@link Stock} objects. + * + * @throws IllegalArgumentException if limit is invalid (negative or zero). * */ - public List getLosers(final int limit) { + public List getLosers(final int limit) + throws IllegalArgumentException { + if (limit < 1) { + throw new IllegalArgumentException("Invalid limit for getting losers!"); + } return stockMap.entrySet().stream() // Only get entries with negative price change. .filter(e -> diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java index dadc496..b7b9cf5 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java @@ -3,11 +3,15 @@ import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase; import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale; import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction; +import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Stores completed transactions. + * Stores completed transactions in an {@link ArrayList} object. + * + * @version 1.1.0 */ public final class TransactionArchive { @@ -20,6 +24,7 @@ public final class TransactionArchive { * Creates an empty transaction archive. */ public TransactionArchive() { + // Empty constructor. } /** @@ -28,8 +33,13 @@ public TransactionArchive() { * @param transaction the transaction to add * * @return true if the transaction was added + * + * @throws IllegalArgumentException if transaction is null */ public boolean add(final Transaction transaction) { + if (transaction == null) { + throw new IllegalArgumentException("Transaction cannot be null!"); + } return transactions.add(transaction); } @@ -48,24 +58,27 @@ public boolean isEmpty() { * @param week the week number * * @return list of transactions from the given week + * + * @throws IllegalArgumentException if week is less than 1. */ public List getTransactions(final int week) { - List result = new ArrayList<>(); - for (Transaction transaction : transactions) { - if (transaction.getWeek() == week) { - result.add(transaction); - } + if (!Validator.VALID_WEEK.isValid(Integer.toString(week))) { + throw new IllegalArgumentException( + Validator.VALID_WEEK.getErrorMessage() + ); } - return result; + return transactions.stream() + .filter(transaction -> transaction.getWeek() == week) + .toList(); } /** - * Returns all transactions. + * Returns an un-mutable reference to the transactions list. * - * @return list of transactions from the given week + * @return unmodifiable version of list. */ public List getTransactions() { - return transactions; + return Collections.unmodifiableList(transactions); } /** @@ -74,16 +87,15 @@ public List getTransactions() { * @param week the week number * * @return list of purchases from the given week + * + * @throws IllegalArgumentException if week is less than 1. */ - public List getPurchases(final int week) { - List result = new ArrayList<>(); - for (Transaction transaction : transactions) { - if (transaction instanceof Purchase purchase - && transaction.getWeek() == week) { - result.add(purchase); - } - } - return result; + public List getPurchases(final int week) + throws IllegalArgumentException { + return getTransactions(week).stream() + .filter(Purchase.class::isInstance) + .map(Purchase.class::cast) + .toList(); } /** @@ -92,15 +104,15 @@ public List getPurchases(final int week) { * @param week the week number * * @return list of sales from the given week + * + * @throws IllegalArgumentException if week is less than 1. */ - public List getSales(final int week) { - List result = new ArrayList<>(); - for (Transaction transaction : transactions) { - if (transaction instanceof Sale sale && transaction.getWeek() == week) { - result.add(sale); - } - } - return result; + public List getSales(final int week) + throws IllegalArgumentException { + return getTransactions(week).stream() + .filter(Sale.class::isInstance) + .map(Sale.class::cast) + .toList(); } /** @@ -109,13 +121,9 @@ public List getSales(final int week) { * @return number of distinct weeks */ public int countDistinctWeeks() { - List weeks = new ArrayList<>(); - for (Transaction transaction : transactions) { - int week = transaction.getWeek(); - if (!weeks.contains(week)) { - weeks.add(week); - } - } - return weeks.size(); + return (int) transactions.stream() + .map(Transaction::getWeek) + .distinct() + .count(); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java new file mode 100644 index 0000000..8d19749 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java @@ -0,0 +1,18 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.exceptions; + +/** + * Exception primarily thrown when the active + * {@link edu.ntnu.idi.idatt2003.g40.mappe.model.Player} object + * does not have enough money for a transaction to complete. + * */ +public class NotEnoughMoneyException extends RuntimeException { + + /** + * Constructor. + * + * @param message the exception message. + * */ + public NotEnoughMoneyException(final String message) { + super(message); + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java index 9cac71e..0b66b11 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java @@ -2,10 +2,11 @@ import edu.ntnu.idi.idatt2003.g40.mappe.controller.PlayerStatusController; import edu.ntnu.idi.idatt2003.g40.mappe.engine.TransactionArchive; +import edu.ntnu.idi.idatt2003.g40.mappe.exceptions.NotEnoughMoneyException; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; import java.math.BigDecimal; -import javafx.beans.property.FloatProperty; -import javafx.beans.property.SimpleFloatProperty; +import javafx.beans.property.ReadOnlyFloatProperty; +import javafx.beans.property.ReadOnlyFloatWrapper; /** * Represents a player in the system. @@ -18,6 +19,8 @@ *
  • Has a set amount of money to use on said exchange.
  • *
  • Has a {@link TransactionArchive}
  • * + * + * @version 1.1.0 * */ public final class Player { @@ -37,14 +40,18 @@ public final class Player { private BigDecimal money; /** - * Current net-worth of player as a listenable {@link FloatProperty} object. + * Current net-worth of player as a listenable, + * read-only, {@link ReadOnlyFloatWrapper} object. * */ - private final FloatProperty networthAsFloatProp = new SimpleFloatProperty(0); + private final ReadOnlyFloatWrapper networthAsFloatProp = + new ReadOnlyFloatWrapper(0f); /** - * Current money of player as a listenable {@link FloatProperty} object. + * Current money of player as a read-only + * {@link ReadOnlyFloatWrapper} object. * */ - private final FloatProperty moneyAsFloatProp = new SimpleFloatProperty(0); + private final ReadOnlyFloatWrapper moneyAsFloatProp + = new ReadOnlyFloatWrapper(0f); /** * The players' portfolio, holding their shares. @@ -63,19 +70,28 @@ public final class Player { * @param name the name of the player * @param startingMoney the starting amount of money * - * @throws IllegalArgumentException if name is null. + * @throws IllegalArgumentException if name is empty, + * or starting money is null, + * zero or negative. */ - public Player(final String name, final BigDecimal startingMoney) throws IllegalArgumentException { + public Player(final String name, + final BigDecimal startingMoney) + throws IllegalArgumentException { if (!Validator.NOT_EMPTY.isValid(name)) { - throw new IllegalArgumentException("Invalid name!"); + throw new IllegalArgumentException("Player name cannot be empty!"); + } + if (startingMoney == null + || startingMoney.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException( + "Starting money cannot be null, zero, or negative!" + ); } this.name = name; this.startingMoney = startingMoney; this.money = this.startingMoney; - this.networthAsFloatProp.setValue(this.startingMoney); - this.moneyAsFloatProp.setValue(this.startingMoney); this.portfolio = new Portfolio(); this.transactionArchive = new TransactionArchive(); + updateObservableProperties(); } /** @@ -109,18 +125,42 @@ public BigDecimal getMoney() { * Adds money to the players balance. * * @param amount the amount to add + * + * @throws IllegalArgumentException if money to add is negative or zero. */ - public void addMoney(final BigDecimal amount) { + public void addMoney(final BigDecimal amount) + throws IllegalArgumentException { + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException( + "Can only add positive values to player!" + ); + } money = money.add(amount); + updateObservableProperties(); } /** * Withdraws money from the players balance. * * @param amount the amount to withdraw + * + * @throws IllegalArgumentException if money to withdraw is negative or zero, + * or if amount is more than current money. */ - public void withdrawMoney(final BigDecimal amount) { + public void withdrawMoney(final BigDecimal amount) + throws IllegalArgumentException { + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException( + "Amount to withdraw must be positive!" + ); + } + if (money.compareTo(amount) < 0) { + throw new IllegalArgumentException( + "Cannot withdraw more money than available balance!" + ); + } money = money.subtract(amount); + updateObservableProperties(); } /** @@ -152,21 +192,23 @@ public BigDecimal getNetWorth() { } /** - * Get net-worth as a {@link FloatProperty} object, allowing listening for changes. + * Get net-worth as a {@link ReadOnlyFloatProperty} object, + * allowing listening for changes. * - * @return FloatProperty. + * @return networth as an immutable value. * */ - public FloatProperty getNetWorthAsFloatProperty() { - return networthAsFloatProp; + public ReadOnlyFloatProperty getNetWorthAsFloatProperty() { + return networthAsFloatProp.getReadOnlyProperty(); } /** - * Get money as a {@link FloatProperty} object, allowing listening for changes. + * Get money as a {@link ReadOnlyFloatProperty} object, + * allowing listening for changes. * - * @return FloatProperty. + * @return money as an immutable value. * */ - public FloatProperty getMoneyAsFloatProperty() { - return moneyAsFloatProp; + public ReadOnlyFloatProperty getMoneyAsFloatProperty() { + return moneyAsFloatProp.getReadOnlyProperty(); } /** @@ -184,22 +226,46 @@ public PlayerStatus getStatus() { * Method for handling a transaction for the player. * * @param transaction the transaction to handle. + * + * @throws IllegalArgumentException if transaction is null. + * @throws NotEnoughMoneyException if player does not have enough + * money for the transaction. * */ - public void handleTransaction(final Transaction transaction) { + public void handleTransaction(final Transaction transaction) + throws IllegalArgumentException, NotEnoughMoneyException { + if (transaction == null) { + throw new IllegalArgumentException("Cannot handle null transaction!"); + } + if (transaction instanceof Purchase purchase) { - if (money.floatValue() > transaction.getCalculator().calculateTotal().floatValue()) { + BigDecimal totalCost = purchase.getCalculator().calculateTotal(); + if (this.money.compareTo(totalCost) < 0) { + throw new NotEnoughMoneyException("Not enough money for transaction!"); + } + } + + switch (transaction) { + case Purchase purchase -> { withdrawMoney(purchase.getCalculator().calculateTotal()); portfolio.addShare(purchase.getShare()); - transactionArchive.add(transaction); - transaction.commit(this); } - } else if (transaction instanceof Sale sale) { - addMoney(sale.getCalculator().calculateTotal()); - portfolio.removeShare(sale.getShare()); - transactionArchive.add(transaction); - transaction.commit(this); + case Sale sale -> { + addMoney(sale.getCalculator().calculateTotal()); + portfolio.removeShare(sale.getShare()); + } + default -> throw new IllegalStateException("Unexpected value: " + transaction); } - networthAsFloatProp.setValue(getNetWorth().floatValue()); - moneyAsFloatProp.setValue(money); + transactionArchive.add(transaction); + transaction.commit(this); + + updateObservableProperties(); + } + + /** + * Helper method to synchronize the listener values. + */ + private void updateObservableProperties() { + this.moneyAsFloatProp.setValue(this.money.floatValue()); + this.networthAsFloatProp.setValue(this.getNetWorth().floatValue()); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java index 483adb8..415a065 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java @@ -1,26 +1,25 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; -import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator; -import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; - import java.math.BigDecimal; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.Objects; +import java.util.Map; /** * Represents a player's portfolio of shares. * *

    The portfolio stores shares and provides operations for adding, removing, * retrieving and checking ownership of shares.

    + * + * @version 1.1.0 */ public final class Portfolio { /** - * List of shares. + * Map used to handle internal shares. * */ - private final List shares = new ArrayList<>(); + private final Map shares = new HashMap<>(); /** * Creates an empty portfolio. @@ -30,24 +29,41 @@ public Portfolio() { } /** - * Adds a share to the portfolio. + * Adds a share to the portfolio. If share already exists, merges shares. * * @param share the share to add * - * @return {@code true} if the share was added, {@code false} otherwise - * * @throws IllegalArgumentException if share is null. */ - public boolean addShare(final Share share) throws IllegalArgumentException { + public void addShare(final Share share) throws IllegalArgumentException { if (share == null) { throw new IllegalArgumentException("Invalid share!"); } - return shares.add(share); + String symbol = share.getStock().getSymbol().toUpperCase(); + + if (shares.containsKey(symbol)) { + Share existingShare = shares.get(symbol); + BigDecimal totalQuantity = + existingShare.getQuantity().add(share.getQuantity()); + + shares.put(symbol, + new Share( + share.getStock(), + totalQuantity, existingShare.getPurchasePrice() + ) + ); + } else { + shares.put(symbol, share); + } } /** * Removes a share from the portfolio. * + *

    Uses the quantity value to deduct share amount from the map. + * If quantity to remove is equal to amount held, removes share entirely. + * If not, splits the share.

    + * * @param share the share to remove * * @return {@code true} if the share was removed, @@ -56,11 +72,37 @@ public boolean addShare(final Share share) throws IllegalArgumentException { * @throws IllegalArgumentException if share is null. * */ - public boolean removeShare(final Share share) throws IllegalArgumentException { + public boolean removeShare(final Share share) + throws IllegalArgumentException { if (share == null) { throw new IllegalArgumentException("Invalid share!"); } - return shares.remove(share); + String symbol = share.getStock().getSymbol().toUpperCase(); + if (!shares.containsKey(symbol)) { + return false; + } + + Share ownedShare = shares.get(symbol); + int comparison = ownedShare.getQuantity().compareTo(share.getQuantity()); + + if (comparison < 0) { + throw new IllegalArgumentException( + "Cannot remove more shares than are currently owned!"); + } else if (comparison == 0) { + + shares.remove(symbol); + } else { + BigDecimal remainingQuantity = + ownedShare.getQuantity().subtract(share.getQuantity()); + shares.put(symbol, + new Share( + share.getStock(), + remainingQuantity, + ownedShare.getPurchasePrice() + ) + ); + } + return true; } /** @@ -69,7 +111,7 @@ public boolean removeShare(final Share share) throws IllegalArgumentException { * @return a list of shares */ public List getShares() { - return List.copyOf(shares); + return List.copyOf(shares.values()); } /** @@ -81,14 +123,14 @@ public List getShares() { * * @throws IllegalArgumentException if symbol is invalid. */ - public List getShares(final String symbol) throws IllegalArgumentException { - if (!Validator.VALID_STOCK_NAME.isValid(symbol)) { + public List getShares(final String symbol) + throws IllegalArgumentException { + if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) { throw new IllegalArgumentException( - Validator.VALID_STOCK_NAME.getErrorMessage()); + Validator.VALID_STOCK_SYMBOL.getErrorMessage()); } - return shares.stream() - .filter(s -> symbol.equalsIgnoreCase(s.getStock().getSymbol())) - .toList(); + Share share = shares.get(symbol.toUpperCase()); + return share != null ? List.of(share) : List.of(); } /** @@ -101,11 +143,15 @@ public List getShares(final String symbol) throws IllegalArgumentExceptio * * @throws IllegalArgumentException if share is null. */ - public boolean contains(final Share share) throws IllegalArgumentException { + public boolean contains(final Share share) + throws IllegalArgumentException { if (share == null) { throw new IllegalArgumentException("Invalid share!"); } - return shares.contains(share); + String symbol = share.getStock().getSymbol().toUpperCase(); + Share owned = shares.get(symbol); + return owned != null + && owned.getQuantity().compareTo(share.getQuantity()) >= 0; } /** @@ -115,11 +161,11 @@ public boolean contains(final Share share) throws IllegalArgumentException { * @return the net worth. * */ public BigDecimal getNetWorth() { - BigDecimal netWorth = new BigDecimal("0"); - - for (Share s : shares) { - SaleCalculator calculator = new SaleCalculator(s); - netWorth = netWorth.add(calculator.calculateTotal()); + BigDecimal netWorth = BigDecimal.ZERO; + for (Share s : shares.values()) { + netWorth = netWorth.add( + s.getQuantity().multiply(s.getStock().getSalesPrice()) + ); } return netWorth; } @@ -127,37 +173,16 @@ public BigDecimal getNetWorth() { /** * Helper method to get total amount of shares owned in a specific stock. * - * @param symbol the symbol of the stock to check for shares. - * */ - public BigDecimal getTotalSharesBySymbol(final String symbol) { - return shares.stream() - .filter(s -> s.getStock().getSymbol().equals(symbol)) - .map(Share::getQuantity) - .reduce(BigDecimal.ZERO, BigDecimal::add); - } - - /** - * "Splits" a share in two pieces based on an amount. - * - * @param share the share to split. - * @param splitAmount the amount to split by. - * - * @return the split share from the original to the split amount. + * @param symbol the symbol of the stock to check for shares. * - * @throws IllegalArgumentException if share or split amount is invalid. + * @return BigDecimal representing total quantity of all + * shares of this symbol. * */ - public Share splitShare(final Share share, final BigDecimal splitAmount) - throws IllegalArgumentException { - if (!contains(share) || splitAmount.compareTo(share.getQuantity()) > 0) { - throw new IllegalArgumentException("Cannot split share!"); + public BigDecimal getTotalShareQuantityBySymbol(final String symbol) { + if (symbol == null) { + return BigDecimal.ZERO; } - BigDecimal remainingAmount = share.getQuantity().subtract(splitAmount); - - Share newShare1 = new Share(share.getStock(), splitAmount, share.getPurchasePrice()); - Share newShare2 = new Share(share.getStock(), remainingAmount, share.getPurchasePrice()); - removeShare(share); - addShare(newShare1); - addShare(newShare2); - return newShare1; + Share share = shares.get(symbol.toUpperCase()); + return share != null ? share.getQuantity() : BigDecimal.ZERO; } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java index ff00e77..e6c60ec 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java @@ -1,5 +1,7 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; + /** * Represents one save game entry. * @@ -14,7 +16,7 @@ * expected to be loaded with the default bundled stock data file. *

    */ -public class SaveGame { +public final class SaveGame { /** Display name of the save. */ private final String name; @@ -44,6 +46,11 @@ public SaveGame(final String name, final double balance, final double startingCapital, final String stockDataPath) { + if (!Validator.NOT_EMPTY.isValid(name) + || balance <= 0 + || startingCapital <= 0) { + throw new IllegalArgumentException("Invalid Save configuration!"); + } this.name = name; this.balance = balance; this.startingCapital = startingCapital; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java index 3328a74..30aba43 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java @@ -32,13 +32,25 @@ public final class Share { * @param quantity the quantity purchased * @param purchasePrice the price per unit at purchase time * - * @throws IllegalArgumentException if stock is null. + * @throws IllegalArgumentException if parameters are null or invalid. */ public Share(final Stock stock, final BigDecimal quantity, final BigDecimal purchasePrice) throws IllegalArgumentException { - if (stock == null) { - throw new IllegalArgumentException("Invalid stock!"); + if (stock == null + || quantity == null + || purchasePrice == null) { + throw new IllegalArgumentException("Invalid share configuration!"); + } + if (quantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException( + "Quantity cannot be negative or zero!" + ); + } + if (purchasePrice.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException( + "Purchase price cannot be negative or zero!" + ); } this.stock = stock; this.quantity = quantity; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java index 1ca9664..d7ff05a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java @@ -40,19 +40,26 @@ public final class Stock { * @param symbol the unique stock symbol * @param company the name of the company * @param salesPrice the initial sales price of the stock + * + * @throws IllegalArgumentException if parameters are null or invalid. */ public Stock(final String symbol, final String company, final BigDecimal salesPrice) throws IllegalArgumentException { - if (!Validator.VALID_STOCK_NAME.isValid(symbol)) { + if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) { throw new IllegalArgumentException( - Validator.VALID_STOCK_NAME.getErrorMessage()); - } else { - this.symbol = symbol; - this.company = company; - this.fortune = 0; - prices.add(salesPrice); + Validator.VALID_STOCK_SYMBOL.getErrorMessage()); + } + if (!Validator.NOT_EMPTY.isValid(company)) { + throw new IllegalArgumentException(Validator.NOT_EMPTY.getErrorMessage()); + } + if (salesPrice == null || salesPrice.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Sales price of cannot be negative or zero!"); } + this.symbol = symbol; + this.company = company; + this.fortune = 0; + prices.add(salesPrice); } /** @@ -109,11 +116,11 @@ public BigDecimal getSalesPrice() { */ public void addNewSalesPrice(final BigDecimal price) throws IllegalArgumentException { - if (price != null && price.intValue() != 0) { - prices.add(price); - } else { - throw new IllegalArgumentException("Invalid price to add to stock: " + getSymbol()); + if (price == null || price.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Invalid price to add to stock: " + + getSymbol()); } + prices.add(price); } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java index fe7e6c5..62993f1 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator; +import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; /** * Transaction abstract class. @@ -40,14 +41,22 @@ public abstract class Transaction { protected Transaction(final Share share, final int week, final TransactionCalculator calculator) - throws IllegalArgumentException{ - if (share == null || calculator == null) { - throw new IllegalArgumentException("Invalid stock or calculator!"); - } else { - this.share = share; - this.week = week; - this.calculator = calculator; + throws IllegalArgumentException { + if (share == null + || calculator == null) { + throw new IllegalArgumentException( + "Invalid configuration for transaction!" + ); } + if (!Validator.VALID_WEEK.isValid(Integer.toString(week))) { + throw new IllegalArgumentException( + Validator.VALID_WEEK.getErrorMessage() + ); + } + this.share = share; + this.week = week; + this.calculator = calculator; + } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java index 2c6aaed..0350071 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java @@ -1,7 +1,6 @@ package edu.ntnu.idi.idatt2003.g40.mappe.service; import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame; - import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java similarity index 67% rename from src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java rename to src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java index 481d423..ac5cb7c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java @@ -1,6 +1,10 @@ package edu.ntnu.idi.idatt2003.g40.mappe.service; -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -22,13 +26,13 @@ * to file, each stock separated by a line. * * - *

    Used with {@link FileConverter}

    + *

    Used with {@link StockFileParser}

    * - * @see FileConverter + * @see StockFileParser * @author tohja * @version 1.0.0 * */ -public class FileParser { +public class StockFileManager { /** The path name this parser is using.*/ private final String pathName; @@ -72,7 +76,7 @@ private enum ParserRuleSet { try { new BigDecimal(s); return true; - } catch (NumberFormatException e) { + } catch (NumberFormatException _) { return false; } }), @@ -102,13 +106,16 @@ private enum ParserRuleSet { * * @param pathName the file path name to read. * */ - public FileParser(final String pathName) { + public StockFileManager(final String pathName) { this.pathName = pathName; } /** * Reads the file and returns a list element of all valid stocks as strings. * + *

    If file is not found, + * falls back to default file in resources folder.

    + * *

    Uses {@link BufferedReader} for opening a file stream.

    * * @return {@link List} object of all valid stock strings in file. @@ -117,17 +124,20 @@ public FileParser(final String pathName) { * * @see Path * */ - public List readFile() throws IOException { - try (InputStream inputStream = getClass().getResourceAsStream(pathName); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + Path path = Paths.get(pathName); + + if (!Files.exists(path)) { + extractResourceFallback(path); + } - List allLines = bufferedReader.readAllLines(); - List readableLines = - allLines.stream() - .filter(ParserRuleSet.VALID_FORMAT.rule).toList(); + try (BufferedReader bufferedReader = + Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + List allLines = bufferedReader.lines().toList(); + List readableLines = allLines.stream() + .filter(ParserRuleSet.VALID_FORMAT.rule) + .toList(); - // Valid lines (following the correct regular expressions) return readableLines.stream().filter(s -> { String[] parts = s.trim().split(","); @@ -135,20 +145,45 @@ public List readFile() throws IOException { return false; } - boolean validCode = ParserRuleSet - .VALID_CODE.rule.test(parts[0].trim()); - - boolean validName = ParserRuleSet - .VALID_NAME.rule.test(parts[1].trim()); - - boolean validPrice = ParserRuleSet - .VALID_PRICE.rule.test(parts[2].trim()); + boolean validCode = + ParserRuleSet.VALID_CODE.rule.test(parts[0].trim()); + boolean validName = + ParserRuleSet.VALID_NAME.rule.test(parts[1].trim()); + boolean validPrice = + ParserRuleSet.VALID_PRICE.rule.test(parts[2].trim()); return validCode && validName && validPrice; }).toList(); } catch (IOException e) { - throw new IOException("File parser could not parse file!"); + throw new IOException("File parser could not parse file!", e); + } + } + + /** + * Extracts the fallback template file from the application resources to + * the local file system. + * + * @param targetPath path to send the fallback file. + * + * @throws IOException if resource or input stream is not found or null. + */ + private void extractResourceFallback(final Path targetPath) + throws IOException { + String resourceName = "/sp500.csv"; + + try (InputStream inputStream = getClass().getResourceAsStream(resourceName)) { + if (inputStream == null) { + throw new FileNotFoundException( + "Resource file not found in JAR: " + resourceName + ); + } + + if (targetPath.getParent() != null) { + Files.createDirectories(targetPath.getParent()); + } + + Files.copy(inputStream, targetPath); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java similarity index 58% rename from src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java rename to src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java index 397328d..07fd802 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java @@ -3,7 +3,9 @@ import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Converts stock objects to/from string format for file handling. @@ -17,13 +19,13 @@ * list of string elements. * * - *

    Used with {@link FileParser}

    + *

    Used with {@link StockFileManager}

    * - * @see FileParser + * @see StockFileManager * @author tohja * @version 1.0.0 * */ -public class FileConverter { +public class StockFileParser { /** * Turns a list of valid string representations @@ -42,29 +44,29 @@ public List getStocksFromStrings(final List validStocks) throws IllegalArgumentException { if (validStocks == null || validStocks.isEmpty()) { throw new IllegalArgumentException("Empty or null stock list!"); - } else { - List stocksFromFile = new ArrayList<>(); - List stockSymbols = new ArrayList<>(); + } + List stocksFromFile = new ArrayList<>(); + Set stockSymbols = new HashSet<>(); - validStocks.forEach(s -> { - String[] lineElements = s.split(","); - String stockSymbol = lineElements[0].trim(); - String stockName = lineElements[1].trim(); - BigDecimal stockPrice = new BigDecimal(lineElements[2].trim()); + validStocks.forEach(s -> { + String[] lineElements = s.split(","); + String stockSymbol = lineElements[0].trim(); + String stockName = lineElements[1].trim(); + BigDecimal stockPrice = new BigDecimal(lineElements[2].trim()); - try { - Stock stockObject = new Stock(stockSymbol, stockName, stockPrice); - if (!stockSymbols.contains(stockSymbol)) { - stockSymbols.add(stockSymbol); - stocksFromFile.add(stockObject); - } - } catch (IllegalArgumentException e) { - System.err.println("(" + s + ") is not a valid stock! Skipping..."); + try { + Stock stockObject = new Stock(stockSymbol, stockName, stockPrice); + if (stockSymbols.add(stockSymbol)) { + stocksFromFile.add(stockObject); } - - }); - return stocksFromFile; + } catch (IllegalArgumentException _) { + // Ignore invalid strings. + } + }); + if (stocksFromFile.isEmpty()) { + throw new IllegalArgumentException("No stocks parsed succesfully!"); } + return stocksFromFile; } /** @@ -81,13 +83,21 @@ public List getStocksFromStrings(final List validStocks) public List stocksToStrings(final List stocks) { if (stocks == null || stocks.isEmpty()) { throw new IllegalArgumentException("Empty or null stock list!"); - } else { - ArrayList stringList = new ArrayList<>(); - stocks.forEach(s -> - stringList.add(s.getSymbol().trim() + "," + s.getCompany().trim() - + "," + s.getSalesPrice().toString()) + } + + List stringList = new ArrayList<>(); + for (Stock s : stocks) { + if (s == null) { + continue; + } + + String csvRow = String.format("%s, %s, %s", + s.getSymbol().trim(), + s.getCompany().trim(), + s.getSalesPrice().toPlainString() ); - return stringList; + stringList.add(csvRow); } + return stringList; } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java index 733b9c5..62e849c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java @@ -23,26 +23,23 @@ private TransactionFactory() { * @param transactionType the type of transaction to create. * @param share the share this transaction is about. * @param week the week this transaction takes place in. - * @param calculator the calculator to use when calculating the transaction. * * @return an implementation of {@link Transaction}. * */ public static Transaction createTransaction(final TransactionType transactionType, final Share share, - final int week, - final TransactionCalculator - calculator) + final int week) throws IllegalArgumentException { if (transactionType == null || share == null - || !Validator.VALID_POSITIVE_INT.isValid(Integer.toString(week)) - || calculator == null) { + || !Validator.VALID_WEEK.isValid(Integer.toString(week)) + ) { throw new IllegalArgumentException("Null or empty parameters for factory!"); } else { return switch (transactionType) { - case SALE -> new Sale(share, week, calculator); - case PURCHASE -> new Purchase(share, week, calculator); + case SALE -> new Sale(share, week, new SaleCalculator(share)); + case PURCHASE -> new Purchase(share, week, new PurchaseCalculator(share)); default -> throw new IllegalArgumentException("Invalid transaction type!"); }; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java index e67e360..215547e 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java @@ -8,11 +8,4 @@ *

    Decreases coupling and enables testing of event types.

    * */ public interface EventChannel { - - /** - * Getter method for enum name. - * - * @return String name of enum. - * */ - String getName(); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java index a4a2bad..79e4f7b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java @@ -32,9 +32,20 @@ public final class EventManager { * @param subscriber the {@link EventSubscriber} object to add. * @param eventChannel the {@link EventChannel} type this subscriber should subscribe to. * - * + * @throws IllegalArgumentException if subscriber is already subscribed to event channel, + * or if parameters are null. */ - public void addSubscriber(final EventSubscriber subscriber, final EventChannel eventChannel) { + public void addSubscriber(final EventSubscriber subscriber, final EventChannel eventChannel) + throws IllegalArgumentException { + if (subscriber == null || eventChannel == null) { + throw new IllegalArgumentException("Parameters cannot be null!"); + } + + List subscribers = subscriberMap.get(eventChannel); + if (subscribers != null && subscribers.contains(subscriber)) { + throw new IllegalArgumentException("Subscriber already subscribed to event channel!"); + } + subscriberMap.computeIfAbsent(eventChannel, k -> new ArrayList<>()).add(subscriber); } @@ -49,9 +60,9 @@ public void addSubscriber(final EventSubscriber subscriber, final EventChannel e */ public void invokeEvent(final EventData data) throws IllegalArgumentException { - if (data == null || !subscriberMap.containsKey(data.channel())) { + if (data == null || data.data() == null) { throw new IllegalArgumentException( - "No subscriber listening to this event!" + "Data cannot be null!" ); } else { for (EventSubscriber e : subscriberMap.get(data.channel())) { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java index ace4933..215ff37 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java @@ -51,12 +51,4 @@ public enum EventType implements EventChannel { * */ SELECT_STOCK_FOR_MINIGAME; - - /** - * {@inheritDoc} - * */ - @Override - public String getName() { - return this.name(); - } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java index 633c60a..10593f1 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java @@ -1,5 +1,5 @@ /** * Contains classes providing modular functionality to the application, - * such as the {@link edu.ntnu.idi.idatt2003.g40.mappe.service.FileConverter}. + * such as the {@link edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileParser}. * */ package edu.ntnu.idi.idatt2003.g40.mappe.service; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java index 6d12cbc..6e1830b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java @@ -1,7 +1,5 @@ package edu.ntnu.idi.idatt2003.g40.mappe.utils; -import java.time.LocalDate; -import java.time.format.DateTimeParseException; import java.util.function.Predicate; /** @@ -55,26 +53,14 @@ public enum Validator { /** * Rule that checks if a string is considered a valid stock name. * */ - VALID_STOCK_NAME(NOT_EMPTY.validationRule.and(s -> - s.length() == 4), "Invalid stock name!"), + VALID_STOCK_SYMBOL(NOT_EMPTY.validationRule.and(s -> + s.length() == 4), "Invalid stock symbol!"), /** - * Rule that checks if a string represents a positive integer. + * Rule that checks if a string represents a valid week. (Greater than 1). * */ - VALID_POSITIVE_INT(VALID_INT.validationRule.and(s -> - Integer.parseInt(s) >= 0), "Number is not positive!"), - /** - * Rule that checks if string is not empty, - * and if it can be parsed into a {@link LocalDate} object. - */ - VALID_DATE(NOT_EMPTY.validationRule.and(s -> { - try { - LocalDate.parse(s); - return true; - } catch (DateTimeParseException e) { - return false; - } - }), "Invalid Date!"); + VALID_WEEK(VALID_INT.validationRule.and(s -> + Integer.parseInt(s) > 0), "Invalid week"); /** The predicate field set when creating constants. */ private final Predicate validationRule; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java index 11e91cd..77e7d8b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java @@ -1,6 +1,5 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view; -import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; import java.util.EnumMap; import java.util.Map; @@ -47,14 +46,18 @@ public abstract class ViewElement> { * @param rootPane an instance of type T (defined in the class). * @param viewName The name of the view as an {@link ViewEnum}. * + * @throws IllegalArgumentException if parameters are invalid. + * */ - protected ViewElement(final T rootPane, final ViewEnum viewName, final Class actionEnum) { + protected ViewElement(final T rootPane, + final ViewEnum viewName, + final Class actionEnum) + throws IllegalArgumentException { this(rootPane, actionEnum); - if (Validator.NOT_EMPTY.isValid(viewName.name())) { - this.viewName = viewName; - } else { + if (!Validator.NOT_EMPTY.isValid(viewName.name())) { throw new IllegalArgumentException(Validator.NOT_EMPTY.getErrorMessage()); } + this.viewName = viewName; } /** @@ -62,16 +65,17 @@ protected ViewElement(final T rootPane, final ViewEnum viewName, final Class * * @param rootPane the root of this view. * + * @throws IllegalArgumentException if parameters are null. */ - protected ViewElement(final T rootPane, final Class actionEnum) { - if (rootPane != null) { - setRootPane(rootPane); - this.buttonMap = new EnumMap<>(actionEnum); - initLayout(); - initStyling(); - } else { + protected ViewElement(final T rootPane, final Class actionEnum) + throws IllegalArgumentException { + if (rootPane == null || actionEnum == null) { throw new IllegalArgumentException("Invalid ViewElement!"); } + setRootPane(rootPane); + this.buttonMap = new EnumMap<>(actionEnum); + initLayout(); + initStyling(); } /** @@ -84,16 +88,6 @@ public ViewEnum getViewName() { return viewName; } - /** - * Setter method for the view name. - * - * @param name the new name to set this view element to. - * - */ - protected void setViewName(final ViewEnum name) { - viewName = name; - } - /** * Getter method for the root pane. * @@ -155,21 +149,10 @@ public void setOnAction(final A action, final Runnable logic) } } - /** - * Method that defines how view elements set data. - * - * @param The type of data to set. - * @param data the data to set. - * - */ - public void setData(final T2 data) { - setViewName(data.getSceneName()); - } - /** * Method called when updating a view. * */ public void onUpdate() { - + // Empty by default. } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java index b3ce064..45da26b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java @@ -108,6 +108,7 @@ public void setScene(final ViewElement viewElement) ); } currentView = viewElement; + viewElement.onUpdate(); } } @@ -128,12 +129,10 @@ public void setScene(final ViewElement viewElement) public void setScene(final ViewData data) throws IllegalArgumentException { if (data == null) { throw new IllegalArgumentException("Data is null!"); - } else { - ViewElement viewElement = viewMap.get(data.getSceneName()); - viewElement.setData(data); - setScene(viewElement); - currentView = viewElement; } + ViewElement viewElement = viewMap.get(data.getSceneName()); + setScene(viewElement); + currentView = viewElement; } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java index a64bec2..48550ac 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java @@ -302,12 +302,6 @@ protected void initStyling() { createGameButton.setDisable(true); } - /** {@inheritDoc} */ - @Override - public void onUpdate() { - resetFields(); - } - /** * Refreshes the highlight on the two stock-source buttons so the * currently-active choice stands out from the inactive one. diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java index 9d31d0d..8bc66c5 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java @@ -116,7 +116,7 @@ private void populateStockList(final String filter) { getViewElement().setOnStockAction(stockBtn, s, (Stock stock) -> { BigDecimal amountOfSharesOwned = player.getPortfolio() - .getTotalSharesBySymbol(s.getSymbol()); + .getTotalShareQuantityBySymbol(s.getSymbol()); handleStockSelection(stock, amountOfSharesOwned.floatValue()); }); } @@ -149,15 +149,13 @@ protected void initInteractions() { getViewElement().setOnAction(DashBoardActions.SELL_SHARES, () -> { if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText()) && Float.parseFloat(getViewElement().getQuantityInputField().getText()) > 0) { - List transactions = exchange.sell( + Transaction sale = exchange.sell( new BigDecimal(getViewElement().getQuantityInputField().getText()), getViewElement().getCurrentStock().getSymbol(), player); - for (Transaction t : transactions) { - if(t.isCommited()) { - getViewElement().addOwnedShares(-t.getShare().getQuantity().floatValue()); - } + if(sale.isCommited()) { + getViewElement().addOwnedShares(-sale.getShare().getQuantity().floatValue()); } } }); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java index e185fb3..86838a9 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java @@ -84,7 +84,7 @@ protected void initInteractions() { * @return the total quantity, or 0 if the symbol is invalid. * */ private int ownedQuantity(final String symbol) { - if (!Validator.VALID_STOCK_NAME.isValid(symbol)) { + if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) { return 0; } return player.getPortfolio().getShares(symbol).stream() diff --git a/src/main/resources/testStockData.txt b/src/main/resources/testStockData.txt deleted file mode 100644 index b349d0e..0000000 --- a/src/main/resources/testStockData.txt +++ /dev/null @@ -1,11 +0,0 @@ -#THIS IS A COMMENT. - -AAPL, Apple Inc., 276.43 -NVID, Nvidida Corporation, 241.591 - -#Above me are some valid formats. -#Below me are some invalid formats - -COOLI, This is a cool name, 252.2 - -COOL, This is a cool name, 252.2a \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java index 8f4f1a5..66ce0c5 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java @@ -1,116 +1,262 @@ package edu.ntnu.idi.idatt2003.g40.mappe.engine; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Player; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Share; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction; +import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator; +import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; - -import edu.ntnu.idi.idatt2003.g40.mappe.model.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class ExchangeTest { + /** + * Test sample stock. + * */ private Stock appleStock; + /** + * Test sample stock. + * */ + private Stock teslaStock; + + /** + * Test sample stock. + * */ + private Stock pearStock; + + /** + * Test exchange to use. + * */ + private Exchange testExchange; + + /** + * Test player for testing transactions. + * */ + private Player testPlayer; + @BeforeEach void setUp() { appleStock = new Stock("AAPL", "Apple", new BigDecimal("100")); + teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200")); + pearStock = new Stock("Pear", "Pear Inc.", new BigDecimal("97")); + testExchange = new Exchange("NASDAQ", + List.of(appleStock, teslaStock, pearStock)); + testPlayer = new Player("Alice", new BigDecimal("1000")); } @Test void constructorSetsNameWeekAndStocksCorrectly() { - Stock tesla = new Stock("TSLA", "Tesla", new BigDecimal("200")); + assertEquals("NASDAQ", testExchange.getName()); + assertEquals(1, testExchange.getWeek()); + assertEquals(1, testExchange.weekProperty().intValue()); + assertTrue(testExchange.hasStock("AAPL")); + assertTrue(testExchange.hasStock("TSLA")); + } + + @Test + void constructorThrowsErrorOnInvalidParameters() { + assertDoesNotThrow(() -> + new Exchange("Valid name", + List.of(appleStock, teslaStock, pearStock))); + + assertThrows(IllegalArgumentException.class, () -> + new Exchange(null, List.of(appleStock, teslaStock, pearStock))); - Exchange exchange = new Exchange("NASDAQ", List.of(appleStock, tesla)); + assertThrows(IllegalArgumentException.class, () -> + new Exchange("Valid name", null)); - assertEquals("NASDAQ", exchange.getName()); - assertEquals(1, exchange.getWeek()); - assertTrue(exchange.hasStock("AAPL")); - assertTrue(exchange.hasStock("TSLA")); + assertThrows(IllegalArgumentException.class, () -> + new Exchange("Valid name", new ArrayList<>())); } @Test void getStockReturnsCorrectStock() { - Exchange exchange = new Exchange("NASDAQ", List.of(appleStock)); - - Stock result = exchange.getStock("AAPL"); + Stock result = testExchange.getStock("AAPL"); assertSame(appleStock, result); } @Test - void findStocksReturnsMatchingStocksBySymbolOrCompany() { - Stock tesla = new Stock("TSLA", "Tesla", new BigDecimal("200")); - Exchange exchange = new Exchange("NASDAQ", List.of(appleStock, tesla)); + void getStockThrowsExceptionOnInvalidStock() { + assertDoesNotThrow( + () -> testExchange.getStock("AAPL") + ); + assertThrows(IllegalArgumentException.class, + () -> testExchange.getStock("INVALIDSYMBOL")); + assertThrows(IllegalArgumentException.class, + () -> testExchange.getStock("NVID")); + } - List resultBySymbol = exchange.findStocks("AAP"); - List resultByCompany = exchange.findStocks("tes"); + @Test + void findStocksReturnsMatchingStocksBySymbolOrCompany() { + List resultBySymbol = testExchange.findStocks("AAP"); + List resultByCompany = testExchange.findStocks("tes"); assertEquals(1, resultBySymbol.size()); assertTrue(resultBySymbol.contains(appleStock)); assertEquals(1, resultByCompany.size()); - assertTrue(resultByCompany.contains(tesla)); + assertTrue(resultByCompany.contains(teslaStock)); } @Test void buyReturnsPurchaseAndWithdrawsMoneyFromPlayer() { - Exchange exchange = new Exchange("NASDAQ", List.of(appleStock)); - Player player = new Player("Alice", new BigDecimal("1000")); + Transaction transaction = + testExchange.buy("AAPL", new BigDecimal("2"), testPlayer); - Transaction transaction = exchange.buy("AAPL", new BigDecimal("2"), player); + TransactionCalculator calculator = + new PurchaseCalculator(transaction.getShare()); + + BigDecimal expectedPlayerMoney = + testPlayer.getStartingMoney().subtract(calculator.calculateTotal()); assertInstanceOf(Purchase.class, transaction); assertEquals(1, transaction.getWeek()); assertEquals(new BigDecimal("2"), transaction.getShare().getQuantity()); - assertEquals(new BigDecimal("100"), transaction.getShare() + assertEquals(appleStock.getSalesPrice(), transaction.getShare() .getPurchasePrice()); - assertEquals(new BigDecimal("799.000"), player.getMoney()); + assertEquals(expectedPlayerMoney, testPlayer.getMoney()); + } + + @Test + void buyThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow(() -> + testExchange.buy("AAPL", new BigDecimal("2"), testPlayer)); + + assertThrows(IllegalArgumentException.class, () -> + testExchange.buy("NVID", new BigDecimal("2"), testPlayer)); + + assertThrows(IllegalArgumentException.class, () -> + testExchange.buy(null, new BigDecimal("2"), testPlayer)); + + assertThrows(IllegalArgumentException.class, () -> + testExchange.buy("AAPL", null, testPlayer)); + + assertThrows(IllegalArgumentException.class, () -> + testExchange.buy("AAPL", new BigDecimal("2"), null)); + } @Test void sellReturnsSaleAndAddsMoneyToPlayer() { - Stock apple = new Stock("AAPL", "Apple", new BigDecimal("150")); - Exchange exchange = new Exchange("NASDAQ", List.of(apple)); - Player player = new Player("Alice", new BigDecimal("1000")); - Share share = new Share(apple, new BigDecimal("2"), new BigDecimal("100")); + Share share = + new Share(appleStock, new BigDecimal("2"), + appleStock.getSalesPrice()); - Transaction transaction = exchange.sell(share, player); + Transaction transaction = testExchange.sell(share, testPlayer); + + TransactionCalculator calculator = new SaleCalculator(share); + BigDecimal expectedPlayerMoney = + testPlayer.getStartingMoney().add(calculator.calculateTotal()); assertInstanceOf(Sale.class, transaction); assertEquals(1, transaction.getWeek()); assertSame(share, transaction.getShare()); - assertEquals(new BigDecimal("1267.9000"), player.getMoney()); + assertEquals(expectedPlayerMoney, testPlayer.getMoney()); + } + + @Test + void sellShareObjectThrowsExceptionOnIllegalArguments() { + Transaction transaction = testExchange.buy("AAPL", new BigDecimal("2"), testPlayer); + Share shareObject = transaction.getShare(); + assertDoesNotThrow(() -> + testExchange.sell(shareObject, testPlayer)); + + assertThrows(IllegalArgumentException.class, () -> + testExchange.sell(null, testPlayer)); + + assertThrows(IllegalArgumentException.class, () -> + testExchange.sell(shareObject, null)); + } + + @Test + void sellingSharesBasedOnAmountFunctionsAsIntended() { + testExchange.buy(appleStock.getSymbol(), new BigDecimal("3"), testPlayer); + + Transaction sale = testExchange.sell(new BigDecimal("1.5"), appleStock.getSymbol(), testPlayer); + + assertInstanceOf(Sale.class, sale); + BigDecimal expectedPlayerMoney = new BigDecimal("847.000"); + assertEquals(expectedPlayerMoney, testPlayer.getMoney()); + + BigDecimal actualQuantity = testPlayer.getPortfolio().getShares(appleStock.getSymbol()).getFirst().getQuantity(); + BigDecimal expectedRemainingShares = new BigDecimal("1.5"); + assertEquals(0, expectedRemainingShares.compareTo(actualQuantity)); + } + + @Test + void attemptingToSellMoreSharesThanOwnedSellsAllSharesOwned() { + testExchange.buy(appleStock.getSymbol(), + new BigDecimal("3.12"), testPlayer); + testExchange.buy(appleStock.getSymbol(), + new BigDecimal("4.56"), testPlayer); + + testExchange.sell(new BigDecimal("100.00"), + appleStock.getSymbol(), testPlayer); + + BigDecimal expectedPlayerMoney = new BigDecimal("988.48000"); + assertEquals(0, expectedPlayerMoney.compareTo(testPlayer.getMoney())); + + assertTrue(testPlayer.getPortfolio().getShares(appleStock.getSymbol()).isEmpty()); + } + + @Test + void attemptingToSellPartialAmountOfSharesWithStockNotOwnedByPlayerThrowsError() { + BigDecimal testAmount = new BigDecimal("6.00"); + assertThrows(IllegalArgumentException.class, + () -> testExchange.sell(testAmount, + appleStock.getSymbol(), testPlayer)); + } + + @Test + void attemptingToSellPartialAmountOfSharesWithInvalidNumberAndPlayerThrowsError() { + BigDecimal testAmount = new BigDecimal("6.00"); + testExchange.buy(appleStock.getSymbol(), testAmount, testPlayer); + + assertDoesNotThrow(() -> testExchange.sell(testAmount, + appleStock.getSymbol(), testPlayer)); + assertThrows(IllegalArgumentException.class, + () -> testExchange.sell(null, appleStock.getSymbol(), testPlayer)); + assertThrows(IllegalArgumentException.class, + () -> testExchange.sell(testAmount, null, testPlayer)); + assertThrows(IllegalArgumentException.class, + () -> testExchange.sell(testAmount, appleStock.getSymbol(), null)); } @Test void advanceIncreasesWeekAndUpdatesStockPrice() { - Exchange exchange = new Exchange("NASDAQ", List.of(appleStock)); BigDecimal oldPrice = appleStock.getSalesPrice(); - exchange.advance(); + testExchange.advance(); - assertEquals(2, exchange.getWeek()); + assertEquals(2, testExchange.getWeek()); assertNotEquals(oldPrice, appleStock.getSalesPrice()); } @Test void getGainersActuallyReturnsProperGainers() { - Stock teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200.00")); - Stock pearStock = new Stock("PEAR", "Pear inc.", new BigDecimal("97.00")); - appleStock.addNewSalesPrice(new BigDecimal("150.00")); teslaStock.addNewSalesPrice(new BigDecimal("230.00")); pearStock.addNewSalesPrice(new BigDecimal("112.00")); - Exchange exchange = new Exchange("Exchange", List.of(appleStock, teslaStock, pearStock)); - - List actualGainers = exchange.getGainers(2); + List actualGainers = testExchange.getGainers(2); boolean actualGainersContainLimitedGainers = actualGainers.contains(teslaStock) && actualGainers.contains(appleStock); @@ -124,17 +270,23 @@ void getGainersActuallyReturnsProperGainers() { } @Test - void getLosersActuallyReturnsProperLosers() { - Stock teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200.00")); - Stock pearStock = new Stock("PEAR", "Pear inc.", new BigDecimal("97.00")); + void getGainersDoesNotReturnLoser() { + + appleStock.addNewSalesPrice(new BigDecimal("150.00")); + teslaStock.addNewSalesPrice(new BigDecimal("230.00")); + pearStock.addNewSalesPrice(new BigDecimal("20")); + List actualGainers = testExchange.getGainers(3); + + assertFalse(actualGainers.contains(pearStock)); + } + @Test + void getLosersActuallyReturnsProperLosers() { appleStock.addNewSalesPrice(new BigDecimal("50.00")); teslaStock.addNewSalesPrice(new BigDecimal("170.00")); - pearStock.addNewSalesPrice(new BigDecimal("82.00")); + pearStock.addNewSalesPrice(new BigDecimal("87")); - Exchange exchange = new Exchange("Exchange", List.of(appleStock, teslaStock, pearStock)); - - List actualLosers = exchange.getLosers(2); + List actualLosers = testExchange.getLosers(2); boolean actualLosersContainsValidLosers = actualLosers.contains(teslaStock) && actualLosers.contains(appleStock); @@ -146,4 +298,29 @@ void getLosersActuallyReturnsProperLosers() { boolean actualLosersNotContainingLoserOutsideOfLimit = !actualLosers.contains(pearStock); assertTrue(actualLosersNotContainingLoserOutsideOfLimit); } + + @Test + void getLosersDoesNotReturnWinners() { + + appleStock.addNewSalesPrice(new BigDecimal("50.00")); + teslaStock.addNewSalesPrice(new BigDecimal("170.00")); + pearStock.addNewSalesPrice(new BigDecimal("200")); + + List actualLosers = testExchange.getLosers(3); + + boolean actualLosersNotContainsWinner = !actualLosers.contains(pearStock); + assertTrue(actualLosersNotContainsWinner); + } + + @Test + void invalidLimitForGettingGainersThrowError() { + assertThrows(IllegalArgumentException.class, () -> testExchange.getGainers(0)); + assertThrows(IllegalArgumentException.class, () -> testExchange.getGainers(-1)); + } + + @Test + void invalidLimitForGettingLosersThrowError() { + assertThrows(IllegalArgumentException.class, () -> testExchange.getLosers(0)); + assertThrows(IllegalArgumentException.class, () -> testExchange.getLosers(-1)); + } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java index 091daeb..fce1960 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java @@ -1,141 +1,163 @@ package edu.ntnu.idi.idatt2003.g40.mappe.engine; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Share; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType; import java.math.BigDecimal; import java.util.List; - -import edu.ntnu.idi.idatt2003.g40.mappe.model.*; -import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class TransactionArchiveTest { /** - * Transaction calculator to be used for testing. + * Test transactionArchive. * */ - private final TransactionCalculator calculator = new TransactionCalculator() { - @Override - public BigDecimal calculateGross() { - return BigDecimal.ZERO; - } - - @Override - public BigDecimal calculateCommission() { - return BigDecimal.ZERO; - } - - @Override - public BigDecimal calculateTax() { - return BigDecimal.ZERO; - } - - @Override - public BigDecimal calculateTotal() { - return BigDecimal.ZERO; - } - }; + private TransactionArchive transactionArchive; + + /** + * Sample share used to emulate transactions. + * */ + private Share sampleShare; + + @BeforeEach + void setUp() { + transactionArchive = new TransactionArchive(); + Stock sampleStock = new Stock("AAPL", "Apple", new BigDecimal("100.00")); + sampleShare = new Share(sampleStock, + new BigDecimal("1.00"), sampleStock.getSalesPrice()); + } @Test void newArchiveIsEmpty() { - TransactionArchive archive = new TransactionArchive(); - - assertTrue(archive.isEmpty()); + assertTrue(transactionArchive.isEmpty()); } @Test - void addMakesArchiveNonEmpty() { - TransactionArchive archive = new TransactionArchive(); - Transaction transaction = createPurchase("AAPL", "Apple", 1); + void addValidTransactionMakesArchiveNonEmpty() { + Transaction purchase = createPurchase(1); - boolean result = archive.add(transaction); + boolean result = transactionArchive.add(purchase); assertTrue(result); - assertFalse(archive.isEmpty()); + assertFalse(transactionArchive.isEmpty()); } @Test - void getTransactionsReturnsOnlyTransactionsFromGivenWeek() { - TransactionArchive archive = new TransactionArchive(); + void addNullTransactionThrowsException() { + Transaction purchase = createPurchase(1); + assertDoesNotThrow(() -> transactionArchive.add(purchase)); + assertThrows(IllegalArgumentException.class, + () -> transactionArchive.add(null)); + } - Transaction transaction1 = createPurchase("AAPL", "Apple", 1); - Transaction transaction2 = createSale("TSLA", "Tesla", 2); - Transaction transaction3 = createPurchase("NVDA", "Nvidia", 1); + @Test + void genericGetTransactionsGetsAllTransactions() { + Transaction week1Purchase1 = createPurchase(1); + Transaction week1Purchase2 = createPurchase(1); + Transaction week2Purchase = createPurchase(2); + Transaction week4Sale = createPurchase(4); + + transactionArchive.add(week1Purchase1); + transactionArchive.add(week1Purchase2); + transactionArchive.add(week2Purchase); + transactionArchive.add(week4Sale); + + List result = transactionArchive.getTransactions(); + + assertEquals(4, result.size()); + assertTrue(result.contains(week1Purchase1)); + assertTrue(result.contains(week1Purchase2)); + assertTrue(result.contains(week2Purchase)); + assertTrue(result.contains(week4Sale)); + } - archive.add(transaction1); - archive.add(transaction2); - archive.add(transaction3); + @Test + void getTransactionsWithWeekSpecifierReturnsOnlyTransactionsFromGivenWeek() { + Transaction week1Purchase1 = createPurchase(1); + Transaction week1Purchase2 = createPurchase(1); + Transaction week2Purchase = createPurchase(2); + + transactionArchive.add(week1Purchase1); + transactionArchive.add(week1Purchase2); + transactionArchive.add(week2Purchase); - List result = archive.getTransactions(1); + List result = transactionArchive.getTransactions(1); assertEquals(2, result.size()); - assertTrue(result.contains(transaction1)); - assertTrue(result.contains(transaction3)); + assertTrue(result.contains(week1Purchase1)); + assertTrue(result.contains(week1Purchase2)); + assertFalse(result.contains(week2Purchase)); } @Test - void getPurchasesReturnsOnlyPurchasesFromGivenWeek() { - TransactionArchive archive = new TransactionArchive(); + void getTransactionsWithWeekSpecifierInvalidWeekThrowsException() { + assertDoesNotThrow(() -> transactionArchive.getTransactions(1)); + assertThrows(IllegalArgumentException.class, + () -> transactionArchive.getTransactions(0)); + } - Purchase purchase1 = createPurchase("AAPL", "Apple", 1); - Purchase purchase2 = createPurchase("NVDA", "Nvidia", 2); - Sale sale = createSale("TSLA", "Tesla", 1); + @Test + void getPurchasesReturnsOnlyPurchasesFromGivenWeek() { + Transaction week1Purchase = createPurchase(1); + Transaction week2Purchase = createPurchase(2); + Transaction week1Sale = createSale(1); - archive.add(purchase1); - archive.add(purchase2); - archive.add(sale); + transactionArchive.add(week1Purchase); + transactionArchive.add(week2Purchase); + transactionArchive.add(week1Sale); - List result = archive.getPurchases(1); + List result = transactionArchive.getPurchases(1); assertEquals(1, result.size()); - assertTrue(result.contains(purchase1)); + assertTrue(result.contains(week1Purchase)); } @Test void getSalesReturnsOnlySalesFromGivenWeek() { - TransactionArchive archive = new TransactionArchive(); + Transaction week1Sale = createSale(1); + Transaction week3Sale = createSale(3); + Transaction week1Purchase = createPurchase(1); - Sale sale1 = createSale("TSLA", "Tesla", 1); - Sale sale2 = createSale("NVDA", "Nvidia", 2); - Purchase purchase = createPurchase("AAPL", "Apple", 1); + transactionArchive.add(week1Sale); + transactionArchive.add(week3Sale); + transactionArchive.add(week1Purchase); - archive.add(sale1); - archive.add(sale2); - archive.add(purchase); - - List result = archive.getSales(1); + List result = transactionArchive.getSales(1); assertEquals(1, result.size()); - assertTrue(result.contains(sale1)); + assertTrue(result.contains(week1Sale)); } @Test void countDistinctWeeksCountsUniqueWeeksOnly() { - TransactionArchive archive = new TransactionArchive(); - - archive.add(createPurchase("AAPL", "Apple", 1)); - archive.add(createSale("TSLA", "Tesla", 1)); - archive.add(createPurchase("NVDA", "Nvidia", 2)); - archive.add(createSale("META", "Meta", 3)); + transactionArchive.add(createPurchase(1)); + transactionArchive.add(createPurchase(1)); + transactionArchive.add(createPurchase(2)); + transactionArchive.add(createSale(3)); - assertEquals(3, archive.countDistinctWeeks()); + assertEquals(3, transactionArchive.countDistinctWeeks()); } - private Purchase createPurchase(final String symbol, - final String company, - final int week) { - Stock stock = new Stock(symbol, company, new BigDecimal("100")); - Share share = new Share(stock, BigDecimal.ONE, new BigDecimal("100")); - return new Purchase(share, week, calculator); + private Transaction createPurchase(final int week) { + return TransactionFactory.createTransaction( + TransactionType.PURCHASE, sampleShare, week + ); } - private Sale createSale(final String symbol, - final String company, - final int week) { - Stock stock = new Stock(symbol, company, new BigDecimal("100")); - Share share = new Share(stock, BigDecimal.ONE, new BigDecimal("100")); - return new Sale(share, week, calculator); + private Transaction createSale(final int week) { + return TransactionFactory.createTransaction( + TransactionType.SALE, sampleShare, week + ); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java index 1765d46..d5952dc 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java @@ -31,7 +31,7 @@ void getStatusAfterDoubleIncreaseReturnsBetterStatus() { @Test void gettingStatusWhenNegativeDifferenceReturnsWorstStatus() { - testPlayer.addMoney(new BigDecimal(-1000)); + testPlayer.withdrawMoney(new BigDecimal(1000)); assertEquals(PlayerStatus.NOOB, testPlayer.getStatus()); } @@ -40,10 +40,10 @@ void multipleChangesInValueCauseCorrectStatus() { testPlayer.addMoney(new BigDecimal(2000)); assertEquals(PlayerStatus.PRO, testPlayer.getStatus()); - testPlayer.addMoney(new BigDecimal(-1000)); + testPlayer.withdrawMoney(new BigDecimal(1000)); assertEquals(PlayerStatus.TRYHARD, testPlayer.getStatus()); - testPlayer.addMoney(new BigDecimal(-500)); + testPlayer.withdrawMoney(new BigDecimal(500)); assertEquals(PlayerStatus.GOOD, testPlayer.getStatus()); } -} \ No newline at end of file +} diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java index 70dd13b..9ac2d64 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java @@ -1,65 +1,197 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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 edu.ntnu.idi.idatt2003.g40.mappe.exceptions.NotEnoughMoneyException; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory; +import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType; import java.math.BigDecimal; - -import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class PlayerTest { + /** + * Test player to use. + * */ + private Player testPlayer; + + @BeforeEach + void setUp() { + testPlayer = new Player("Alice", new BigDecimal("1000")); + } + @Test void constructorSetsNameMoneyPortfolioAndArchive() { - Player player = new Player("Alice", new BigDecimal("1000")); + assertEquals("Alice", testPlayer.getName()); + assertEquals(new BigDecimal("1000"), testPlayer.getMoney()); + assertNotNull(testPlayer.getPortfolio()); + assertNotNull(testPlayer.getTransactionArchive()); + assertEquals(1000f, + testPlayer.getMoneyAsFloatProperty().floatValue()); + assertEquals(1000f, + testPlayer.getNetWorthAsFloatProperty().floatValue()); + } - assertEquals("Alice", player.getName()); - assertEquals(new BigDecimal("1000"), player.getMoney()); - assertNotNull(player.getPortfolio()); - assertNotNull(player.getTransactionArchive()); + @Test + void constructorThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> new Player("Bob", new BigDecimal("2000")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Player("", new BigDecimal("2000")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Player(null, new BigDecimal("2000")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Player("Bob", null) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Player("Bob", new BigDecimal("0")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Player("Bob", new BigDecimal("-100")) + ); } @Test void addMoneyIncreasesBalance() { - Player player = new Player("Bob", new BigDecimal("500")); - - player.addMoney(new BigDecimal("200")); + testPlayer.addMoney(new BigDecimal("200")); + assertEquals(new BigDecimal("1200"), testPlayer.getMoney()); + } - assertEquals(new BigDecimal("700"), player.getMoney()); + @Test + void addMoneyThrowsExceptionWhenIllegalArguments() { + assertDoesNotThrow( + () -> testPlayer.addMoney(new BigDecimal("200")) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.addMoney(null) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.addMoney(new BigDecimal("-10")) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.addMoney(new BigDecimal("0")) + ); } @Test void withdrawMoneyDecreasesBalance() { - Player player = new Player("Charlie", new BigDecimal("500")); - - player.withdrawMoney(new BigDecimal("150")); + testPlayer.withdrawMoney(new BigDecimal("150")); + assertEquals(new BigDecimal("850"), testPlayer.getMoney()); + } - assertEquals(new BigDecimal("350"), player.getMoney()); + @Test + void withdrawMoneyThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> testPlayer.withdrawMoney(new BigDecimal("200")) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.withdrawMoney(null) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.withdrawMoney(new BigDecimal("-10")) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.withdrawMoney(new BigDecimal("0")) + ); + + assertThrows(IllegalArgumentException.class, + () -> testPlayer.withdrawMoney(new BigDecimal("99999")) + ); } @Test void addAndWithdrawMoneyUpdateBalanceCorrectly() { - Player player = new Player("Dana", new BigDecimal("1000")); + testPlayer.addMoney(new BigDecimal("250")); + testPlayer.withdrawMoney(new BigDecimal("300")); - player.addMoney(new BigDecimal("250")); - player.withdrawMoney(new BigDecimal("300")); - - assertEquals(new BigDecimal("950"), player.getMoney()); + assertEquals(new BigDecimal("950"), testPlayer.getMoney()); } @Test - void getNetWorthCalculatesCorrectly() { + void getNetWorthCalculatesCorrectlyForSales() { Stock stock = new Stock("AAPL", "Apple inc.,", new BigDecimal("100.00")); - Player player = new Player("Bob", new BigDecimal("900")); Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("100.00")); - player.getPortfolio().addShare(share); - SaleCalculator saleCalculator = new SaleCalculator(share); + BigDecimal expectedNetWorth = testPlayer.getNetWorth(); + Transaction transaction = TransactionFactory.createTransaction(TransactionType.SALE, share, 1); + + testPlayer.handleTransaction(transaction); + + BigDecimal actualNetWorth = testPlayer.getNetWorth(); + expectedNetWorth = expectedNetWorth.add(transaction.getCalculator().calculateTotal()); + + assertEquals(expectedNetWorth, actualNetWorth); + assertEquals(actualNetWorth.floatValue(), + testPlayer.getNetWorthAsFloatProperty().floatValue()); + assertEquals(testPlayer.getMoney().floatValue(), + testPlayer.getMoneyAsFloatProperty().floatValue()); + } + + @Test + void getNetWorthCalculatesCorrectlyForPurchases() { + Stock stock = new Stock("AAPL", "Apple inc.,", new BigDecimal("100.00")); + Share share = new Share(stock, new BigDecimal("1"), stock.getSalesPrice()); + + BigDecimal expectedNetWorth = testPlayer.getNetWorth(); + Transaction transaction = TransactionFactory.createTransaction(TransactionType.PURCHASE, share, 1); + + testPlayer.handleTransaction(transaction); + + BigDecimal actualNetWorth = testPlayer.getNetWorth(); + expectedNetWorth = expectedNetWorth.subtract( + transaction.getCalculator().calculateCommission() + ); + + assertEquals(expectedNetWorth, actualNetWorth); + assertEquals(actualNetWorth.floatValue(), + testPlayer.getNetWorthAsFloatProperty().floatValue()); + assertEquals(testPlayer.getMoney().floatValue(), + testPlayer.getMoneyAsFloatProperty().floatValue()); + } + + @Test + void handleTransactionThrowsExceptionsOnIllegalArgumentsAndNotEnoughMoney() { + Stock stock = new Stock("AAPL", "Apple inc.,", new BigDecimal("100.00")); + Share share = new Share(stock, new BigDecimal("1"), stock.getSalesPrice()); + + Share expensiveShare = new Share(stock, new BigDecimal("999.99"), new BigDecimal("999.99")); + + Transaction validTransaction = TransactionFactory.createTransaction( + TransactionType.PURCHASE, share, 1 + ); + + Transaction tooExpensiveTransaction = TransactionFactory.createTransaction( + TransactionType.PURCHASE, expensiveShare, 1 + ); + + assertDoesNotThrow( + () -> testPlayer.handleTransaction(validTransaction) + ); - BigDecimal calculatedNetWorth = player.getMoney().add(saleCalculator.calculateTotal()); - BigDecimal actualNetWorth = player.getNetWorth(); + assertThrows(IllegalArgumentException.class, + () -> testPlayer.handleTransaction(null) + ); - assertEquals(calculatedNetWorth, actualNetWorth); + assertThrows(NotEnoughMoneyException.class, + () -> testPlayer.handleTransaction(tooExpensiveTransaction) + ); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java index 470f26a..fde5639 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java @@ -1,104 +1,152 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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; import java.math.BigDecimal; import java.util.List; -import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class PortfolioTest { + /** + * Test portfolio used. + * */ + private Portfolio testPortfolio; + + /** + * Test stock used. + * */ + private Stock testStock; + + /** + * Test share used. + * */ + private Share testShare; + + @BeforeEach + void setUp() { + testPortfolio = new Portfolio(); + testStock = new Stock("AAPL", "Apple", new BigDecimal("150")); + testShare = new Share(testStock, new BigDecimal("2"), new BigDecimal("100")); + } + @Test void addShareAddsShareToPortfolio() { - Portfolio portfolio = new Portfolio(); - Stock stock = new Stock("AAPL", "Apple", new BigDecimal("150")); - Share share = new Share(stock, new BigDecimal("2"), new BigDecimal("100")); + assertDoesNotThrow( + () -> testPortfolio.addShare(testShare)); - boolean result = portfolio.addShare(share); + assertTrue(testPortfolio.contains(testShare)); + } - assertTrue(result); - assertTrue(portfolio.contains(share)); + @Test + void addingNullShareThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> testPortfolio.addShare(null)); + + assertFalse(testPortfolio.contains(testShare)); } @Test void removeShareRemovesShareFromPortfolio() { - Portfolio portfolio = new Portfolio(); - Stock stock = new Stock("TSLA", "Tesla", new BigDecimal("200")); - Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("200")); + assertFalse(testPortfolio.contains(testShare)); + + testPortfolio.addShare(testShare); - portfolio.addShare(share); - boolean result = portfolio.removeShare(share); + assertTrue(testPortfolio.contains(testShare)); + + boolean result = testPortfolio.removeShare(testShare); assertTrue(result); - assertFalse(portfolio.contains(share)); + assertFalse(testPortfolio.contains(testShare)); } @Test - void getSharesReturnsAllShares() { - Portfolio portfolio = new Portfolio(); + void removingNullShareThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> testPortfolio.removeShare(null)); + } - Stock stock = new Stock("AAPL", "Apple", new BigDecimal("150")); - Share share = new Share(stock, new BigDecimal("3"), new BigDecimal("150")); + @Test + void removingMoreSharesThanOwnedThrowsException() { + testPortfolio.addShare(testShare); + Share testShare2 = new Share(testStock, new BigDecimal("4"), testStock.getSalesPrice()); + assertThrows(IllegalArgumentException.class, + () -> testPortfolio.removeShare(testShare2) + ); + } - portfolio.addShare(share); + @Test + void getSharesReturnsAllShares() { + testPortfolio.addShare(testShare); - List shares = portfolio.getShares(); + List shares = testPortfolio.getShares(); assertEquals(1, shares.size()); - assertTrue(shares.contains(share)); + assertTrue(shares.contains(testShare)); } @Test void getSharesWithSymbolReturnsMatchingShares() { - Portfolio portfolio = new Portfolio(); + Stock testStock2 = new Stock("TSLA", "Tesla", new BigDecimal("200")); - Stock apple = new Stock("AAPL", "Apple", new BigDecimal("150")); - Stock tesla = new Stock("TSLA", "Tesla", new BigDecimal("200")); - - Share appleShare = new Share(apple, - new BigDecimal("1"), - new BigDecimal("150")); - Share teslaShare = new Share(tesla, + Share testShare2 = new Share(testStock2, new BigDecimal("1"), new BigDecimal("200")); - portfolio.addShare(appleShare); - portfolio.addShare(teslaShare); + testPortfolio.addShare(testShare); + testPortfolio.addShare(testShare2); - List result = portfolio.getShares("AAPL"); + List result = testPortfolio.getShares("AAPL"); assertEquals(1, result.size()); - assertTrue(result.contains(appleShare)); + assertTrue(result.contains(testShare)); } @Test - void containsReturnsFalseWhenShareNotPresent() { - Portfolio portfolio = new Portfolio(); - - Stock stock = new Stock("NVDA", "Nvidia", new BigDecimal("800")); - Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("800")); - - assertFalse(portfolio.contains(share)); + void getSharesWithSymbolThrowsExceptionOnIllegalArgument() { + assertThrows(IllegalArgumentException.class, + () -> testPortfolio.getShares(null) + ); } @Test - void getNetWorthReturnsNetWorth() { - Portfolio portfolio = new Portfolio(); - - Stock stock = new Stock("NVDA", "Nvidia", new BigDecimal("800")); - Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("800")); - portfolio.addShare(share); - - SaleCalculator saleCalculator = new SaleCalculator(share); - - BigDecimal calculatedNetWorth = saleCalculator.calculateTotal(); + void containsReturnsFalseWhenShareNotPresent() { + assertFalse(testPortfolio.contains(testShare)); + } - BigDecimal actualNetWorth = portfolio.getNetWorth(); + @Test + void containsThrowsExceptionOnIllegalArgument() { + assertThrows(IllegalArgumentException.class, + () -> testPortfolio.contains(null) + ); + } - assertEquals(calculatedNetWorth, actualNetWorth); + @Test + void getTotalSharesBySymbolReturnsCorrectValues() { + assertEquals(0, + BigDecimal.ZERO.compareTo( + testPortfolio.getTotalShareQuantityBySymbol("AAPL") + ) + ); + + assertEquals(0, + BigDecimal.ZERO.compareTo( + testPortfolio.getTotalShareQuantityBySymbol(null) + ) + ); + + testPortfolio.addShare(testShare); + + assertEquals(0, + new BigDecimal("2.0").compareTo( + testPortfolio.getTotalShareQuantityBySymbol("AAPL") + ) + ); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java index 42caf80..b2c03c6 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java @@ -1,11 +1,14 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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; -import java.math.BigDecimal; - import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator; +import java.math.BigDecimal; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** @@ -14,44 +17,70 @@ final class PurchaseTest { /** - * Valid test stock. + * Test purchase used in various tests. * */ - private final Stock testStock = - new Stock("AAPL", "Apple Inc.", new BigDecimal("100.00")); + private Purchase testPurchase; /** * Valid test share. * */ - private final Share testShare = - new Share(testStock, new BigDecimal("10"), new BigDecimal("10")); + private Share testShare; /** * Valid test purchase calculator. * */ - private final PurchaseCalculator testPurchaseCalculator = - new PurchaseCalculator(testShare); + private PurchaseCalculator testPurchaseCalculator; /** * Valid test player. * */ - private final Player testPlayer = - new Player("TestName", new BigDecimal("1000.00")); + private Player testPlayer; + + @BeforeEach + void setUp() { + Stock testStock = new Stock( + "AAPL", + "Apple Inc.", + new BigDecimal("100.00") + ); + testShare = new Share( + testStock, + new BigDecimal("10"), + new BigDecimal("10") + ); + testPurchaseCalculator = new PurchaseCalculator(testShare); + testPlayer = new Player("TestName", new BigDecimal("1000.00")); + testPurchase = new Purchase(testShare, 1, testPurchaseCalculator); + } @Test void constructorSetsValues() { - Purchase purchase = new Purchase(testShare, 1, testPurchaseCalculator); - - assertEquals(testShare, purchase.getShare()); - assertEquals(1, purchase.getWeek()); - assertEquals(testPurchaseCalculator, purchase.getCalculator()); + assertEquals(testShare, testPurchase.getShare()); + assertEquals(1, testPurchase.getWeek()); + assertEquals(testPurchaseCalculator, testPurchase.getCalculator()); } @Test - void commitMethodSetsCommitToTrue() { - Purchase purchase = new Purchase(testShare, 1, testPurchaseCalculator); + void constructorThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> new Purchase(testShare, 1, testPurchaseCalculator) + ); - purchase.commit(testPlayer); + assertThrows(IllegalArgumentException.class, + () -> new Purchase(null, 1, testPurchaseCalculator) + ); + assertThrows(IllegalArgumentException.class, + () -> new Purchase(testShare, 0, testPurchaseCalculator) + ); + assertThrows(IllegalArgumentException.class, + () -> new Purchase(testShare, 1, null) + ); + } - assertTrue(purchase.isCommited()); + @Test + void commitMethodSetsCommitToTrue() { + assertFalse(testPurchase.isCommited()); + testPurchase.commit(testPlayer); + assertTrue(testPurchase.isCommited()); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java index 22e4e4e..feda7ac 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java @@ -1,11 +1,14 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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; +import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; import java.math.BigDecimal; - -import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** @@ -14,44 +17,70 @@ final class SaleTest { /** - * Valid test stock. + * Test sale used in various tests. * */ - private final Stock testStock = - new Stock("AAPL", "Apple Inc.", new BigDecimal("100.00")); + private Sale testSale; /** * Valid test share. * */ - private final Share testShare = - new Share(testStock, new BigDecimal("10"), new BigDecimal("10")); + private Share testShare; /** * Valid test purchase calculator. * */ - private final PurchaseCalculator testSaleCalculator = - new PurchaseCalculator(testShare); + private SaleCalculator testSaleCalculator; /** * Valid test player. * */ - private final Player testPlayer = - new Player("TestName", new BigDecimal("1000.00")); + private Player testPlayer; + + @BeforeEach + void setUp() { + Stock testStock = new Stock( + "AAPL", + "Apple Inc.", + new BigDecimal("100.00") + ); + testShare = new Share( + testStock, + new BigDecimal("10"), + new BigDecimal("10") + ); + testSaleCalculator = new SaleCalculator(testShare); + testPlayer = new Player("TestName", new BigDecimal("1000.00")); + testSale = new Sale(testShare, 1, testSaleCalculator); + } @Test void constructorSetsValues() { - Sale sale = new Sale(testShare, 1, testSaleCalculator); - - assertEquals(testShare, sale.getShare()); - assertEquals(1, sale.getWeek()); - assertEquals(testSaleCalculator, sale.getCalculator()); + assertEquals(testShare, testSale.getShare()); + assertEquals(1, testSale.getWeek()); + assertEquals(testSaleCalculator, testSale.getCalculator()); } @Test - void commitMethodSetsCommitToTrue() { - Sale sale = new Sale(testShare, 1, testSaleCalculator); + void constructorThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> new Purchase(testShare, 1, testSaleCalculator) + ); - sale.commit(testPlayer); + assertThrows(IllegalArgumentException.class, + () -> new Purchase(null, 1, testSaleCalculator) + ); + assertThrows(IllegalArgumentException.class, + () -> new Purchase(testShare, 0, testSaleCalculator) + ); + assertThrows(IllegalArgumentException.class, + () -> new Purchase(testShare, 1, null) + ); + } - assertTrue(sale.isCommited()); + @Test + void commitMethodSetsCommitToTrue() { + assertFalse(testSale.isCommited()); + testSale.commit(testPlayer); + assertTrue(testSale.isCommited()); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java new file mode 100644 index 0000000..907431e --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java @@ -0,0 +1,53 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.model; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test class for {@link SaveGame}. + * */ +class SaveGameTest { + + /** + * {@link SaveGame} object to use for testing. + * */ + private SaveGame testSaveGame; + + @BeforeEach + void setUp() { + testSaveGame = new SaveGame("Save 1", 10, 100, "Stock path"); + } + + @Test + void constructorSetsValuesAsExpected() { + Assertions.assertEquals("Save 1", testSaveGame.getName()); + Assertions.assertEquals(10, testSaveGame.getBalance()); + Assertions.assertEquals(100, testSaveGame.getStartingCapital()); + Assertions.assertEquals("Stock path", testSaveGame.getStockDataPath()); + } + + @Test + void constructorThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> new SaveGame("Save 2", 10, 100, "Stock path 2") + ); + + assertThrows(IllegalArgumentException.class, + () -> new SaveGame("", 10, 100, "Stock path 2") + ); + + assertThrows(IllegalArgumentException.class, + () -> new SaveGame("Save 2", 0, 100, "Stock path 2") + ); + + assertThrows(IllegalArgumentException.class, + () -> new SaveGame("Save 2", 10, -10, "Stock path 2") + ); + } +} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java index bb78ea1..f73a2e4 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java @@ -1,52 +1,114 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.math.BigDecimal; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class ShareTest { - @Test - void constructorStoresAllValuesCorrectly() { - Stock stock = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.00")); + /** + * Share to use for testing. + */ + private Share testShare; + + /** + * Stock to use for testing. + * */ + private Stock testStock; + + @BeforeEach + void setUp() { + testStock = new Stock( + "AAPL", + "Apple Inc.", + new BigDecimal("150.00") + ); BigDecimal quantity = new BigDecimal("10"); BigDecimal purchasePrice = new BigDecimal("145.50"); + testShare = new Share(testStock, quantity, purchasePrice); + } + + @Test + void constructorStoresAllValuesCorrectly() { + assertSame(testStock, testShare.getStock()); + + assertEquals(0, + new BigDecimal("10") + .compareTo(testShare.getQuantity()) + ); + + assertEquals(0, + new BigDecimal("145.50") + .compareTo(testShare.getPurchasePrice()) + ); + } + + @Test + void constructorThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> new Share(testStock, new BigDecimal("1"), + new BigDecimal("100")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Share(null, new BigDecimal("1"), new BigDecimal("100")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Share(testStock, null, new BigDecimal("100")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Share(testStock, new BigDecimal("1"), null) + ); - Share share = new Share(stock, quantity, purchasePrice); + assertThrows(IllegalArgumentException.class, + () -> new Share(testStock, new BigDecimal("0"), + new BigDecimal("100")) + ); - assertSame(stock, share.getStock()); - assertEquals(quantity, share.getQuantity()); - assertEquals(purchasePrice, share.getPurchasePrice()); + assertThrows(IllegalArgumentException.class, + () -> new Share(testStock, new BigDecimal("1"), new BigDecimal("0")) + ); } @Test void shareSupportsDecimalValues() { - Stock stock = new Stock("TSLA", "Tesla Inc.", new BigDecimal("200.00")); - Share share = new Share( - stock, + Share testShare2 = new Share( + testStock, new BigDecimal("2.5"), new BigDecimal("198.75") ); - assertEquals(new BigDecimal("2.5"), share.getQuantity()); - assertEquals(new BigDecimal("198.75"), share.getPurchasePrice()); + assertEquals(0, + new BigDecimal("2.5") + .compareTo(testShare2.getQuantity()) + ); + + assertEquals(0, + new BigDecimal("198.75") + .compareTo(testShare2.getPurchasePrice()) + ); } @Test void getStockReturnsCorrectStockObject() { - Stock stock = new Stock("NVDA", - "NVIDIA Corporation", - new BigDecimal("875.40")); - Share share = new Share(stock, - new BigDecimal("4"), - new BigDecimal("850.00")); - - assertSame(stock, share.getStock()); - assertEquals("NVDA", share.getStock().getSymbol()); - assertEquals("NVIDIA Corporation", share.getStock().getCompany()); + assertSame(testStock, testShare.getStock()); + assertEquals( + "AAPL", + testShare.getStock().getSymbol() + ); + + assertEquals( + "Apple Inc.", + testShare.getStock().getCompany() + ); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java index c5f907d..8c7f48d 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java @@ -1,15 +1,20 @@ package edu.ntnu.idi.idatt2003.g40.mappe.model; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.math.BigDecimal; import java.util.List; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - final class StockTest { + /** + * Stock to use for testing. + * */ private Stock testStock; @BeforeEach @@ -23,6 +28,28 @@ void constructorSetsSymbolAndCompany() { assertEquals("Apple Inc.", testStock.getCompany()); } + @Test + void constructorThrowsExceptionOnIllegalArguments() { + assertDoesNotThrow( + () -> new Stock("AAPL", "APPLE INC.", new BigDecimal("100")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Stock("", "APPLE INC.", new BigDecimal("100")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Stock("AAPL", "", new BigDecimal("100")) + ); + + assertThrows(IllegalArgumentException.class, + () -> new Stock("AAPL", "APPLE INC.", new BigDecimal("0")) + ); + assertThrows(IllegalArgumentException.class, + () -> new Stock("AAPL", "APPLE INC.", null) + ); + } + @Test void addNewSalesPriceThenGetSalesPriceReturnsLastAddedPrice() { testStock.addNewSalesPrice(new BigDecimal("123.45")); @@ -39,11 +66,18 @@ void addNewSalesPriceTwiceGetSalesPriceReturnsMostRecent() { } @Test - void addNewSalesPriceDoesNotAllowNullCurrentImplementation() { - + void addNewSalesPriceThrowsExceptionOnIllegalArguments() { assertThrows(IllegalArgumentException.class, () -> { testStock.addNewSalesPrice(null); }); + + assertThrows(IllegalArgumentException.class, () -> { + testStock.addNewSalesPrice(new BigDecimal("0")); + }); + + assertThrows(IllegalArgumentException.class, () -> { + testStock.addNewSalesPrice(new BigDecimal("-10")); + }); } @Test diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java deleted file mode 100644 index 5cb5444..0000000 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package edu.ntnu.idi.idatt2003.g40.mappe.service; - -import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -class FileConverterTest { - - private FileConverter converter; - - private String validStockAsString1; - private String validStockAsString2; - private String validStockAsString3; - private ArrayList allStocks; - private String invalidStockAsString1; - - @BeforeEach - void setUp() { - validStockAsString1 = "AAPL, Apple inc., 251.42"; - validStockAsString2 = "NVID, Nvidia corp., 100.25"; - validStockAsString3 = "SAMS, Samsung corporation, 103.21"; - - invalidStockAsString1 = "INVALID, This stock has an invalid code!, 100.21"; - - allStocks = new ArrayList<>(); - - allStocks.add(validStockAsString1); - allStocks.add(validStockAsString2); - allStocks.add(validStockAsString3); - allStocks.add(invalidStockAsString1); - - converter = new FileConverter(); - } - - @Test - void converter_returns_valid_stock_apple() { - - boolean stockIncluded = false; - - List stocksFromConverter = converter.getStocksFromStrings(allStocks); - - for (Stock s : stocksFromConverter) { - if (s.getSymbol().equals("AAPL")) { - stockIncluded = true; - break; - } - } - - Assertions.assertTrue(stockIncluded); - } - - @Test - void converter_ignores_invalid_stock_representation() { - - boolean stockIncluded = false; - - List stocksFromConverter = converter.getStocksFromStrings(allStocks); - - for (Stock s : stocksFromConverter) { - if (s.getSymbol().equals("INVALID")) { - stockIncluded = true; - break; - } - } - - Assertions.assertFalse(stockIncluded); - } -} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java deleted file mode 100644 index 7d87b1a..0000000 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package edu.ntnu.idi.idatt2003.g40.mappe.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class FileParserTest { - - private final String testStockDataPath = "/testStockData.txt"; - - private final String absoluteTestStockDataPath = "src/main/resources/testStockData.txt"; - FileParser fileParser; - - private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591"; - - private final String invalidStockFromFile = "COOLI, This is a cool name, 252.2"; - - private final String commentFromFile = "#Above me are some valid formats."; - - private List allLines = new ArrayList<>(); - - private List validStocks = new ArrayList<>(); - - @BeforeEach - void setUp() throws Exception { - fileParser = new FileParser(testStockDataPath); - Path path = Paths.get(absoluteTestStockDataPath); - allLines = Files.readAllLines(path); - try { - validStocks = fileParser.readFile(); - } catch (Exception _) { - throw new Exception("Test failed"); - } - } - - @Test - void parser_gets_valid_stock_from_file() { - assertTrue(allLines.contains(validStockFromFile)); - assertTrue(validStocks.contains(validStockFromFile)); - } - - @Test - void parser_skips_comments_from_file() { - assertTrue(allLines.contains(commentFromFile)); - assertFalse(validStocks.contains(commentFromFile)); - } - - @Test - void parser_skips_invalid_stock_from_file() { - assertTrue(allLines.contains(invalidStockFromFile)); - assertFalse(validStocks.contains(invalidStockFromFile)); - } -} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java new file mode 100644 index 0000000..74f0470 --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java @@ -0,0 +1,56 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class StockFileManagerTest { + + /** + * Path used to test file system. + * */ + @TempDir + private Path tempDir; + + @Test + void readFileParsesValidStocksAndFiltersOutCommentsAndInvalidData() + throws IOException { + Path tempFile = tempDir.resolve("testStockData.txt"); + List mockFileData = List.of( + "# This is a comment header line and should be skipped", + "NVID, Nvidida Corporation, 241.591", + "AAPL, Apple Inc, 175.50", + "", + "INVALID_ROW_MISSING_COLUMNS", + "COOLI, This is a cool name but missing price token" + ); + Files.write(tempFile, mockFileData); + + StockFileManager stockFileManager = + new StockFileManager(tempFile.toString()); + List parsingResults = stockFileManager.readFile(); + + assertEquals(2, parsingResults.size()); + assertTrue(parsingResults.contains("NVID, Nvidida Corporation, 241.591")); + assertTrue(parsingResults.contains("AAPL, Apple Inc, 175.50")); + assertFalse(parsingResults.stream().anyMatch(line -> line.startsWith("#"))); + } + + @Test + void readFileHandlesEmptyFileGracefully() throws IOException { + Path emptyFile = tempDir.resolve("emptyStockData.txt"); + Files.createFile(emptyFile); + + StockFileManager stockFileManager = new StockFileManager(emptyFile.toString()); + List results = stockFileManager.readFile(); + + assertTrue(results.isEmpty()); + } +} diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java new file mode 100644 index 0000000..8fe5158 --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java @@ -0,0 +1,105 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.service; + +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class StockFileParserTest { + + /** + * Test stock parser to use. + * */ + private StockFileParser testParser; + + /** + * List of strings containing valid and invalid representations + * of stocks. + * */ + private ArrayList allStocks; + + @BeforeEach + void setUp() { + String validStockAsString1 = "AAPL, Apple inc., 251.42"; + String validStockAsString2 = "NVID, Nvidia corp., 100.25"; + String validStockAsString3 = "SAMS, Samsung corporation, 103.21"; + + String invalidStockAsString1 = "INVALID, This stock has an invalid code!, 100.21"; + + allStocks = new ArrayList<>(); + + allStocks.add(validStockAsString1); + allStocks.add(validStockAsString2); + allStocks.add(validStockAsString3); + allStocks.add(invalidStockAsString1); + + testParser = new StockFileParser(); + } + + @Test + void getStocksFromStringsReturnsValidStocks() { + + boolean stockIncluded = false; + + List stocksFromConverter = testParser.getStocksFromStrings(allStocks); + + for (Stock s : stocksFromConverter) { + if (s.getSymbol().equals("AAPL")) { + stockIncluded = true; + break; + } + } + + Assertions.assertTrue(stockIncluded); + } + + @Test + void getStocksFromStringsIgnoresInvalidStocks() { + + boolean stockIncluded = false; + + List stocksFromConverter = testParser.getStocksFromStrings(allStocks); + + for (Stock s : stocksFromConverter) { + if (s.getSymbol().equals("INVALID")) { + stockIncluded = true; + break; + } + } + + Assertions.assertFalse(stockIncluded); + } + + @Test + void stocksToStringsConvertsValidStocksToCsvFormat() { + Stock apple = new Stock("AAPL", "Apple Inc", new BigDecimal("175.50")); + Stock tesla = new Stock("TSLA", "Tesla Inc", new BigDecimal("200.00")); + List stocks = List.of(apple, tesla); + + List result = testParser.stocksToStrings(stocks); + + assertEquals(2, result.size()); + assertEquals("AAPL, Apple Inc, 175.50", result.get(0)); + assertEquals("TSLA, Tesla Inc, 200.00", result.get(1)); + } + + @Test + void stocksToStringsThrowsExceptionOnNullOrEmptyList() { + List emptyList = new ArrayList<>(); + + assertThrows(IllegalArgumentException.class, + () -> testParser.stocksToStrings(null) + ); + + assertThrows(IllegalArgumentException.class, + () -> testParser.stocksToStrings(emptyList) + ); + } +} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java index 6b25c35..1f49c32 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java @@ -1,7 +1,9 @@ package edu.ntnu.idi.idatt2003.g40.mappe.service; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase; import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale; @@ -15,56 +17,45 @@ class TransactionFactoryTest { private Stock testStock; private Share testShare; - private Sale testSale; - private Purchase testPurchase; - private SaleCalculator testSaleCalculator; - private PurchaseCalculator testPurchaseCalculator; @BeforeEach void setUp() { testStock = new Stock("AAPL", "APPLE INC.", new BigDecimal("100.00")); testShare = new Share(testStock, new BigDecimal("10.0"), testStock.getSalesPrice()); - testSaleCalculator = new SaleCalculator(testShare); - testSale = new Sale(testShare, 1, testSaleCalculator); - testPurchaseCalculator = new PurchaseCalculator(testShare); - testPurchase = new Purchase(testShare, 1, testPurchaseCalculator); } @Test void factoryReturnsCorrectSale() { - Transaction sale2 = TransactionFactory.createTransaction(TransactionType.SALE, testShare, 1, testSaleCalculator); - assertTrue(equalTransactions(testSale, sale2)); + int targetWeek = 1; + Transaction transaction = TransactionFactory.createTransaction(TransactionType.SALE, testShare, targetWeek); + assertNotNull(transaction); + assertInstanceOf(Sale.class, transaction); + assertEquals(targetWeek, transaction.getWeek()); + assertEquals(testShare, transaction.getShare()); + assertNotNull(transaction.getCalculator()); } @Test void factoryReturnsCorrectPurchase() { - Transaction purchase2 = TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, 1, testPurchaseCalculator); - assertTrue(equalTransactions(testPurchase, purchase2)); + int targetWeek = 1; + Transaction transaction = TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, targetWeek); + assertNotNull(transaction); + assertInstanceOf(Purchase.class, transaction); + assertEquals(targetWeek, transaction.getWeek()); + assertEquals(testShare, transaction.getShare()); + assertNotNull(transaction.getCalculator()); } @Test void factoryThrowsErrors() { - assertThrows(IllegalArgumentException.class, () -> { - TransactionFactory.createTransaction(TransactionType.PURCHASE, null, 1, testPurchaseCalculator); - }); - - assertThrows(IllegalArgumentException.class, () -> { - TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, -1, testPurchaseCalculator); - }); - - assertThrows(IllegalArgumentException.class, () -> { - TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, 1, null); - }); - - assertThrows(IllegalArgumentException.class, () -> { - TransactionFactory.createTransaction(null, testShare, 1, testPurchaseCalculator); - }); - } - - private boolean equalTransactions(final Transaction transaction1, final Transaction transaction2) { - return (transaction1.getWeek() == transaction2.getWeek() - && transaction1.getShare() == transaction2.getShare() - && transaction1.getCalculator() == transaction2.getCalculator() - && transaction1.isCommited() == transaction2.isCommited()); + assertThrows(IllegalArgumentException.class, + () -> TransactionFactory.createTransaction(TransactionType.PURCHASE, null, 1) + ); + assertThrows(IllegalArgumentException.class, + () -> TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, -1) + ); + assertThrows(IllegalArgumentException.class, + () -> TransactionFactory.createTransaction(null, testShare, 1) + ); } } \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java index 716e96e..eee20c2 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java @@ -1,99 +1,188 @@ package edu.ntnu.idi.idatt2003.g40.mappe.service.event; -import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewData; -import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - class EventManagerTest { private enum TestEventTypes implements EventChannel { + /** + * Test event type 1. + * */ TEST_TYPE_1, + + /** + * Test event type 2. + * */ TEST_TYPE_2, + + /** + * Test event type 3 (Used by both subscribers). + * */ TEST_TYPE_3; - @Override - public String getName() { - return this.name(); - } } - private GenericEventPublisher testEventPublisher; - private GenericEventPublisher testEventPublisher2; - private GenericEventPublisher testEventPublisher3; + /** + * Event manager used for testing. + * */ + private EventManager testEventManager; - private GenericEventSubscriber testEventSubscriber; - private GenericEventSubscriber testEventSubscriber2; + /** + * Example event subscriber 1. + * */ + private SampleEventSubscriber sampleEventSubscriber1; - private EventManager testEventManager; + /** + * Example event subscriber 2. + * */ + private SampleEventSubscriber sampleEventSubscriber2; @BeforeEach void setUp() { testEventManager = new EventManager(); - testEventSubscriber = new GenericEventSubscriber(); - testEventSubscriber2 = new GenericEventSubscriber(); + sampleEventSubscriber1 = new SampleEventSubscriber(); + sampleEventSubscriber2 = new SampleEventSubscriber(); + + testEventManager.addSubscriber(sampleEventSubscriber1, TestEventTypes.TEST_TYPE_1); + testEventManager.addSubscriber(sampleEventSubscriber1, TestEventTypes.TEST_TYPE_3); - testEventPublisher = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_1); - testEventPublisher2 = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_2); - testEventPublisher3 = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_3); + testEventManager.addSubscriber(sampleEventSubscriber2, TestEventTypes.TEST_TYPE_2); + testEventManager.addSubscriber(sampleEventSubscriber2, TestEventTypes.TEST_TYPE_3); + } + + @Test + void addingSubscriberNullParametersThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> testEventManager.addSubscriber( + null, + TestEventTypes.TEST_TYPE_2)); + + assertThrows(IllegalArgumentException.class, + () -> testEventManager.addSubscriber( + sampleEventSubscriber1, + null)); + + // Will only throw exception if sample event subscriber 1 is duplicate. + assertDoesNotThrow( + () -> testEventManager.addSubscriber( + sampleEventSubscriber1, + TestEventTypes.TEST_TYPE_2)); - testEventManager.addSubscriber(testEventSubscriber, TestEventTypes.TEST_TYPE_1); - testEventManager.addSubscriber(testEventSubscriber2, TestEventTypes.TEST_TYPE_2); + } + + @Test + void addingDuplicateSubscriberForSameChannelThrowsException() { + assertDoesNotThrow( + () -> testEventManager.addSubscriber( + sampleEventSubscriber1, + TestEventTypes.TEST_TYPE_2)); + + assertThrows(IllegalArgumentException.class, + () -> testEventManager.addSubscriber( + sampleEventSubscriber1, + TestEventTypes.TEST_TYPE_1)); } @Test void firedEventCaughtByCorrectSubscriber() { - assertFalse(testEventSubscriber.invokedEvent); - testEventPublisher.fireEvent(); - assertTrue(testEventSubscriber.invokedEvent); + String dataToSend = "Data for type 1"; + + assertFalse(sampleEventSubscriber1.getInvoked()); + assertNull(sampleEventSubscriber1.getLastReceivedData()); + + EventData eventData = new EventData<>( + TestEventTypes.TEST_TYPE_1, + dataToSend + ); + testEventManager.invokeEvent(eventData); + + assertTrue(sampleEventSubscriber1.getInvoked()); + assertEquals(dataToSend, sampleEventSubscriber1.getLastReceivedData()); } @Test void firedEventNotCaughtByIncorrectSubscriber() { - assertFalse(testEventSubscriber.invokedEvent); - testEventPublisher2.fireEvent(); - assertFalse(testEventSubscriber.invokedEvent); + String dataToSend = "Data for type 1"; + + assertFalse(sampleEventSubscriber1.getInvoked()); + assertNull(sampleEventSubscriber1.getLastReceivedData()); + assertFalse(sampleEventSubscriber2.getInvoked()); + assertNull(sampleEventSubscriber2.getLastReceivedData()); + + EventData eventData = new EventData<>( + TestEventTypes.TEST_TYPE_2, + dataToSend + ); + testEventManager.invokeEvent(eventData); + + assertFalse(sampleEventSubscriber1.getInvoked()); + assertNull(sampleEventSubscriber1.getLastReceivedData()); + + assertTrue(sampleEventSubscriber2.getInvoked()); + assertEquals(dataToSend, sampleEventSubscriber2.getLastReceivedData()); } @Test - void firedEventThrowsErrorWhenNoSubscribers() { - assertFalse(testEventSubscriber.invokedEvent); - assertThrows(IllegalArgumentException.class, () -> { - testEventPublisher3.fireEvent(); - }); - assertFalse(testEventSubscriber.invokedEvent); - } + void firedEventsWithMultipleSubscribersCaughtByAllSubscribers() { + String dataToSend = "Data for type 1"; - private class GenericEventPublisher implements EventPublisher { + assertFalse(sampleEventSubscriber1.getInvoked()); + assertNull(sampleEventSubscriber1.getLastReceivedData()); + assertFalse(sampleEventSubscriber2.getInvoked()); + assertNull(sampleEventSubscriber2.getLastReceivedData()); - private final ViewData viewData; - private final EventData eventData; - private final EventManager eventManager; + EventData eventData = new EventData<>( + TestEventTypes.TEST_TYPE_3, + dataToSend + ); + testEventManager.invokeEvent(eventData); - public GenericEventPublisher(final EventManager eventManager, final TestEventTypes eventType) { - viewData = new ViewData(ViewEnum.IN_GAME); - eventData = new EventData(eventType, viewData); - this.eventManager = eventManager; - } + assertTrue(sampleEventSubscriber1.getInvoked()); + assertEquals(dataToSend, sampleEventSubscriber1.getLastReceivedData()); - public void fireEvent() { - invoke(eventData, eventManager); - } + assertTrue(sampleEventSubscriber2.getInvoked()); + assertEquals(dataToSend, sampleEventSubscriber2.getLastReceivedData()); + } - @Override - public void invoke(EventData data, EventManager eventManager) { - eventManager.invokeEvent(data); - } + @Test + void firedEventThrowsErrorWhenDataIsNull() { + + EventData invalidEventData = new EventData<>( + TestEventTypes.TEST_TYPE_1, + null + ); + + assertThrows(IllegalArgumentException.class, + () -> testEventManager.invokeEvent(null)); + + assertThrows(IllegalArgumentException.class, + () -> testEventManager.invokeEvent(invalidEventData)); } - private class GenericEventSubscriber implements EventSubscriber { - public boolean invokedEvent = false; + private static class SampleEventSubscriber implements EventSubscriber { + private boolean invokedEvent = false; + private Object lastReceivedData = null; @Override - public void handleEvent(EventData data) { + public void handleEvent(final EventData data) { invokedEvent = true; + lastReceivedData = data.data(); + } + + private boolean getInvoked() { + return invokedEvent; + } + + private Object getLastReceivedData() { + return lastReceivedData; } } } \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java index e105cdf..948d9ca 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java @@ -11,30 +11,40 @@ import org.testfx.framework.junit5.ApplicationTest; class ViewControllerTest extends ApplicationTest { - private EventManager testEventManager; - private GenericViewController testViewController; + + /** + * View element instance used for testing. + * */ private GenericViewElement testViewElement; @Override - public void start(Stage stage) { - testEventManager = new EventManager(); - testViewElement = new ViewControllerTest.GenericViewElement(new Pane()); - testViewController = new GenericViewController(testViewElement, testEventManager); + public void start(final Stage stage) { + EventManager testEventManager = new EventManager(); + testViewElement = new GenericViewElement(new Pane()); + new GenericViewController(testViewElement, testEventManager); } @Test void controllerElementSetsButtonBehavior() { - assertFalse(testViewElement.buttonPressed); + assertFalse(testViewElement.getButtonPressed()); testViewElement.getInteractableButton().fire(); - assertTrue(testViewElement.buttonPressed); + assertTrue(testViewElement.getButtonPressed()); } private enum GenericViewActions { + /** + * Action used for testing purposes. + * */ TEST_ACTION; } - private class GenericViewElement extends ViewElement { - public Boolean buttonPressed = false; + /** + * Test class meant for simulating a view element instance. + * + * @see ViewElement + * */ + private static class GenericViewElement extends ViewElement { + private boolean buttonPressed = false; private Button interactableButton; protected GenericViewElement(final Pane rootPane) { @@ -51,11 +61,22 @@ public Button getInteractableButton() { return interactableButton; } + public boolean getButtonPressed() { + return buttonPressed; + } + @Override - protected void initStyling() { } + protected void initStyling() { + // Empty + } } - private class GenericViewController extends ViewController { + /** + * View controller class used for testing. + * + * @see ViewController + * */ + private static class GenericViewController extends ViewController { protected GenericViewController(final ViewControllerTest.GenericViewElement viewElement, final EventManager eventManager) diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java new file mode 100644 index 0000000..ad4b19e --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java @@ -0,0 +1,107 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import javafx.scene.control.Button; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import org.junit.jupiter.api.Test; +import org.testfx.framework.junit5.ApplicationTest; + +class ViewElementTest extends ApplicationTest { + + /** + * View element instance used for testing. + * */ + private ViewElementTest.GenericViewElement testViewElement; + + /** + * Root of generic view instance. + * */ + private Pane rootPane; + + @Override + public void start(final Stage stage) { + rootPane = new Pane(); + testViewElement = new ViewElementTest.GenericViewElement(rootPane); + } + + @Test + void constructorSetsValuesAsExpected() { + assertEquals(rootPane, testViewElement.getRootPane()); + } + + @Test + void constructorThrowsExceptionWhenIllegalArguments() { + assertDoesNotThrow( + () -> new ViewElementTest.GenericViewElement(new VBox()) + ); + + assertThrows(IllegalArgumentException.class, + () -> new ViewElementTest.GenericViewElement(null) + ); + } + + @Test + void setOnActionThrowsExceptionOnIllegalArguments() { + assertThrows(IllegalArgumentException.class, + () -> testViewElement.setOnAction( + GenericViewActions.UNUSED_TEST_ACTION, + () -> testViewElement.setButtonPressed() + ) + ); + } + + private enum GenericViewActions { + /** + * Action used for testing purposes. + * */ + TEST_ACTION, + + /** + * Unused test action to check exception throwing. + * */ + UNUSED_TEST_ACTION + } + + /** + * Test class meant for simulating a view element instance. + * + * @see ViewElement + * */ + private static class GenericViewElement extends ViewElement { + private boolean buttonPressed = false; + private Button interactableButton; + + protected GenericViewElement(final Pane rootPane) { + super(rootPane, ViewElementTest.GenericViewActions.class); + } + + @Override + protected void initLayout() { + interactableButton = new Button("Click me!"); + registerButton(ViewElementTest.GenericViewActions.TEST_ACTION, interactableButton); + } + + public Button getInteractableButton() { + return interactableButton; + } + + public boolean getButtonPressed() { + return buttonPressed; + } + + public void setButtonPressed() { + buttonPressed = true; + } + + @Override + protected void initStyling() { + // Empty + } + } +} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java index f4d04e9..0ecc2a9 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java @@ -2,7 +2,6 @@ import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; import javafx.scene.Scene; -import javafx.scene.control.Button; import javafx.scene.layout.Pane; import javafx.stage.Stage; import org.junit.jupiter.api.Assertions; @@ -17,16 +16,22 @@ */ class ViewManagerTest extends ApplicationTest { + /** + * View manager object used for testing. + * */ private ViewManager testViewManager; - private EventManager testEventManager; - private ViewManagerTest.GenericViewElement genericViewElement; + + /** + * Generic view element instance used for testing. + * */ + private ViewManagerTest.GenericViewElement testViewElement; @Override public void start(final Stage stage) { stage.setScene(new Scene(new Pane())); - testEventManager = new EventManager(); + EventManager testEventManager = new EventManager(); testViewManager = new ViewManager(stage, testEventManager); - genericViewElement = new GenericViewElement(new Pane(), ViewEnum.IN_GAME); + testViewElement = new GenericViewElement(new Pane(), ViewEnum.IN_GAME); } @Test @@ -36,42 +41,59 @@ void testViewManagerHoldsNoViewAtStart() { @Test void addingMultipleOfSameViewThrowsException() { - testViewManager.addView(genericViewElement); + testViewManager.addView(testViewElement); Assertions.assertThrows(IllegalArgumentException.class, () -> { - testViewManager.addView(genericViewElement); + testViewManager.addView(testViewElement); }); } @Test void testViewManagerSettingCorrectView() { - testViewManager.addView(genericViewElement); - testViewManager.setScene(genericViewElement); - Assertions.assertEquals(genericViewElement, testViewManager.getCurrentView()); + testViewManager.addView(testViewElement); + testViewManager.setScene(testViewElement); + Assertions.assertEquals(testViewElement, testViewManager.getCurrentView()); } @Test void testViewManagerThrowingErrorWhenSettingNonExistentView() { Assertions.assertThrows(IllegalArgumentException.class, () -> { - testViewManager.setScene(genericViewElement); + testViewManager.setScene(testViewElement); }); } @Test void throwsErrorWhenAddingWidgetWithNoViewName() { - genericViewElement = new GenericViewElement(new Pane()); + testViewElement = new GenericViewElement(new Pane()); Assertions.assertThrows(IllegalArgumentException.class, () -> { - testViewManager.addView(genericViewElement); + testViewManager.addView(testViewElement); }); } - private enum GenericActions { - ACTION_1; + @Test + void settingViewThroughViewDataWorksAsExpected() { + GenericViewElement testViewElement2 = + new GenericViewElement(new Pane(), ViewEnum.MAIN_MENU); + + testViewManager.addView(testViewElement2); + ViewData viewData = new ViewData(ViewEnum.MAIN_MENU); + + Assertions.assertNotEquals(testViewElement2, + testViewManager.getCurrentView()); + + testViewManager.setScene(viewData); + Assertions.assertEquals(testViewElement2, testViewManager.getCurrentView()); } + private enum GenericActions { + // Empty. + } - private class GenericViewElement extends ViewElement { - public Boolean buttonPressed = false; - private Button interactableButton; + /** + * Generic view element class used to test view elements within + * the view manager. + * */ + private static class GenericViewElement + extends ViewElement { protected GenericViewElement(final Pane rootPane, final ViewEnum viewName) { super(rootPane, viewName, GenericActions.class); @@ -83,15 +105,12 @@ protected GenericViewElement(final Pane rootPane) { @Override protected void initLayout() { - interactableButton = new Button("Click me!"); - registerButton(GenericActions.ACTION_1, interactableButton); - } - - public Button getInteractableButton() { - return interactableButton; + // Empty for view manager testing. } @Override - protected void initStyling() { } + protected void initStyling() { + // Empty for view manager testing. + } } }