NavisworksTransport/UnitTests/Core/AliasTreeTests.cs
tian 3f985aa235 phase 1: data layer for alias tree
- AliasNodeIdentity: stable composite node identifier (path + sibling index)
- AliasDataStore: CRUD operations on SQLite AliasMap table
- PathDatabase: add AliasMap table creation + index
- Unit tests: 30 tests for identity parsing/equality, store CRUD/batch/import
- Both projects compile successfully
2026-05-31 12:46:33 +08:00

354 lines
12 KiB
C#

using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Core.AliasTree;
using System;
using System.Data.SQLite;
using System.IO;
namespace NavisworksTransport.UnitTests.Core
{
[TestClass]
public class AliasNodeIdentityTests
{
// ── 构造与序列化 ──
[TestMethod]
public void Constructor_WithValidArgs_ShouldSetProperties()
{
var id = new AliasNodeIdentity("building/floor", 2);
Assert.AreEqual("building/floor", id.HierarchicalPath);
Assert.AreEqual(2, id.SiblingIndex);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Constructor_WithEmptyPath_ShouldThrow()
{
new AliasNodeIdentity("", 0);
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Constructor_WithNegativeIndex_ShouldThrow()
{
new AliasNodeIdentity("building/floor", -1);
}
[TestMethod]
public void ToKey_ShouldReturnCorrectFormat()
{
var id = new AliasNodeIdentity("building/floor/wall", 3);
Assert.AreEqual("building/floor/wall#3", id.ToKey());
}
[TestMethod]
public void FromKey_ShouldRestoreIdentity()
{
var restored = AliasNodeIdentity.FromKey("building/floor/wall#3");
Assert.AreEqual("building/floor/wall", restored.HierarchicalPath);
Assert.AreEqual(3, restored.SiblingIndex);
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
public void FromKey_WithoutSeparator_ShouldThrow()
{
AliasNodeIdentity.FromKey("building/floor/wall");
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
public void FromKey_EmptyIndex_ShouldThrow()
{
AliasNodeIdentity.FromKey("building/floor/wall#");
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
public void FromKey_NonNumericIndex_ShouldThrow()
{
AliasNodeIdentity.FromKey("building/floor/wall#abc");
}
// ── TryParseKey ──
[TestMethod]
public void TryParseKey_ValidKey_ShouldReturnTrue()
{
bool ok = AliasNodeIdentity.TryParseKey("building/floor#0", out var id);
Assert.IsTrue(ok);
Assert.AreEqual("building/floor", id.HierarchicalPath);
Assert.AreEqual(0, id.SiblingIndex);
}
[TestMethod]
public void TryParseKey_InvalidKey_ShouldReturnFalse()
{
Assert.IsFalse(AliasNodeIdentity.TryParseKey("", out _));
Assert.IsFalse(AliasNodeIdentity.TryParseKey("nohash", out _));
Assert.IsFalse(AliasNodeIdentity.TryParseKey("hashonly#", out _));
Assert.IsFalse(AliasNodeIdentity.TryParseKey("path#-1", out _));
Assert.IsFalse(AliasNodeIdentity.TryParseKey("path#abc", out _));
}
// ── 值语义 ──
[TestMethod]
public void Equals_IdenticalIdentities_ShouldBeEqual()
{
var a = new AliasNodeIdentity("root/a", 0);
var b = new AliasNodeIdentity("root/a", 0);
Assert.AreEqual(a, b);
Assert.IsTrue(a == b);
Assert.IsFalse(a != b);
Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
}
[TestMethod]
public void Equals_DifferentPath_ShouldNotBeEqual()
{
var a = new AliasNodeIdentity("root/a", 0);
var b = new AliasNodeIdentity("root/b", 0);
Assert.AreNotEqual(a, b);
}
[TestMethod]
public void Equals_DifferentIndex_ShouldNotBeEqual()
{
var a = new AliasNodeIdentity("root/a", 0);
var b = new AliasNodeIdentity("root/a", 1);
Assert.AreNotEqual(a, b);
}
// ── HasPathPrefix ──
[TestMethod]
public void HasPathPrefix_MatchingPrefix_ShouldReturnTrue()
{
var id = new AliasNodeIdentity("building/floor/wall", 0);
Assert.IsTrue(id.HasPathPrefix("building/floor"));
Assert.IsTrue(id.HasPathPrefix("building"));
Assert.IsTrue(id.HasPathPrefix("building/floor/wall"));
}
[TestMethod]
public void HasPathPrefix_NonMatchingPrefix_ShouldReturnFalse()
{
var id = new AliasNodeIdentity("building/floor/wall", 0);
Assert.IsFalse(id.HasPathPrefix("building/roof"));
Assert.IsFalse(id.HasPathPrefix("Build"));
}
// ── ToString ──
[TestMethod]
public void ToString_ShouldMatchToKey()
{
var id = new AliasNodeIdentity("a/b/c", 1);
Assert.AreEqual(id.ToKey(), id.ToString());
}
}
[TestClass]
public class AliasDataStoreTests
{
private string _dbPath;
private SQLiteConnection _connection;
[TestInitialize]
public void Setup()
{
// 使用临时文件创建独立的 SQLite 数据库
_dbPath = Path.Combine(Path.GetTempPath(), $"AliasDataStoreTest_{Guid.NewGuid():N}.db");
_connection = new SQLiteConnection($"Data Source={_dbPath};Version=3;");
_connection.Open();
}
[TestCleanup]
public void Cleanup()
{
_connection?.Close();
_connection?.Dispose();
try { File.Delete(_dbPath); }
catch { /* 清理临时文件 */ }
}
private AliasDataStore CreateStore()
{
var store = new AliasDataStore(_connection);
store.EnsureTableExists();
return store;
}
private AliasNodeIdentity Id(string path, int index = 0)
=> new AliasNodeIdentity(path, index);
// ── CRUD ──
[TestMethod]
public void SetAndGetAlias_ShouldReturnSavedValue()
{
var store = CreateStore();
store.SetAlias(Id("building/floor", 0), "1F-办公层");
string result = store.GetAlias(Id("building/floor", 0));
Assert.AreEqual("1F-办公层", result);
}
[TestMethod]
public void GetAlias_Nonexistent_ShouldReturnNull()
{
var store = CreateStore();
string result = store.GetAlias(Id("some/nonexistent", 0));
Assert.IsNull(result);
}
[TestMethod]
public void SetAlias_Overwrite_ShouldUpdateValue()
{
var store = CreateStore();
store.SetAlias(Id("building/floor", 0), "旧名称");
store.SetAlias(Id("building/floor", 0), "新名称");
Assert.AreEqual("新名称", store.GetAlias(Id("building/floor", 0)));
}
[TestMethod]
public void SetAlias_EmptyString_ShouldDelete()
{
var store = CreateStore();
store.SetAlias(Id("building/floor", 0), "原本");
Assert.AreEqual(1, store.Count);
store.SetAlias(Id("building/floor", 0), " ");
Assert.IsNull(store.GetAlias(Id("building/floor", 0)));
Assert.AreEqual(0, store.Count);
}
[TestMethod]
public void DeleteAlias_ShouldRemoveEntry()
{
var store = CreateStore();
store.SetAlias(Id("building/floor", 0), "别名");
Assert.AreEqual(1, store.Count);
store.DeleteAlias(Id("building/floor", 0));
Assert.AreEqual(0, store.Count);
Assert.IsNull(store.GetAlias(Id("building/floor", 0)));
}
// ── 批量操作 ──
[TestMethod]
public void DeleteByPathPrefix_ShouldRemoveAllUnderPath()
{
var store = CreateStore();
store.SetAlias(Id("building/floor/a", 0), "别名1");
store.SetAlias(Id("building/floor/b", 0), "别名2");
store.SetAlias(Id("building/roof/c", 0), "别名3");
store.DeleteAliasesByPathPrefix("building/floor");
Assert.IsNull(store.GetAlias(Id("building/floor/a", 0)));
Assert.IsNull(store.GetAlias(Id("building/floor/b", 0)));
Assert.AreEqual("别名3", store.GetAlias(Id("building/roof/c", 0)));
}
[TestMethod]
public void LoadAll_ShouldReturnAllEntries()
{
var store = CreateStore();
store.SetAlias(Id("a", 0), "别名A");
store.SetAlias(Id("b", 1), "别名B");
store.SetAlias(Id("c", 2), "别名C");
var all = store.LoadAll();
Assert.AreEqual(3, all.Count);
Assert.AreEqual("别名A", all[Id("a", 0).ToKey()]);
Assert.AreEqual("别名B", all[Id("b", 1).ToKey()]);
Assert.AreEqual("别名C", all[Id("c", 2).ToKey()]);
}
[TestMethod]
public void Count_EmptyStore_ShouldBeZero()
{
var store = CreateStore();
Assert.AreEqual(0, store.Count);
}
[TestMethod]
public void Count_AfterInsert_ShouldIncrease()
{
var store = CreateStore();
store.SetAlias(Id("a", 0), "alias1");
store.SetAlias(Id("b", 0), "alias2");
Assert.AreEqual(2, store.Count);
}
// ── 批量导入 ──
[TestMethod]
public void ImportBatch_ShouldMergeEntries()
{
var store = CreateStore();
store.SetAlias(Id("existing", 0), "保留值");
var entries = new System.Collections.Generic.Dictionary<string, string>
{
{ Id("existing", 0).ToKey(), "覆盖值" },
{ Id("new", 0).ToKey(), "新值" }
};
store.ImportBatch(entries);
Assert.AreEqual("覆盖值", store.GetAlias(Id("existing", 0)));
Assert.AreEqual("新值", store.GetAlias(Id("new", 0)));
Assert.AreEqual(2, store.Count);
}
[TestMethod]
public void ImportBatch_EmptyDict_ShouldNotAffectStore()
{
var store = CreateStore();
store.SetAlias(Id("a", 0), "值");
store.ImportBatch(new System.Collections.Generic.Dictionary<string, string>());
Assert.AreEqual(1, store.Count);
}
// ── 清除全部 ──
[TestMethod]
public void ClearAll_ShouldRemoveEverything()
{
var store = CreateStore();
store.SetAlias(Id("a", 0), "别名1");
store.SetAlias(Id("b", 1), "别名2");
store.ClearAll();
Assert.AreEqual(0, store.Count);
}
// ── 同名兄弟节点去重 ──
[TestMethod]
public void SiblingNodes_WithDifferentIndex_ShouldStoreSeparately()
{
var store = CreateStore();
store.SetAlias(Id("floor/wall", 0), "墙-1");
store.SetAlias(Id("floor/wall", 1), "墙-2");
store.SetAlias(Id("floor/wall", 2), "墙-3");
Assert.AreEqual("墙-1", store.GetAlias(Id("floor/wall", 0)));
Assert.AreEqual("墙-2", store.GetAlias(Id("floor/wall", 1)));
Assert.AreEqual("墙-3", store.GetAlias(Id("floor/wall", 2)));
Assert.AreEqual(3, store.Count);
}
[TestMethod]
public void LoadAll_SiblingNodes_ShouldReturnAll()
{
var store = CreateStore();
store.SetAlias(Id("same/path", 0), "第一个");
store.SetAlias(Id("same/path", 1), "第二个");
var all = store.LoadAll();
Assert.AreEqual(2, all.Count);
Assert.AreEqual("第一个", all["same/path#0"]);
Assert.AreEqual("第二个", all["same/path#1"]);
}
}
}