feat(m2): repository layer
- Data models: ColumnModel, CardModel (tags loaded), TagModel - PathComparer: platform-parameterized path normalization (case rule injected, trailing separator trimmed, roots kept) - WorkspaceRepository: column/card/tag CRUD, card moves, tag assignment; single-transaction edit confirmation with atomic rollback; no-change confirmations write nothing; move refreshes last_edited_at but not card updated_at; write-through to app.db after every successful data write - WorkspaceConnectionPool: fixed-capacity LRU session pool (lock + repository); eviction releases resources only; typed folder state errors; sidebar registry registration on open - 35 new unit tests: cascade chains, atomicity, timestamp rules, LRU behavior, lock conflicts, path equivalence
This commit is contained in:
1 parent
31fc58d9c7
commit
01367edaa9
10 files changed
+1308
No files matched your search
@@ -0,0 +1,93 @@
|
||||
using YKanBan.Platform;
|
||||
using YKanBan.Tests.TestUtilities;
|
||||
|
||||
namespace YKanBan.Tests.Platform;
|
||||
|
||||
/// <summary>Tests for path normalization and platform-parameterized comparison.</summary>
|
||||
[TestClass]
|
||||
public class PathComparerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void OrdinalComparerTreatsCaseAsSignificant()
|
||||
{
|
||||
// Unix-style rule, injected so it is testable on any host.
|
||||
var comparer = new PathComparer(ignoreCase: false);
|
||||
Assert.IsFalse(comparer.Equals(@"C:\A", @"C:\a"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IgnoreCaseComparerTreatsCaseAsInsignificant()
|
||||
{
|
||||
// Windows-style rule.
|
||||
var comparer = new PathComparer(ignoreCase: true);
|
||||
Assert.IsTrue(comparer.Equals(@"C:\A", @"c:\a"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EqualPathsProduceEqualHashCodes()
|
||||
{
|
||||
var comparer = new PathComparer(ignoreCase: true);
|
||||
Assert.AreEqual(
|
||||
comparer.GetHashCode(@"C:\Users\Data\MyRepo"),
|
||||
comparer.GetHashCode(@"c:\users\data\myrepo"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RelativeSegmentsCollapse()
|
||||
{
|
||||
var comparer = new PathComparer(ignoreCase: true);
|
||||
using var directory = new TempDirectory();
|
||||
|
||||
string viaParent = Path.Combine(directory.FullPath, "x", "..", "a");
|
||||
string direct = Path.Combine(directory.FullPath, "a");
|
||||
|
||||
Assert.IsTrue(comparer.Equals(viaParent, direct));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SeparatorStylesUnify()
|
||||
{
|
||||
var comparer = new PathComparer(ignoreCase: true);
|
||||
|
||||
Assert.IsTrue(comparer.Equals(@"C:\a/b\c", @"C:\a\b\c"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TrailingSeparatorIsTrimmedForNonRoots()
|
||||
{
|
||||
var comparer = new PathComparer(ignoreCase: true);
|
||||
using var directory = new TempDirectory();
|
||||
|
||||
string withTrailing = directory.FullPath + Path.DirectorySeparatorChar;
|
||||
Assert.IsTrue(comparer.Equals(withTrailing, directory.FullPath));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NormalizeReturnsCanonicalAbsoluteForm()
|
||||
{
|
||||
var comparer = new PathComparer(ignoreCase: false);
|
||||
using var directory = new TempDirectory();
|
||||
|
||||
string withRelativeSegment = Path.Combine(directory.FullPath, "sub", "..");
|
||||
Assert.AreEqual(directory.FullPath, comparer.Normalize(withRelativeSegment));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RootPathKeepsItsTrailingSeparator()
|
||||
{
|
||||
var comparer = new PathComparer(ignoreCase: true);
|
||||
|
||||
Assert.AreEqual(@"C:\", comparer.Normalize(@"C:\"));
|
||||
Assert.AreEqual(@"C:\x", comparer.Normalize(@"C:\x\"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ForCurrentPlatformMatchesHostRule()
|
||||
{
|
||||
PathComparer comparer = PathComparer.ForCurrentPlatform();
|
||||
|
||||
Assert.AreEqual(
|
||||
OperatingSystem.IsWindows(),
|
||||
comparer.Equals(@"C:\Same", @"c:\same"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user