feat: Add unit tests and fakes for Crestron environment and data store

- Introduced `FakeCrestronEnvironment` and `FakeEthernetHelper` for testing purposes.
- Implemented `InMemoryCrestronDataStore` to facilitate unit tests for data storage.
- Created `DebugServiceTests` to validate the behavior of debug-related services.
- Added project files for unit tests targeting .NET 9.0 with necessary dependencies.
- Developed production adapters (`CrestronConsoleAdapter`, `CrestronDataStoreAdapter`, `CrestronEnvironmentAdapter`, `CrestronEthernetAdapter`) to interface with the Crestron SDK.
- Updated `Debug` class to utilize injected service abstractions instead of direct SDK calls.
- Enhanced `ControlSystem` initialization to register service adapters before usage.
This commit is contained in:
Neil Dorin 2026-04-08 12:41:15 -06:00
parent bfb9838743
commit 24df4e7a03
21 changed files with 1020 additions and 93 deletions

View file

@ -0,0 +1,35 @@
using System;
using Crestron.SimplSharp;
using PdCore = PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates ICrestronConsole calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronConsoleAdapter : PdCore.ICrestronConsole
{
public void PrintLine(string message) => CrestronConsole.PrintLine(message);
public void Print(string message) => CrestronConsole.Print(message);
public void ConsoleCommandResponse(string message) =>
CrestronConsole.ConsoleCommandResponse(message);
public void AddNewConsoleCommand(
Action<string> callback,
string command,
string helpText,
PdCore.ConsoleAccessLevel accessLevel)
{
var crestronLevel = accessLevel switch
{
PdCore.ConsoleAccessLevel.AccessAdministrator => ConsoleAccessLevelEnum.AccessAdministrator,
PdCore.ConsoleAccessLevel.AccessProgrammer => ConsoleAccessLevelEnum.AccessProgrammer,
_ => ConsoleAccessLevelEnum.AccessOperator,
};
// Wrap Action<string> in a lambda — Crestron's delegate is not a standard Action<string>.
CrestronConsole.AddNewConsoleCommand(s => callback(s), command, helpText, crestronLevel);
}
}

View file

@ -0,0 +1,42 @@
using Crestron.SimplSharp.CrestronDataStore;
using PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates ICrestronDataStore calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronDataStoreAdapter : ICrestronDataStore
{
public void InitStore() => CrestronDataStoreStatic.InitCrestronDataStore();
public bool TryGetLocalInt(string key, out int value)
{
var err = CrestronDataStoreStatic.GetLocalIntValue(key, out value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool SetLocalInt(string key, int value)
{
var err = CrestronDataStoreStatic.SetLocalIntValue(key, value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool SetLocalUint(string key, uint value)
{
var err = CrestronDataStoreStatic.SetLocalUintValue(key, value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool TryGetLocalBool(string key, out bool value)
{
var err = CrestronDataStoreStatic.GetLocalBoolValue(key, out value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool SetLocalBool(string key, bool value)
{
var err = CrestronDataStoreStatic.SetLocalBoolValue(key, value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
}

View file

@ -0,0 +1,68 @@
using System;
using Crestron.SimplSharp;
using PdCore = PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates ICrestronEnvironment calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment
{
// Subscribe once in constructor and re-raise as our event types.
private event EventHandler<PdCore.ProgramStatusEventArgs>? _programStatusChanged;
private event EventHandler<PdCore.PepperDashEthernetEventArgs>? _ethernetEventReceived;
public CrestronEnvironmentAdapter()
{
CrestronEnvironment.ProgramStatusEventHandler += type =>
_programStatusChanged?.Invoke(this, new PdCore.ProgramStatusEventArgs(MapProgramStatus(type)));
CrestronEnvironment.EthernetEventHandler += args =>
_ethernetEventReceived?.Invoke(this, new PdCore.PepperDashEthernetEventArgs(
args.EthernetEventType == eEthernetEventType.LinkDown
? PdCore.EthernetEventType.LinkDown
: PdCore.EthernetEventType.LinkUp,
(short)args.EthernetAdapter));
}
public PdCore.DevicePlatform DevicePlatform =>
CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
? PdCore.DevicePlatform.Appliance
: PdCore.DevicePlatform.Server;
public PdCore.RuntimeEnvironment RuntimeEnvironment =>
CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro
? PdCore.RuntimeEnvironment.SimplSharpPro
: PdCore.RuntimeEnvironment.Other;
public string NewLine => CrestronEnvironment.NewLine;
public uint ApplicationNumber => InitialParametersClass.ApplicationNumber;
public uint RoomId => uint.TryParse(InitialParametersClass.RoomId, out var r) ? r : 0;
public event EventHandler<PdCore.ProgramStatusEventArgs> ProgramStatusChanged
{
add => _programStatusChanged += value;
remove => _programStatusChanged -= value;
}
public event EventHandler<PdCore.PepperDashEthernetEventArgs> EthernetEventReceived
{
add => _ethernetEventReceived += value;
remove => _ethernetEventReceived -= value;
}
public string GetApplicationRootDirectory() =>
Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory();
private static PdCore.ProgramStatusEventType MapProgramStatus(eProgramStatusEventType type) =>
type switch
{
eProgramStatusEventType.Stopping => PdCore.ProgramStatusEventType.Stopping,
eProgramStatusEventType.Paused => PdCore.ProgramStatusEventType.Paused,
eProgramStatusEventType.Resumed => PdCore.ProgramStatusEventType.Resumed,
_ => PdCore.ProgramStatusEventType.Starting,
};
}

View file

@ -0,0 +1,39 @@
using System;
using Crestron.SimplSharp;
using PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates IEthernetHelper calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronEthernetAdapter : IEthernetHelper
{
public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId)
{
var crestronParam = parameter switch
{
EthernetParameterType.GetCurrentIpAddress =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS,
EthernetParameterType.GetHostname =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME,
EthernetParameterType.GetDomainName =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME,
EthernetParameterType.GetLinkStatus =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_LINK_STATUS,
EthernetParameterType.GetCurrentDhcpState =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE,
EthernetParameterType.GetCurrentIpMask =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK,
EthernetParameterType.GetCurrentRouter =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER,
EthernetParameterType.GetMacAddress =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS,
EthernetParameterType.GetDnsServer =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER,
_ => throw new ArgumentOutOfRangeException(nameof(parameter), parameter, null),
};
return CrestronEthernetHelper.GetEthernetParameter(crestronParam, ethernetAdapterId);
}
}