Merge re/r1.10-rename-tags: RenameTagsAsync via History StartJob
# Conflicts: # docs/plans/hcal-capability-matrix.md # docs/plans/hcal-roadmap.md # src/AVEVA.Historian.Client/Wcf/HistorianWcfTagWriteOrchestrator.cs # tests/AVEVA.Historian.Client.Tests/HistorianClientIntegrationTests.cs # tools/AVEVA.Historian.NativeTraceHarness/Program.cs
This commit is contained in:
@@ -1038,4 +1038,73 @@ public sealed class HistorianClientIntegrationTests
|
||||
// SQL read-back is diagnostic only; never fail the send test on a query issue.
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenameTagsAsync_AgainstLocalHistorian_RenamesSandboxTag()
|
||||
{
|
||||
// Safety: localhost only, names must start with "RetestSdkWrite". Requires the server's
|
||||
// AllowRenameTags system parameter to be enabled (otherwise StartJob returns false). Gated
|
||||
// on HISTORIAN_RENAME_SANDBOX so it stays skipped unless explicitly enabled.
|
||||
string? host = Environment.GetEnvironmentVariable("HISTORIAN_HOST");
|
||||
string? sandbox = Environment.GetEnvironmentVariable("HISTORIAN_RENAME_SANDBOX");
|
||||
if (string.IsNullOrWhiteSpace(host) || !string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase) || !OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(sandbox) || !sandbox.StartsWith("RetestSdkWrite", StringComparison.Ordinal))
|
||||
{
|
||||
return; // safety gate
|
||||
}
|
||||
|
||||
string src = sandbox + "Src";
|
||||
string dst = sandbox + "Dst";
|
||||
|
||||
HistorianClient client = new(new HistorianClientOptions
|
||||
{
|
||||
Host = host,
|
||||
IntegratedSecurity = true,
|
||||
Transport = HistorianTransport.LocalPipe
|
||||
});
|
||||
|
||||
// Fresh source tag.
|
||||
await client.EnsureTagAsync(new AVEVA.Historian.Client.Models.HistorianTagDefinition
|
||||
{
|
||||
TagName = src,
|
||||
Description = "SDK rename live test",
|
||||
EngineeringUnit = "test",
|
||||
DataType = AVEVA.Historian.Client.Models.HistorianDataType.Float,
|
||||
MinEU = 0.0,
|
||||
MaxEU = 100.0,
|
||||
}, CancellationToken.None);
|
||||
|
||||
try
|
||||
{
|
||||
AVEVA.Historian.Client.Models.HistorianTagRenameResult result =
|
||||
await client.RenameTagAsync(src, dst, CancellationToken.None);
|
||||
|
||||
Assert.True(result.Accepted, "RenameTagsAsync was not accepted by the server (is AllowRenameTags enabled?).");
|
||||
Assert.NotEqual(Guid.Empty, result.JobId);
|
||||
Assert.Equal(1, result.PairCount);
|
||||
|
||||
// Rename completes asynchronously; poll the new name's metadata briefly.
|
||||
bool renamed = false;
|
||||
for (int i = 0; i < 10 && !renamed; i++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
try
|
||||
{
|
||||
var md = await client.GetTagMetadataAsync(dst, CancellationToken.None);
|
||||
renamed = md is not null && string.Equals(md.Name, dst, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch { /* not yet visible */ }
|
||||
}
|
||||
Assert.True(renamed, $"Renamed tag '{dst}' did not become visible after the job completed.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up whichever name ended up in the DB.
|
||||
try { await client.DeleteTagAsync(dst, CancellationToken.None); } catch { }
|
||||
try { await client.DeleteTagAsync(src, CancellationToken.None); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using AVEVA.Historian.Client.Wcf;
|
||||
|
||||
namespace AVEVA.Historian.Client.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Golden-byte tests for the History <c>StartJob</c> (StJb) rename job buffer (HCAL R1.10).
|
||||
/// The reference buffer is the exact byte[] the SDK handed the WCF channel in the live
|
||||
/// <c>RenameTagsAsync_AgainstLocalHistorian_RenamesSandboxTag</c> run — the server accepted it and
|
||||
/// the tag was renamed, so it is server-validated, not hand-derived from a chunk-mangled capture.
|
||||
/// </summary>
|
||||
public sealed class WcfTagRenameProtocolTests
|
||||
{
|
||||
// Server-accepted clean jobBuffer for pairs [("RetestSdkWriteSdkRenameSrc","RetestSdkWriteSdkRenameDst")],
|
||||
// dumped via AVEVA_HISTORIAN_RENAME_DUMP during the live rename test.
|
||||
private const string ServerAcceptedJobBufferBase64 =
|
||||
"AAAAAAAAAAEAAAAaAAAAUgBlAHQAZQBzAHQAUwBkAGsAVwByAGkAdABlAFMAZABrAFIAZQBuAGEAbQBlAFMAcgBjABoAAABSAGUAdABlAHMAdABTAGQAawBXAHIAaQB0AGUAUwBkAGsAUgBlAG4AYQBtAGUARABzAHQA";
|
||||
|
||||
[Fact]
|
||||
public void SerializeRenameJob_MatchesServerAcceptedBuffer()
|
||||
{
|
||||
byte[] expected = Convert.FromBase64String(ServerAcceptedJobBufferBase64);
|
||||
byte[] actual = HistorianTagRenameProtocol.SerializeRenameJob(
|
||||
[("RetestSdkWriteSdkRenameSrc", "RetestSdkWriteSdkRenameDst")]);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeRenameJob_SinglePair_HasExpectedLayout()
|
||||
{
|
||||
byte[] buf = HistorianTagRenameProtocol.SerializeRenameJob([("ReactorTempA", "ReactorTempB")]);
|
||||
|
||||
// 7-byte zero prefix + uint32 pairCount + (uint32 charCount + UTF-16)×2.
|
||||
Assert.Equal(7 + 4 + 4 + 24 + 4 + 24, buf.Length);
|
||||
for (int i = 0; i < 7; i++) Assert.Equal(0, buf[i]);
|
||||
Assert.Equal(1u, BinaryPrimitives.ReadUInt32LittleEndian(buf.AsSpan(7, 4)));
|
||||
Assert.Equal(12u, BinaryPrimitives.ReadUInt32LittleEndian(buf.AsSpan(11, 4)));
|
||||
Assert.Equal("ReactorTempA", Encoding.Unicode.GetString(buf.AsSpan(15, 24)));
|
||||
Assert.Equal(12u, BinaryPrimitives.ReadUInt32LittleEndian(buf.AsSpan(39, 4)));
|
||||
Assert.Equal("ReactorTempB", Encoding.Unicode.GetString(buf.AsSpan(43, 24)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeRenameJob_MultiplePairs_EncodesCountAndOrder()
|
||||
{
|
||||
byte[] buf = HistorianTagRenameProtocol.SerializeRenameJob(
|
||||
[("AaOld", "AaNew"), ("BbOld", "BbNew")]);
|
||||
|
||||
Assert.Equal(2u, BinaryPrimitives.ReadUInt32LittleEndian(buf.AsSpan(7, 4)));
|
||||
// First pair old name immediately follows the count + its length field.
|
||||
Assert.Equal(5u, BinaryPrimitives.ReadUInt32LittleEndian(buf.AsSpan(11, 4)));
|
||||
Assert.Equal("AaOld", Encoding.Unicode.GetString(buf.AsSpan(15, 10)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeRenameJob_EmptyBatch_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
HistorianTagRenameProtocol.SerializeRenameJob(Array.Empty<(string, string)>()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user