using DodoSSH.Client.ObjectStore; namespace DodoSSH.Client.ObjectStore.Tests; /// /// Translating between the paths a file browser uses and the keys a bucket has. /// /// /// Small and worth having, because every one of these cases is a way a bucket listing goes subtly wrong /// rather than loudly: a missing trailing slash lists a sibling directory's contents, and a leading one asks /// the service for a key beginning with /, which is legal and is never what anybody stored. /// public sealed class ObjectKeysTests { [Theory] [InlineData("/", "")] [InlineData("/reports", "reports")] [InlineData("/reports/2026/q3.csv", "reports/2026/q3.csv")] public void APathBecomesAKeyWithNoLeadingSlash(string path, string expected) { ObjectKeys.ToKey(path).ShouldBe(expected); } [Theory] [InlineData("", "/")] [InlineData("reports/2026/q3.csv", "/reports/2026/q3.csv")] public void AKeyBecomesAnAbsolutePath(string key, string expected) { ObjectKeys.ToPath(key).ShouldBe(expected); } /// /// The one that matters most. Without the trailing slash, listing /reports also returns everything /// under /reports-archive — a prefix match knows nothing about path segments, and the result is a /// directory listing with another directory's files in it. /// [Theory] [InlineData("/reports", "reports/")] [InlineData("/reports/", "reports/")] [InlineData("/reports/2026", "reports/2026/")] public void APrefixAlwaysEndsInASlash(string path, string expected) { ObjectKeys.ToPrefix(path).ShouldBe(expected); } /// /// Empty rather than "/", because a prefix of / matches nothing: no key stored by anything /// begins with a slash. This is the case that makes the root of a bucket list at all. /// [Fact] public void TheRootHasAnEmptyPrefix() { ObjectKeys.ToPrefix("/").ShouldBeEmpty(); } /// /// A common prefix arrives with its trailing slash on, so trimming has to happen before the last segment /// is taken — otherwise every directory in the listing is named "". /// [Theory] [InlineData("reports/2026/", "2026")] [InlineData("reports/2026/q3.csv", "q3.csv")] [InlineData("q3.csv", "q3.csv")] [InlineData("reports/", "reports")] public void ANameIsTheLastSegment(string key, string expected) { ObjectKeys.NameOf(key).ShouldBe(expected); } [Theory] [InlineData("/")] [InlineData("/reports")] [InlineData("/reports/2026/q3.csv")] public void APathSurvivesARoundTrip(string path) { ObjectKeys.ToPath(ObjectKeys.ToKey(path)).ShouldBe(path); } }