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:
Joseph Doherty
2026-06-21 16:31:44 -04:00
10 changed files with 694 additions and 0 deletions
@@ -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 { }
}
}
}