Files
suitelinkclient/docs/plans/2026-03-16-suitelink-client-implementation-plan.md
2026-03-16 14:43:31 -04:00

28 KiB

SuiteLink Tag Client Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Build a cross-platform .NET 10 C# SuiteLink V2 client for tag-only operations: connect, subscribe, receive updates, write values, and unsubscribe for bool, int32, float32, and string.

Architecture: The implementation is split into transport, protocol codec, session state, and public client API layers. The first version targets normal non-encrypted SuiteLink tag traffic only and validates behavior with unit tests, golden packet tests, and optional live integration tests against an AVEVA/OI server in mixed or legacy mode.

Tech Stack: .NET 10, C#, xUnit, TcpClient/NetworkStream, System.Buffers, System.Buffers.Binary


Task 1: Create Solution Skeleton

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLink.Client.csproj
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Class1.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLink.Client.Tests.csproj
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/UnitTest1.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln

Step 1: Write the failing structure check

Create a test project with one placeholder test:

using Xunit;

namespace SuiteLink.Client.Tests;

public sealed class UnitTest1
{
    [Fact]
    public void Placeholder()
    {
        Assert.True(true);
    }
}

Step 2: Run test to verify the solution builds

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln Expected: PASS with one test executed

Step 3: Write minimal implementation

Create the library and solution structure only. Leave the default library type empty or minimal.

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln /Users/dohertj2/Desktop/suitelinkclient/src /Users/dohertj2/Desktop/suitelinkclient/tests
git commit -m "chore: scaffold suitelink client solution"

Task 2: Define Public Value And Option Models

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkConnectionOptions.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkValue.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkTagUpdate.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SubscriptionHandle.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkValueTests.cs

Step 1: Write the failing test

using Xunit;

namespace SuiteLink.Client.Tests;

public sealed class SuiteLinkValueTests
{
    [Fact]
    public void BoolFactory_CreatesBoolValue()
    {
        var value = SuiteLinkValue.FromBoolean(true);

        Assert.True(value.TryGetBoolean(out var result));
        Assert.True(result);
    }
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter BoolFactory_CreatesBoolValue Expected: FAIL with missing type or method errors

Step 3: Write minimal implementation

Implement:

  • immutable SuiteLinkConnectionOptions
  • SuiteLinkValue discriminated wrapper for bool, int, float, string
  • SuiteLinkTagUpdate
  • SubscriptionHandle placeholder with async disposal hook

Minimal SuiteLinkValue pattern:

public enum SuiteLinkValueKind
{
    Boolean,
    Int32,
    Float32,
    String
}

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkValueTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkValueTests.cs
git commit -m "feat: add public suitelink value models"

Task 3: Add Frame Reader And Writer

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkFrame.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkFrameWriter.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkFrameReader.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkFrameWriterTests.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkFrameReaderTests.cs

Step 1: Write the failing test

using Xunit;

namespace SuiteLink.Client.Tests.Protocol;

public sealed class SuiteLinkFrameWriterTests
{
    [Fact]
    public void WriteFrame_AppendsLengthTypeAndMarker()
    {
        var bytes = SuiteLinkFrameWriter.WriteFrame(0x2440, []);

        Assert.Equal(5, bytes.Length);
        Assert.Equal(0x03, bytes[0]);
        Assert.Equal(0x00, bytes[1]);
        Assert.Equal(0x40, bytes[2]);
        Assert.Equal(0x24, bytes[3]);
        Assert.Equal(0xA5, bytes[4]);
    }
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter WriteFrame_AppendsLengthTypeAndMarker Expected: FAIL with missing frame writer

Step 3: Write minimal implementation

Implement:

  • SuiteLinkFrame record holding message type and payload span or byte array
  • frame writer for normal SuiteLink messages
  • frame reader that:
    • reads two-byte remaining length
    • reads remaining bytes plus trailing marker
    • validates final 0xA5

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkFrame Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol
git commit -m "feat: add suitelink frame reader and writer"

Task 4: Add UTF-16LE And Primitive Wire Encoding Helpers

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkEncoding.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkEncodingTests.cs

Step 1: Write the failing test

using Xunit;

namespace SuiteLink.Client.Tests.Protocol;

public sealed class SuiteLinkEncodingTests
{
    [Fact]
    public void EncodeLengthPrefixedUtf16_WritesCharacterCountThenUtf16Bytes()
    {
        var bytes = SuiteLinkEncoding.EncodeLengthPrefixedUtf16("AB");

        Assert.Equal(1 + 4, bytes.Length);
        Assert.Equal(2, bytes[0]);
        Assert.Equal((byte)'A', bytes[1]);
    }
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeLengthPrefixedUtf16_WritesCharacterCountThenUtf16Bytes Expected: FAIL

Step 3: Write minimal implementation

Implement helper methods for:

  • one-byte-length-prefixed UTF-16LE strings
  • null-terminated UTF-16LE strings
  • little-endian primitive reads/writes
  • FILETIME conversion helper if needed for future time messages

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkEncodingTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkEncoding.cs /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkEncodingTests.cs
git commit -m "feat: add suitelink encoding helpers"

Task 5: Encode Handshake And Connect Messages

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkHandshakeCodec.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkConnectCodec.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkHandshakeCodecTests.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkConnectCodecTests.cs

Step 1: Write the failing test

Use known bytes captured from the reverse-engineered protocol and assert:

[Fact]
public void EncodeConnect_WritesConnectMessageType()
{
    var options = new SuiteLinkConnectionOptions(
        host: "127.0.0.1",
        application: "App",
        topic: "Topic",
        clientName: "Client",
        clientNode: "Node",
        userName: "User",
        serverNode: "Server");

    var bytes = SuiteLinkConnectCodec.Encode(options);

    Assert.Equal(0x80, bytes[2]);
    Assert.Equal(0x01, bytes[3]);
    Assert.Equal(0xA5, bytes[^1]);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeConnect_WritesConnectMessageType Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • normal SuiteLink handshake encoder
  • handshake acknowledgement parser
  • connect encoder using the observed field order
  • isolate unknown fixed bytes in constants with comments pointing to capture evidence

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter CodecTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol
git commit -m "feat: encode suitelink handshake and connect"

Task 6: Encode Advise And Unadvise Messages

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkSubscriptionCodec.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkSubscriptionCodecTests.cs

Step 1: Write the failing test

[Fact]
public void EncodeAdvise_WritesTagNameInUtf16()
{
    var bytes = SuiteLinkSubscriptionCodec.EncodeAdvise("Pump001.Run");

    Assert.Equal(0x80, bytes[2]);
    Assert.Equal(0x10, bytes[3]);
    Assert.Equal(0xA5, bytes[^1]);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeAdvise_WritesTagNameInUtf16 Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • EncodeAdvise(string itemName)
  • EncodeUnadvise(uint tagId)
  • DecodeAdviseAck(ReadOnlySpan<byte>)

Add a small result model for advise acknowledgements that captures tagId.

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkSubscriptionCodecTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkSubscriptionCodecTests.cs
git commit -m "feat: add advise and unadvise codec support"

Task 7: Decode Update Messages For Primitive Types

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkUpdateCodec.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkWireValueType.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkUpdateCodecTests.cs

Step 1: Write the failing test

[Fact]
public void DecodeUpdate_DecodesIntegerValue()
{
    var frame = new byte[]
    {
        0x0D, 0x00, 0x00, 0x09,
        0x34, 0x12, 0x00, 0x00,
        0x01, 0x00,
        0xC0, 0x00,
        0x02,
        0x2A, 0x00, 0x00, 0x00,
        0xA5
    };

    var update = SuiteLinkUpdateCodec.Decode(frame);

    Assert.Equal(0x1234u, update.TagId);
    Assert.True(update.Value.TryGetInt32(out var value));
    Assert.Equal(42, value);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter DecodeUpdate_DecodesIntegerValue Expected: FAIL

Step 3: Write minimal implementation

Implement decoding for:

  • binary to bool
  • integer to int
  • real to float
  • message to string

Return a parsed update model containing:

  • TagId
  • Quality
  • ElapsedMilliseconds
  • SuiteLinkValue

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkUpdateCodecTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkUpdateCodecTests.cs
git commit -m "feat: decode primitive suitelink update values"

Task 8: Encode Poke Messages For Primitive Writes

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkWriteCodec.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkWriteCodecTests.cs

Step 1: Write the failing test

[Fact]
public void EncodeWrite_Int32Value_WritesPokeMessage()
{
    var bytes = SuiteLinkWriteCodec.Encode(0x1234, SuiteLinkValue.FromInt32(42));

    Assert.Equal(0x08, bytes[2]);
    Assert.Equal(0x0B, bytes[3]);
    Assert.Equal(0xA5, bytes[^1]);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeWrite_Int32Value_WritesPokeMessage Expected: FAIL

Step 3: Write minimal implementation

Implement primitive write encoding for:

  • bool
  • int32
  • float32
  • string if confirmed by packet format used for wire message values

Reject unsupported value kinds with a clear exception.

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkWriteCodecTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkWriteCodecTests.cs
git commit -m "feat: encode primitive suitelink writes"

Task 9: Implement Session State And Tag Mapping

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSessionState.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSession.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Internal/SuiteLinkSessionTests.cs

Step 1: Write the failing test

[Fact]
public void RegisterSubscription_MapsItemNameToTagId()
{
    var session = new SuiteLinkSession();

    session.RegisterSubscription("Pump001.Run", 0x1234);

    Assert.Equal(0x1234u, session.GetTagId("Pump001.Run"));
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter RegisterSubscription_MapsItemNameToTagId Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • session state enum
  • tag map for itemName -> tagId
  • reverse lookup for tagId -> itemName
  • subscription callback registration
  • update dispatch helper

Keep transport mocking simple. Do not implement full socket I/O in this task.

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkSessionTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Internal/SuiteLinkSessionTests.cs
git commit -m "feat: add session state and tag mapping"

Task 10: Implement Tcp Transport Loop

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Transport/SuiteLinkTcpTransport.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Transport/ISuiteLinkTransport.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Transport/SuiteLinkTcpTransportTests.cs

Step 1: Write the failing test

[Fact]
public async Task SendAsync_WritesFrameToUnderlyingStream()
{
    var stream = new MemoryStream();
    var transport = new SuiteLinkTcpTransport(stream);

    await transport.SendAsync(new byte[] { 0x01, 0x02 }, CancellationToken.None);

    Assert.Equal(new byte[] { 0x01, 0x02 }, stream.ToArray());
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SendAsync_WritesFrameToUnderlyingStream Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • transport abstraction
  • stream-backed send and receive methods
  • real TcpClient constructor path
  • test-friendly stream injection path

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkTcpTransportTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Transport /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Transport/SuiteLinkTcpTransportTests.cs
git commit -m "feat: add suitelink transport abstraction"

Task 11: Implement Public Client Connect And Disconnect

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs
  • Modify: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SubscriptionHandle.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientConnectionTests.cs

Step 1: Write the failing test

[Fact]
public async Task ConnectAsync_TransitionsClientToConnectedState()
{
    var client = new SuiteLinkClient(new FakeTransport());

    await client.ConnectAsync(TestOptions.Create());

    Assert.True(client.IsConnected);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter ConnectAsync_TransitionsClientToConnectedState Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • SuiteLinkClient
  • connect flow invoking handshake then connect codec
  • disconnect path
  • basic disposal

Expose a minimal IsConnected property or equivalent state for tests.

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkClientConnectionTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SubscriptionHandle.cs /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientConnectionTests.cs
git commit -m "feat: implement suitelink client connection flow"

Task 12: Implement Subscribe And Read Flow

Files:

  • Modify: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs
  • Modify: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSession.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientSubscriptionTests.cs

Step 1: Write the failing test

[Fact]
public async Task ReadAsync_ReturnsFirstUpdateForRequestedTag()
{
    var client = TestClientFactory.CreateConnectedClientWithUpdate("Pump001.Run", SuiteLinkValue.FromBoolean(true));

    var update = await client.ReadAsync("Pump001.Run", TimeSpan.FromSeconds(1));

    Assert.True(update.Value.TryGetBoolean(out var value));
    Assert.True(value);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter ReadAsync_ReturnsFirstUpdateForRequestedTag Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • SubscribeAsync
  • ReadAsync via temporary subscription
  • callback dispatch on incoming updates
  • unadvise when subscription handle is disposed

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkClientSubscriptionTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientSubscriptionTests.cs
git commit -m "feat: implement suitelink subscribe and read flow"

Task 13: Implement Primitive Write Flow

Files:

  • Modify: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs
  • Modify: /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSession.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientWriteTests.cs

Step 1: Write the failing test

[Fact]
public async Task WriteAsync_SendsPokeForSubscribedTag()
{
    var client = TestClientFactory.CreateConnectedSubscribedClient("Pump001.Speed", 0x1234);

    await client.WriteAsync("Pump001.Speed", SuiteLinkValue.FromInt32(42));

    Assert.Contains(client.SentFrames, frame => frame[2] == 0x08 && frame[3] == 0x0B);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter WriteAsync_SendsPokeForSubscribedTag Expected: FAIL

Step 3: Write minimal implementation

Implement:

  • WriteAsync
  • tag lookup by item name
  • fail if item is unknown or not yet subscribed
  • use primitive write codec

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkClientWriteTests Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientWriteTests.cs
git commit -m "feat: implement primitive suitelink writes"

Task 14: Add Golden Packet Fixtures

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Fixtures/README.md
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Fixtures/*.bin
  • Modify: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/*Tests.cs

Step 1: Write the failing test

Add one fixture-backed assertion that compares encoded output to a stored handshake or connect frame.

[Fact]
public void EncodeConnect_MatchesGoldenFixture()
{
    var expected = File.ReadAllBytes("Fixtures/connect.bin");
    var actual = SuiteLinkConnectCodec.Encode(TestOptions.Create()).ToArray();

    Assert.Equal(expected, actual);
}

Step 2: Run test to verify it fails

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter MatchesGoldenFixture Expected: FAIL until fixture and codec match

Step 3: Write minimal implementation

Add fixture files and normalize tests to read them from disk. Document where the fixture bytes came from and which captures or protocol references justify them.

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter Fixture Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Fixtures /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol
git commit -m "test: add golden packet fixtures for suitelink codec"

Task 15: Add Live Integration Test Harness

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/SuiteLink.Client.IntegrationTests.csproj
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/IntegrationSettings.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/TagRoundTripTests.cs
  • Create: /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/README.md
  • Modify: /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln

Step 1: Write the failing test

[Fact(Skip = "Requires live AVEVA SuiteLink endpoint")]
public async Task CanSubscribeAndWriteBooleanTag()
{
    var client = new SuiteLinkClient();
    await client.ConnectAsync(IntegrationSettings.Load());
}

Step 2: Run test to verify the harness builds

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter CanSubscribeAndWriteBooleanTag Expected: PASS or SKIP with the test discovered

Step 3: Write minimal implementation

Add:

  • integration project
  • environment-based settings loader
  • skipped or conditional tests for boolean, integer, float, and string tags

Step 4: Run test to verify it passes

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln Expected: PASS with integration tests skipped by default

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
git commit -m "test: add suitelink integration test harness"

Task 16: Add Package Documentation

Files:

  • Create: /Users/dohertj2/Desktop/suitelinkclient/README.md
  • Modify: /Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-design.md

Step 1: Write the failing documentation check

Define the required README sections:

  • project purpose
  • supported protocol scope
  • supported types
  • unsupported features
  • local build and test commands
  • integration test setup

Step 2: Run documentation review

Run: rg -n "Supported|Unsupported|Build|Test|Integration" /Users/dohertj2/Desktop/suitelinkclient/README.md Expected: FAIL until README exists

Step 3: Write minimal implementation

Create a README that states:

  • v1 supports normal SuiteLink V2 tag operations only
  • v1 does not support AlarmMgr or secure V3
  • primitive types only
  • exact dotnet commands for build and test

Step 4: Run documentation review

Run: rg -n "Supported|Unsupported|Build|Test|Integration" /Users/dohertj2/Desktop/suitelinkclient/README.md Expected: PASS

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/README.md /Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-design.md
git commit -m "docs: describe suitelink client scope and usage"

Task 17: Full Verification Pass

Files:

  • Modify: /Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-implementation-plan.md

Step 1: Run unit and integration-default test suite

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln Expected: PASS with integration tests skipped by default

Step 2: Run build verification

Run: dotnet build /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln -c Release Expected: PASS

Step 3: Run formatting or analyzer checks if added

Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln -c Release Expected: PASS

Step 4: Update plan status notes if execution deviated

Add a short note to the plan if any task required deviation due to verified protocol differences.

Step 5: Commit

git add /Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-implementation-plan.md
git commit -m "docs: finalize suitelink implementation verification"