From 3d4d35a6b9ac9a8484d477d63a6344255a7d9010 Mon Sep 17 00:00:00 2001
From: Rushfan <rushfan@wwivbbs.org>
Date: Sun, 2 Jan 2022 09:55:21 -0800
Subject: [PATCH] more changes to improve testability

---
 bbs/bbs_helper.h             |  8 --------
 binkp/binkp_config.cpp       |  2 +-
 binkp/file_manager.cpp       |  2 +-
 binkp/file_manager_test.cpp  |  4 ++--
 network3/network3.cpp        |  6 ++++--
 networkb/networkb.cpp        |  3 ++-
 networkf/networkf.cpp        | 38 ++++++++++++++++--------------------
 networkf/networkf.h          | 14 +++++++++++--
 networkf/networkf_main.cpp   | 28 ++++++++++++++------------
 sdk/config.h                 | 21 ++++++++++----------
 sdk/config_test.cpp          | 24 +++++++++++------------
 sdk/fido/fido_callout.cpp    | 13 ++++++------
 sdk/fido/fido_callout.h      |  6 +++++-
 sdk/files/files_ext_test.cpp |  2 +-
 sdk/files/files_test.cpp     |  4 ++--
 sdk/msgapi/email_test.cpp    | 10 +++++-----
 sdk/msgapi/msgapi_test.cpp   |  4 ++--
 sdk/names_test.cpp           |  2 +-
 sdk/net/ftn_msgdupe_test.cpp | 16 +++++++--------
 sdk/net/network_test.cpp     |  4 ++--
 sdk/phone_numbers_test.cpp   |  6 +++---
 sdk/sdk_helper.cpp           |  2 +-
 sdk/sdk_helper.h             | 23 +++++++++++++---------
 sdk/subxtr_test.cpp          |  8 ++++----
 wwivconfig/convert.cpp       |  2 +-
 wwivconfig/networks.cpp      |  2 +-
 wwivd/nets.cpp               |  2 +-
 27 files changed, 135 insertions(+), 121 deletions(-)

diff --git a/bbs/bbs_helper.h b/bbs/bbs_helper.h
index dce1d38cd..763604a7b 100644
--- a/bbs/bbs_helper.h
+++ b/bbs/bbs_helper.h
@@ -40,15 +40,7 @@ class BbsHelper : public CommonHelper {
   [[nodiscard]] wwiv::sdk::User* user() override;
   [[nodiscard]] const wwiv::sdk::User* user() const;
   [[nodiscard]] wwiv::common::Context& context() override;
-/*
- [[nodiscard]] wwiv::sdk::User* user() const { return user_; }
-  [[nodiscard]] TestIO* io() const { return io_.get(); }
 
-  // Accessors for various directories
-  FileHelper& files() { return files_; }
-  [[nodiscard]] const std::string& data() const { return dir_data_; }
-  [[nodiscard]] const std::string& gfiles() const { return dir_gfiles_; }
-*/
   std::unique_ptr<Application> app_;
 
 };
diff --git a/binkp/binkp_config.cpp b/binkp/binkp_config.cpp
index bf84d14ec..ba95f3a7c 100644
--- a/binkp/binkp_config.cpp
+++ b/binkp/binkp_config.cpp
@@ -127,7 +127,7 @@ const binkp_session_config_t* BinkConfig::binkp_session_config_for(const std::st
   } else if (callout_network().type == network_type_t::ftn) {
     try {
       FidoAddress address(node);
-      FidoCallout fc(config_, callout_network());
+      FidoCallout fc(config_.root_directory(), config_.max_backups(), callout_network());
       if (!fc.IsInitialized())
         return nullptr;
       auto fido_node = fc.fido_node_config_for(address);
diff --git a/binkp/file_manager.cpp b/binkp/file_manager.cpp
index a88025c1c..05e6336b5 100644
--- a/binkp/file_manager.cpp
+++ b/binkp/file_manager.cpp
@@ -212,7 +212,7 @@ void FileManager::rename_ftn_pending_files(const Remote& remote) {
         continue;
       }
       LOG(INFO) << "Tic file " << ticpath.string() << " is valid.";
-      FidoCallout callout(config_, remote.network());
+      FidoCallout callout(config_.root_directory(), config_.max_backups(), remote.network());
       std::string expected_tic_pw;
       try {
         FidoAddress address(remote.ftn_address());
diff --git a/binkp/file_manager_test.cpp b/binkp/file_manager_test.cpp
index 38115aeb3..de164bdc9 100644
--- a/binkp/file_manager_test.cpp
+++ b/binkp/file_manager_test.cpp
@@ -149,7 +149,7 @@ TEST_F(FileManagerTest, WithPassword) {
 
   fm->ReceiveFile("foo.zip");
   fm->ReceiveFile("foo.tic");
-  wwiv::sdk::fido::FidoCallout callout(*config_, net);
+  wwiv::sdk::fido::FidoCallout callout(config_->root_directory(), config_->max_backups(), net);
   const wwiv::sdk::fido::FidoAddress remote_addr("1:1/1");
   fido_node_config_t node_config{};
   node_config.packet_config.tic_password = "rush";
@@ -166,7 +166,7 @@ TEST_F(FileManagerTest, WithPassword_WrongPassword) {
 
   fm->ReceiveFile("foo.zip");
   fm->ReceiveFile("foo.tic");
-  wwiv::sdk::fido::FidoCallout callout(*config_, net);
+  wwiv::sdk::fido::FidoCallout callout(config_->root_directory(), config_->max_backups(), net);
   const wwiv::sdk::fido::FidoAddress remote_addr("1:1/1");
   fido_node_config_t node_config{};
   node_config.packet_config.tic_password = "rush";
diff --git a/network3/network3.cpp b/network3/network3.cpp
index 15158f4a5..b1a066d57 100644
--- a/network3/network3.cpp
+++ b/network3/network3.cpp
@@ -488,11 +488,13 @@ static int network3_fido(const NetworkCommandLine& net_cmdline) {
   if (!File::Exists(FilePath(dirs.net_dir(), FIDO_CALLOUT_JSON))) {
     text << " ** fido_callout.json file DOES NOT EXIST.\r\n\n";
   }
-  FidoCallout callout(net_cmdline.config(), net);
+  FidoCallout callout(net_cmdline.config().root_directory(), net_cmdline.config().max_backups(),
+                      net);
   if (!callout.IsInitialized()) {
     text << " ** Unable to read fido_callout.json\r\n\n";
   } else {
-    check_fido_host_networks(net_cmdline.config(), net_cmdline.networks(), net, net_cmdline.network_number(), text);
+    check_fido_host_networks(net_cmdline.config(), net_cmdline.networks(), net,
+                             net_cmdline.network_number(), text);
   }
 
   text << "Using nodelist base:     " << net.fido.nodelist_base << "\r\n";
diff --git a/networkb/networkb.cpp b/networkb/networkb.cpp
index 7db9da37b..af579b355 100644
--- a/networkb/networkb.cpp
+++ b/networkb/networkb.cpp
@@ -203,7 +203,8 @@ static int Main(const NetworkCommandLine& net_cmdline) {
             LOG(ERROR) << "Domain is empty for FTN address. Please set it for: "
                        << n.fido.fido_address;
           }
-          auto c = std::make_unique<FidoCallout>(net_cmdline.config(), n);
+          auto c = std::make_unique<FidoCallout>(net_cmdline.config().root_directory(),
+                                                 net_cmdline.config().max_backups(), n);
           for (const auto& kv : c->node_configs_map()) {
             bink_config.address_pw_map.try_emplace(kv.first, kv.second.binkp_config.password);
           }
diff --git a/networkf/networkf.cpp b/networkf/networkf.cpp
index 060c9704b..53d6ab3d9 100644
--- a/networkf/networkf.cpp
+++ b/networkf/networkf.cpp
@@ -109,14 +109,13 @@ static std::string get_echomail_areaname(const std::string& text) {
   return "";
 }
 
-NetworkF::NetworkF(const wwiv::net::NetworkCommandLine& net_cmdline,
-                   const wwiv::sdk::BbsListNet& bbslist, wwiv::core::Clock& clock)
-    : net_cmdline_(net_cmdline), bbslist_(bbslist), clock_(clock), net_(net_cmdline_.network()),
-      fido_callout_(net_cmdline_.config(), net_),
-      netdat_(net_cmdline_.config().gfilesdir(), net_cmdline_.config().logdir(), net_,
-              net_cmdline_.net_cmd(), clock_),
-      dirs_(net_cmdline_.config().root_directory(), net_) {
-  const IniFile ini(FilePath(net_cmdline_.config().root_directory(), WWIV_INI), {"WWIV"});
+NetworkF::NetworkF(const sdk::BbsDirectories& bbsdirs, const networkf_options_t& opts,
+                   const sdk::net::Network net, const sdk::BbsListNet& bbslist, core::Clock& clock)
+    : bbslist_(bbslist), clock_(clock), net_(net),
+      fido_callout_(bbsdirs.root_directory(), opts.max_backups, net_),
+      netdat_(bbsdirs.gfilesdir(), bbsdirs.logdir(), net_, opts.net_cmd, clock_),
+      dirs_(bbsdirs.root_directory(), net_), opts_(opts), datadir_(bbsdirs.datadir()) {
+  const IniFile ini(FilePath(bbsdirs.root_directory(), WWIV_INI), {"WWIV"});
   if (ini.IsOpen()) {
     // pull out new user colors
     for (auto i = 0; i < 10; i++) {
@@ -247,7 +246,7 @@ bool NetworkF::import_packets(const std::string& dir, const std::string& mask) {
   for (const auto& f : files) {
     if (import_packet_file(FilePath(dir, f.name))) {
       LOG(INFO) << "Successfully imported packet: " << FilePath(dir, f.name);
-      if (net_cmdline_.skip_delete()) {
+      if (opts_.skip_delete) {
         backup_file(FilePath(net_.dir, f.name));
       }
       File::Remove(FilePath(dir, f.name));
@@ -273,7 +272,7 @@ bool NetworkF::import_bundle_file(const std::filesystem::path& path) {
   File::set_current_directory(dirs_.temp_inbound_dir());
 
   // were in the temp dir now.
-  const auto arcs = files::read_arcs(net_cmdline_.config().datadir());
+  const auto arcs = files::read_arcs(datadir_);
   if (arcs.empty()) {
     LOG(ERROR) << "No archivers defined!";
     return false;
@@ -322,7 +321,7 @@ int NetworkF::import_bundles(const std::string& dir, const std::string& mask) {
       if (import_packet_file(path)) {
         LOG(INFO) << "Successfully imported packet: " << path;
         ++num_bundles_processed;
-        if (net_cmdline_.skip_delete()) {
+        if (opts_.skip_delete) {
           backup_file(path);
         }
         File::Remove(path);
@@ -330,7 +329,7 @@ int NetworkF::import_bundles(const std::string& dir, const std::string& mask) {
     } else if (import_bundle_file(path)) {
       LOG(INFO) << "Successfully imported bundle: " << path;
       ++num_bundles_processed;
-      if (net_cmdline_.skip_delete()) {
+      if (opts_.skip_delete) {
         backup_file(path);
       }
       File::Remove(path);
@@ -359,7 +358,7 @@ static std::string rename_fido_packet(const std::string& dir, const std::string&
 std::optional<std::string> NetworkF::create_ftn_bundle(const FidoAddress& route_to,
                                                        const std::string& fido_packet_name) {
   // were in the temp dir now.
-  auto arcs = files::read_arcs(net_cmdline_.config().datadir());
+  auto arcs = files::read_arcs(datadir_);
   if (arcs.empty()) {
     LOG(ERROR) << "No archivers defined!";
     return std::nullopt;
@@ -577,7 +576,7 @@ std::optional<std::string> NetworkF::create_ftn_packet(const FidoAddress& dest,
       vh.to_user_name = "All";
     }
 
-    FtnMessageDupe dupe(net_cmdline_.config());
+    FtnMessageDupe dupe(datadir_, true);
     auto msgid = FtnMessageDupe::GetMessageIDFromWWIVText(raw_text);
     auto needs_msgid = false;
     if (msgid.empty()) {
@@ -634,7 +633,7 @@ std::optional<std::string> NetworkF::create_ftn_packet(const FidoAddress& dest,
     auto origin_line = net_.fido.origin_line;
     if (origin_line.empty()) {
       // default origin line to system name if it doesn't exist.
-      origin_line = net_cmdline_.config().system_name();
+      origin_line = opts_.system_name;
     }
 
     if (from_address.point() == 0) {
@@ -919,12 +918,12 @@ bool NetworkF::export_main_type_email_name(std::set<std::string>& bundles, Packe
 
 sdk::FtnMessageDupe& NetworkF::dupe() {
   if (!dupe_) {
-    dupe_ = std::make_unique<wwiv::sdk::FtnMessageDupe>(net_cmdline_.config());
+    dupe_ = std::make_unique<wwiv::sdk::FtnMessageDupe>(datadir_, true);
   }
   return *dupe_;
 }
 
-bool NetworkF::Run() {
+bool NetworkF::Run(std::vector<std::string> cmds) {
   if (!fido_callout_.IsInitialized()) {
     LOG(ERROR) << "Unable to initialize fido_callout.";
     return false;
@@ -932,10 +931,8 @@ bool NetworkF::Run() {
 
   auto num_packets_processed = 0;
 
-  auto cmds = net_cmdline_.cmdline().remaining();
   if (cmds.empty()) {
     LOG(ERROR) << "No command specified. Exiting.";
-    ShowNetworkfHelp(net_cmdline_);
     return false;
   }
 
@@ -979,7 +976,7 @@ bool NetworkF::Run() {
       if (response == ReadPacketResponse::END_OF_FILE) {
         // Delete the packet.
         f.Close();
-        if (net_cmdline_.skip_delete()) {
+        if (opts_.skip_delete) {
           backup_file(f.full_pathname());
         }
         File::Remove(f.path());
@@ -1011,7 +1008,6 @@ bool NetworkF::Run() {
 
   } else {
     LOG(ERROR) << "Unknown command: " << cmd;
-    ShowNetworkfHelp(net_cmdline_);
     return false;
   }
   return num_packets_processed > 0;
diff --git a/networkf/networkf.h b/networkf/networkf.h
index 7b81a0b1a..d139a3e96 100644
--- a/networkf/networkf.h
+++ b/networkf/networkf.h
@@ -31,13 +31,21 @@
 
 namespace wwiv::net::networkf {
 
+struct networkf_options_t {
+  int max_backups{0};
+  bool skip_delete{false};
+  char net_cmd{'f'};
+  std::string system_name;
+};
+
 class NetworkF final {
 public:
-  NetworkF(const NetworkCommandLine& cmdline, const sdk::BbsListNet& bbslist,
+  NetworkF(const sdk::BbsDirectories& bbsdirs,
+           const networkf_options_t& opts, const sdk::net::Network, const sdk::BbsListNet& bbslist,
            core::Clock& clock);
   ~NetworkF();
 
-  bool Run();
+  bool Run(std::vector<std::string> cmds);
 
 private:
   bool import_packet_file(const std::filesystem::path& path);
@@ -104,6 +112,8 @@ class NetworkF final {
   sdk::fido::FidoCallout fido_callout_;
   NetDat netdat_;
   sdk::fido::FtnDirectories dirs_;
+  const networkf_options_t opts_;
+  const std::string datadir_;
 
 
   std::unique_ptr<sdk::FtnMessageDupe> dupe_;
diff --git a/networkf/networkf_main.cpp b/networkf/networkf_main.cpp
index 3dc791ddd..f0099178f 100644
--- a/networkf/networkf_main.cpp
+++ b/networkf/networkf_main.cpp
@@ -80,29 +80,29 @@ int main(int argc, char** argv) {
   Logger::Init(argc, argv, config);
 
   CommandLine cmdline(argc, argv, "net");
-  const NetworkCommandLine net_cmdline_(cmdline, 'f');
+  const NetworkCommandLine net_cmdline(cmdline, 'f');
   try {
     ScopeExit at_exit(Logger::ExitLogger);
-    if (!net_cmdline_.IsInitialized() || net_cmdline_.cmdline().help_requested()) {
-      ShowNetworkfHelp(net_cmdline_);
+    if (!net_cmdline.IsInitialized() || net_cmdline.cmdline().help_requested()) {
+      ShowNetworkfHelp(net_cmdline);
       return 1;
     }
-    const auto& net = net_cmdline_.network();
+    const auto& net = net_cmdline.network();
     if (net.type != network_type_t::ftn) {
       LOG(ERROR) << "NETWORKF is only for use on FTN type networks.";
-      ShowNetworkfHelp(net_cmdline_);
+      ShowNetworkfHelp(net_cmdline);
       return 1;
     }
 
     VLOG(3) << "Reading bbsdata.net_..";
-    auto b = BbsListNet::ReadBbsDataNet(net.dir);
-    if (b.empty()) {
+    auto bbslist = BbsListNet::ReadBbsDataNet(net.dir);
+    if (bbslist.empty()) {
       LOG(ERROR) << "ERROR: Unable to read bbsdata.net_.";
       LOG(ERROR) << "       Do you need to run network3?";
       return 3;
     }
 
-    const auto fake_ftn_node = b.node_config_for(FTN_FAKE_OUTBOUND_NODE);
+    const auto fake_ftn_node = bbslist.node_config_for(FTN_FAKE_OUTBOUND_NODE);
     if (!fake_ftn_node) {
       LOG(ERROR) << "Can not find node for outbound FTN address.";
       LOG(ERROR) << "       Do you need to run network3?";
@@ -110,12 +110,16 @@ int main(int argc, char** argv) {
     }
 
     auto semaphore =
-        SemaphoreFile::try_acquire(net_cmdline_.semaphore_path(), net_cmdline_.semaphore_timeout());
+        SemaphoreFile::try_acquire(net_cmdline.semaphore_path(), net_cmdline.semaphore_timeout());
     SystemClock clock{};
-    NetworkF nf(net_cmdline_, b, clock);
-    return nf.Run() ? 0 : 2;
+
+    networkf_options_t opts{net_cmdline.config().max_backups(), net_cmdline.skip_delete()};
+    opts.system_name = net_cmdline.config().system_name();
+    const auto& net = net_cmdline.network();
+    NetworkF nf(net_cmdline.config(), opts, net, bbslist, clock);
+    return nf.Run(net_cmdline.cmdline().remaining()) ? 0 : 2;
   } catch (const semaphore_not_acquired& e) {
-    LOG(ERROR) << "ERROR: [network" << net_cmdline_.net_cmd()
+    LOG(ERROR) << "ERROR: [network" << net_cmdline.net_cmd()
                << "]: Unable to Acquire Network Semaphore: " << e.what();
   } catch (const std::exception& e) {
     LOG(ERROR) << "ERROR: [networkf]: " << e.what();
diff --git a/sdk/config.h b/sdk/config.h
index 54d634da6..4b23a439d 100644
--- a/sdk/config.h
+++ b/sdk/config.h
@@ -18,6 +18,7 @@
 #ifndef INCLUDED_SDK_CONFIG_H
 #define INCLUDED_SDK_CONFIG_H
 
+#include "sdk/bbs_directories.h"
 #include "sdk/vardec.h"
 #include <filesystem>
 #include <map>
@@ -173,7 +174,7 @@ struct config_t {
   system_toggles_t toggles;
 };
 
-class Config final {
+class Config final  : public BbsDirectories {
 public:
   Config(std::filesystem::path root_directory, config_t config);
   explicit Config(const Config& c);
@@ -209,20 +210,20 @@ class Config final {
   [[nodiscard]] int config_revision_number() const { return config_.header.config_revision_number; }
   void config_revision_number(int v) { config_.header.config_revision_number = v; }
 
-  [[nodiscard]] std::string root_directory() const { return root_directory_.string(); }
-  [[nodiscard]] std::string datadir() const { return datadir_; }
+  [[nodiscard]] std::string root_directory() const override { return root_directory_.string(); }
+  [[nodiscard]] std::string datadir() const override { return datadir_; }
   void datadir(const std::string& d) { config_.datadir = d; }
-  [[nodiscard]] std::string msgsdir() const { return msgsdir_; }
+  [[nodiscard]] std::string msgsdir() const override { return msgsdir_; }
   void msgsdir(const std::string& d) { config_.msgsdir = d; }
-  [[nodiscard]] std::string gfilesdir() const { return gfilesdir_; }
+  [[nodiscard]] std::string gfilesdir() const override { return gfilesdir_; }
   void gfilesdir(const std::string& d) { config_.gfilesdir = d; }
-  [[nodiscard]] std::string menudir() const { return menudir_; }
+  [[nodiscard]] std::string menudir() const override { return menudir_; }
   void menudir(const std::string& d) { config_.menudir = d; }
-  [[nodiscard]] std::string dloadsdir() const { return dloadsdir_; }
+  [[nodiscard]] std::string dloadsdir() const override { return dloadsdir_; }
   void dloadsdir(const std::string& d) { config_.dloadsdir = d; }
-  [[nodiscard]] std::string scriptdir() const { return script_dir_; }
+  [[nodiscard]] std::string scriptdir() const override { return script_dir_; }
   void scriptdir(const std::string& d) { config_.scriptdir = d; }
-  [[nodiscard]] std::string logdir() const { return log_dir_; }
+  [[nodiscard]] std::string logdir() const override { return log_dir_; }
   void logdir(const std::string& d) { config_.logdir = d; }
 
   // moved from wwiv.ini
@@ -236,7 +237,7 @@ class Config final {
   /**
    * Returns the scrarch directory for a given node.
    */
-  [[nodiscard]] std::filesystem::path scratch_dir(int node) const;
+  [[nodiscard]] std::filesystem::path scratch_dir(int node) const override;
   
   [[nodiscard]] int num_instances() const { return config_.num_instances; }
   void num_instances(int n) { config_.num_instances = n; }
diff --git a/sdk/config_test.cpp b/sdk/config_test.cpp
index 364bcc708..1a78563a1 100644
--- a/sdk/config_test.cpp
+++ b/sdk/config_test.cpp
@@ -39,13 +39,13 @@ class ConfigTest : public testing::Test {
 };
 
 TEST_F(ConfigTest, Helper_CreatedBBSRoot) {
-  ASSERT_TRUE(ends_with(helper.root(), "bbs")) << helper.root();
+  ASSERT_TRUE(ends_with(helper.root_directory(), "bbs")) << helper.root_directory();
 }
 
-TEST_F(ConfigTest, Helper_ConfigWorks) { ASSERT_EQ(helper.config().datadir(), helper.data()); }
+TEST_F(ConfigTest, Helper_ConfigWorks) { ASSERT_EQ(helper.config().datadir(), helper.datadir()); }
 
 TEST_F(ConfigTest, Config_CurrentDirectory) {
-  ASSERT_EQ(0, chdir(helper.root().c_str()));
+  ASSERT_EQ(0, chdir(helper.root_directory().c_str()));
 
   const Config config(File::current_directory());
   ASSERT_TRUE(config.IsInitialized());
@@ -53,18 +53,18 @@ TEST_F(ConfigTest, Config_CurrentDirectory) {
 }
 
 TEST_F(ConfigTest, Config_DifferentDirectory) {
-  const Config config(helper.root());
+  const Config config(helper.root_directory());
   ASSERT_TRUE(config.IsInitialized());
   EXPECT_EQ(helper.data_, config.datadir());
 }
 
 TEST_F(ConfigTest, Config_WrongDirectory) {
-  const Config config(StrCat(helper.root(), "x"));
+  const Config config(StrCat(helper.root_directory(), "x"));
   ASSERT_FALSE(config.IsInitialized());
 }
 
 TEST_F(ConfigTest, SetConfig) {
-  Config config(helper.root());
+  Config config(helper.root_directory());
   ASSERT_TRUE(config.IsInitialized());
 
   config_t c{};
@@ -74,23 +74,23 @@ TEST_F(ConfigTest, SetConfig) {
 }
 
 TEST_F(ConfigTest, WrittenByNumVersion) {
-  const Config config(helper.root());
+  const Config config(helper.root_directory());
 
   ASSERT_EQ(wwiv_config_version(), config.written_by_wwiv_num_version());
 }
 
 TEST_F(ConfigTest, Is5XXOrLater) {
-  const Config config(helper.root());
+  const Config config(helper.root_directory());
 
   ASSERT_TRUE(config.is_5xx_or_later());
 }
 
 TEST_F(ConfigTest, LogDirFromConfig_Found) {
-  const Config config(helper.root());
-  ASSERT_EQ(config.logdir(), LogDirFromConfig(helper.root()));
+  const Config config(helper.root_directory());
+  ASSERT_EQ(config.logdir(), LogDirFromConfig(helper.root_directory()));
 }
 
 TEST_F(ConfigTest, LogDirFromConfig_NotFound) {
-  Config config(helper.root());
-  ASSERT_EQ("", LogDirFromConfig(StrCat(helper.root(), "x")));
+  Config config(helper.root_directory());
+  ASSERT_EQ("", LogDirFromConfig(StrCat(helper.root_directory(), "x")));
 }
diff --git a/sdk/fido/fido_callout.cpp b/sdk/fido/fido_callout.cpp
index 84d09dcd8..9df17cb23 100644
--- a/sdk/fido/fido_callout.cpp
+++ b/sdk/fido/fido_callout.cpp
@@ -55,12 +55,8 @@ void load_minimal(Archive const&, wwiv::sdk::fido::FidoAddress& a, const std::st
 
 namespace wwiv::sdk::fido {
 
-FidoCallout::FidoCallout(const Config& config, const Network& net)
-    : Callout(net, config.max_backups()), root_dir_(config.root_directory()), net_(net) {
-
-  if (!config.IsInitialized()) {
-    return;
-  }
+FidoCallout::FidoCallout(const std::string& root_directory, int max_backups, const Network& net)
+    : Callout(net, max_backups), root_dir_(root_directory), net_(net) {
 
   initialized_ = Load();
   if (!initialized_) {
@@ -69,7 +65,10 @@ FidoCallout::FidoCallout(const Config& config, const Network& net)
   initialized_ = true;
 }
 
-FidoCallout::~FidoCallout() = default;
+FidoCallout::FidoCallout(const Config& config, const Network& net)
+    : FidoCallout(config.root_directory(), config.max_backups(), net) {}
+
+    FidoCallout::~FidoCallout() = default;
 
 const net_call_out_rec* FidoCallout::net_call_out_for(int node) const {
   VLOG(2) << "       FidoCallout::net_call_out_for(" << node << ")";
diff --git a/sdk/fido/fido_callout.h b/sdk/fido/fido_callout.h
index 43646ce94..4b16fd94c 100644
--- a/sdk/fido/fido_callout.h
+++ b/sdk/fido/fido_callout.h
@@ -31,7 +31,9 @@ class FidoCallout final : public Callout {
 public:
   typedef int size_type;
   static const size_type npos = -1;
-  FidoCallout(const wwiv::sdk::Config& config, const net::Network& net);
+  
+  FidoCallout(const std::string& root_directory, int max_backups, const wwiv::sdk::net::Network& net);
+
   // [[ VisibleForTesting ]]
   ~FidoCallout() override;
 
@@ -65,6 +67,8 @@ class FidoCallout final : public Callout {
   }
 
 private:
+  // TODO(rushfan): Remove this eventually
+  FidoCallout(const wwiv::sdk::Config& config, const net::Network& net);
   bool initialized_{false};
   const std::string root_dir_;
   net::Network net_;
diff --git a/sdk/files/files_ext_test.cpp b/sdk/files/files_ext_test.cpp
index 08c2626f8..c5b65b63a 100644
--- a/sdk/files/files_ext_test.cpp
+++ b/sdk/files/files_ext_test.cpp
@@ -41,7 +41,7 @@ using namespace wwiv::strings;
 
 class FilesExtTest : public testing::Test {
 public:
-  FilesExtTest() : api_(helper.data()), api_helper_(&api_) {
+  FilesExtTest() : api_(helper.datadir()), api_helper_(&api_) {
   }
 
   void SetUp() override { helper.SetUp(); }
diff --git a/sdk/files/files_test.cpp b/sdk/files/files_test.cpp
index a5969b472..1b48cd03c 100644
--- a/sdk/files/files_test.cpp
+++ b/sdk/files/files_test.cpp
@@ -40,7 +40,7 @@ using namespace wwiv::strings;
 
 class FilesTest : public testing::Test {
 public:
-  FilesTest() : api_(helper.data()), api_helper_(&api_) {
+  FilesTest() : api_(helper.datadir()), api_helper_(&api_) {
     files_.emplace_back(ul("FILE0001.ZIP", "", 1234));
     files_.emplace_back(ul("FILE0002.ZIP", "", 2345));
     files_.emplace_back(ul("FILE0003.ZIP", "", 3456));
@@ -82,7 +82,7 @@ TEST_F(FilesTest, Smoke) {
 TEST_F(FilesTest, Create) {
   const string name = test_info_->name();
 
-  FileApi api(helper.data());
+  FileApi api(helper.datadir());
   ASSERT_FALSE(File::Exists(path_for(name)));
   ASSERT_TRUE(api.Create(name));
   EXPECT_TRUE(File::Exists(path_for(name)));
diff --git a/sdk/msgapi/email_test.cpp b/sdk/msgapi/email_test.cpp
index 9a020c883..93c9fa61d 100644
--- a/sdk/msgapi/email_test.cpp
+++ b/sdk/msgapi/email_test.cpp
@@ -67,8 +67,8 @@ TEST_F(EmailTest, BasicCase) {
   // new area.
   EXPECT_EQ(0, email->number_of_messages());
 
-  EXPECT_TRUE(File::Exists(FilePath(helper.data(), EMAIL_DAT)));
-  EXPECT_TRUE(File::Exists(FilePath(helper.msgs(), EMAIL_DAT)));
+  EXPECT_TRUE(File::Exists(FilePath(helper.datadir(), EMAIL_DAT)));
+  EXPECT_TRUE(File::Exists(FilePath(helper.msgsdir(), EMAIL_DAT)));
 }
 
 TEST_F(EmailTest, Create) {
@@ -77,7 +77,7 @@ TEST_F(EmailTest, Create) {
 
   // Add a new message, expect it to be there.
   ASSERT_TRUE(Add(1, 2, "Title", "Text"));
-  EXPECT_EQ(1, email->number_of_messages()) << FilePath(helper.data(), EMAIL_DAT);
+  EXPECT_EQ(1, email->number_of_messages()) << FilePath(helper.datadir(), EMAIL_DAT);
 
   // Read it back and make sure.
   mailrec nm{};
@@ -89,10 +89,10 @@ TEST_F(EmailTest, Delete) {
   ASSERT_TRUE(Add(1, 2, "Title", "Text"));
   ASSERT_TRUE(Add(1, 2, "Title2", "Text2"));
   ASSERT_TRUE(Add(1, 3, "Title3", "Text3"));
-  EXPECT_EQ(3, email->number_of_messages()) << FilePath(helper.data(), EMAIL_DAT);
+  EXPECT_EQ(3, email->number_of_messages()) << FilePath(helper.datadir(), EMAIL_DAT);
 
   email->DeleteAllMailToOrFrom(2);
-  EXPECT_EQ(1, email->number_of_messages()) << FilePath(helper.data(), EMAIL_DAT);
+  EXPECT_EQ(1, email->number_of_messages()) << FilePath(helper.datadir(), EMAIL_DAT);
   mailrec nm{};
   EXPECT_FALSE(email->read_email_header(0, nm));
   EXPECT_FALSE(email->read_email_header(1, nm));
diff --git a/sdk/msgapi/msgapi_test.cpp b/sdk/msgapi/msgapi_test.cpp
index a0fb1ae04..ced61778b 100644
--- a/sdk/msgapi/msgapi_test.cpp
+++ b/sdk/msgapi/msgapi_test.cpp
@@ -71,8 +71,8 @@ TEST_F(MsgApiTest, CreateArea) {
   unique_ptr<MessageArea> a1(api->Open(sub, -1));
   EXPECT_TRUE(a1->Close());
 
-  EXPECT_TRUE(File::Exists(FilePath(helper.data(), "a1.sub")));
-  EXPECT_TRUE(File::Exists(FilePath(helper.msgs(), "a1.dat")));
+  EXPECT_TRUE(File::Exists(FilePath(helper.datadir(), "a1.sub")));
+  EXPECT_TRUE(File::Exists(FilePath(helper.msgsdir(), "a1.dat")));
 }
 
 TEST_F(MsgApiTest, SmokeTest) {
diff --git a/sdk/names_test.cpp b/sdk/names_test.cpp
index 7e45fe686..6b066dc10 100644
--- a/sdk/names_test.cpp
+++ b/sdk/names_test.cpp
@@ -43,7 +43,7 @@ class NamesTest : public testing::Test {
   }
 
   [[nodiscard]] bool CreateNames() const {
-    File file(FilePath(helper.data(), NAMES_LST));
+    File file(FilePath(helper.datadir(), NAMES_LST));
     file.Open(File::modeBinary|File::modeWriteOnly|File::modeCreateFile, File::shareDenyNone);
     if (!file.IsOpen()) {
       return false;
diff --git a/sdk/net/ftn_msgdupe_test.cpp b/sdk/net/ftn_msgdupe_test.cpp
index 4514e257e..740e24dfa 100644
--- a/sdk/net/ftn_msgdupe_test.cpp
+++ b/sdk/net/ftn_msgdupe_test.cpp
@@ -47,7 +47,7 @@ class FtnMsgDupeTest : public testing::Test {
   FtnMsgDupeTest() {}
 
   [[nodiscard]] bool CreateDupes(const std::vector<msgids>& ids) const {
-    DataFile<msgids> file(FilePath(helper.data(), MSGDUPE_DAT),
+    DataFile<msgids> file(FilePath(helper.datadir(), MSGDUPE_DAT),
                           File::modeReadWrite | File::modeBinary | File::modeCreateFile |
                               File::modeTruncate);
     if (!file) {
@@ -59,7 +59,7 @@ class FtnMsgDupeTest : public testing::Test {
 
   [[nodiscard]] bool SetLastMessageId(uint32_t message_id) const {
     uint64_t id = message_id;
-    DataFile<uint64_t> file(FilePath(helper.data(), MSGID_DAT),
+    DataFile<uint64_t> file(FilePath(helper.datadir(), MSGID_DAT),
                             File::modeReadWrite | File::modeBinary | File::modeCreateFile,
                             File::shareDenyReadWrite);
     if (!file) {
@@ -90,7 +90,7 @@ TEST_F(FtnMsgDupeTest, As64) {
 }
 
 TEST_F(FtnMsgDupeTest, MsgId_NoExists) { 
-  FtnMessageDupe dupe(helper.data(), true);
+  FtnMessageDupe dupe(helper.datadir(), true);
   const FidoAddress a{"1:2/3"};
   const auto now = time(nullptr);
   const auto line = dupe.CreateMessageID(a);
@@ -100,15 +100,15 @@ TEST_F(FtnMsgDupeTest, MsgId_NoExists) {
   const auto id = std::stol(parts.at(1), nullptr, 16);
 
   EXPECT_LT(std::abs(id - now), 11) << "id: " << id << "; now: " << now;
-  EXPECT_TRUE(File::Exists(FilePath(helper.data(), MSGID_DAT)));
+  EXPECT_TRUE(File::Exists(FilePath(helper.datadir(), MSGID_DAT)));
 }
 
 TEST_F(FtnMsgDupeTest, Exists) {
   const auto now = daten_t_now();
   const auto last_message_id = now + 10000;
   ASSERT_TRUE(SetLastMessageId(last_message_id));
-  ASSERT_TRUE(File::Exists(FilePath(helper.data(), MSGID_DAT)));
-  FtnMessageDupe dupe(helper.data(), true);
+  ASSERT_TRUE(File::Exists(FilePath(helper.datadir(), MSGID_DAT)));
+  FtnMessageDupe dupe(helper.datadir(), true);
   const FidoAddress a{"1:2/3"};
   const auto line = dupe.CreateMessageID(a);
   auto parts = SplitString(line, " ");
@@ -120,14 +120,14 @@ TEST_F(FtnMsgDupeTest, Exists) {
 }
 
 TEST_F(FtnMsgDupeTest, Smoke) {
-  FtnMessageDupe dupe(helper.data(), false);
+  FtnMessageDupe dupe(helper.datadir(), false);
   dupe.add(1, 2);
   EXPECT_TRUE(dupe.is_dupe(1, 2));
   EXPECT_FALSE(dupe.is_dupe(2, 1));
 }
 
 TEST_F(FtnMsgDupeTest, Remove) {
-  FtnMessageDupe dupe(helper.data(), false);
+  FtnMessageDupe dupe(helper.datadir(), false);
   dupe.add(1, 2);
   EXPECT_TRUE(dupe.is_dupe(1, 2));
   dupe.remove(1, 2);
diff --git a/sdk/net/network_test.cpp b/sdk/net/network_test.cpp
index 57edc8b07..9307af4f9 100644
--- a/sdk/net/network_test.cpp
+++ b/sdk/net/network_test.cpp
@@ -48,8 +48,8 @@ class NetworkTest : public testing::Test {
   }
 
   [[nodiscard]] bool CreateNetworksDat(std::vector<std::string> names) const {
-    std::clog << "Writing NETWORK.DAT to: " << helper.data() << std::endl;
-    File file(FilePath(helper.data(), NETWORKS_DAT));
+    std::clog << "Writing NETWORK.DAT to: " << helper.datadir() << std::endl;
+    File file(FilePath(helper.datadir(), NETWORKS_DAT));
     file.Open(File::modeBinary|File::modeWriteOnly|File::modeCreateFile, File::shareDenyNone);
     if (!file.IsOpen()) {
       return false;
diff --git a/sdk/phone_numbers_test.cpp b/sdk/phone_numbers_test.cpp
index 56ba39a44..a23fd3daa 100644
--- a/sdk/phone_numbers_test.cpp
+++ b/sdk/phone_numbers_test.cpp
@@ -59,7 +59,7 @@ class PhoneNumbersTest : public testing::Test {
 };
 
 TEST_F(PhoneNumbersTest, Find) {
-  const Config config(helper.root());
+  const Config config(helper.root_directory());
   ASSERT_TRUE(config.IsInitialized());
   ASSERT_TRUE(CreatePhoneNumDat(config));
 
@@ -72,7 +72,7 @@ TEST_F(PhoneNumbersTest, Find) {
 }
 
 TEST_F(PhoneNumbersTest, Insert) {
-  const Config config(helper.root());
+  const Config config(helper.root_directory());
   ASSERT_TRUE(config.IsInitialized());
   ASSERT_TRUE(CreatePhoneNumDat(config));
 
@@ -88,7 +88,7 @@ TEST_F(PhoneNumbersTest, Insert) {
 }
 
 TEST_F(PhoneNumbersTest, Erase) {
-  const Config config(helper.root());
+  const Config config(helper.root_directory());
   ASSERT_TRUE(config.IsInitialized());
   ASSERT_TRUE(CreatePhoneNumDat(config));
 
diff --git a/sdk/sdk_helper.cpp b/sdk/sdk_helper.cpp
index db3c56742..282a0279f 100644
--- a/sdk/sdk_helper.cpp
+++ b/sdk/sdk_helper.cpp
@@ -91,7 +91,7 @@ SdkHelper::SdkHelper()
     c.max_subs = 64;
 
     config_ = std::make_unique<wwiv::sdk::Config>(root_, c);
-    config_->set_paths_for_test(data(), msgs(), gfiles(), menus(), dloads(), scripts());
+    config_->set_paths_for_test(datadir(), msgsdir(), gfilesdir(), menudir(), dloadsdir(), scriptdir());
 
     // Force this to be read-write since we're in a test environment (same as
     // in the upgrade case)
diff --git a/sdk/sdk_helper.h b/sdk/sdk_helper.h
index 885a9e2f5..a3f956d27 100644
--- a/sdk/sdk_helper.h
+++ b/sdk/sdk_helper.h
@@ -23,22 +23,27 @@
 #include <string>
 
 #include "core/test/file_helper.h"
+#include "sdk/bbs_directories.h"
 #include "sdk/config.h"
 
-class SdkHelper {
+class SdkHelper : public wwiv::sdk::BbsDirectories {
 public:
   SdkHelper();
   ~SdkHelper();
   bool SetUp() { return true; }
 
-  [[nodiscard]] std::string root() const { return root_.string(); }
-  [[nodiscard]] std::string data() const { return data_.string(); }
-  [[nodiscard]] std::string dloads() const { return dloads_.string(); }
-  [[nodiscard]] std::string msgs() const { return msgs_.string(); }
-  [[nodiscard]] std::string gfiles() const { return gfiles_.string(); }
-  [[nodiscard]] std::string menus() const { return menus_.string(); }
-  [[nodiscard]] std::string scripts() const { return scripts_.string(); }
-  [[nodiscard]] std::string logs() const { return logs_.string(); }
+  [[nodiscard]] virtual std::string root_directory() const override { return root_.string(); }
+  [[nodiscard]] virtual std::string datadir() const override { return data_.string(); }
+  [[nodiscard]] virtual std::string msgsdir() const override { return msgs_.string(); }
+  [[nodiscard]] virtual std::string gfilesdir() const override { return gfiles_.string(); }
+  [[nodiscard]] virtual std::string menudir() const override { return menus_.string(); }
+  [[nodiscard]] virtual std::string dloadsdir() const override { return dloads_.string(); }
+  [[nodiscard]] virtual std::string scriptdir() const override { return scripts_.string(); }
+  [[nodiscard]] virtual std::string logdir() const override { return logs_.string(); }
+  [[nodiscard]] virtual std::filesystem::path scratch_dir(int) const override {
+    return scratch_;
+  }
+
   [[nodiscard]] std::string scratch() const { return scratch_.string(); }
   [[nodiscard]] wwiv::core::test::FileHelper& files() { return files_; }
   [[nodiscard]] wwiv::sdk::Config& config() const;
diff --git a/sdk/subxtr_test.cpp b/sdk/subxtr_test.cpp
index 101723504..f19a0ce37 100644
--- a/sdk/subxtr_test.cpp
+++ b/sdk/subxtr_test.cpp
@@ -68,8 +68,8 @@ TEST_F(SubXtrTest, Write) {
   s2.nets.emplace_back(xtrasubsnetrec{0, 0, 1, 1, "S2"});
   xsubs.emplace_back(s2);
 
-  write_subs_xtr(helper.data(), net_networks_, xsubs, 0);
-  TextFile subs_xtr_file(FilePath(helper.data(), "subs.xtr"), "r");
+  write_subs_xtr(helper.datadir(), net_networks_, xsubs, 0);
+  TextFile subs_xtr_file(FilePath(helper.datadir(), "subs.xtr"), "r");
   auto actual = SplitString(subs_xtr_file.ReadFileIntoString(), "\n");
   ASSERT_EQ(4u, actual.size());
   std::vector<std::string> expected = {
@@ -120,13 +120,13 @@ TEST_F(SubXtrTest, Read) {
     std::vector<std::string> contents{
       {"!1", "@this is sub2", "#0", "$testnet S2 0 1 1"},
     };
-    TextFile subs_xtr_file(FilePath(helper.data(), "subs.xtr"), "w");
+    TextFile subs_xtr_file(FilePath(helper.datadir(), "subs.xtr"), "w");
     for (const auto& line : contents) {
       subs_xtr_file.WriteLine(line);
     }
   }
   std::vector<xtrasubsrec> actual;
-  read_subs_xtr(helper.data(), net_networks_, subs_, actual);
+  read_subs_xtr(helper.datadir(), net_networks_, subs_, actual);
   ASSERT_EQ(subs_.size(), actual.size());
   ASSERT_EQ(expected.size(), actual.size());
   for (auto i = 0; i < wwiv::stl::ssize(subs_); i++) {
diff --git a/wwivconfig/convert.cpp b/wwivconfig/convert.cpp
index f7349c4bd..69c0471fd 100644
--- a/wwivconfig/convert.cpp
+++ b/wwivconfig/convert.cpp
@@ -463,7 +463,7 @@ bool convert_to_v5(UIWindow* window, Config& config) {
   // Update networks to get rid of base packet config.
   for (const auto& net : networks.networks()) {
     if (net.type == network_type_t::ftn) {
-      fido::FidoCallout callout(config, net);
+      fido::FidoCallout callout(config.root_directory(), config.max_backups(), net);
       for (auto& [a, node_config] : callout.node_configs_map()) {
         auto merged_config = callout.merged_packet_config_for(a, net.fido.packet_config);
         node_config.packet_config = merged_config;
diff --git a/wwivconfig/networks.cpp b/wwivconfig/networks.cpp
index 21c06df83..7d67142e6 100644
--- a/wwivconfig/networks.cpp
+++ b/wwivconfig/networks.cpp
@@ -325,7 +325,7 @@ class FidoPacketConfigSubDialog : public SubDialog<Network> {
 
   void RunSubDialog(CursesWindow* window) override {
     window->GotoXY(x_, y_);
-    FidoCallout callout(config(), t_);
+    FidoCallout callout(config().root_directory(), config().max_backups(), t_);
     if (!callout.IsInitialized()) {
       messagebox(window, "Unable to initialize fido_callout.json.");
       return;
diff --git a/wwivd/nets.cpp b/wwivd/nets.cpp
index 3bb8c7270..1ff37fd29 100644
--- a/wwivd/nets.cpp
+++ b/wwivd/nets.cpp
@@ -67,7 +67,7 @@ static NetworkContact network_contact_from_last_time(const fido::FidoAddress& ad
 
 static void one_net_ftn_callout(const Config& config, const Network& net,
                                 const wwivd_config_t& c, int network_number) {
-  const fido::FidoCallout callout(config, net);
+  const fido::FidoCallout callout(config.root_directory(), config.max_backups(), net);
 
   // TODO(rushfan): 1. Right now we just keep the map of last call-out
   // time in memory, but we should checkpoint this to disk and reload