diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ArgumentGuardsTests.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ArgumentGuardsTests.cs
deleted file mode 100644
index 82d12f65..00000000
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ArgumentGuardsTests.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace RarelySimple.AvatarScriptLink.Objects.Helpers.Tests
-{
- ///
- /// Tests for internal ArgumentGuards validation helpers.
- ///
- [TestClass]
- public class ArgumentGuardsTests
- {
- [TestMethod]
- public void ValidateFieldNumber_ArgumentGuards_WithNullFieldNumber_ThrowsArgumentNullException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateFieldNumber(null!, "fieldNumber"));
-
- Assert.AreEqual("fieldNumber", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateFieldNumber_ArgumentGuards_WithEmptyFieldNumber_ThrowsArgumentException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateFieldNumber(string.Empty, "fieldNumber"));
-
- Assert.AreEqual("fieldNumber", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateFieldNumber_ArgumentGuards_WithValidFieldNumber_DoesNotThrow()
- {
- ArgumentGuards.ValidateFieldNumber("100", "fieldNumber");
- }
-
- [TestMethod]
- public void ValidateAndNormalizeFieldNumbers_ArgumentGuards_WithNullFieldNumbers_ThrowsArgumentNullException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateAndNormalizeFieldNumbers(null!, "fieldNumbers"));
-
- Assert.AreEqual("fieldNumbers", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateAndNormalizeFieldNumbers_ArgumentGuards_WithEmptyFieldNumbers_ThrowsArgumentException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateAndNormalizeFieldNumbers(new List(), "fieldNumbers"));
-
- Assert.AreEqual("fieldNumbers", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateAndNormalizeFieldNumbers_ArgumentGuards_WithOnlyEmptyValues_ThrowsArgumentException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateAndNormalizeFieldNumbers(new List { string.Empty, null!, string.Empty }, "fieldNumbers"));
-
- Assert.AreEqual("fieldNumbers", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateAndNormalizeFieldNumbers_ArgumentGuards_WithDuplicatesAndEmptyValues_ReturnsDistinctNonEmptyValues()
- {
- var result = ArgumentGuards.ValidateAndNormalizeFieldNumbers(
- new List { "100", string.Empty, "100", "101", "101" },
- "fieldNumbers");
-
- CollectionAssert.AreEqual(new List { "100", "101" }, result);
- }
-
- [TestMethod]
- public void ValidateAndGetFieldNumbers_ArgumentGuards_WithNullFieldObjects_ThrowsArgumentNullException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateAndGetFieldNumbers(null!, "fieldObjects"));
-
- Assert.AreEqual("fieldObjects", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateAndGetFieldNumbers_ArgumentGuards_WithEmptyFieldObjects_ThrowsArgumentException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateAndGetFieldNumbers(new List(), "fieldObjects"));
-
- Assert.AreEqual("fieldObjects", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateAndGetFieldNumbers_ArgumentGuards_WithNoValidFieldNumbers_ThrowsArgumentException()
- {
- var ex = Assert.ThrowsException(
- () => ArgumentGuards.ValidateAndGetFieldNumbers(
- new List { null!, new FieldObject { FieldNumber = string.Empty }, new FieldObject { FieldNumber = null! } },
- "fieldObjects"));
-
- Assert.AreEqual("fieldObjects", ex.ParamName);
- }
-
- [TestMethod]
- public void ValidateAndGetFieldNumbers_ArgumentGuards_WithDuplicatesAndInvalidEntries_ReturnsDistinctFieldNumbers()
- {
- var result = ArgumentGuards.ValidateAndGetFieldNumbers(
- new List
- {
- new FieldObject { FieldNumber = "100" },
- null!,
- new FieldObject { FieldNumber = string.Empty },
- new FieldObject { FieldNumber = "100" },
- new FieldObject { FieldNumber = "101" }
- },
- "fieldObjects");
-
- CollectionAssert.AreEqual(new List { "100", "101" }, result);
- }
- }
-}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ScriptLinkParsingTests.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ScriptLinkParsingTests.cs
new file mode 100644
index 00000000..96a2b098
--- /dev/null
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ScriptLinkParsingTests.cs
@@ -0,0 +1,609 @@
+namespace RarelySimple.AvatarScriptLink.Objects.Helpers.Tests
+{
+ [TestClass]
+ public class ScriptLinkParsingTests
+ {
+ #region SafeGetInt Tests
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_Zero_ReturnsZero()
+ {
+ // Arrange
+ string intString = "0";
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_PositiveInteger_ReturnsValue()
+ {
+ // Arrange
+ string intString = "42";
+ int expected = 42;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_NegativeInteger_ReturnsValue()
+ {
+ // Arrange
+ string intString = "-42";
+ int expected = -42;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_DecimalString_ReturnsZero()
+ {
+ // Arrange
+ string intString = "10.5";
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_AlphabeticString_ReturnsZero()
+ {
+ // Arrange
+ string intString = "abc";
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_SpecialCharacters_ReturnsZero()
+ {
+ // Arrange
+ string intString = "@#$";
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_NullString_ReturnsZero()
+ {
+ // Arrange
+ string? intString = null;
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_EmptyString_ReturnsZero()
+ {
+ // Arrange
+ string intString = "";
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_LargeNumber_ReturnsValue()
+ {
+ // Arrange
+ string intString = "2147483647"; // Max int
+ int expected = 2147483647;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_WhitespaceString_ReturnsZero()
+ {
+ // Arrange
+ string intString = " ";
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetInt")]
+ public void SafeGetInt_OverflowValue_ReturnsZero()
+ {
+ // Arrange
+ string intString = "2147483648"; // int.MaxValue + 1
+ int expected = 0;
+
+ // Act
+ int actual = ScriptLinkParsing.SafeGetInt(intString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ #endregion
+
+ #region SafeGetBool Tests
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_One_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "1";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_Zero_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "0";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_TUppercase_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "T";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_TrueLowercase_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "true";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_TrueUppercase_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "TRUE";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_YUppercase_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "Y";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_YesLowercase_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "yes";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_YesMixedCase_ReturnsTrue()
+ {
+ // Arrange
+ string boolString = "Yes";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsTrue(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_FUppercase_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "F";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_FalseLowercase_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "false";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_FalseUppercase_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "FALSE";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_NUppercase_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "N";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_NoLowercase_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "no";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_NoUppercase_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "NO";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_InvalidString_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "not a bool";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_NullString_ReturnsFalse()
+ {
+ // Arrange
+ string? boolString = null;
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_EmptyString_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_TwoString_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = "2";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetBool")]
+ public void SafeGetBool_WhitespaceString_ReturnsFalse()
+ {
+ // Arrange
+ string boolString = " ";
+
+ // Act
+ bool actual = ScriptLinkParsing.SafeGetBool(boolString);
+
+ // Assert
+ Assert.IsFalse(actual);
+ }
+
+ #endregion
+
+ #region SafeGetDateTime Tests
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_ValidDateMMDDYYYYSlash_ReturnsDate()
+ {
+ // Arrange
+ string dateTimeString = "10/10/2010";
+ DateTime expected = new(2010, 10, 10, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_ValidDateMMDDYYYYDash_ReturnsDate()
+ {
+ // Arrange
+ string dateTimeString = "10-10-2010";
+ DateTime expected = new(2010, 10, 10, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_ValidDateMMDDYYDash_ReturnsDate()
+ {
+ // Arrange
+ string dateTimeString = "10-10-10";
+ DateTime expected = new(2010, 10, 10, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_ValidDateTextFormat_ReturnsDate()
+ {
+ // Arrange
+ string dateTimeString = "October 10, 2010";
+ DateTime expected = new(2010, 10, 10, 0, 0, 0, DateTimeKind.Unspecified);
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+
+ try
+ {
+ Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_InvalidDateString_ReturnsMinDate()
+ {
+ // Arrange
+ string dateTimeString = "not a date";
+ DateTime expected = new(1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_NullString_ReturnsMinDate()
+ {
+ // Arrange
+ string? dateTimeString = null;
+ DateTime expected = new(1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_EmptyString_ReturnsMinDate()
+ {
+ // Arrange
+ string dateTimeString = "";
+ DateTime expected = new(1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_ValidDateWithTime_ReturnsDateTime()
+ {
+ // Arrange
+ string dateTimeString = "10/10/2010 14:30:00";
+ DateTime expected = new(2010, 10, 10, 14, 30, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_WhitespaceString_ReturnsMinDate()
+ {
+ // Arrange
+ string dateTimeString = " ";
+ DateTime expected = new(1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkParsing-SafeGetDateTime")]
+ public void SafeGetDateTime_ValidIso8601Format_ReturnsDate()
+ {
+ // Arrange
+ string dateTimeString = "2010-10-10";
+ DateTime expected = new(2010, 10, 10, 0, 0, 0, DateTimeKind.Unspecified);
+
+ // Act
+ DateTime actual = ScriptLinkParsing.SafeGetDateTime(dateTimeString);
+
+ // Assert
+ Assert.AreEqual(expected, actual);
+ }
+
+ #endregion
+ }
+}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ScriptLinkValidationTests.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ScriptLinkValidationTests.cs
new file mode 100644
index 00000000..cc12c537
--- /dev/null
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers.Tests/ScriptLinkValidationTests.cs
@@ -0,0 +1,524 @@
+using System;
+
+namespace RarelySimple.AvatarScriptLink.Objects.Helpers.Tests
+{
+ [TestClass]
+ public class ScriptLinkValidationTests
+ {
+ #region IsValidUrl(string) Tests
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidHttpUrl_ReturnsTrue()
+ {
+ // Arrange
+ string url = "http://example.com";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidHttpsUrl_ReturnsTrue()
+ {
+ // Arrange
+ string url = "https://example.com";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidHttpUrlWithPath_ReturnsTrue()
+ {
+ // Arrange
+ string url = "http://example.com/path";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidHttpUrlWithQueryString_ReturnsTrue()
+ {
+ // Arrange
+ string url = "http://example.com/?e=123";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidHttpsUrlWithQueryString_ReturnsTrue()
+ {
+ // Arrange
+ string url = "https://example.com/?e=123";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_RelativeUrl_ReturnsFalse()
+ {
+ // Arrange
+ string url = "example.com";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_RelativeUrlWithSpaces_ReturnsFalse()
+ {
+ // Arrange
+ string url = "example domain.com";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_NullString_ReturnsFalse()
+ {
+ // Arrange
+ string? url = null;
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_EmptyString_ReturnsFalse()
+ {
+ // Arrange
+ string url = "";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_WhitespaceString_ReturnsFalse()
+ {
+ // Arrange
+ string url = " ";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidFileScheme_ReturnsTrue()
+ {
+ // Arrange
+ string url = "file:///C:/path/to/file.txt";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidUrlWithFragment_ReturnsTrue()
+ {
+ // Arrange
+ string url = "https://example.com/page#section";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidUrlWithPort_ReturnsTrue()
+ {
+ // Arrange
+ string url = "https://example.com:8443/path";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl")]
+ public void IsValidUrl_ValidFtpUrl_ReturnsTrue()
+ {
+ // Arrange
+ string url = "ftp://files.example.com/report.csv";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(url);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ #endregion
+
+ #region IsValidUrl(Uri) Tests
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl-Uri")]
+ public void IsValidUrl_ValidUri_ReturnsTrue()
+ {
+ // Arrange
+ var uri = new Uri("http://example.com");
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(uri);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl-Uri")]
+ public void IsValidUrl_AbsoluteUri_ReturnsTrue()
+ {
+ // Arrange
+ var uri = new Uri("https://example.com/path?query=value");
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(uri);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl-Uri")]
+ public void IsValidUrl_RelativeUri_ReturnsFalse()
+ {
+ // Arrange
+ var uri = new Uri("/path/to/resource", UriKind.Relative);
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(uri);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidUrl-Uri")]
+ public void IsValidUrl_NullUri_ReturnsFalse()
+ {
+ // Arrange
+ Uri? uri = null;
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidUrl(uri);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ #endregion
+
+ #region IsValidOpenFormString Tests
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_SimpleForm_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "PR0001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_FormWithModule_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "[PM]PR0001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_RadplusForm_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "RADplus_Imaging001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_CWSModule_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "[CWS]RADplus_Imaging001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_MSOModule_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "[MSO]PR0001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_FormWithMessage_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "PR0001|Error occurred";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_FormWithMessageAndPatientId_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "PR0001|Error occurred|12345";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_FormWithAllComponents_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "[PM]PR0001|Error occurred|12345|1";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_MultipleForms_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "PR0001&PR0002";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_InvalidFormName_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = "0001"; // Missing letter prefix
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_InvalidModule_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = "[XX]PR0001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_NullString_ReturnsFalse()
+ {
+ // Arrange
+ string? openForm = null;
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_EmptyString_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = "";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_WhitespaceString_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = " ";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_LowercaseFormName_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = "pr0001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_NumbersOnlyFormName_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = "0001";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_MultipleFormsWithSpacesAroundAmpersand_ReturnsTrue()
+ {
+ // Arrange
+ string openForm = "PR0001 & PR0002";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ [TestCategory("ScriptLinkValidation-IsValidOpenFormString")]
+ public void IsValidOpenFormString_TabInMessage_ReturnsFalse()
+ {
+ // Arrange
+ string openForm = "PR0001|Error\there";
+
+ // Act
+ bool result = ScriptLinkValidation.IsValidOpenFormString(openForm);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ #endregion
+ }
+}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/FormObjectHelpers.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/FormObjectHelpers.cs
index 35ac68c4..6c7e60c0 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/FormObjectHelpers.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/FormObjectHelpers.cs
@@ -155,12 +155,12 @@ public static FormObject AddRowObject(this FormObject formObject, RowObject rowO
if (!formObject.MultipleIteration && formObject.CurrentRow != null)
{
- throw new ArgumentException("Cannot add another row to a non-multiple-iteration form.", nameof(formObject));
+ throw new ArgumentException(StructuralMutationMessages.CannotAddAnotherRowToNonMultipleIterationForm, nameof(formObject));
}
if (!string.IsNullOrEmpty(rowObject.RowId) && formObject.IsRowPresent(rowObject.RowId))
{
- throw new ArgumentException("A row with the provided RowId already exists.", nameof(rowObject));
+ throw new ArgumentException(StructuralMutationMessages.RowObjectAlreadyExists, nameof(rowObject));
}
if (formObject.CurrentRow == null)
@@ -565,7 +565,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && formObject.OtherRows.Any(r => r.IsFieldPresent(fieldNumber)));
if (!hasFieldInForm)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
if (formObject.CurrentRow.IsFieldPresent(fieldNumber))
{
@@ -602,7 +602,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && fieldsToSet.Any(f => formObject.OtherRows.Any(r => r.IsFieldPresent(f))));
if (!hasAnyField)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
var currentRowFieldNumbers = fieldsToSet.Where(formObject.CurrentRow.IsFieldPresent).ToList();
if (currentRowFieldNumbers.Count > 0)
@@ -644,7 +644,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && formObject.OtherRows.Any(r => r.IsFieldPresent(fieldNumber)));
if (!hasFieldInForm)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
if (formObject.CurrentRow.IsFieldPresent(fieldNumber))
{
@@ -681,7 +681,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && fieldsToSet.Any(f => formObject.OtherRows.Any(r => r.IsFieldPresent(f))));
if (!hasAnyField)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
var currentRowFieldNumbers = fieldsToSet.Where(formObject.CurrentRow.IsFieldPresent).ToList();
if (currentRowFieldNumbers.Count > 0)
@@ -723,7 +723,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && formObject.OtherRows.Any(r => r.IsFieldPresent(fieldNumber)));
if (!hasFieldInForm)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
if (formObject.CurrentRow.IsFieldPresent(fieldNumber))
{
@@ -760,7 +760,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && fieldsToSet.Any(f => formObject.OtherRows.Any(r => r.IsFieldPresent(f))));
if (!hasAnyField)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
var currentRowFieldNumbers = fieldsToSet.Where(formObject.CurrentRow.IsFieldPresent).ToList();
if (currentRowFieldNumbers.Count > 0)
@@ -802,7 +802,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && formObject.OtherRows.Any(r => r.IsFieldPresent(fieldNumber)));
if (!hasFieldInForm)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
if (formObject.CurrentRow.IsFieldPresent(fieldNumber))
{
@@ -839,7 +839,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && fieldsToSet.Any(f => formObject.OtherRows.Any(r => r.IsFieldPresent(f))));
if (!hasAnyField)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
var currentRowFieldNumbers = fieldsToSet.Where(formObject.CurrentRow.IsFieldPresent).ToList();
if (currentRowFieldNumbers.Count > 0)
@@ -881,7 +881,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && formObject.OtherRows.Any(r => r.IsFieldPresent(fieldNumber)));
if (!hasFieldInForm)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
if (formObject.CurrentRow.IsFieldPresent(fieldNumber))
{
@@ -918,7 +918,7 @@ public static bool IsRowPresent(this FormObject formObject, string rowId)
|| (formObject.MultipleIteration && formObject.HasOtherRows() && fieldsToSet.Any(f => formObject.OtherRows.Any(r => r.IsFieldPresent(f))));
if (!hasAnyField)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
var currentRowFieldNumbers = fieldsToSet.Where(formObject.CurrentRow.IsFieldPresent).ToList();
if (currentRowFieldNumbers.Count > 0)
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2015Helpers.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2015Helpers.cs
index 7cef7340..4c5846fd 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2015Helpers.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2015Helpers.cs
@@ -222,7 +222,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -267,7 +267,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -297,7 +297,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -342,7 +342,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -372,7 +372,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -417,7 +417,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -447,7 +447,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -492,7 +492,7 @@ public static OptionObject2015 DeleteRowObject(this OptionObject2015 optionObjec
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2Helpers.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2Helpers.cs
index c7217ea7..4fe44418 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2Helpers.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObject2Helpers.cs
@@ -202,7 +202,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -247,7 +247,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -277,7 +277,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -322,7 +322,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -352,7 +352,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -397,7 +397,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -427,7 +427,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -472,7 +472,7 @@ public static OptionObject2 DeleteRowObject(this OptionObject2 optionObject, str
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObjectHelpers.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObjectHelpers.cs
index 7dd34893..44520c47 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObjectHelpers.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/OptionObjectHelpers.cs
@@ -372,7 +372,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -417,7 +417,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -447,7 +447,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -492,7 +492,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -522,7 +522,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -567,7 +567,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -597,7 +597,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -642,7 +642,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -672,7 +672,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -720,7 +720,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
@@ -750,7 +750,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
return optionObject;
if (!optionObject.Forms.Any(f => f.IsFieldPresent(fieldNumber)))
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumber));
foreach (var form in optionObject.Forms)
{
@@ -798,7 +798,7 @@ public static bool IsFieldRequired(this OptionObject optionObject, string fieldN
.ToList();
if (fieldsToSet.Count == 0)
- throw new ArgumentException(ArgumentGuards.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
+ throw new ArgumentException(StructuralMutationMessages.NoMatchingFieldObjectsMessage, nameof(fieldNumbers));
foreach (var form in optionObject.Forms)
{
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/ScriptLinkParsing.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/ScriptLinkParsing.cs
new file mode 100644
index 00000000..72de4610
--- /dev/null
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/ScriptLinkParsing.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Globalization;
+
+namespace RarelySimple.AvatarScriptLink.Objects.Helpers
+{
+ ///
+ /// Provides safe parsing helpers for converting strings to common types with documented fallback behaviors.
+ ///
+ public static class ScriptLinkParsing
+ {
+ ///
+ /// Safely converts a string to an integer.
+ ///
+ /// The string to convert.
+ /// Returns the converted string as an int. Otherwise, returns 0 if string is not a valid integer.
+ ///
+ /// Behavior:
+ ///
+ /// - Valid integers (including negative) are converted correctly.
+ /// - Decimal strings (e.g., "10.5") return 0.
+ /// - Alphabetic or special character strings return 0.
+ /// - Null or empty strings return 0.
+ ///
+ ///
+ public static int SafeGetInt(string? intString)
+ {
+ if (int.TryParse(intString, out int convertedInt))
+ return convertedInt;
+ return 0;
+ }
+
+ ///
+ /// Safely converts a string to a boolean.
+ ///
+ /// The string to convert.
+ /// Returns true for "1", "T", "TRUE", "Y", "YES" (case-insensitive). Returns false for all other values.
+ ///
+ /// Behavior:
+ ///
+ /// - "1", "T", "TRUE", "Y", "YES" (case-insensitive) convert to true.
+ /// - "0", "F", "FALSE", "N", "NO" (case-insensitive) convert to false.
+ /// - Any other string returns false.
+ /// - Null or empty strings return false.
+ ///
+ ///
+ public static bool SafeGetBool(string? boolString)
+ {
+ if (boolString != null)
+ {
+ switch (boolString.ToUpperInvariant())
+ {
+ case "1":
+ case "T":
+ case "TRUE":
+ case "Y":
+ case "YES":
+ return true;
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Safely converts a string to a DateTime.
+ ///
+ /// The string to convert.
+ /// Returns the converted string as a DateTime. Otherwise, returns new DateTime(0, DateTimeKind.Unspecified) if string is not a valid DateTime.
+ ///
+ /// Behavior:
+ ///
+ /// - Supports common date formats: MM/DD/YYYY, MM-DD-YYYY, October 10, 2010, etc.
+ /// - Uses CurrentCulture for parsing.
+ /// - Invalid date strings return new DateTime(0) (January 1, 0001).
+ /// - Null or empty strings return new DateTime(0).
+ ///
+ ///
+ public static DateTime SafeGetDateTime(string? dateTimeString)
+ {
+ if (DateTime.TryParse(dateTimeString, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime convertedDateTime))
+ return convertedDateTime;
+ return new DateTime(0, DateTimeKind.Unspecified);
+ }
+ }
+}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/ScriptLinkValidation.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/ScriptLinkValidation.cs
new file mode 100644
index 00000000..d7d45d6b
--- /dev/null
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/ScriptLinkValidation.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace RarelySimple.AvatarScriptLink.Objects.Helpers
+{
+ ///
+ /// Provides validation helpers for ScriptLink-specific formats and values.
+ ///
+ public static class ScriptLinkValidation
+ {
+ ///
+ /// Returns whether a string is a valid URL for use with error code 5.
+ ///
+ /// The string to validate as a URL.
+ /// True if the string is a valid absolute URL; otherwise, false.
+ ///
+ /// Behavior:
+ ///
+ /// - Null or empty strings return false.
+ /// - Relative URLs (e.g., "everydaymatters.com") return false.
+ /// - Only absolute URLs (with scheme like http:// or https://) return true.
+ /// - Query strings and fragments are allowed.
+ ///
+ ///
+ public static bool IsValidUrl(string? strUrl)
+ {
+ return !string.IsNullOrWhiteSpace(strUrl) && Uri.IsWellFormedUriString(strUrl, UriKind.Absolute);
+ }
+
+ ///
+ /// Returns whether a Uri is a valid URL for use with error code 5.
+ ///
+ /// The Uri to validate.
+ /// True if the Uri is an absolute URI; otherwise, false.
+ ///
+ /// Behavior:
+ ///
+ /// - Null Uri returns false.
+ /// - Relative URIs return false.
+ /// - Only absolute URIs return true.
+ ///
+ ///
+ public static bool IsValidUrl(Uri? uri)
+ {
+ if (uri == null)
+ return false;
+ return uri.IsAbsoluteUri;
+ }
+
+ ///
+ /// Returns whether a string is a valid OpenForm string for use with error code 6.
+ ///
+ /// The string to validate as an OpenForm string.
+ /// True if the string matches the OpenForm format; otherwise, false.
+ ///
+ /// OpenForm strings follow the pattern: [MODULE]FORM[|message][|patientId][|episodeNumber]
+ /// Valid examples:
+ ///
+ /// - PR0001
+ /// - [PM]PR0001
+ /// - [CWS]RADplus_Imaging001
+ /// - PR0001|Error occurred|12345
+ /// - PR0001|Error occurred|12345|1
+ ///
+ /// Behavior:
+ ///
+ /// - Null or whitespace strings return false.
+ /// - Strings with invalid characters or format return false.
+ ///
+ ///
+ public static bool IsValidOpenFormString(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return false;
+
+ try
+ {
+ return Regex.IsMatch(value, RegexPatterns.FullPattern, RegexOptions.None, TimeSpan.FromMilliseconds(100));
+ }
+ catch (RegexMatchTimeoutException)
+ {
+ return false;
+ }
+ }
+
+ private static class RegexPatterns
+ {
+ private const string ModulePrefix = @"(\[(PM|CWS|MSO)\])?";
+ private const string ModuleForm = @"[A-Z]+[0-9]+";
+ private const string RadplusForm = @"RADplus_[A-Za-z]+[0-9]+";
+ private const string Form = @"(?:" + ModuleForm + "|" + RadplusForm + ")";
+ private const string PrefixedForm = @"(\s*)" + ModulePrefix + Form;
+ private const string FormList = PrefixedForm + @"((\s*)&(\s*)" + ModulePrefix + Form + ")*";
+ private const string Message = @"((\s*)\|(\s*)([^\|\t\n\r])*)?";
+ private const string PatientId = @"((\s*)\|(\s*)\d+)?";
+ private const string EpisodeNumber = @"((\s*)\|(\s*)([1-9][0-9]*)+|(\s*)\|(\s*))?";
+ public const string FullPattern = "^" + FormList + Message + PatientId + EpisodeNumber + "$";
+ }
+ }
+}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/StructuralMutationCore.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/StructuralMutationCore.cs
index 30e48669..e6ea76de 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/StructuralMutationCore.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/StructuralMutationCore.cs
@@ -10,6 +10,9 @@ internal static class StructuralMutationMessages
internal const string FormsCollectionCannotBeNull = "Forms collection cannot be null.";
internal const string NoMatchingFormForFormId = "No matching form was found for the provided formId.";
internal const string NoMatchingRowForRowId = "No matching row was found for the provided rowId.";
+ internal const string NoMatchingFieldObjectsMessage = "No matching field objects were found.";
+ internal const string CannotAddAnotherRowToNonMultipleIterationForm = "Cannot add another row to a non-multiple-iteration form.";
+ internal const string RowObjectAlreadyExists = "A row with the provided RowId already exists.";
}
internal static class StructuralMutationCore
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/Validators/ArgumentGuards.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/Validators/ArgumentGuards.cs
index dd8f043f..5c4d2681 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/Validators/ArgumentGuards.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Helpers/Validators/ArgumentGuards.cs
@@ -7,12 +7,12 @@ namespace RarelySimple.AvatarScriptLink.Objects.Helpers.Validators
///
/// Shared guard helpers for argument validation and normalization used by helper extension methods.
///
- public static class ArgumentGuards
+ internal static class ArgumentGuards
{
- internal const string NoMatchingFieldObjectsMessage = "No matching field objects were found.";
- private const string NoFieldObjectsProvidedMessage = "No field objects were provided.";
- private const string NoFieldNumbersProvidedMessage = "No field numbers were provided.";
- private const string FieldNumberCannotBeEmptyMessage = "Field number cannot be empty.";
+ internal const string NoMatchingFieldObjectsMessage = StructuralMutationMessages.NoMatchingFieldObjectsMessage;
+ internal const string NoFieldObjectsProvidedMessage = "No field objects were provided.";
+ internal const string NoFieldNumbersProvidedMessage = "No field numbers were provided.";
+ internal const string FieldNumberCannotBeEmptyMessage = "Field number cannot be empty.";
///
/// Validates a single field number argument.
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators.Tests/ResponseValidatorTests.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators.Tests/ResponseValidatorTests.cs
index d702b287..7270ac8e 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators.Tests/ResponseValidatorTests.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators.Tests/ResponseValidatorTests.cs
@@ -31,6 +31,34 @@ public void ValidateResponse_ReturnsError_WhenEntityIdIsMissing()
CollectionAssert.Contains(result.Errors.ToList(), "EntityID is required.");
}
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenErrorCodeIsNaN()
+ {
+ // Arrange
+ var optionObject = new OptionObject { EntityID = "PATID", ErrorCode = double.NaN, ErrorMesg = string.Empty };
+
+ // Act
+ ResponseValidationResult result = optionObject.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "ErrorCode must be a finite number between 0 and 6.");
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenErrorCodeIsInfinity()
+ {
+ // Arrange
+ var optionObject = new OptionObject { EntityID = "PATID", ErrorCode = double.PositiveInfinity, ErrorMesg = string.Empty };
+
+ // Act
+ ResponseValidationResult result = optionObject.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "ErrorCode must be a finite number between 0 and 6.");
+ }
+
[TestMethod]
public void ValidateResponse_ReturnsError_WhenErrorCodeIsInvalid()
{
@@ -161,6 +189,34 @@ public void ValidateResponsePayload_ReturnsErrors_ForInvalidForm()
[TestClass]
public class FormObjectResponseValidatorsTests
{
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenFormObjectIsNull()
+ {
+ // Arrange
+ FormObject? form = null;
+
+ // Act
+ ResponseValidationResult result = form.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "FormObject is null.");
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenFormIdIsMissing()
+ {
+ // Arrange
+ var form = new FormObject { FormId = string.Empty };
+
+ // Act
+ ResponseValidationResult result = form.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "FormId is required.");
+ }
+
[TestMethod]
public void ValidateResponse_ReturnsError_WhenMultipleIterationFalseAndOtherRowsPresent()
{
@@ -175,11 +231,65 @@ public void ValidateResponse_ReturnsError_WhenMultipleIterationFalseAndOtherRows
Assert.IsFalse(result.IsValid);
CollectionAssert.Contains(result.Errors.ToList(), "OtherRows must be empty when MultipleIteration is false.");
}
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsSuccess_WhenFormIsValid()
+ {
+ // Arrange
+ var form = new FormObject { FormId = "FORM1", MultipleIteration = false };
+
+ // Act
+ ResponseValidationResult result = form.ValidateResponse();
+
+ // Assert
+ Assert.IsTrue(result.IsValid);
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsInvalid_WhenFormIdIsMissing()
+ {
+ // Arrange
+ var form = new FormObject { FormId = string.Empty };
+
+ // Act
+ bool result = form.ValidateResponse().IsValid;
+
+ // Assert
+ Assert.IsFalse(result);
+ }
}
[TestClass]
public class RowObjectResponseValidatorsTests
{
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenRowObjectIsNull()
+ {
+ // Arrange
+ RowObject? row = null;
+
+ // Act
+ ResponseValidationResult result = row.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "RowObject is null.");
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenRowIdIsMissing()
+ {
+ // Arrange
+ var row = new RowObject { RowId = string.Empty };
+
+ // Act
+ ResponseValidationResult result = row.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "RowId is required.");
+ }
+
[TestMethod]
public void ValidateResponse_ReturnsError_WhenRowActionMissingWithFields()
{
@@ -208,11 +318,51 @@ public void ValidateResponse_ReturnsError_WhenRowActionInvalid()
Assert.IsFalse(result.IsValid);
CollectionAssert.Contains(result.Errors.ToList(), "RowAction must be ADD, EDIT, DELETE, or empty.");
}
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsSuccess_WhenRowIsValid()
+ {
+ // Arrange
+ var row = new RowObject { RowId = "1", RowAction = RowObject.RowActions.Edit };
+
+ // Act
+ ResponseValidationResult result = row.ValidateResponse();
+
+ // Assert
+ Assert.IsTrue(result.IsValid);
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsInvalid_WhenRowIdIsMissing()
+ {
+ // Arrange
+ var row = new RowObject { RowId = string.Empty };
+
+ // Act
+ bool result = row.ValidateResponse().IsValid;
+
+ // Assert
+ Assert.IsFalse(result);
+ }
}
[TestClass]
public class FieldObjectResponseValidatorsTests
{
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenFieldObjectIsNull()
+ {
+ // Arrange
+ FieldObject? field = null;
+
+ // Act
+ ResponseValidationResult result = field.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "FieldObject is null.");
+ }
+
[TestMethod]
public void ValidateResponse_ReturnsError_WhenFieldNumberMissing()
{
@@ -240,5 +390,59 @@ public void ValidateResponse_ReturnsError_WhenFlagIsInvalid()
Assert.IsFalse(result.IsValid);
CollectionAssert.Contains(result.Errors.ToList(), "Enabled must be \"0\" or \"1\" when set.");
}
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenLockFlagIsInvalid()
+ {
+ // Arrange
+ var field = new FieldObject { FieldNumber = "100", Lock = "X" };
+
+ // Act
+ ResponseValidationResult result = field.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "Lock must be \"0\" or \"1\" when set.");
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsError_WhenRequiredFlagIsInvalid()
+ {
+ // Arrange
+ var field = new FieldObject { FieldNumber = "100", Required = "X" };
+
+ // Act
+ ResponseValidationResult result = field.ValidateResponse();
+
+ // Assert
+ Assert.IsFalse(result.IsValid);
+ CollectionAssert.Contains(result.Errors.ToList(), "Required must be \"0\" or \"1\" when set.");
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsSuccess_WhenFieldIsValid()
+ {
+ // Arrange
+ var field = new FieldObject { FieldNumber = "100", Enabled = "1", Lock = "0", Required = "0" };
+
+ // Act
+ ResponseValidationResult result = field.ValidateResponse();
+
+ // Assert
+ Assert.IsTrue(result.IsValid);
+ }
+
+ [TestMethod]
+ public void ValidateResponse_ReturnsInvalid_WhenFieldNumberMissing()
+ {
+ // Arrange
+ var field = new FieldObject { FieldNumber = string.Empty };
+
+ // Act
+ bool result = field.ValidateResponse().IsValid;
+
+ // Assert
+ Assert.IsFalse(result);
+ }
}
}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FieldObjectResponseValidators.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FieldObjectResponseValidators.cs
index 94035bfc..fbb41502 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FieldObjectResponseValidators.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FieldObjectResponseValidators.cs
@@ -18,13 +18,13 @@ public static ResponseValidationResult ValidateResponse(this FieldObject? fieldO
if (fieldObject == null)
{
- errors.Add("FieldObject is null.");
+ errors.Add(ResponseValidationMessages.FieldObjectIsNull);
return new ResponseValidationResult(errors);
}
if (string.IsNullOrWhiteSpace(fieldObject.FieldNumber))
{
- errors.Add("FieldNumber is required.");
+ errors.Add(ResponseValidationMessages.FieldNumberIsRequired);
}
ValidateFlag(fieldObject.Enabled, "Enabled", errors);
@@ -38,7 +38,7 @@ private static void ValidateFlag(string value, string name, List errors)
{
if (!string.IsNullOrEmpty(value) && value != "0" && value != "1")
{
- errors.Add($"{name} must be \"0\" or \"1\" when set.");
+ errors.Add(string.Format(ResponseValidationMessages.FieldFlagMustBeZeroOrOne, name));
}
}
}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FormObjectResponseValidators.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FormObjectResponseValidators.cs
index 9a42ff8e..ccd1be20 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FormObjectResponseValidators.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/FormObjectResponseValidators.cs
@@ -18,24 +18,24 @@ public static ResponseValidationResult ValidateResponse(this FormObject? formObj
if (formObject == null)
{
- errors.Add("FormObject is null.");
+ errors.Add(ResponseValidationMessages.FormObjectIsNull);
return new ResponseValidationResult(errors);
}
if (string.IsNullOrWhiteSpace(formObject.FormId))
{
- errors.Add("FormId is required.");
+ errors.Add(ResponseValidationMessages.FormIdIsRequired);
}
if (formObject.OtherRows == null)
{
- errors.Add("OtherRows collection must not be null.");
+ errors.Add(ResponseValidationMessages.OtherRowsCollectionMustNotBeNull);
return new ResponseValidationResult(errors);
}
if (!formObject.MultipleIteration && formObject.OtherRows.Count > 0)
{
- errors.Add("OtherRows must be empty when MultipleIteration is false.");
+ errors.Add(ResponseValidationMessages.OtherRowsMustBeEmptyWhenMultipleIterationIsFalse);
}
if (formObject.CurrentRow != null)
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/OptionObjectResponseValidators.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/OptionObjectResponseValidators.cs
index a10766a3..ea6be2b6 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/OptionObjectResponseValidators.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/OptionObjectResponseValidators.cs
@@ -21,13 +21,13 @@ public static ResponseValidationResult ValidateResponse(this IOptionObject? opti
if (optionObject == null)
{
- errors.Add("OptionObject is null.");
+ errors.Add(ResponseValidationMessages.OptionObjectIsNull);
return new ResponseValidationResult(errors);
}
if (string.IsNullOrWhiteSpace(optionObject.EntityID))
{
- errors.Add("EntityID is required.");
+ errors.Add(ResponseValidationMessages.EntityIdIsRequired);
}
ValidateErrorCode(optionObject.ErrorCode, errors);
@@ -101,20 +101,20 @@ private static void ValidateErrorCode(double errorCode, List errors)
{
if (double.IsNaN(errorCode) || double.IsInfinity(errorCode))
{
- errors.Add("ErrorCode must be a finite number between 0 and 6.");
+ errors.Add(ResponseValidationMessages.ErrorCodeMustBeFiniteBetween0And6);
return;
}
if (errorCode < 0 || errorCode > 6)
{
- errors.Add("ErrorCode must be between 0 and 6.");
+ errors.Add(ResponseValidationMessages.ErrorCodeMustBeBetween0And6);
return;
}
double rounded = Math.Round(errorCode, 0, MidpointRounding.AwayFromZero);
if (Math.Abs(errorCode - rounded) > 1e-9)
{
- errors.Add("ErrorCode must be an integer between 0 and 6.");
+ errors.Add(ResponseValidationMessages.ErrorCodeMustBeIntegerBetween0And6);
}
}
@@ -128,25 +128,25 @@ private static void ValidateErrorMessage(double errorCode, string errorMessage,
if (normalizedCode == 0 && !string.IsNullOrEmpty(errorMessage))
{
- errors.Add("ErrorMesg must be empty when ErrorCode is 0.");
+ errors.Add(ResponseValidationMessages.ErrorMesgMustBeEmptyWhenErrorCodeIs0);
return;
}
if (normalizedCode >= 1 && normalizedCode <= 4 && string.IsNullOrWhiteSpace(errorMessage))
{
- errors.Add("ErrorMesg is required when ErrorCode is between 1 and 4.");
+ errors.Add(ResponseValidationMessages.ErrorMesgIsRequiredWhenErrorCodeIsBetween1And4);
return;
}
if (normalizedCode == 5 && !IsValidUrl(errorMessage))
{
- errors.Add("ErrorMesg must be a valid absolute URL when ErrorCode is 5.");
+ errors.Add(ResponseValidationMessages.ErrorMesgMustBeValidAbsoluteUrlWhenErrorCodeIs5);
return;
}
if (normalizedCode == 6 && !IsValidOpenFormString(errorMessage))
{
- errors.Add("ErrorMesg must be a valid OpenForm string when ErrorCode is 6.");
+ errors.Add(ResponseValidationMessages.ErrorMesgMustBeValidOpenFormStringWhenErrorCodeIs6);
}
}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/ResponseValidationMessages.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/ResponseValidationMessages.cs
new file mode 100644
index 00000000..3c410937
--- /dev/null
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/ResponseValidationMessages.cs
@@ -0,0 +1,30 @@
+namespace RarelySimple.AvatarScriptLink.Objects.Validators
+{
+ internal static class ResponseValidationMessages
+ {
+ internal const string FieldObjectIsNull = "FieldObject is null.";
+ internal const string FieldNumberIsRequired = "FieldNumber is required.";
+ internal const string FieldFlagMustBeZeroOrOne = "{0} must be \"0\" or \"1\" when set.";
+
+ internal const string FormObjectIsNull = "FormObject is null.";
+ internal const string FormIdIsRequired = "FormId is required.";
+ internal const string OtherRowsCollectionMustNotBeNull = "OtherRows collection must not be null.";
+ internal const string OtherRowsMustBeEmptyWhenMultipleIterationIsFalse = "OtherRows must be empty when MultipleIteration is false.";
+
+ internal const string RowObjectIsNull = "RowObject is null.";
+ internal const string RowIdIsRequired = "RowId is required.";
+ internal const string RowActionMustBeAddEditDeleteOrEmpty = "RowAction must be ADD, EDIT, DELETE, or empty.";
+ internal const string FieldsCollectionMustNotBeNull = "Fields collection must not be null.";
+ internal const string RowActionIsRequiredWhenRowContainsFields = "RowAction is required when row contains fields.";
+
+ internal const string OptionObjectIsNull = "OptionObject is null.";
+ internal const string EntityIdIsRequired = "EntityID is required.";
+ internal const string ErrorCodeMustBeFiniteBetween0And6 = "ErrorCode must be a finite number between 0 and 6.";
+ internal const string ErrorCodeMustBeBetween0And6 = "ErrorCode must be between 0 and 6.";
+ internal const string ErrorCodeMustBeIntegerBetween0And6 = "ErrorCode must be an integer between 0 and 6.";
+ internal const string ErrorMesgMustBeEmptyWhenErrorCodeIs0 = "ErrorMesg must be empty when ErrorCode is 0.";
+ internal const string ErrorMesgIsRequiredWhenErrorCodeIsBetween1And4 = "ErrorMesg is required when ErrorCode is between 1 and 4.";
+ internal const string ErrorMesgMustBeValidAbsoluteUrlWhenErrorCodeIs5 = "ErrorMesg must be a valid absolute URL when ErrorCode is 5.";
+ internal const string ErrorMesgMustBeValidOpenFormStringWhenErrorCodeIs6 = "ErrorMesg must be a valid OpenForm string when ErrorCode is 6.";
+ }
+}
diff --git a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/RowObjectResponseValidators.cs b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/RowObjectResponseValidators.cs
index 7608b77b..c7817970 100644
--- a/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/RowObjectResponseValidators.cs
+++ b/dotnet/RarelySimple.AvatarScriptLink.Objects.Validators/RowObjectResponseValidators.cs
@@ -18,29 +18,29 @@ public static ResponseValidationResult ValidateResponse(this RowObject? rowObjec
if (rowObject == null)
{
- errors.Add("RowObject is null.");
+ errors.Add(ResponseValidationMessages.RowObjectIsNull);
return new ResponseValidationResult(errors);
}
if (string.IsNullOrWhiteSpace(rowObject.RowId))
{
- errors.Add("RowId is required.");
+ errors.Add(ResponseValidationMessages.RowIdIsRequired);
}
if (!IsValidRowAction(rowObject.RowAction))
{
- errors.Add("RowAction must be ADD, EDIT, DELETE, or empty.");
+ errors.Add(ResponseValidationMessages.RowActionMustBeAddEditDeleteOrEmpty);
}
if (rowObject.Fields == null)
{
- errors.Add("Fields collection must not be null.");
+ errors.Add(ResponseValidationMessages.FieldsCollectionMustNotBeNull);
return new ResponseValidationResult(errors);
}
if (rowObject.Fields.Count > 0 && string.IsNullOrEmpty(rowObject.RowAction))
{
- errors.Add("RowAction is required when row contains fields.");
+ errors.Add(ResponseValidationMessages.RowActionIsRequiredWhenRowContainsFields);
}
for (int i = 0; i < rowObject.Fields.Count; i++)