diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/Link.java b/src/main/java/edu/ntnu/idatt2001/group_30/Link.java
index 64804272c2efdb57658952b52d7873c120b73949..5b62790b8dc10ee87a2b03dba6c57428d737387a 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/Link.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/Link.java
@@ -15,7 +15,7 @@ import java.util.Objects;
 public class Link {
     private final String text;
     private final String reference;
-    private final List<Action> actions;
+    private final List<Action<?>> actions;
 
     /**
      * This constructor creates a Link object, which contains information surrounding a linking point in the story.
@@ -36,7 +36,7 @@ public class Link {
      * Adds an action to the list of actions
      * @param action, the action to be added to the list
      */
-    public void addAction(Action action) {
+    public void addAction(Action<?> action) {
         this.actions.add(action);
     }
 
@@ -60,7 +60,7 @@ public class Link {
      * This method retrieves the list of actions attached to the Link object.
      * @return  The actions of the Link object, given as a List{@code <Action>}.
      */
-    public List<Action> getActions() {
+    public List<Action<?>> getActions() {
         return this.actions;
     }
 
@@ -79,6 +79,13 @@ public class Link {
 
     @Override
     public String toString() {
-        return "[" + text + "](" + reference + ")";
+        StringBuilder sb = new StringBuilder();
+        sb.append("[").append(text).append("](").append(reference).append(")\n");
+
+        for(Action<?> action : actions) {
+            sb.append("<").append(action.getClass().getSimpleName()).append(">\\").append(action.getActionValue()).append("/\n");
+        }
+
+        return sb.toString();
     }
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/Passage.java b/src/main/java/edu/ntnu/idatt2001/group_30/Passage.java
index e4c2d88e7faa2e578634bb963081e86956354677..924cb4fca502e7bdd05ee34c72d452a6bad142dc 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/Passage.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/Passage.java
@@ -79,7 +79,7 @@ public class Passage {
         sb.append("::").append(title).append("\n")
                 .append(content).append("\n");
 
-        links.forEach(link -> sb.append(link.toString()).append("\n"));
+        links.forEach(link -> sb.append(link.toString()));
 
         return sb.toString();
     }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/Story.java b/src/main/java/edu/ntnu/idatt2001/group_30/Story.java
index 93bf57b59d44052731974ad9546ed056c7712bd9..08379f01cba59831e486dc2cdaacd0f84b79c2d8 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/Story.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/Story.java
@@ -4,6 +4,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Stream;
 
 /**
@@ -32,6 +33,7 @@ public class Story {
         if (openingPassage == null) throw new IllegalArgumentException("Opening passage cannot be null");
         this.openingPassage = openingPassage;
         this.passages = new HashMap<>();
+        addPassage(this.openingPassage);
     }
 
     /**
@@ -103,7 +105,7 @@ public class Story {
 
     /**
      * This method retrieves all the passages of a story.
-     * @return All the pages of the Story as a {@code Collection<Passages>}.
+     * @return All the passages of the Story as a {@code Collection<Passages>}.
      */
     public Collection<Passage> getPassages() {
         return this.passages.values();
@@ -122,8 +124,28 @@ public class Story {
         sb.append(this.title).append("\n\n");
         sb.append(this.openingPassage.toString()).append("\n");
 
-        this.passages.values().forEach(passage -> sb.append(passage.toString()).append("\n"));
+        this.passages.values().forEach(passage -> {
+            if(!passage.equals(openingPassage)) sb.append(passage.toString()).append("\n");
+        });
 
         return sb.toString();
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Story story)) return false;
+
+        if (!Objects.equals(title, story.title)) return false;
+        if (!Objects.equals(passages, story.passages)) return false;
+        return Objects.equals(openingPassage, story.openingPassage);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = title != null ? title.hashCode() : 0;
+        result = 31 * result + (passages != null ? passages.hashCode() : 0);
+        result = 31 * result + (openingPassage != null ? openingPassage.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/Action.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/Action.java
index 0ab4177629d2965bfc56facc15ae4d6e25fe54a3..8714a90830a37b3673eb9552a0c32a8f2d2e1cc9 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/actions/Action.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/Action.java
@@ -3,13 +3,12 @@ package edu.ntnu.idatt2001.group_30.actions;
 import edu.ntnu.idatt2001.group_30.Player;
 
 /**
- * The functional interface Action provides the method signature for executing an attribute
+ * The Action interface provides the method signature for executing an attribute
  * action on the player.
  *
  * @author Trym Hamer Gudvangen
  */
-@FunctionalInterface
-public interface Action {
+public interface Action<T> {
 
     /**
      * This method changes a given player's attribute:
@@ -17,4 +16,17 @@ public interface Action {
      */
     void execute(Player player);
 
+    /**
+     * This method retrieves the action value of a given action.
+     * @return Action value, given as an Object.
+     */
+    T getActionValue();
+
+    /**
+     * This method ensures that all action implementations has a way to check if two action objects are equal.
+     * @param o Object being compared
+     * @return  Boolean representing {@code true} if the actions are equal, otherwise {@code false}
+     */
+    boolean equals(Object o);
+
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/ActionFactory.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/ActionFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfa8198f39564195645e1e8f1416586c67799377
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/ActionFactory.java
@@ -0,0 +1,27 @@
+package edu.ntnu.idatt2001.group_30.actions;
+
+/**
+ * This class represents a factory for producing Action objects. It, therefore, has methods for instantiating a single
+ * object from the information provided.
+ *
+ * @author Trym Hamer Gudvangen
+ */
+public class ActionFactory {
+
+    /**
+     * This method takes in an ActionType and the action value in order to create an Action object.
+     *
+     * @param actionType    The type of action, represented as a ActionType enumeration
+     * @param actionValue   The action value, given as a String.
+     * @return              An action object with the information specified
+     */
+    public static Action<?> getAction(ActionType actionType, String actionValue) throws IllegalArgumentException{
+        return switch (actionType) {
+            case GOLD_ACTION -> new GoldAction(Integer.parseInt(actionValue));
+            case HEALTH_ACTION -> new HealthAction(Integer.parseInt(actionValue));
+            case INVENTORY_ACTION -> new InventoryAction(actionValue);
+            case SCORE_ACTION -> new ScoreAction(Integer.parseInt(actionValue));
+        };
+    }
+
+}
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/ActionType.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/ActionType.java
new file mode 100644
index 0000000000000000000000000000000000000000..41f56dfa967e73e3495876c607d94c75274ffa48
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/ActionType.java
@@ -0,0 +1,13 @@
+package edu.ntnu.idatt2001.group_30.actions;
+
+/**
+ * This enumeration represents the different types of actions that exist: gold, health, inventory, and score.
+ *
+ * @author Trym Hamer Gudvangen
+ */
+public enum ActionType {
+    GOLD_ACTION,
+    HEALTH_ACTION,
+    INVENTORY_ACTION,
+    SCORE_ACTION
+}
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/GoldAction.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/GoldAction.java
index d788b51efb976e232ff28e37eae22f8a28b6b052..43e6f8b475da7ca4b0ac04bf29a104ee2bfcbb7c 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/actions/GoldAction.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/GoldAction.java
@@ -8,7 +8,7 @@ import java.util.Objects;
  *
  * @author Trym Hamer Gudvangen
  */
-public class GoldAction implements Action {
+public class GoldAction implements Action<Integer> {
 
     private final int gold;
 
@@ -31,4 +31,26 @@ public class GoldAction implements Action {
         Objects.requireNonNull(player);
         player.addGold(this.gold);
     }
+
+    /**
+     * This method retrieves the gold value.
+     * @return  Gold value, given as an int.
+     */
+    @Override
+    public Integer getActionValue() {
+        return gold;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GoldAction that)) return false;
+
+        return gold == that.gold;
+    }
+
+    @Override
+    public int hashCode() {
+        return gold;
+    }
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/HealthAction.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/HealthAction.java
index f7fb16f021d336015be95a63dec5b2c09712902b..97dae448955ac2e3943a2ae94551c3e6c1a0652b 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/actions/HealthAction.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/HealthAction.java
@@ -9,7 +9,7 @@ import java.util.Objects;
  *
  * @author Trym Hamer Gudvangen
  */
-public class HealthAction implements Action {
+public class HealthAction implements Action<Integer> {
 
     private final int health;
 
@@ -31,4 +31,26 @@ public class HealthAction implements Action {
         Objects.requireNonNull(player);
         player.addHealth(this.health);
     }
+
+    /**
+     * This method retrieves the health value;
+     * @return Health value, given as an int.
+     */
+    @Override
+    public Integer getActionValue() {
+        return health;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof HealthAction that)) return false;
+
+        return health == that.health;
+    }
+
+    @Override
+    public int hashCode() {
+        return health;
+    }
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/InventoryAction.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/InventoryAction.java
index e82df09415ce831eb3427d9abf87a91d2526955c..35da920a66ed7047bd5589e32facfef25bd72f8b 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/actions/InventoryAction.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/InventoryAction.java
@@ -9,7 +9,7 @@ import java.util.Objects;
  *
  * @author Trym Hamer Gudvangen
  */
-public class InventoryAction implements Action {
+public class InventoryAction implements Action<String> {
 
     private final String item;
 
@@ -31,4 +31,26 @@ public class InventoryAction implements Action {
         Objects.requireNonNull(player);
         player.addToInventory(this.item);
     }
+
+    /**
+     * This method retrieves the item value.
+     * @return  Item value, given as a String.
+     */
+    @Override
+    public String getActionValue() {
+        return this.item;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof InventoryAction that)) return false;
+
+        return Objects.equals(item, that.item);
+    }
+
+    @Override
+    public int hashCode() {
+        return item != null ? item.hashCode() : 0;
+    }
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/actions/ScoreAction.java b/src/main/java/edu/ntnu/idatt2001/group_30/actions/ScoreAction.java
index a76d05d6c91d02402fb9e03b11ceb22f8405de0e..9d20b1b2044f96877b7a9d3c4a9f603bd991cbc5 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/actions/ScoreAction.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/actions/ScoreAction.java
@@ -9,7 +9,7 @@ import java.util.Objects;
  *
  * @author Trym Hamer Gudvangen
  */
-public class ScoreAction implements Action {
+public class ScoreAction implements Action<Integer> {
 
     private final int points;
 
@@ -31,4 +31,26 @@ public class ScoreAction implements Action {
         Objects.requireNonNull(player);
         player.addScore(this.points);
     }
+
+    /**
+     * This method retrieves the point value.
+     * @return  Point value, given as an int.
+     */
+    @Override
+    public Integer getActionValue() {
+        return this.points;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ScoreAction that)) return false;
+
+        return points == that.points;
+    }
+
+    @Override
+    public int hashCode() {
+        return points;
+    }
 }
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/CorruptFileException.java b/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/CorruptFileException.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3897248fe64034ad6ce4d034f89e456a279514f
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/CorruptFileException.java
@@ -0,0 +1,43 @@
+package edu.ntnu.idatt2001.group_30.exceptions;
+
+/**
+ * An exception that is thrown when Link information a paths file has been corrupted.
+ */
+public class CorruptFileException extends RuntimeException{
+
+    /**
+     * Constructs a new CorruptFileException with no detail message.
+     */
+    public CorruptFileException() {
+
+    }
+
+    /**
+     * Constructs a new CorruptFileException with the specified detail message.
+     *
+     * @param message The detail message, given as a String.
+     */
+    public CorruptFileException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new CorruptFileException with the specified detail message and cause.
+     *
+     * @param message The detail message, given as a String
+     * @param cause   The cause, given as a Throwable Object.
+     */
+    public CorruptFileException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new CorruptFileException with the specified cause and a detail message of
+     * {@code cause == null ? null : cause.toString()} (which usually contains the class and detail message of cause).
+     *
+     * @param cause The cause, given as a Throwable Object.
+     */
+    public CorruptFileException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/CorruptLinkException.java b/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/CorruptLinkException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a247bb5f01e964d97de8656f71527249cd32b67
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/CorruptLinkException.java
@@ -0,0 +1,43 @@
+package edu.ntnu.idatt2001.group_30.exceptions;
+
+/**
+ * An exception that is thrown when Link information a paths file has been corrupted.
+ */
+public class CorruptLinkException extends RuntimeException{
+
+    /**
+     * Constructs a new CorruptLinkException with no detail message.
+     */
+    public CorruptLinkException() {
+    }
+
+    /**
+     * Constructs a new CorruptLinkException with the specified detail message.
+     *
+     * @param message The detail message, given as a String.
+     */
+    public CorruptLinkException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new CorruptLinkException with the specified detail message and cause.
+     *
+     * @param message The detail message, given as a String
+     * @param cause   The cause, given as a Throwable Object.
+     */
+    public CorruptLinkException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new CorruptLinkException with the specified cause and a detail message of
+     * {@code cause == null ? null : cause.toString()} (which usually contains the class and detail message of cause).
+     *
+     * @param cause The cause, given as a Throwable Object.
+     */
+    public CorruptLinkException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/InvalidExtensionException.java b/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/InvalidExtensionException.java
new file mode 100644
index 0000000000000000000000000000000000000000..093521ec6b636c665775b23f4f6673e65af0fc49
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/exceptions/InvalidExtensionException.java
@@ -0,0 +1,43 @@
+package edu.ntnu.idatt2001.group_30.exceptions;
+
+/**
+ * An exception that is thrown when an invalid file extension is used.
+ */
+public class InvalidExtensionException extends IllegalArgumentException{
+
+    /**
+     * Constructs a new InvalidExtensionException with no detail message.
+     */
+    public InvalidExtensionException() {
+    }
+
+    /**
+     * Constructs a new InvalidExtensionException with the specified detail message.
+     *
+     * @param message The detail message, given as a String.
+     */
+    public InvalidExtensionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new InvalidExtensionException with the specified detail message and cause.
+     *
+     * @param message The detail message, given as a String
+     * @param cause   The cause, given as a Throwable Object.
+     */
+    public InvalidExtensionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new InvalidExtensionException with the specified cause and a detail message of
+     * {@code cause == null ? null : cause.toString()} (which usually contains the class and detail message of cause).
+     *
+     * @param cause The cause, given as a Throwable Object.
+     */
+    public InvalidExtensionException(Throwable cause) {
+        super(cause);
+    }
+}
+
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/filehandling/FileHandler.java b/src/main/java/edu/ntnu/idatt2001/group_30/filehandling/FileHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdcc1b244a01d653a0a4661479c32ac69df31116
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/filehandling/FileHandler.java
@@ -0,0 +1,84 @@
+package edu.ntnu.idatt2001.group_30.filehandling;
+
+import edu.ntnu.idatt2001.group_30.exceptions.InvalidExtensionException;
+
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class contains general-use methods for handling files such as file creation and name and path validation.
+ *
+ * @author Trym Hamer Gudvangen
+ */
+public class FileHandler {
+
+    private static final Pattern VALID_CHAR = Pattern.compile("[^<>:\"/*|?\\\\]*");
+    private static final Pattern VALID_EXTENSION = Pattern.compile(".*\\.paths$");
+    private static String defaultPath = "src/main/resources/story-files";
+
+
+    /**
+     * This method checks whether the given file name is valid.
+     * @param fileName                  Name of the given file, given as a String.
+     * @return                          {@code true} if the file name is valid.
+     * @throws IllegalArgumentException This exception is thrown if given file name is blank or has invalid characters.
+     */
+    public static boolean isFileNameValid(String fileName) throws IllegalArgumentException{
+        if(fileName.isBlank()) throw new IllegalArgumentException("File name cannot be blank");
+        Matcher matcher = VALID_CHAR.matcher(fileName);
+        if(!matcher.matches()) throw new IllegalArgumentException("File name contains invalid characters");
+        return true;
+    }
+
+    /**
+     * This method checks whether the given file contains .paths as the extension.
+     * @param fileName                   Name of the file including extension, given as a String
+     * @return                           {@code true} if file name contains .paths, else {@code false}
+     * @throws InvalidExtensionException This exception is thrown if the file name does not contain .paths at the end
+     */
+    public static boolean isFileExtensionValid(String fileName) throws InvalidExtensionException{
+        Matcher matcher = VALID_EXTENSION.matcher(fileName);
+        if(!matcher.matches()) throw new InvalidExtensionException("File name contains invalid characters");
+        return true;
+    }
+
+    /**
+     * This method checks if a file exists with a given directory path and whether it contains any information.
+     * @param file The file to be checked, given as a File object
+     * @return     If the file contains no information, {@code false} is returned. Else, {@code true} is returned
+     */
+    public static boolean fileExists(File file) {
+        return file.length() > 0;
+    }
+
+    /**
+     * This method takes a file name. It, then, checks whether the name is valid and if so, it creates a file for it.
+     * @param fileName                      Name of the file, given as a String.
+     * @return                              The file with the given name, represented using a File object.
+     * @throws IllegalArgumentException     This exception is thrown if the file name is invalid.
+     */
+    public static File createFile(String fileName) throws IllegalArgumentException{
+        isFileNameValid(fileName);
+        return new File(getFileSourcePath(fileName));
+    }
+
+    /**
+     * This method retrieves the file source path of a story file with the file name given.
+     * @param fileName Name of the desired file, represented as a String
+     * @return         The source path to the file, represented as a String
+     */
+    public static String getFileSourcePath(String fileName){
+        return FileSystems.getDefault().getPath(defaultPath, fileName) + ".paths";
+    }
+    //TODO: test for different OS
+
+    /**
+     * This method changes the default path used to create files. This may, for example, be used for testing purposes.
+     * @param newPath New default path, given as a String
+     */
+    public static void changeDefaultPath(String newPath) {
+        defaultPath = newPath;
+    }
+}
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/filehandling/StoryFileHandler.java b/src/main/java/edu/ntnu/idatt2001/group_30/filehandling/StoryFileHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..715be06a6117936c30ecfbf2762205074de902bd
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/filehandling/StoryFileHandler.java
@@ -0,0 +1,158 @@
+package edu.ntnu.idatt2001.group_30.filehandling;
+
+import edu.ntnu.idatt2001.group_30.Link;
+import edu.ntnu.idatt2001.group_30.Passage;
+import edu.ntnu.idatt2001.group_30.Story;
+import edu.ntnu.idatt2001.group_30.actions.Action;
+import edu.ntnu.idatt2001.group_30.actions.ActionFactory;
+import edu.ntnu.idatt2001.group_30.actions.ActionType;
+import edu.ntnu.idatt2001.group_30.exceptions.CorruptFileException;
+import edu.ntnu.idatt2001.group_30.exceptions.CorruptLinkException;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * This class maintains the storage and retrieval of a Story file. This is done through a Buffered
+ * writer and reader.
+ */
+public class StoryFileHandler {
+
+    private final Pattern LINK_PATTERN = Pattern.compile("\\[.*]\\(.*\\)");
+    private final Pattern ACTION_PATTERN = Pattern.compile("<.*>\\\\.*/");
+
+    /**
+     * This method takes a story and writes its contents to a .paths file. The story information is transcribed
+     * in the given format:
+     * <pre>
+     *  Story title
+     *
+     *  ::Opening Passage Title
+     *  Opening Passage Content
+     *  [Link Text](Link Reference)
+     *
+     *  ::Another Passage Title
+     *  Passage Content
+     *  [Link Text](Link Reference)
+     *  {@code <Action Type>}\Action Value/
+     *  [Link Text](Link Reference)
+     *
+     *  ...
+     * </pre>
+     * @param story         The story to be saved, given as a Story object.
+     * @param fileName      The name of the file the story will be saved to, given as a String.
+     * @throws IOException  This exception is thrown if an I/O error occurs with the writer.
+     */
+    public void createStoryFile(Story story, String fileName) throws IOException {
+        Objects.requireNonNull(story, "Story cannot be null");
+        Objects.requireNonNull(fileName, "File name cannot be null");
+        File file = FileHandler.createFile(fileName);
+        if(FileHandler.fileExists(file)) throw new IllegalArgumentException("You cannot overwrite a pre-existing story file");
+        try(BufferedWriter storyBufferedWriter = new BufferedWriter(new FileWriter(file))){
+            storyBufferedWriter.write(story.toString());
+        }
+    }
+
+    /**
+     * This method takes a story file and parses it to create a story object.
+     * @param fileName      The name of the story file, given as a String.
+     * @return              The story from the file, given as a Story object.
+     * @throws IOException  This exception is thrown if an I/O error occurs with the reader.
+     */
+    public Story readStoryFromFile(String fileName) throws IOException, InstantiationException {
+        Objects.requireNonNull(fileName, "File name cannot be null");
+        File file = new File(FileHandler.getFileSourcePath(fileName));
+        if(!FileHandler.fileExists(file)) throw new IllegalArgumentException("There is no story file with that name!");
+        Story story;
+
+        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
+
+            String storyTitle = bufferedReader.readLine();
+
+            List<String> passageInfo = new ArrayList<>(List.of(bufferedReader.lines()
+                    .collect(Collectors.joining("\n")).split("\n::")));
+
+            if(passageInfo.size() == 1) throw new CorruptFileException(storyTitle);
+            passageInfo.remove(0);
+
+            Passage openingPassage = parseStringToPassage(passageInfo.remove(0));
+
+            story = new Story(storyTitle, openingPassage);
+
+            for(String passage : passageInfo) {
+                story.addPassage(parseStringToPassage(passage));
+            }
+        }
+
+        return story;
+    }
+
+    /**
+     * This method takes a String containing the information that is vital for a Passage. It, then, parses the
+     * string into the title, content, and links of the passage. The Links are added to the passage through the
+     * {@link Passage#addLink(Link)} method.
+     * @param passageInfo   Info of the passage, given as a String.
+     * @return              The passage, given as a Passage object.
+     */
+    private Passage parseStringToPassage(String passageInfo) throws InstantiationException {
+        String[] splitPassageInfo = passageInfo.split("\n");
+        Passage passage = new Passage(splitPassageInfo[0], splitPassageInfo[1]);
+        for(int i = 2; i < splitPassageInfo.length; i++) {
+            Link link = parseStringToLink(splitPassageInfo[i]);
+            passage.addLink(link);
+            while(i + 1 < splitPassageInfo.length && ACTION_PATTERN.matcher(splitPassageInfo[i+1]).matches()){
+                link.addAction(parseStringToAction(splitPassageInfo[++i]));
+            }
+        }
+
+        return passage;
+    }
+
+    /**
+     * This method takes a String containing the information that is vital for a Link. It, then, parses the
+     * string into the text and reference of the Link.
+     * @param linkInfo  The information of the link, given as a String.
+     * @return          The link, given as a Link object.
+     */
+    private Link parseStringToLink(String linkInfo){
+        if(!LINK_PATTERN.matcher(linkInfo).matches()) throw new CorruptLinkException(linkInfo);
+        String text = linkInfo.substring(linkInfo.indexOf("[") + 1, linkInfo.indexOf("]") );
+        String reference = linkInfo.substring(linkInfo.indexOf("(") + 1, linkInfo.indexOf(")"));
+
+        return new Link(text, reference);
+    }
+
+    /**
+     * This method takes a String containing the information that is vital for an Action. It, then, parses the
+     * string into the implementation and value.
+     * @param actionInfo  The information of the Action, given as a String.
+     * @return          The action implementation, given as an Action object.
+     */
+    private Action<?> parseStringToAction(String actionInfo) throws InstantiationException {
+        String className = actionInfo.substring(actionInfo.indexOf("<") + 1, actionInfo.indexOf(">"));
+        String value = actionInfo.substring(actionInfo.indexOf("\\") + 1, actionInfo.indexOf("/"));
+
+        ActionType actionType = extractActionTypeFromInfo(className);
+        return ActionFactory.getAction(actionType, value);
+    }
+
+    /**
+     * This method takes in the Action Class info, given as a String. This method checks what ActionType the information
+     * belongs to.
+     * @param actionTypeInfo          Information of an action's class from a paths File, represented as a String
+     * @return                        The type of Action extracted from the String, given as a ActionType enumeration
+     * @throws InstantiationException This exception is thrown if the action type information is corrupt
+     */
+    private ActionType extractActionTypeFromInfo(String actionTypeInfo) throws InstantiationException{
+        return switch(actionTypeInfo) {
+            case "GoldAction" -> ActionType.GOLD_ACTION;
+            case "HealthAction" -> ActionType.HEALTH_ACTION;
+            case "InventoryAction" -> ActionType.INVENTORY_ACTION;
+            case "ScoreAction" -> ActionType.SCORE_ACTION;
+            default -> throw new InstantiationException("The Action type information is corrupt");
+        };
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2001/group_30/goals/Goal.java b/src/main/java/edu/ntnu/idatt2001/group_30/goals/Goal.java
index f37482fd8b808c1a5df813d0ffe578672eee89de..398acf28e6a2bd57a75812a5b3fc2bb2f84e7140 100644
--- a/src/main/java/edu/ntnu/idatt2001/group_30/goals/Goal.java
+++ b/src/main/java/edu/ntnu/idatt2001/group_30/goals/Goal.java
@@ -8,6 +8,7 @@ import edu.ntnu.idatt2001.group_30.Player;
  *
  * @author Trym Hamer Gudvangen
  */
+@FunctionalInterface
 public interface Goal {
 
     /**
diff --git a/src/test/java/edu/ntnu/idatt2001/group_30/StoryTest.java b/src/test/java/edu/ntnu/idatt2001/group_30/StoryTest.java
index a926487ba724a2094ae373d49f1b04579827ae72..3889af3ddde2bdcc54bac211a2e5e67fef85d25d 100644
--- a/src/test/java/edu/ntnu/idatt2001/group_30/StoryTest.java
+++ b/src/test/java/edu/ntnu/idatt2001/group_30/StoryTest.java
@@ -42,7 +42,7 @@ public class StoryTest {
             Passage passage2 = new Passage("Befriend Eilor", "You befriend Eilor");
             story.addPassage(passage);
             story.addPassage(passage2);
-            assertEquals(2, story.getPassages().size());
+            assertEquals(3, story.getPassages().size());
         }
 
         @Test
diff --git a/src/test/java/edu/ntnu/idatt2001/group_30/actions/ActionFactoryTest.java b/src/test/java/edu/ntnu/idatt2001/group_30/actions/ActionFactoryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ee99bc6c32055335f078c5e4320685892abd126
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2001/group_30/actions/ActionFactoryTest.java
@@ -0,0 +1,78 @@
+package edu.ntnu.idatt2001.group_30.actions;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class ActionFactoryTest {
+
+    @Nested
+    public class ActionFactory_with_valid_value {
+
+        @Test
+        void can_get_GoldAction(){
+            ActionType goldAction = ActionType.GOLD_ACTION;
+            String value = "5";
+            Action<Integer> expectedAction = new GoldAction(5);
+
+            Action<?> actualAction = ActionFactory.getAction(goldAction, value);
+
+            Assertions.assertTrue(actualAction instanceof GoldAction);
+            Assertions.assertEquals(expectedAction, actualAction);
+        }
+
+        @Test
+        void can_get_HealthAction(){
+            ActionType healthAction = ActionType.HEALTH_ACTION;
+            String value = "5";
+            Action<Integer> expectedAction = new HealthAction(5);
+
+            Action<?> actualAction = ActionFactory.getAction(healthAction, value);
+
+            Assertions.assertTrue(actualAction instanceof HealthAction);
+            Assertions.assertEquals(expectedAction, actualAction);
+        }
+
+        @Test
+        void can_get_InventoryAction(){
+            ActionType inventoryAction = ActionType.INVENTORY_ACTION;
+            String value = "Sword";
+            Action<String> expectedAction = new InventoryAction("Sword");
+
+            Action<?> actualAction = ActionFactory.getAction(inventoryAction, value);
+
+            Assertions.assertTrue(actualAction instanceof InventoryAction);
+            Assertions.assertEquals(expectedAction, actualAction);
+        }
+
+        @Test
+        void can_get_ScoreAction(){
+            ActionType scoreAction = ActionType.SCORE_ACTION;
+            String value = "5";
+            Action<Integer> expectedAction = new ScoreAction(5);
+
+            Action<?> actualAction = ActionFactory.getAction(scoreAction, value);
+
+            Assertions.assertTrue(actualAction instanceof ScoreAction);
+            Assertions.assertEquals(expectedAction, actualAction);
+        }
+
+    }
+
+    @Nested
+    public class ActionFactory_with_invalid_value_such_as {
+        @Test
+        void null_action_type_throws_NullPointerException() {
+            Assertions.assertThrows(NullPointerException.class, () -> {
+                Action<?> action = ActionFactory.getAction(null, "5");
+            });
+        }
+
+        @Test
+        void value_throws_NumberFormatException() {
+            Assertions.assertThrows(IllegalArgumentException.class, () -> {
+                Action<?> action = ActionFactory.getAction(ActionType.GOLD_ACTION, "Invalid value");
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idatt2001/group_30/filehandling/FileHandlerTest.java b/src/test/java/edu/ntnu/idatt2001/group_30/filehandling/FileHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e67958924bc2323105672d43e1e48ee73954edb9
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2001/group_30/filehandling/FileHandlerTest.java
@@ -0,0 +1,133 @@
+package edu.ntnu.idatt2001.group_30.filehandling;
+
+import edu.ntnu.idatt2001.group_30.exceptions.InvalidExtensionException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.util.concurrent.atomic.AtomicReference;
+
+class FileHandlerTest {
+
+    @BeforeAll
+    static void setFileHandlerPath() {
+        FileHandler.changeDefaultPath("src/test/resources/storytestfiles");
+    }
+
+    @Nested
+    public class The_FileHandler_makes_sure_a_file_name {
+
+        @ParameterizedTest(name = "{index}. File name: {0}")
+        @ValueSource(strings = {"$123test", "50%Off", "***Story***", "LOTR?", "Winnie the Pooh!",
+                "LOTR > Hobbit", "The/Hobbit", "[LOTF]", "{LOTR}", "Trym's : Adventure", "Story.paths"})
+        void does_not_contain_special_characters(String fileName) {
+            String expectedExceptionMessage = "File name contains invalid characters";
+
+            try {
+                FileHandler.isFileNameValid(fileName);
+            } catch (IllegalArgumentException e) {
+                Assertions.assertEquals(expectedExceptionMessage, e.getMessage());
+            }
+        }
+
+        @ParameterizedTest(name = "{index}. File name: {0}")
+        @ValueSource(strings = {"", "  "})
+        void is_not_empty_or_blank(String fileName){
+            String expectedExceptionMessage = "File name cannot be blank";
+
+            try {
+                FileHandler.isFileNameValid(fileName);
+            } catch (IllegalArgumentException e) {
+                Assertions.assertEquals(expectedExceptionMessage, e.getMessage());
+            }
+        }
+
+        @ParameterizedTest(name = "{index}. File name: {0}")
+        @ValueSource(strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void only_contains_valid_characters(String fileName) {
+            boolean expectedStatus = true;
+
+            boolean actualStatusOfFile = FileHandler.isFileNameValid(fileName);
+
+            Assertions.assertEquals(expectedStatus, actualStatusOfFile);
+        }
+    }
+
+    @Nested
+    public class The_FileHandler_can_check {
+        @Test
+        void if_a_file_exists(){
+            boolean expectedStatus = true;
+            File validFile = new File(FileSystems.getDefault()
+                    .getPath("src", "test", "resources", "storytestfiles", "Bones") + ".paths");
+
+            boolean actualStatusOfFile = FileHandler.fileExists(validFile);
+
+            Assertions.assertEquals(expectedStatus, actualStatusOfFile);
+        }
+
+        @Test
+        void if_a_file_does_not_exist(){
+            boolean expectedStatus = false;
+            File validFile = new File(FileSystems.getDefault()
+                    .getPath("src", "test", "resources", "storytestfiles", "Fairy tale") + ".paths");
+
+            boolean actualStatusOfFile = FileHandler.fileExists(validFile);
+
+            Assertions.assertEquals(expectedStatus, actualStatusOfFile);
+        }
+
+        @Test
+        void the_file_source_path(){
+            String expectedFileSourcePath = "src/test/resources/storytestfiles/story.paths";
+            String fileName = "story";
+
+            String actualFileSourcePath = FileHandler.getFileSourcePath(fileName);
+
+            Assertions.assertEquals(expectedFileSourcePath, actualFileSourcePath);
+        }
+
+        @Test
+        void if_file_extension_is_valid() {
+            String validPath = "The Hobbit.paths";
+
+            boolean isPathValid = FileHandler.isFileExtensionValid(validPath);
+
+            Assertions.assertTrue(isPathValid);
+
+        }
+
+        @ParameterizedTest(name = "{index}. File path: {0}")
+        @ValueSource(strings = {"Winnie the Pooh.exe", "78924378.doc", "The-Bible.txt", "Story123.csv"})
+        void if_file_extension_is_invalid(String filePath) {
+
+            Assertions.assertThrows(InvalidExtensionException.class, () -> {
+                FileHandler.isFileExtensionValid(filePath);
+            });
+        }
+    }
+
+    @Nested
+    public class The_FileHandler_can_create {
+        @ParameterizedTest(name = "{index}. File name: {0}")
+        @ValueSource(strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void new_files_with_valid_names(String fileName) {
+            try {
+                File file = FileHandler.createFile(fileName);
+                file.createNewFile();
+                Assertions.assertTrue(file.isFile());
+                Assertions.assertTrue(file.exists());
+
+                file.delete();
+            } catch (Exception e) {
+                Assertions.fail(e.getMessage());
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idatt2001/group_30/filehandling/StoryFileHandlerTest.java b/src/test/java/edu/ntnu/idatt2001/group_30/filehandling/StoryFileHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..08dc95b5455492b4c28e81ec8160aafebd625a0b
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2001/group_30/filehandling/StoryFileHandlerTest.java
@@ -0,0 +1,289 @@
+package edu.ntnu.idatt2001.group_30.filehandling;
+
+import edu.ntnu.idatt2001.group_30.Link;
+import edu.ntnu.idatt2001.group_30.Passage;
+import edu.ntnu.idatt2001.group_30.Story;
+import edu.ntnu.idatt2001.group_30.actions.*;
+import edu.ntnu.idatt2001.group_30.exceptions.CorruptFileException;
+import edu.ntnu.idatt2001.group_30.exceptions.CorruptLinkException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.*;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class StoryFileHandlerTest {
+
+    @BeforeAll
+    static void setFileHandlerPath() {
+        FileHandler.changeDefaultPath("src/test/resources/storytestfiles");
+    }
+
+    StoryFileHandler storyFileHandler = new StoryFileHandler();
+
+    public File getValidFile(String fileName) {
+        return FileHandler.createFile(fileName);
+    }
+
+    static Story validStory(){
+        Story story = new Story("The Hobbit", new Passage("Beginning", "Once upon a time..."));
+        Passage secondChapter = new Passage("The Great Barrier", "After having completed the arduous...");
+        story.addPassage(secondChapter);
+        story.getOpeningPassage().addLink(new Link(secondChapter.getTitle(), secondChapter.getTitle()));
+        story.getOpeningPassage().getLinks().forEach(link -> link.addAction(new GoldAction(5)));
+        story.getOpeningPassage().getLinks().get(0).addAction(new ScoreAction(5));
+        story.getOpeningPassage().getLinks().get(0).addAction(new HealthAction(6));
+        story.getOpeningPassage().getLinks().get(0).addAction(new InventoryAction("Sword"));
+        return story;
+    }
+
+    @Nested
+    public class A_StoryFile_is_valid_if {
+
+        @ParameterizedTest(name = "{index}. File name: {0}")
+        @ValueSource(strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void a_file_has_a_valid_name(String fileName) {
+            Story story = validStory();
+
+            try {
+                storyFileHandler.createStoryFile(story, fileName);
+            } catch (Exception e) {
+                if(!e.getMessage().equals("You cannot overwrite a pre-existing story file")){
+                    System.out.println(e.getMessage());
+                    fail("An exception was thrown when it shouldn't have.");
+                }
+            }
+
+            File expectedFileCreated = getValidFile(fileName);
+
+            Assertions.assertTrue(expectedFileCreated.isFile());
+            expectedFileCreated.delete();
+        }
+
+        @ParameterizedTest (name = "{index}. File name: {0}")
+        @ValueSource (strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void files_created_can_be_accessed_and_read(String fileName) {
+            Story story = validStory();
+
+            try{
+                storyFileHandler.createStoryFile(story, fileName);
+            }catch (Exception e){
+                fail("An exception was thrown when it shouldn't have. " + e.getMessage());
+            }
+
+            File expectedFileCreated = getValidFile(fileName);
+
+            Assertions.assertTrue(expectedFileCreated.canRead());
+            expectedFileCreated.delete();
+        }
+
+        @ParameterizedTest (name = "{index}. File name: {0}")
+        @ValueSource (strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void the_pathing_is_correctly_set(String fileName) {
+            Story story = validStory();
+            boolean fileDoesNotExistAtStart = !getValidFile(fileName).exists();
+
+            try{
+                storyFileHandler.createStoryFile(story, fileName);
+            }catch (Exception e){
+                fail("An exception was thrown when it shouldn't have.");
+            }
+
+            File expectedFileCreated = getValidFile(fileName);
+            boolean fileDoesExistAfterWrite = expectedFileCreated.exists();
+
+            //Then/Assert
+            Assertions.assertTrue(fileDoesNotExistAtStart);
+            Assertions.assertTrue(fileDoesExistAfterWrite);
+            expectedFileCreated.delete();
+        }
+
+        @Test
+        void it_cannot_create_new_file_with_preexisting_file_name() {
+            Story story = validStory();
+            String fileName = "Bones";
+
+            File preexistingFile = getValidFile(fileName);
+            if(getValidFile(fileName).isFile()) {
+                Assertions.assertThrows(IllegalArgumentException.class, () -> storyFileHandler.createStoryFile(story, fileName));
+            }
+            else fail("The file check for doesn't exist, so this test is invalid");
+        }
+
+    }
+
+    @Nested
+    public class A_StoryFile_properly_writes_a_story_to_new_file_if_it {
+
+        @ParameterizedTest (name = "{index}. File name: {0}")
+        @ValueSource (strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void saves_the_story_title_correctly(String fileName) throws IOException, InstantiationException {
+            Story story = validStory();
+            String expectedTitle = story.getTitle();
+
+            storyFileHandler.createStoryFile(story, fileName);
+            Story storyReadFromFile = storyFileHandler.readStoryFromFile(fileName);
+            String actualTitle = storyReadFromFile.getTitle();
+
+            Assertions.assertEquals(expectedTitle, actualTitle);
+
+            File file = getValidFile(fileName);
+            file.delete();
+        }
+
+        @ParameterizedTest (name = "{index}. File name: {0}")
+        @ValueSource (strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void saves_the_opening_passage_after_title(String fileName) throws IOException, InstantiationException {
+            Story story = validStory();
+            Passage expectedOpeningPassage = story.getOpeningPassage();
+
+            storyFileHandler.createStoryFile(story, fileName);
+            Story storyReadFromFile = storyFileHandler.readStoryFromFile(fileName);
+            Passage actualOpeningPassage = storyReadFromFile.getOpeningPassage();
+
+            Assertions.assertEquals(expectedOpeningPassage, actualOpeningPassage);
+
+            File file = getValidFile(fileName);
+            file.delete();
+        }
+
+        @ParameterizedTest (name = "{index}. File name: {0}")
+        @ValueSource (strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void saves_all_the_links_of_passage_correctly(String fileName) throws IOException, InstantiationException {
+            Story story = validStory();
+            List<Link> expectedOpeningPassageLinks = story.getOpeningPassage().getLinks();
+
+            storyFileHandler.createStoryFile(story, fileName);
+            Story storyReadFromFile = storyFileHandler.readStoryFromFile(fileName);
+            List<Link> actualOpeningPassageLinks = storyReadFromFile.getOpeningPassage().getLinks();
+
+            Assertions.assertEquals(expectedOpeningPassageLinks, actualOpeningPassageLinks);
+
+            File file = getValidFile(fileName);
+            file.delete();
+        }
+
+        @ParameterizedTest (name = "{index}. File name: {0}")
+        @ValueSource (strings = {"Winnie the Pooh", "L.O.T.R", "The-Bible", "Story123"})
+        void saves_all_the_actions_of_links_correctly(String fileName) throws IOException, InstantiationException {
+            Story story = validStory();
+            List<Action<?>> expectedOpeningPassageActions = story.getOpeningPassage().getLinks().get(0).getActions();
+
+            storyFileHandler.createStoryFile(story, fileName);
+            Story storyReadFromFile = storyFileHandler.readStoryFromFile(fileName);
+            List<Action<?>> actualOpeningPassageActions = storyReadFromFile.getOpeningPassage().getLinks().get(0).getActions();
+
+            Assertions.assertEquals(expectedOpeningPassageActions, actualOpeningPassageActions);
+
+            File file = getValidFile(fileName);
+            file.delete();
+        }
+
+    }
+
+    @Nested
+    public class A_StoryFile_properly_reads_a_story_if_it {
+        @Test
+        void constructs_a_Story_correctly_when_read() throws IOException, InstantiationException {
+            Story expectedStory = validStory();
+
+            Story actualStory = storyFileHandler.readStoryFromFile("The Hobbit");
+
+            assertEquals(expectedStory, actualStory);
+        }
+
+    }
+
+    @Nested
+    public class A_StoryFile_with_invalid_information_such_as {
+        @Test
+        void a_null_story_when_creating_new_file_will_throw_NullPointerException(){
+            Story story = null;
+
+            Assertions.assertThrows(NullPointerException.class, () ->{
+                storyFileHandler.createStoryFile(story, "Null story test");
+            });
+        }
+
+        @Test
+        void a_null_file_name_when_creating_new_file_will_throw_NullPointerException(){
+            Story story = validStory();
+
+            Assertions.assertThrows(NullPointerException.class, () ->{
+                storyFileHandler.createStoryFile(story, null);
+            });
+        }
+
+        @Test
+        void a_null_file_name_when_reading_file_will_throw_NullPointerException(){
+            Assertions.assertThrows(NullPointerException.class, () ->{
+                Story story = storyFileHandler.readStoryFromFile(null);
+            });
+        }
+
+        //TODO: change this actually test the link information
+        @Test
+        void corrupt_link_information_throws_CorruptLinkException_when_read(){
+            Story expectedStory = validStory();
+
+            Assertions.assertThrows(CorruptLinkException.class, () -> {
+                Story actualStory = storyFileHandler.readStoryFromFile("Corrupt Link File");
+                assertNotEquals(expectedStory, actualStory);
+            });
+
+        }
+
+        @Test
+        void file_with_improper_format_throws_CorruptFileException() {
+            Story expectedStory = validStory();
+
+            Assertions.assertThrows(CorruptFileException.class, () ->{
+                Story actualStory = storyFileHandler.readStoryFromFile("Corrupt .paths Format");
+            });
+        }
+
+        @Test
+        void not_existing_throws_IllegalArgumentException() {
+            Story expectedStory = validStory();
+
+            Assertions.assertThrows(IllegalArgumentException.class, () ->{
+                Story actualStory = storyFileHandler.readStoryFromFile("File that does not exist");
+            });
+        }
+
+        @Test
+        void action_class_throws_InstantiationException() {
+            Story expectedStory = validStory();
+
+            Assertions.assertThrows(InstantiationException.class, () -> {
+                Story actualStory = storyFileHandler.readStoryFromFile("Corrupt Action Class");
+            });
+        }
+
+        @Test
+        void corrupt_action_format_throws_CorruptLinkException() {
+            Story expectedStory = validStory();
+
+            Assertions.assertThrows(CorruptLinkException.class, () -> {
+                Story actualStory = storyFileHandler.readStoryFromFile("Corrupt Action");
+            });
+        }
+
+        @Test
+        void valid_action_class_but_invalid_value_throws_IllegalArgumentException() {
+            Story expectedStory = validStory();
+
+            Assertions.assertThrows(IllegalArgumentException.class, () -> {
+                Story actualStory = storyFileHandler.readStoryFromFile("Corrupt Action Value");
+            });
+        }
+
+    }
+
+}
diff --git a/src/test/resources/storytestfiles/Bones.paths b/src/test/resources/storytestfiles/Bones.paths
new file mode 100644
index 0000000000000000000000000000000000000000..59af36f46e6ff1e582aa3b0cf503a5ec7b374fe2
--- /dev/null
+++ b/src/test/resources/storytestfiles/Bones.paths
@@ -0,0 +1,10 @@
+Haunted House
+
+::Beginnings
+You are in a small, dimly lit room. There is a door in front of you.
+[Try to open the door](Another room)
+
+::Another room
+The door opens to another room. You see a desk with a large, dusty book.
+[Open the book](The book of spells)
+[Go back](Beginnings)
\ No newline at end of file
diff --git a/src/test/resources/storytestfiles/Corrupt .paths Format.paths b/src/test/resources/storytestfiles/Corrupt .paths Format.paths
new file mode 100644
index 0000000000000000000000000000000000000000..021a79ea01300abbaa023777a96d089b858f828d
--- /dev/null
+++ b/src/test/resources/storytestfiles/Corrupt .paths Format.paths	
@@ -0,0 +1,4 @@
+Title goes here
+
+Passage not starting with two colons
+Content of passage here
diff --git a/src/test/resources/storytestfiles/Corrupt Action Class.paths b/src/test/resources/storytestfiles/Corrupt Action Class.paths
new file mode 100644
index 0000000000000000000000000000000000000000..96b396b42e4749e2ca01f270b2cba5e6eb9b267f
--- /dev/null
+++ b/src/test/resources/storytestfiles/Corrupt Action Class.paths	
@@ -0,0 +1,9 @@
+The Hobbit
+
+::Beginning
+Once upon a time...
+[The Great Barrier](The Great Barrier)
+<Invalid class>\5/
+
+::The Great Barrier
+After having completed the arduous...
\ No newline at end of file
diff --git a/src/test/resources/storytestfiles/Corrupt Action Value.paths b/src/test/resources/storytestfiles/Corrupt Action Value.paths
new file mode 100644
index 0000000000000000000000000000000000000000..a51c2c4785bea1fc13283626a0f7bb8548ff69bd
--- /dev/null
+++ b/src/test/resources/storytestfiles/Corrupt Action Value.paths	
@@ -0,0 +1,10 @@
+The Hobbit
+
+::Beginning
+Once upon a time...
+[The Great Barrier](The Great Barrier)
+<GoldAction>\Hello/
+
+::The Great Barrier
+After having completed the arduous...
+
diff --git a/src/test/resources/storytestfiles/Corrupt Action.paths b/src/test/resources/storytestfiles/Corrupt Action.paths
new file mode 100644
index 0000000000000000000000000000000000000000..2998dbd99aac7b747ffb0a6ccebe3af09f258bdf
--- /dev/null
+++ b/src/test/resources/storytestfiles/Corrupt Action.paths	
@@ -0,0 +1,10 @@
+The Hobbit
+
+::Beginning
+Once upon a time...
+[The Great Barrier](The Great Barrier)
+-GoldAction-\5/
+
+::The Great Barrier
+After having completed the arduous...
+
diff --git a/src/test/resources/storytestfiles/Corrupt Link File.paths b/src/test/resources/storytestfiles/Corrupt Link File.paths
new file mode 100644
index 0000000000000000000000000000000000000000..b65dac173633e19696a487ee38ba793560a9aa93
--- /dev/null
+++ b/src/test/resources/storytestfiles/Corrupt Link File.paths	
@@ -0,0 +1,5 @@
+The Hobbit
+
+::Beginning
+Once upon a time...
+[link name (broken because of no end bracket)
\ No newline at end of file
diff --git a/src/test/resources/storytestfiles/The Hobbit.paths b/src/test/resources/storytestfiles/The Hobbit.paths
new file mode 100644
index 0000000000000000000000000000000000000000..51531969e0ba640f31514f0485bf262f1d2b2502
--- /dev/null
+++ b/src/test/resources/storytestfiles/The Hobbit.paths	
@@ -0,0 +1,13 @@
+The Hobbit
+
+::Beginning
+Once upon a time...
+[The Great Barrier](The Great Barrier)
+<GoldAction>\5/
+<ScoreAction>\5/
+<HealthAction>\6/
+<InventoryAction>\Sword/
+
+::The Great Barrier
+After having completed the arduous...
+