Categories
Uncategorized

Reading Data from a myAvatar Form Using ScriptLink

While a “Hello, World!” example may be good for demonstrating how a solution is built, it typically isn’t very useful. This certainly describes our ScriptLink project so far. We can provide the myAvatar user a popup that reads “Hello, World!” and that is about it. For the next few articles, we are going to look at working with the form data from myAvatar. Today, we are going to read a value from a myAvatar form.

While a “Hello, World!” example may be good for demonstrating how a solution is built, it typically isn’t very useful. This certainly describes our ScriptLink project so far. We can provide the myAvatar user a popup that reads “Hello, World!” and that is about it. For the next few articles, we are going to look at working with the form data from myAvatar. Today, we are going to read a value from a myAvatar form.

When your ScriptLink API is called, myAvatar sends an OptionObject payload contained all of the contents of the Form being used. Correction, most of the contents of the Form. There are some fields that are not provided (e.g., some parts of the Diagnosis form), nor are any field labels provided. In order to read data from the myAvatar form we will need to know the Field numbers of each field to read and potentially the meaning of various dictionary values, if you want to read a single-select or multi-select FieldObject.

You will spend a bit of time in Form and Table Documentation while designing your Commands to identify any FieldNumbers that you may need or other IDs, such as OptionID or FormID. Additionally, you will want to use Dictionary Update to view the possible Dictionary Codes used by the fields on the Form. Please note: Form and Table Documentation can show you possible Dictionary Values, while ScriptLink passes the Dictionary Code. This is why you will want to confirm the meaning of the possible Dictionary Codes received.

The Specification

For this example, we will check to see if the Form is marked as Draft or Final and prompt the user about the status. I will use an imaginary modeled form for this example, however you are welcome to replace the IDs with real ones from your environment. Here’s what we will use:

  • Command Name: IsDraftCommand
  • Draft/Final FieldNumber: 123.45
  • Draft FieldValue: D
  • Final FieldValue: F

The Unit Tests

Test-Driven Design recommends writing your tests first and then writing the code. We will follow that principle here. Here are the tests that we will write:

  • The Command returns an appropriate OptionObject version.
  • The Command returns ErrorCode 3 when the FieldValue is D or F.
  • The Command returns ErrorCode 0 when the FieldValue is not D or F.
  • The Command returns ErrorCode 0 when the FieldObject is not present.
  • The Command returns a message when the FieldValue is D.
  • The Command returns a message when the FieldValue is F.
  • The Command returns no message when the FieldValue is not D or F.
  • The Command returns no message when the FieldObject is not present.

Any other tests you can think of that we should write? I have opted not to test the specific message returned for this example, but that could be valuable to confirm that the message you return is the one expected.

In our Unit Test project, let’s add a Unit Test file named IsDraftCommandTests.cs and add the following tests.

[TestMethod]
public void Execute_IsDraftCommand_ReturnsOptionObject()
{
    // Arrange
    OptionObject expected = new OptionObject();

    OptionObject optionObject = new OptionObject();
    var command = new IsDraftCommand(optionObject);

    // Act
    var actual = (OptionObject) command.Execute();

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

[TestMethod]
public void Execute_IsDraftCommand_ReturnsOptionObject2()
{
    // Arrange
    OptionObject2 expected = new OptionObject2();

    OptionObject2 optionObject2 = new OptionObject2();
    var command = new IsDraftCommand(optionObject2);

    // Act
    var actual = (OptionObject2) command.Execute();

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

[TestMethod]
public void Execute_IsDraftCommand_ReturnsOptionObject2015()
{
    // Arrange
    OptionObject2015 expected = new OptionObject2015();

    OptionObject2015 optionObject2015 = new OptionObject2015();
    var command = new IsDraftCommand(optionObject2015);

    // Act
    var actual = command.Execute();

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

[TestMethod]
public void Execute_IsDraftCommand_D_ReturnsErrorCode3()
{
    // Arrange
    Double expected = 3;
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "123.45",
        FieldValue = "D",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject>() { fieldObject },
        RowId = "1||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "1",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    { 
        Forms = new List<FormObject>() { formObject },
        OptionId = "USER999"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.AreEqual(expected, actual.ErrorCode);
}

[TestMethod]
public void Execute_IsDraftCommand_F_ReturnsErrorCode3()
{
    // Arrange
    Double expected = 3;
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "123.45",
        FieldValue = "F",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "1||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "1",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER999"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.AreEqual(expected, actual.ErrorCode);
}

[TestMethod]
public void Execute_IsDraftCommand_NA_ReturnsErrorCode0()
{
    // Arrange
    Double expected = 0;
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "123.45",
        FieldValue = "NA",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "1||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "1",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER999"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.AreEqual(expected, actual.ErrorCode);
}

[TestMethod]
public void Execute_IsDraftCommand_Missing_ReturnsErrorCode0()
{
    // Arrange
    Double expected = 0;
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "987.65",
        FieldValue = "Some other field value.",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "777||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "777",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER998"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.AreEqual(expected, actual.ErrorCode);
}

[TestMethod]
public void Execute_IsDraftCommand_D_ReturnsErrorMesg()
{
    // Arrange
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "123.45",
        FieldValue = "D",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "1||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "1",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER999"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.IsNotNull(actual.ErrorMesg);
}

[TestMethod]
public void Execute_IsDraftCommand_F_ReturnsErrorMesg()
{
    // Arrange
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "123.45",
        FieldValue = "F",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "1||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "1",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER999"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.IsNotNull(actual.ErrorMesg);
}

[TestMethod]
public void Execute_IsDraftCommand_NA_ReturnsEmptyErrorMesg()
{
    // Arrange
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "123.45",
        FieldValue = "NA",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "1||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "1",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER999"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.IsNull(actual.ErrorMesg);
}

[TestMethod]
public void Execute_IsDraftCommand_Missing_ReturnsEmptyErrorMesg()
{
    // Arrange
    FieldObject fieldObject = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "987.65",
        FieldValue = "Some other field value.",
        Lock = "0",
        Required = "1"
    };
    RowObject rowObject = new RowObject()
    {
        Fields = new List<FieldObject> { fieldObject },
        RowId = "777||1"
    };
    FormObject formObject = new FormObject()
    {
        CurrentRow = rowObject,
        FormId = "777",
        MultipleIteration = false
    };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject> { formObject },
        OptionId = "USER998"
    };
    var command = new IsDraftCommand(optionObject2015);

    // Act
    IOptionObject2015 actual = command.Execute();

    // Assert
    Assert.IsNull(actual.ErrorMesg);
}

In a previous article we added support for multiple OptionObject versions. I have accounted for those scenarios with the first two tests. If you did not add that capability to you project, then you can remove the first two tests and configure the remaining test to use the OptionObject version you chose to implement.

Ok. We have our tests, but they of course will fail because of the compile error caused by the missing IsDraftCommand. Let’s create it.

The Command

Test-Driven Design recommends testing to failure before testing to success. This helps to protect us from creating a test that always returns the expected result regardless of the code it is intended to test. For example, we don’t want tests like this.

[TestMethod]
public void MethodToTest_Context_IsTrue()
{
    Assert.IsTrue(true);
}

Of course, tests like this are to be avoided in Test-Eventual Design as well.

It also protects from false positives and negatives due to mistakes in the test itself. To keep this article a little more concise, I will only show one failure test here. So lets create our command see the tests fail.

In our Commands folder let’s add a new Class names IsDraftCommand.cs and configure it. We will have it throw a new NotImplementedException to confirm we can compile and the expected tests fail.

namespace RS.ScriptLinkDemo.Soap.Commands
{
    public class IsDraftCommand : IRunScriptCommand
    {
        private static readonly Logger logger = LogManager.GetCurrentClassLogger();

        private readonly OptionObjectBase _optionObject;

        public IsDraftCommand(OptionObjectBase optionObject)
        {
            _optionObject = optionObject;
        }

        public IOptionObject2015 Execute()
        {
            logger.Debug("IsDraftCommand executed.");
            throw new NotImplementedException();
        }
    }
}

We have inherited the IRunScriptCommand, implemented logging, the constructor, and required Execute method. However, instead of writing our logic yet we’re just throwing the NotImplementedException. Let’s confirm our solution compiles and the tests fail as expected. Go ahead and run all Unit Tests.

Note: You may need to go back to the Unit Test and add the using for the IsDraftCommand namespace.

Did the solution compile? Did the new tests fail as expected? If so, let’s continue.

Reading FieldValues

The common method to read FieldValues is to loop through the OptionObject and check for the presence of the FieldObject and get its value. Here’s what that could look like.

public IOptionObject2015 Execute()
{
    logger.Debug("IsDraftCommand executed.");

    string returnMessage = null;
    if (_optionObject.Forms != null)
    {
        foreach (var form in _optionObject.Forms)
        {
            if (form.CurrentRow != null && form.CurrentRow.Fields != null)
            {
                foreach (var field in form.CurrentRow.Fields)
                {
                    switch (field.FieldNumber)
                    {
                        case "123.45":
                            if (field.FieldValue == "D")
                                returnMessage = "This form is in Draft.";
                            else if (field.FieldValue == "F")
                                returnMessage = "This form is Final.";
                            break;
                    }
                }
            }
        }
    }

    if (!string.IsNullOrEmpty(returnMessage))
        return _optionObject.ToReturnOptionObject(3, returnMessage);
    return _optionObject.ToReturnOptionObject();
}

In our code above you can see the loops, the field check, and a few null checks. The null checks are in place to allow this Command properly respond even when bad data is provided. Additionally, you also see use of both overrides of the ToReturnOptionObject method we created earlier.

Ok. Let’s run our Unit Tests and see if everything passes. If everything is passing, then we are ready to add to the Factory.

Add Command to CommandSelector

Let’s edit our CommandSelector and its Unit Tests. Here is the new Unit Test to add to our CommandSelectorTests.cs file.

[TestMethod]
public void GetCommand_IsDraft_ReturnsIsDraftCommand()
{
    // Arrange
    OptionObject2015 optionObject2015 = new OptionObject2015();
    string parameter = "IsDraft";
    IsDraftCommand expected = new IsDraftCommand(optionObject2015);

    // Act
    IRunScriptCommand actual = CommandSelector.GetCommand(optionObject2015, parameter);

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}   

And the code to add to the CommandSelector.

public static IRunScriptCommand GetCommand(IOptionObject2015 iOptionObject, string parameter)
{
    OptionObjectBase optionObjectBase = (OptionObjectBase)iOptionObject;

    switch (parameter)
    {
        case "HelloWorld":
            logger.Debug("Creating a HelloWorld command.");
            return new HelloWorld(optionObjectBase);
        case "IsDraft":
            logger.Debug("Creating an IsDraftCommand.");
            return new IsDraftCommand(optionObjectBase);
        default:
            logger.Warn("No command found matching parameter '" + (parameter ?? "") + "'. Creating a DefaultCommand.");
            return new DefaultCommand(optionObjectBase, parameter);
    }
}

Let’s run our Unit Tests again. If everything is passing, then let’s test in SoapUI.

Creating SoapUI Test

Let’s open up SoapUI and select our Test project.

  • Expand the test project, one of the Soap interfaces, and the RunScript service.
  • Right-click on the HelloWorld Request we previously created and select Clone Request. Alternatively, press F9.
  • Set the name to IsDraftCommand Request and select OK.
  • Lastly, set/replace the following values:
    • Under CurrentRow,
      • Set the FieldNumber to 123.45.
      • Set the FieldValue to D.
    • Set Parameter to IsDraft.

Submit the request. Did you get the expected ErrorCode and ErrorMesg?

Change the FieldValue to F. Does the ErrorMesg change?

Set the FieldValue to something else. Is the ErrorCode 0 and the ErrorMesg empty?

Now change the FieldNumber to something else. Does the response also succeed with the ErrorCode 0 and the ErrorMesg empty?

Closing Thoughts

Congratulations! You are now able to read information from a standard form. Next week, we will read values from a multiple iteration table which is a little more involved, but follows the same principles. In the future, I will demonstrate how this code can be simplified using AvatarScriptLink.NET.

One reply on “Reading Data from a myAvatar Form Using ScriptLink”

Comments are closed.