diff --git a/ExportDXF.sln b/ExportDXF.sln
index 6f84954..5c9b6de 100644
--- a/ExportDXF.sln
+++ b/ExportDXF.sln
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "netDxf", "EtchBendLines\net
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabWorks.Core", "FabWorks.Core\FabWorks.Core.csproj", "{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabWorks.Tests", "FabWorks.Tests\FabWorks.Tests.csproj", "{6DD89774-D86B-47E9-B982-2794BD95616A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +71,18 @@ Global
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x64.Build.0 = Release|Any CPU
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x86.ActiveCfg = Release|Any CPU
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x86.Build.0 = Release|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x64.Build.0 = Debug|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x86.Build.0 = Debug|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x64.ActiveCfg = Release|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x64.Build.0 = Release|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x86.ActiveCfg = Release|Any CPU
+ {6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/FabWorks.Tests/FabWorks.Tests.csproj b/FabWorks.Tests/FabWorks.Tests.csproj
new file mode 100644
index 0000000..354f1fb
--- /dev/null
+++ b/FabWorks.Tests/FabWorks.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FabWorks.Tests/PressBrake/ProgramReaderTests.cs b/FabWorks.Tests/PressBrake/ProgramReaderTests.cs
new file mode 100644
index 0000000..b2614cf
--- /dev/null
+++ b/FabWorks.Tests/PressBrake/ProgramReaderTests.cs
@@ -0,0 +1,48 @@
+using FabWorks.Core.PressBrake;
+using Xunit;
+
+namespace FabWorks.Tests.PressBrake
+{
+ public class ProgramReaderTests
+ {
+ [Fact]
+ public void Load_SamplePgm_ParsesProgramAttributes()
+ {
+ var pgm = Program.Load("TestData/sample.pgm");
+
+ // ProgName may be empty on some exports; verify PartName was parsed instead
+ Assert.False(string.IsNullOrEmpty(pgm.PartName));
+ }
+
+ [Fact]
+ public void Load_SamplePgm_ParsesThickness()
+ {
+ var pgm = Program.Load("TestData/sample.pgm");
+ Assert.True(pgm.MatThick > 0);
+ }
+
+ [Fact]
+ public void Load_SamplePgm_ParsesSteps()
+ {
+ var pgm = Program.Load("TestData/sample.pgm");
+ Assert.NotEmpty(pgm.Steps);
+ }
+
+ [Fact]
+ public void Load_SamplePgm_ParsesToolSetups()
+ {
+ var pgm = Program.Load("TestData/sample.pgm");
+ Assert.NotEmpty(pgm.UpperToolSets);
+ Assert.NotEmpty(pgm.LowerToolSets);
+ }
+
+ [Fact]
+ public void Load_SamplePgm_ResolvesStepToolReferences()
+ {
+ var pgm = Program.Load("TestData/sample.pgm");
+ var step = pgm.Steps[0];
+ Assert.NotNull(step.UpperTool);
+ Assert.NotNull(step.LowerTool);
+ }
+ }
+}
diff --git a/FabWorks.Tests/TestData/sample.pgm b/FabWorks.Tests/TestData/sample.pgm
new file mode 100644
index 0000000..76b06ad
--- /dev/null
+++ b/FabWorks.Tests/TestData/sample.pgm
@@ -0,0 +1,593 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+