diff --git a/pom.xml b/pom.xml index 12869a8..1045991 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ org.junit.jupiter junit-jupiter - 6.0.1 + 5.11.4 diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/GameController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/GameController.java index 8c751ff..5a255d4 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/GameController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/GameController.java @@ -92,9 +92,9 @@ public void stopGame() { /** * Returns whether the current game state is a weekend. * - * @return {@code true} if the game state is not {@link GameState#WORKDAY}. + * @return {@code true} if the game state is {@link GameState#FREEDAY}. */ public boolean isWeekend() { - return gameState != GameState.WORKDAY; + return gameState == GameState.FREEDAY; } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/MarketController.java index 5159d33..5715876 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/MarketController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/MarketController.java @@ -1,36 +1,37 @@ package edu.ntnu.idi.idatt2003.gruppe42.controller; +import edu.ntnu.idi.idatt2003.gruppe42.controller.appcontrollers.AppController; import edu.ntnu.idi.idatt2003.gruppe42.model.Exchange; import edu.ntnu.idi.idatt2003.gruppe42.model.Stock; import edu.ntnu.idi.idatt2003.gruppe42.model.StockFileHandler; import edu.ntnu.idi.idatt2003.gruppe42.model.exceptions.StockFileParseException; import java.io.IOException; import java.nio.file.Path; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Controller class for managing the stock market exchange. */ -public final class MarketController { +public final class MarketController implements AppController { private Exchange exchange; private final int stockResolution; + private final Map stockControllers = new HashMap<>(); /** * Creates a new market controller and loads stock data from a file. * * @param path the path to the stock data file */ - public MarketController(Path path) { + public MarketController(Path path) throws IOException, StockFileParseException { this.stockResolution = 120; - try { - exchange = new Exchange(StockFileHandler.readFromFile(path)); - startMarket(); - System.out.println("Market loaded"); - } catch (IOException exception) { - System.out.println("File not found"); - } catch (StockFileParseException exception) { - System.out.println("Invalid stock file format"); + exchange = new Exchange(StockFileHandler.readFromFile(path)); + for (Stock stock : exchange.getAllStocks()) { + stockControllers.put(stock.getSymbol(), new StockController(stock)); } + startMarket(); + System.out.println("Market loaded"); } public Exchange getExchange() { @@ -52,19 +53,29 @@ public List getMarket(final String searchTerm) { */ public void updateMarket() { for (Stock stock : exchange.getAllStocks()) { - stock.updatePrice(); - stock.updateStockGraph(); + StockController controller = stockControllers.get(stock.getSymbol()); + if (controller != null) { + stock.addNewSalesPrice(controller.updatePrice()); + } } } + @Override + public void nextTick() { + updateMarket(); + } + /** * Initializes the market by generating stock history and graphs. */ public void startMarket() { for (Stock stock : exchange.getAllStocks()) { - stock.fakeHistory(stockResolution); - stock.updateStockGraph(); - + StockController controller = stockControllers.get(stock.getSymbol()); + if (controller != null) { + for (int i = 0; i < stockResolution; i++) { + stock.addNewSalesPrice(controller.updatePrice()); + } + } } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java index 80a8b12..8a7b015 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java @@ -113,17 +113,15 @@ public StockAppController( /** Navigates to stock detail page. */ public void navigateToStock(final Stock stock) { currentStock = stock; - stock.getStockGraph().setVisibility(true); - Platform.runLater(stock::updateStockGraph); + stockApp.getStockGraph().setVisibility(true); stockApp.showStockPage(stock); + stockApp.getStockGraph().update(stock); updateReceipt(); } /** Navigates to search page. */ private void navigateToSearch() { - if (currentStock != null) { - currentStock.getStockGraph().setVisibility(false); - } + stockApp.getStockGraph().setVisibility(false); currentStock = null; stockApp.showSearchPage(); } @@ -270,12 +268,11 @@ private void handleTransaction() { } } - private void handleBuy(final Stock stock, final BigDecimal quantity) throws Exception { + private void handleBuy(final Stock stock, final BigDecimal quantity) { Transaction transaction = marketController.getExchange() .buy(stock.getSymbol(), quantity); if (transaction != null) { transaction.commit(player); - player.getPortfolio().addShare(transaction.getShare()); player.updateStatus(); } } @@ -292,12 +289,7 @@ private void handleSell(final Stock stock, final BigDecimal quantity) { Transaction transaction = marketController.getExchange().sell(existingShare, quantity); if (transaction != null) { - try { - transaction.commit(player); - } catch (Exception e) { - System.out.println("Transaction failed"); - } - player.getPortfolio().removeShare(transaction.getShare()); + transaction.commit(player); player.updateStatus(); } }); @@ -334,7 +326,6 @@ private void updateStockList() { /** Processes next tick. */ @Override public void nextTick() { - marketController.updateMarket(); Platform.runLater(() -> stockApp.getStockList().refresh()); if (currentStock != null) { stockApp.updatePriceLabel(currentStock); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java index 1de0926..06d08ea 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java @@ -89,7 +89,12 @@ public DesktopViewController( this.timeAndWeatherController = new TimeAndWeatherController(gameController); gameController.addAppController(timeAndWeatherController); - this.marketController = new MarketController(userSelectedPath); + try { + this.marketController = new MarketController(userSelectedPath); + } catch (Exception e) { + // In a real app we'd show an error dialog, but here we'll at least not swallow it silently + throw new RuntimeException("Failed to load market", e); + } this.mailController = new MailController(timeAndWeatherController); gameController.addAppController(mailController); @@ -134,6 +139,7 @@ private List initializeApps(GameController gameController) { StockApp stockApp = new StockApp(player); this.stockAppController = new StockAppController(stockApp, marketController, player); gameController.addAppController(stockAppController); + gameController.addAppController(marketController); final MailApp mailApp = new MailApp(mailController); final NewsApp newsApp = new NewsApp(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/EventBus.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/EventBus.java index 13d1d48..dd53d28 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/EventBus.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/EventBus.java @@ -38,6 +38,20 @@ public void subscribe(Class type, Consumer listener) { .add((Consumer) listener); } + /** + * Unsubscribes a listener from events of the given type. + * + * @param the event type. + * @param type the class of the event. + * @param listener the listener to remove. + */ + public void unsubscribe(Class type, Consumer listener) { + List> handlers = listeners.get(type); + if (handlers != null) { + handlers.remove(listener); + } + } + /** * Publishes an event to all subscribers of its type. * diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Exchange.java index a1f745f..4257a34 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Exchange.java @@ -25,10 +25,15 @@ public class Exchange { * @param stocks the stocks to list on this exchange. */ public Exchange(final List stocks) { + if (stocks == null) { + throw new IllegalArgumentException("Stocks list cannot be null"); + } this.week = 0; this.stockMap = new HashMap<>(); for (Stock stock : stocks) { - stockMap.put(stock.getSymbol(), stock); + if (stock != null) { + stockMap.put(stock.getSymbol(), stock); + } } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java index 1f60af7..4c7390f 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java @@ -34,6 +34,9 @@ public News(String company, int trend) { public String generateAuthor() { List authors = new ArrayList<>(); authors.add("market@news.com"); + authors.add("financial@journal.com"); + authors.add("insider@trading.com"); + authors.add("economy@update.com"); return authors.get(random.nextInt(authors.size())); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java index c2517fa..1357057 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java @@ -40,6 +40,12 @@ public class Player { * @param startingMoney the initial cash balance; must not be {@code null} or negative */ public Player(final String name, final BigDecimal startingMoney) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Name cannot be null or blank"); + } + if (startingMoney == null || startingMoney.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Starting money cannot be null or negative"); + } this.name.set(name); this.startingMoney = startingMoney; this.money = startingMoney; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Portfolio.java index dd4700c..c3e556c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Portfolio.java @@ -108,7 +108,7 @@ public boolean removeShare(final Share newShare) { } public List getShares() { - return shares; + return List.copyOf(shares); } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Share.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Share.java index d3c782b..c527b20 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Share.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Share.java @@ -23,6 +23,15 @@ public Share( final BigDecimal quantity, final BigDecimal purchasePrice ) { + if (stock == null) { + throw new IllegalArgumentException("Stock cannot be null"); + } + if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Quantity must be positive"); + } + if (purchasePrice == null || purchasePrice.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Purchase price cannot be negative"); + } this.stock = stock; this.quantity = quantity; this.purchasePrice = purchasePrice; @@ -80,6 +89,10 @@ public boolean equals(Object object) { */ @Override public int hashCode() { - return Objects.hash(stock.getSymbol(), quantity, purchasePrice); + return Objects.hash( + stock.getSymbol(), + quantity == null ? null : quantity.stripTrailingZeros(), + purchasePrice == null ? null : purchasePrice.stripTrailingZeros() + ); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Status.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Status.java index 7c7d9c5..f36fe1a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Status.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Status.java @@ -11,7 +11,7 @@ *
  • {@link #INVESTOR}: Achieved by trading for at least 10 distinct weeks * and increasing net worth by at least 20%.
  • *
  • {@link #SPECULATOR}: Achieved by trading for at least 20 distinct weeks - * and doubling net worth.
  • + * and increasing net worth by at least 5 times. * */ public enum Status { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java index 430c5e1..0bea46a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java @@ -1,7 +1,5 @@ package edu.ntnu.idi.idatt2003.gruppe42.model; -import edu.ntnu.idi.idatt2003.gruppe42.controller.StockController; -import edu.ntnu.idi.idatt2003.gruppe42.view.views.components.StockGraph; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -9,21 +7,17 @@ /** * Represents a publicly traded stock belonging to a company. * - *

    A stock maintains a full chronological price history, exposes summary - * statistics (highest, lowest, the latest change), and delegates price-update - * logic to a {@link StockController}. An optional {@link StockGraph} can be - * lazily attached for visual rendering. + *

    A stock maintains a full chronological price history and exposes summary + * statistics (highest, lowest, the latest change). * *

    Price history is append-only: new prices are recorded via - * {@link #addNewSalesPrice(BigDecimal)} or {@link #updatePrice()}, and the + * {@link #addNewSalesPrice(BigDecimal)} and the * most recent entry is always considered the current market price. */ public class Stock { private final String symbol; private final String company; - private StockGraph stockGraph; private final List prices = new ArrayList<>(); - private final StockController stockController; /** Constructs a new stock. */ public Stock( @@ -31,10 +25,18 @@ public Stock( final String company, final BigDecimal salesPrice ) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); + } + if (company == null || company.isBlank()) { + throw new IllegalArgumentException("Company cannot be null or blank"); + } + if (salesPrice == null || salesPrice.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Sales price cannot be negative"); + } this.symbol = symbol; this.company = company; prices.add(salesPrice); - stockController = new StockController(this); } public String getSymbol() { @@ -75,38 +77,4 @@ public BigDecimal getLatestPriceChange() { int index120Ago = Math.max(0, prices.size() - 121); return getSalesPrice().subtract(prices.get(index120Ago)); } - - /** Updates the price. */ - public void updatePrice() { - BigDecimal newPrice = stockController.updatePrice(); - prices.add(newPrice); - } - - /** - * Returns the {@link StockGraph} component associated with this stock, - * creating it lazily on the first call. - * - * @return the stock graph; never {@code null} - */ - public StockGraph getStockGraph() { - if (stockGraph == null) { - stockGraph = new StockGraph(); - } - return stockGraph; - } - - /** Generates fake history. */ - public void fakeHistory(int stockResolution) { - for (int i = 0; i < stockResolution; i++) { - updatePrice(); - } - java.util.Collections.reverse(prices); - } - - /** Updates the stock graph. */ - public void updateStockGraph() { - if (stockGraph != null && stockGraph.getVisibility()) { - stockGraph.update(this); - } - } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java index 17859fd..838731c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java @@ -82,7 +82,7 @@ public static List readFromFile(final Path path) if (!SYMBOL_PATTERN.matcher(symbol).matches()) { throw new StockFileParseException(lineNumber, trimmed, String.format("Ticker symbol \"%s\" is invalid — " - + "must be exactly 3 uppercase letters (A-Z)", symbol)); + + "must be 1-5 uppercase letters (A-Z) or dots (.)", symbol)); } String company = parts[1].trim(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculator.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculator.java index 332b1f9..c9610f2 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculator.java @@ -53,8 +53,11 @@ public BigDecimal calculateCommission() { public BigDecimal calculateTax() { final BigDecimal taxRate = new BigDecimal("0.22"); BigDecimal differencePrice = salesPrice.subtract(purchasePrice); - BigDecimal difference = differencePrice.multiply(quantity); - return difference.multiply(taxRate); + BigDecimal profit = differencePrice.multiply(quantity); + if (profit.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO; + } + return profit.multiply(taxRate); } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java index 8f22e11..051fa7d 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Purchase.java @@ -33,6 +33,7 @@ public Purchase(final Share share, final int week) { @Override public void commit(final Player player) { player.withdrawMoney(getCalculator().calculateTotal()); + player.getPortfolio().addShare(getShare()); player.getTransactionArchive().add(this); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java index 1ccb8aa..5ddd2a0 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Sale.java @@ -33,6 +33,7 @@ public Sale(final Share share, final int week) { @Override public void commit(final Player player) { player.addMoney(getCalculator().calculateTotal()); + player.getPortfolio().removeShare(getShare()); player.getTransactionArchive().add(this); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java index 60d72be..9733c0c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/transaction/Transaction.java @@ -51,7 +51,6 @@ public TransactionCalculator getCalculator() { *

    Subclasses define the specific behavior (e.g., buying or selling shares). * * @param player the player executing the transaction - * @throws Exception if the transaction cannot be completed */ - public abstract void commit(Player player) throws Exception; + public abstract void commit(Player player); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java index 03d20e8..da7ac61 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java @@ -46,6 +46,7 @@ public class StockApp extends Popup { private final Button confirmButton; private final Button backButton; private final Receipt receipt; + private final StockGraph stockGraph; /** * Constructs the Stock trading popup. @@ -55,6 +56,8 @@ public class StockApp extends Popup { public StockApp(final Player player) { super(475, 450, 140, 140, App.STOCK); + this.stockGraph = new StockGraph(); + this.searchField = new TextField(); this.searchField.setPromptText("Search stocks..."); this.searchField.getStyleClass().add("search-field"); @@ -137,15 +140,15 @@ public void showStockPage(final Stock stock) { priceLabel = new Label("$" + String.format("%,.2f", stock.getSalesPrice())); priceLabel.getStyleClass().add("stock-price-large"); - StockGraph graph = stock.getStockGraph(); - VBox.setVgrow(graph, Priority.ALWAYS); + VBox.setVgrow(stockGraph, Priority.ALWAYS); + stockGraph.update(stock); receipt.update(stock, new BigDecimal(quantitySpinner.getValue())); HBox receiptCentered = new HBox(receipt); receiptCentered.setAlignment(Pos.CENTER); content.setSpacing(20); - content.getChildren().setAll(topControls, headerBox, priceLabel, graph, receiptCentered); + content.getChildren().setAll(topControls, headerBox, priceLabel, stockGraph, receiptCentered); } /** @@ -155,12 +158,19 @@ public void showStockPage(final Stock stock) { */ public void updatePriceLabel(final Stock stock) { if (priceLabel != null) { - Platform.runLater(() -> - priceLabel.setText("$" + String.format("%,.2f", stock.getSalesPrice())) - ); + Platform.runLater(() -> { + priceLabel.setText("$" + String.format("%,.2f", stock.getSalesPrice())); + if (stockGraph.getVisibility()) { + stockGraph.update(stock); + } + }); } } + public StockGraph getStockGraph() { + return stockGraph; + } + public TextField getSearchField() { return searchField; } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PlayerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PlayerTest.java index fb02274..c64d2d7 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PlayerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PlayerTest.java @@ -89,4 +89,32 @@ void getNetworthTest() { new BigDecimal("5000"))); assertEquals(new BigDecimal("14950.00"), player.getNetWorth()); } + + @Test + void testUpdateStatusToInvestor() { + // Requires 10 distinct weeks and net worth >= 1.2 * starting money (12000) + for (int i = 0; i < 10; i++) { + player.getTransactionArchive().add(new edu.ntnu.idi.idatt2003.gruppe42.model.transaction.Purchase( + new Share(new Stock("AAPL", "Apple Inc.", new BigDecimal("100")), new BigDecimal("1"), new BigDecimal("100")), + i + )); + } + player.addMoney(new BigDecimal("2000")); // Net worth becomes 12000 + player.updateStatus(); + assertEquals("INVESTOR", player.getStatus()); + } + + @Test + void testUpdateStatusToSpeculator() { + // Requires 20 distinct weeks and net worth >= 5 * starting money (50000) + for (int i = 0; i < 20; i++) { + player.getTransactionArchive().add(new edu.ntnu.idi.idatt2003.gruppe42.model.transaction.Purchase( + new Share(new Stock("AAPL", "Apple Inc.", new BigDecimal("100")), new BigDecimal("1"), new BigDecimal("100")), + i + )); + } + player.addMoney(new BigDecimal("40000")); // Net worth becomes 50000 + player.updateStatus(); + assertEquals("SPECULATOR", player.getStatus()); + } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PortfolioTest.java index a71b58d..78c79fd 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/PortfolioTest.java @@ -9,9 +9,9 @@ import org.junit.jupiter.api.Test; /** - * Unit tests for the {@link Exchange} class. + * Unit tests for the {@link Portfolio} class. * - *

    Verifies stock management, search functionality, and week progression behavior. + *

    Verifies share management, merging logic, and quantity tracking. */ public class PortfolioTest { private Portfolio portfolio; diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java index d61bcb4..22c4d92 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java @@ -17,28 +17,20 @@ public class StockFileHandlerTest { @Test - void readFromFileTest() throws IOException { + void readFromFileTest() throws Exception { Path testFile = Files.createTempFile("stocks", "csv"); Files.writeString(testFile, "AAPL,Apple Inc.,276.43\nMSFT,Microsoft,404.68"); - try { - List stocks = StockFileHandler.readFromFile(testFile); - assertEquals(2, stocks.size()); - assertEquals("AAPL", stocks.get(0).getSymbol()); - } catch (Exception e) { - throw new RuntimeException(e); - } + List stocks = StockFileHandler.readFromFile(testFile); + assertEquals(2, stocks.size()); + assertEquals("AAPL", stocks.get(0).getSymbol()); } @Test - void readFromFileWithCommentsAndEmptyLines() throws IOException { + void readFromFileWithCommentsAndEmptyLines() throws Exception { Path testFile = Files.createTempFile("stocks", "csv"); Files.writeString(testFile, "#Comment\n\nAAPL,Apple Inc.,276.43"); - try { - List stocks = StockFileHandler.readFromFile(testFile); - assertEquals(1, stocks.size()); - } catch (Exception e) { - throw new RuntimeException(e); - } + List stocks = StockFileHandler.readFromFile(testFile); + assertEquals(1, stocks.size()); } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculatorTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculatorTest.java index 1231d97..6c76b27 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculatorTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/calculator/SaleCalculatorTest.java @@ -69,4 +69,21 @@ void testCalculateTotal() { assertNotEquals(0, new BigDecimal("1375.01").compareTo(calculator.calculateTotal())); assertNotEquals(0, new BigDecimal("1374.99").compareTo(calculator.calculateTotal())); } + + @Test + void testCalculateTaxLoss() { + BigDecimal purchasePrice = new BigDecimal("150"); + BigDecimal salesPrice = new BigDecimal("100"); + BigDecimal quantity = new BigDecimal("10"); + Stock stock = new Stock("AAPL", "Apple Inc.", salesPrice); + Share share = new Share(stock, quantity, purchasePrice); + SaleCalculator lossCalculator = new SaleCalculator(share); + + // Tax should be 0 on a loss, not negative + assertEquals(0, BigDecimal.ZERO.compareTo(lossCalculator.calculateTax()), "Tax should be zero on loss"); + + // Total should be: gross(1000) - commission(10) - tax(0) = 990 + BigDecimal expectedTotal = new BigDecimal("990"); + assertEquals(0, expectedTotal.compareTo(lossCalculator.calculateTotal()), "Total payout should not include negative tax"); + } }