From c296b52909e1716464de12e466dc06567542ea83 Mon Sep 17 00:00:00 2001 From: Shukri Adams Date: Sat, 9 Dec 2023 14:49:54 +0100 Subject: [PATCH 1/3] misc - refactored humanizer lib out of project - added controller test for search --- src/Tetrifact.Core/DateTimeSpanExtensions.cs | 122 ++++++++++++++++++ src/Tetrifact.Core/PackagePruneService.cs | 8 +- src/Tetrifact.Core/PageableData.cs | 3 + src/Tetrifact.Core/TimeHelper.cs | 18 --- .../Web/Controllers/Home/Search.cs | 31 +++++ .../Controllers/HomeController.cs | 2 +- src/Tetrifact.Web/Tetrifact.Web.csproj | 1 - src/Tetrifact.Web/Views/Home/Index.cshtml | 3 +- src/Tetrifact.Web/Views/Home/Package.cshtml | 4 +- src/Tetrifact.Web/Views/Home/Packages.cshtml | 5 +- .../Views/Home/PackagesWithTag.cshtml | 3 +- src/Tetrifact.Web/Views/Home/Search.cshtml | 7 +- src/Tetrifact.Web/wwwroot/css/style.css | 5 + 13 files changed, 173 insertions(+), 39 deletions(-) create mode 100644 src/Tetrifact.Core/DateTimeSpanExtensions.cs delete mode 100644 src/Tetrifact.Core/TimeHelper.cs create mode 100644 src/Tetrifact.Tests/Web/Controllers/Home/Search.cs diff --git a/src/Tetrifact.Core/DateTimeSpanExtensions.cs b/src/Tetrifact.Core/DateTimeSpanExtensions.cs new file mode 100644 index 0000000..695fc05 --- /dev/null +++ b/src/Tetrifact.Core/DateTimeSpanExtensions.cs @@ -0,0 +1,122 @@ +using System; + +namespace Tetrifact.Core +{ + /// + /// Extensions for datetime and timespan + /// + public static class DateTimeSpanExtensions + { + public static string Ago(this DateTime? beforeUtc) + { + if (beforeUtc == null) + return string.Empty; + + return _agoLocalTime(beforeUtc.Value); + } + + public static string ToIso(this DateTime date) + { + string iso = date + .ToLocalTime() + .ToString("s") // convert to ymdhms + .Replace("T", " "); // replace T after ymd + + return iso + .Substring(0, iso.Length - 3); // remove sec + } + + public static string ToHumanString(this TimeSpan ts, bool shorten = false) + { + return _ago(ts, shorten); + } + + public static string ToHumanString(this TimeSpan? ts, bool shorten = false) + { + if (ts == null) + return string.Empty; + return _ago(ts.Value, shorten); + } + + public static string Ago(this DateTime beforeUtc, bool shorten = false) + { + return _agoLocalTime(beforeUtc, shorten); + } + + private static string _agoLocalTime(DateTime beforeUtc, bool shorten = false) + { + return _ago(DateTime.Now.ToLocalTime() - beforeUtc.ToLocalTime(), shorten); + } + + private static string _ago(TimeSpan ts, bool shorten) + { + int count = 0; + string unit = string.Empty; + string pluralMod = string.Empty; + if (ts.TotalDays > 364) + { + count = (int)Math.Round(ts.TotalDays / 364, 0); + unit = shorten ? "y" : "year"; + pluralMod = shorten ? string.Empty : "s"; + return $"{count} {unit}" + (count == 1 ? "" : pluralMod); + } + + if (ts.TotalHours >= 24) + { + count = (int)Math.Round(ts.TotalDays, 0); + unit = shorten ? "d" : "day"; + pluralMod = shorten ? string.Empty : "s"; + return $"{count} {unit}" + (count == 1 ? "" : pluralMod); + } + + if (ts.TotalMinutes >= 60) + { + count = (int)Math.Round(ts.TotalHours, 0); + unit = shorten ? "h" : "hour"; + pluralMod = shorten ? string.Empty : "s"; + return $"{count} {unit}" + (count == 1 ? "" : pluralMod); + } + + if (ts.TotalSeconds >= 60) + { + count = (int)Math.Round(ts.TotalMinutes, 0); + unit = shorten ? "m" : "minute"; + pluralMod = shorten ? string.Empty : "s"; + return $"{count} {unit}" + (count == 1 ? "" : pluralMod); + } + + count = (int)Math.Round(ts.TotalSeconds, 0); + unit = shorten ? "s" : "second"; + pluralMod = shorten ? string.Empty : "s"; + return $"{count} {unit}" + (count == 1 ? "" : pluralMod); + } + + /// + /// Generates a human-friendly date in in local time. Assumes input is UTC. + /// + /// + /// + /// + public static string ToHumanString(this DateTime dateUtc, bool shorten = true) + { + DateTime date = dateUtc.ToLocalTime(); + string format = "yy-MM-dd HH:mm"; + // for date great than a day since, we don't care about time + if (shorten && (DateTime.Now.ToLocalTime() - date).TotalHours > 24) + format = "yy-MM-dd"; + + string shortened = date.ToString(format) + .Replace(".", ":"); // .net in its infinite stupidity ignores the ":" in the format string and forces fullstops, replace those + + return shortened; + } + + public static string ToHumanString(this DateTime? dateUtc) + { + if (!dateUtc.HasValue) + return string.Empty; + + return ToHumanString(dateUtc.Value); + } + } +} \ No newline at end of file diff --git a/src/Tetrifact.Core/PackagePruneService.cs b/src/Tetrifact.Core/PackagePruneService.cs index 7c1d153..ab407e0 100644 --- a/src/Tetrifact.Core/PackagePruneService.cs +++ b/src/Tetrifact.Core/PackagePruneService.cs @@ -122,7 +122,7 @@ public PruneReport Report() bool isTaggedKeep = manifest.Tags.Any(tag => _settings.PruneIgnoreTags.Any(protectedTag => protectedTag.Equals(tag))); string flattenedTags = manifest.Tags.Count == 0 ? string.Empty : $"Tags : {string.Join(",", manifest.Tags)}"; int ageInDays = (int)Math.Round((utcNow - manifest.CreatedUtc).TotalDays, 0); - report.Add($"- {packageId}, added {TimeHelper.ToIsoString(manifest.CreatedUtc)} ({ageInDays}) days ago). {flattenedTags}"); + report.Add($"- {packageId}, added {manifest.CreatedUtc.ToIso()} ({ageInDays}) days ago). {flattenedTags}"); if (isTaggedKeep){ taggedKeep.Add(packageId); @@ -188,9 +188,9 @@ public PruneReport Report() report.Add($"Pre-weekly ignore count is {newKeep.Count()} - {string.Join(",", newKeep)}"); if (taggedKeep.Count > 0) report.Add($"Kept due to tagging - {string.Join(",", taggedKeep)}."); - report.Add($"WEEKLY prune (before {TimeHelper.ToIsoString(weeklyPruneFloor)}, {_settings.PruneWeeklyThreshold} days ago) count is {_settings.PruneWeeklyKeep}. Keeping {weeklyKeep.Count()} of {inWeeky}. {string.Join(",", weeklyKeep)}{weeklyPruneFlattened}."); - report.Add($"MONTHLY prune (before {TimeHelper.ToIsoString(monthlyPruneFloor)}, {_settings.PruneMonthlyThreshold} days ago) count is {_settings.PruneMonthlyKeep}. Keeping {monthlyKeep.Count()} of {inMonthly}. {string.Join(",", monthlyKeep)}{monthlyPruneFlattened}."); - report.Add($"YEARLY prune (before {TimeHelper.ToIsoString(yearlyPruneFloor)}, {_settings.PruneYearlyThreshold} days ago) count is {_settings.PruneYearlyKeep}. Keeping {yearlyKeep.Count()} of {inYearly}. {string.Join(",", yearlyKeep)}{yearlyPruneFlattened}."); + report.Add($"WEEKLY prune (before {weeklyPruneFloor.ToIso()}, {_settings.PruneWeeklyThreshold} days ago) count is {_settings.PruneWeeklyKeep}. Keeping {weeklyKeep.Count()} of {inWeeky}. {string.Join(",", weeklyKeep)}{weeklyPruneFlattened}."); + report.Add($"MONTHLY prune (before {monthlyPruneFloor.ToIso()}, {_settings.PruneMonthlyThreshold} days ago) count is {_settings.PruneMonthlyKeep}. Keeping {monthlyKeep.Count()} of {inMonthly}. {string.Join(",", monthlyKeep)}{monthlyPruneFlattened}."); + report.Add($"YEARLY prune (before {yearlyPruneFloor.ToIso()}, {_settings.PruneYearlyThreshold} days ago) count is {_settings.PruneYearlyKeep}. Keeping {yearlyKeep.Count()} of {inYearly}. {string.Join(",", yearlyKeep)}{yearlyPruneFlattened}."); report.Add(string.Empty); report.Add($"Pruning {packageIds.Count} packages{pruneIdList}."); report.Add(" ******************************** Prune audit **********************************"); diff --git a/src/Tetrifact.Core/PageableData.cs b/src/Tetrifact.Core/PageableData.cs index dc47210..8fc63f2 100644 --- a/src/Tetrifact.Core/PageableData.cs +++ b/src/Tetrifact.Core/PageableData.cs @@ -43,6 +43,9 @@ public class PageableData public PageableData(IEnumerable items, int pageIndex, int pageSize, long virtualItemCount) { + if (pageSize == 0) + throw new Exception("PageableData page size cannot be zero, will divide overflow. Set to at least 1."); + this.Items = items; this.PageSize = pageSize; this.PageIndex = pageIndex; diff --git a/src/Tetrifact.Core/TimeHelper.cs b/src/Tetrifact.Core/TimeHelper.cs deleted file mode 100644 index ce5bcc8..0000000 --- a/src/Tetrifact.Core/TimeHelper.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Tetrifact.Core -{ - public class TimeHelper - { - public static string ToIsoString(DateTime date) - { - string iso = date - .ToLocalTime() - .ToString("s") // convert to ymdhms - .Replace("T", " "); // replace T after ymd - - return iso - .Substring(0, iso.Length - 3); // remove sec - } - } -} diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs new file mode 100644 index 0000000..05a8a4b --- /dev/null +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using Moq; +using Tetrifact.Core; +using Xunit; +using W = Tetrifact.Web; + +namespace Tetrifact.Tests.Web.Controllers.Home +{ + public class Search : FileSystemBase + { + /// + /// + /// + [Fact] + public void Search_happy_path() + { + Mock packageList = new Mock(); + + // mock a single search result + packageList + .Setup(r => r.Find(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new PageableData(new Tetrifact.Core.Package[] { + new Tetrifact.Core.Package() + }, 0, 1, 1)); + + W.HomeController controller = MoqHelper.CreateInstanceWithDependencies(new object[] { this.Settings, packageList }); + ViewResult result = controller.Search("", 0) as ViewResult; + Assert.NotNull(result); + } + } +} diff --git a/src/Tetrifact.Web/Controllers/HomeController.cs b/src/Tetrifact.Web/Controllers/HomeController.cs index afea22b..ca66c16 100755 --- a/src/Tetrifact.Web/Controllers/HomeController.cs +++ b/src/Tetrifact.Web/Controllers/HomeController.cs @@ -141,7 +141,7 @@ public IActionResult Search(string search, int page) Pager pager = new Pager(); ViewData["serverName"] = _settings.ServerName; ViewData["search"] = search; - ViewData["results"] = results; + ViewData["packages"] = results; ViewData["pager"] = pager.Render(results, _settings.PagesPerPageGroup, "/search", "page"); return View(); } diff --git a/src/Tetrifact.Web/Tetrifact.Web.csproj b/src/Tetrifact.Web/Tetrifact.Web.csproj index 207f17a..71fa137 100644 --- a/src/Tetrifact.Web/Tetrifact.Web.csproj +++ b/src/Tetrifact.Web/Tetrifact.Web.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Tetrifact.Web/Views/Home/Index.cshtml b/src/Tetrifact.Web/Views/Home/Index.cshtml index 69aabff..bd26557 100644 --- a/src/Tetrifact.Web/Views/Home/Index.cshtml +++ b/src/Tetrifact.Web/Views/Home/Index.cshtml @@ -1,5 +1,4 @@ @using Tetrifact.Core; -@using Humanizer; @{ Layout = "~/Views/Shared/_Layout.cshtml"; IEnumerable packages = ViewData["packages"] as IEnumerable; @@ -16,7 +15,7 @@ @foreach (Package package in packages) {
  • - @package.Id
    (@TimeHelper.ToIsoString(package.CreatedUtc) - @package.CreatedUtc.ToLocalTime().Humanize())
    + @package.Id
    (@package.CreatedUtc.ToIso() - @package.CreatedUtc.Ago() ago)
    @await Html.PartialAsync("TagsList", package.Tags)
  • } diff --git a/src/Tetrifact.Web/Views/Home/Package.cshtml b/src/Tetrifact.Web/Views/Home/Package.cshtml index c1dce18..7ff289d 100644 --- a/src/Tetrifact.Web/Views/Home/Package.cshtml +++ b/src/Tetrifact.Web/Views/Home/Package.cshtml @@ -1,6 +1,4 @@ @using Tetrifact.Core; -@using Humanizer; - @{ string packageId = ViewData["packageId"] as string; Manifest manifest = ViewData["manifest"] as Manifest; @@ -28,7 +26,7 @@ Created
    - @TimeHelper.ToIsoString(manifest.CreatedUtc) (@manifest.CreatedUtc.ToLocalTime().Humanize()) + @manifest.CreatedUtc.ToIso() (@manifest.CreatedUtc.Ago() ago)
    diff --git a/src/Tetrifact.Web/Views/Home/Packages.cshtml b/src/Tetrifact.Web/Views/Home/Packages.cshtml index 197a10a..c07d630 100644 --- a/src/Tetrifact.Web/Views/Home/Packages.cshtml +++ b/src/Tetrifact.Web/Views/Home/Packages.cshtml @@ -1,10 +1,9 @@ @using Tetrifact.Core; -@using Humanizer; @{ ViewData["Title"] = "Packages"; Layout = "~/Views/Shared/_Layout.cshtml"; - PageableData packages = ViewData["results"] as PageableData; + PageableData packages = ViewData["packages"] as PageableData; string pager = ViewData["pager"] as string; } @@ -19,7 +18,7 @@ {
  • @package.Id -
    (@TimeHelper.ToIsoString(package.CreatedUtc) - @package.CreatedUtc.ToLocalTime().Humanize() )
    +
    (@package.CreatedUtc.ToIso() - @package.CreatedUtc.Ago() ago)
    @await Html.PartialAsync("TagsList", package.Tags)
  • } diff --git a/src/Tetrifact.Web/Views/Home/PackagesWithTag.cshtml b/src/Tetrifact.Web/Views/Home/PackagesWithTag.cshtml index 76070c8..5c19fcc 100644 --- a/src/Tetrifact.Web/Views/Home/PackagesWithTag.cshtml +++ b/src/Tetrifact.Web/Views/Home/PackagesWithTag.cshtml @@ -1,5 +1,4 @@ @using Tetrifact.Core; -@using Humanizer; @{ string tag = ViewData["tag"] as string; ViewData["Title"] = $"Packages tagged with \"{tag}\""; @@ -14,7 +13,7 @@ @foreach (Package package in packages) {
  • - @package.Id
    (@TimeHelper.ToIsoString(package.CreatedUtc) - @package.CreatedUtc.ToLocalTime().Humanize() )
    + @package.Id
    (@package.CreatedUtc.ToIso() - @package.CreatedUtc.Ago() ago)
  • } diff --git a/src/Tetrifact.Web/Views/Home/Search.cshtml b/src/Tetrifact.Web/Views/Home/Search.cshtml index a3f57ab..6aa3d2e 100644 --- a/src/Tetrifact.Web/Views/Home/Search.cshtml +++ b/src/Tetrifact.Web/Views/Home/Search.cshtml @@ -1,10 +1,9 @@ @using Tetrifact.Core; -@using Humanizer; @{ string tag = ViewData["tag"] as string; ViewData["Title"] = $"Search"; Layout = "~/Views/Shared/_Layout.cshtml"; - PageableData packages = ViewData["results"] as PageableData; + PageableData packages = ViewData["packages"] as PageableData; string pager = ViewData["pager"] as string; string search = ViewData["search"] as string; } @@ -16,16 +15,14 @@ @Html.Raw(pager)
      - @foreach (Package package in packages.Items) {
    • @package.Id -
      (@TimeHelper.ToIsoString(package.CreatedUtc) - @package.CreatedUtc.ToLocalTime().Humanize() )
      +
      (@package.CreatedUtc.ToIso() - @package.CreatedUtc.Ago() ago)
      @await Html.PartialAsync("TagsList", package.Tags)
    • } -
    @Html.Raw(pager) diff --git a/src/Tetrifact.Web/wwwroot/css/style.css b/src/Tetrifact.Web/wwwroot/css/style.css index 5f1926d..0d7c08f 100644 --- a/src/Tetrifact.Web/wwwroot/css/style.css +++ b/src/Tetrifact.Web/wwwroot/css/style.css @@ -66,6 +66,7 @@ input[type=text] { font-size: 16px; font-weight: 100; padding-left: 10px; + white-space: nowrap; } .fixed { @@ -92,12 +93,16 @@ header { display: inline-block; position: absolute; right: 10px; + max-width: 50%; + white-space: nowrap; } .title { color: white; font-size: 34px; font-weight: bold; + display: inline-block; + max-width: 50% } main { From f40b0ef6174c8f0cae3a8fc0a7a0ca0df07fd7c6 Mon Sep 17 00:00:00 2001 From: Shukri Adams Date: Sat, 9 Dec 2023 15:18:25 +0100 Subject: [PATCH 2/3] refactor simplified some tests to no longer require FileSystemBase --- src/Tetrifact.Core/ArchiveService.cs | 15 ++++++--- src/Tetrifact.Core/IndexReadService.cs | 2 +- .../ArchiveService/GetPackageArchiveStatus.cs | 31 ++++++++++++++++--- .../Web/Controllers/Home/Api.cs | 2 +- .../Web/Controllers/Home/Error404.cs | 2 +- .../Web/Controllers/Home/Error500.cs | 2 +- .../Web/Controllers/Home/Index.cs | 2 +- .../Web/Controllers/Home/Package.cs | 2 +- .../Web/Controllers/Home/Packages.cs | 2 +- .../Web/Controllers/Home/PackagesWithTag.cs | 2 +- .../Web/Controllers/Home/Search.cs | 2 +- .../Web/Controllers/Home/SpaceCheck.cs | 2 +- .../Web/Controllers/Home/UploadPackage.cs | 2 +- 13 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Tetrifact.Core/ArchiveService.cs b/src/Tetrifact.Core/ArchiveService.cs index aa4eb6a..618c82b 100644 --- a/src/Tetrifact.Core/ArchiveService.cs +++ b/src/Tetrifact.Core/ArchiveService.cs @@ -93,14 +93,19 @@ public int GetPackageArchiveStatus(string packageId) string archivePath = this.GetPackageArchivePath(packageId); string temptPath = this.GetPackageArchiveTempPath(packageId); - // archive doesn't exist and isn't being created - if (!_fileSystem.File.Exists(archivePath) && !_fileSystem.File.Exists(temptPath)) - return 0; + bool archiveExists = _fileSystem.File.Exists(archivePath); + bool archiveTemptExists = _fileSystem.File.Exists(temptPath); + + // archive exists already + if (archiveExists) + return 2; - if (_fileSystem.File.Exists(temptPath)) + // archive is being created + if (archiveTemptExists) return 1; - return 2; + // neither archive nor temp file exists + return 0; } /// diff --git a/src/Tetrifact.Core/IndexReadService.cs b/src/Tetrifact.Core/IndexReadService.cs index 22dd516..a0f28c3 100644 --- a/src/Tetrifact.Core/IndexReadService.cs +++ b/src/Tetrifact.Core/IndexReadService.cs @@ -59,7 +59,7 @@ public void Initialize() Directory.CreateDirectory(_settings.PackageDiffsPath); } - public bool PackageExists(string packageId) + public virtual bool PackageExists(string packageId) { return this.GetManifest(packageId) != null; } diff --git a/src/Tetrifact.Tests/Core/ArchiveService/GetPackageArchiveStatus.cs b/src/Tetrifact.Tests/Core/ArchiveService/GetPackageArchiveStatus.cs index 9dd6a26..af3b6eb 100644 --- a/src/Tetrifact.Tests/Core/ArchiveService/GetPackageArchiveStatus.cs +++ b/src/Tetrifact.Tests/Core/ArchiveService/GetPackageArchiveStatus.cs @@ -1,4 +1,6 @@ -using System.IO; +using Moq; +using System.IO; +using System.IO.Abstractions; using Tetrifact.Core; using Xunit; @@ -12,17 +14,36 @@ public class GetPackageArchiveStatus : FileSystemBase [Fact] public void GetNonExistent() { - Assert.Throws(() => this.ArchiveService.GetPackageArchiveStatus("invalid-id")); + // mock a non-existent package + Mock indexReader = new Mock(); + indexReader + .Setup(r => r.PackageExists(It.IsAny())) + .Returns(false); + + IArchiveService archiveService = MoqHelper.CreateInstanceWithDependencies(new object[]{ base.Settings, indexReader }); + Assert.Throws(()=> archiveService.GetPackageArchiveStatus("invalid-id")); } /// - /// Archive create status should be 0 (started) + /// Archive create status should be 0 (starting) when neither archive exists, nor archive creation has started yet /// [Fact] public void GetArchiveStarted() { - TestPackage testPackage = PackageHelper.CreateNewPackage(this.Settings); - Assert.Equal(0, this.ArchiveService.GetPackageArchiveStatus(testPackage.Id)); + // force package exists + Mock indexReader = new Mock(); + indexReader + .Setup(r => r.PackageExists(It.IsAny())) + .Returns(true); + + // force filesystem to return false for all file checks (ie, archive + archive temp) + Mock filesystem = new Mock(); + filesystem + .Setup(r => r.File.Exists(It.IsAny())) + .Returns(false); + + IArchiveService archiveService = MoqHelper.CreateInstanceWithDependencies(new object[] { base.Settings, indexReader, filesystem }); + Assert.Equal(0, archiveService.GetPackageArchiveStatus("any-package-id")); } /// diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Api.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Api.cs index aedd917..e1dfd82 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Api.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Api.cs @@ -7,7 +7,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Api : FileSystemBase + public class Api : TestBase { /// /// Confirms that the controller initialized and can be called. diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Error404.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Error404.cs index 962fab2..474283f 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Error404.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Error404.cs @@ -3,7 +3,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Error404 : FileSystemBase + public class Error404 : TestBase { /// /// coverage diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Error500.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Error500.cs index ee71558..c629794 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Error500.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Error500.cs @@ -3,7 +3,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Error500 : FileSystemBase + public class Error500 : TestBase { /// /// coverage diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Index.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Index.cs index e4081b6..4c2ff55 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Index.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Index.cs @@ -7,7 +7,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Index : FileSystemBase + public class Index : TestBase { /// /// Confirms that the controller initialized and can be called. diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Package.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Package.cs index a018277..c25dc0a 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Package.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Package.cs @@ -6,7 +6,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Package : FileSystemBase + public class Package : TestBase { [Fact] public void Index_happy_path() diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Packages.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Packages.cs index 9bafc1e..ae0eea2 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Packages.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Packages.cs @@ -6,7 +6,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Packages : FileSystemBase + public class Packages : TestBase { [Fact] public void Happy_path() diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/PackagesWithTag.cs b/src/Tetrifact.Tests/Web/Controllers/Home/PackagesWithTag.cs index d80bc90..3c8eff1 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/PackagesWithTag.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/PackagesWithTag.cs @@ -7,7 +7,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class PackagesWithTag : FileSystemBase + public class PackagesWithTag : TestBase { [Fact] public void Happy_path() diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs b/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs index 05a8a4b..eccb9eb 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/Search.cs @@ -6,7 +6,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class Search : FileSystemBase + public class Search : TestBase { /// /// diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/SpaceCheck.cs b/src/Tetrifact.Tests/Web/Controllers/Home/SpaceCheck.cs index 7e2fed9..f9e3603 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/SpaceCheck.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/SpaceCheck.cs @@ -3,7 +3,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class SpaceCheck : FileSystemBase + public class SpaceCheck : TestBase { [Fact] public void Happy_path() diff --git a/src/Tetrifact.Tests/Web/Controllers/Home/UploadPackage.cs b/src/Tetrifact.Tests/Web/Controllers/Home/UploadPackage.cs index f7707cb..b77a1b1 100644 --- a/src/Tetrifact.Tests/Web/Controllers/Home/UploadPackage.cs +++ b/src/Tetrifact.Tests/Web/Controllers/Home/UploadPackage.cs @@ -4,7 +4,7 @@ namespace Tetrifact.Tests.Web.Controllers.Home { - public class UploadPackage : FileSystemBase + public class UploadPackage : TestBase { [Fact] public void Happy_path() From a97dbce98757b70763a9692d81caa8b0e60eb116 Mon Sep 17 00:00:00 2001 From: Shukri Adams Date: Sat, 9 Dec 2023 16:51:43 +0100 Subject: [PATCH 3/3] implicit casting proof of concept for mocking concrete to implicit interface type --- src/Tetrifact.Core/IndexReadService.cs | 40 +++++++++---------- src/Tetrifact.Core/LinqExtensions.cs | 3 +- .../Core/PackagePrune/Prune.cs | 5 +-- src/Tetrifact.Tests/Helpers/MoqHelper.cs | 17 ++++++++ 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/Tetrifact.Core/IndexReadService.cs b/src/Tetrifact.Core/IndexReadService.cs index a0f28c3..d75385c 100644 --- a/src/Tetrifact.Core/IndexReadService.cs +++ b/src/Tetrifact.Core/IndexReadService.cs @@ -59,12 +59,12 @@ public void Initialize() Directory.CreateDirectory(_settings.PackageDiffsPath); } - public virtual bool PackageExists(string packageId) + bool IIndexReadService.PackageExists(string packageId) { - return this.GetManifest(packageId) != null; + return ((IIndexReadService)this).GetManifest(packageId) != null; } - public void WriteManifest(string packageId, Manifest manifest) + void IIndexReadService.WriteManifest(string packageId, Manifest manifest) { string targetFolder = Path.Join(_settings.PackagePath, packageId); string packageTempPath = Path.Join(targetFolder, "~manifest.json"); @@ -94,7 +94,7 @@ public void WriteManifest(string packageId, Manifest manifest) _fileSystem.File.Move(packageHeadTempPath, packageHeadPath); } - public void UpdatePackageCreateDate(string packageId, string createdUtcDate) + void IIndexReadService.UpdatePackageCreateDate(string packageId, string createdUtcDate) { DateTime created; @@ -107,37 +107,37 @@ public void UpdatePackageCreateDate(string packageId, string createdUtcDate) throw new FormatException($"{createdUtcDate} could not be parsed into a datetime"); } - Manifest manifest = this.GetManifest(packageId); + Manifest manifest = ((IIndexReadService)this).GetManifest(packageId); if (manifest == null) throw new PackageNotFoundException(packageId); manifest.CreatedUtc = created; - this.WriteManifest(packageId, manifest); + ((IIndexReadService)this).WriteManifest(packageId, manifest); } - public IEnumerable GetAllPackageIds() + IEnumerable IIndexReadService.GetAllPackageIds() { IEnumerable rawList = _fileSystem.Directory.GetDirectories(_settings.PackagePath); return rawList.Select(r => Path.GetFileName(r)); } - public bool PackageNameInUse(string id) + bool IIndexReadService.PackageNameInUse(string id) { string packagePath = Path.Join(_settings.PackagePath, id); return _fileSystem.Directory.Exists(packagePath); } - public virtual Manifest GetExpectedManifest(string packageId) + Manifest IIndexReadService.GetExpectedManifest(string packageId) { - Manifest manifest = this.GetManifest(packageId); + Manifest manifest = ((IIndexReadService)this).GetManifest(packageId); if (manifest == null) throw new PackageNotFoundException(packageId); return manifest; } - public virtual FileOnDiskProperties GetRepositoryFileProperties(string path, string hash) + FileOnDiskProperties IIndexReadService.GetRepositoryFileProperties(string path, string hash) { string filePath = Path.Join(_settings.RepositoryPath, path, hash, "bin"); if (!File.Exists(filePath)) @@ -148,12 +148,12 @@ public virtual FileOnDiskProperties GetRepositoryFileProperties(string path, str return new FileOnDiskProperties { Hash = hash, Size = fileInfo.Length }; } - public virtual Manifest GetManifest(string packageId) + Manifest IIndexReadService.GetManifest(string packageId) { return this.GetManifest(packageId, "manifest.json"); } - public virtual Manifest GetManifestHead(string packageId) + Manifest IIndexReadService.GetManifestHead(string packageId) { return this.GetManifest(packageId, "manifest-head.json"); } @@ -180,7 +180,7 @@ private Manifest GetManifest(string packageId, string type) } } - public GetFileResponse GetFile(string id) + GetFileResponse IIndexReadService.GetFile(string id) { FileIdentifier fileIdentifier = FileIdentifier.Decloak(id); string directFilePath = Path.Combine(_settings.RepositoryPath, fileIdentifier.Path, fileIdentifier.Hash, "bin"); @@ -191,9 +191,9 @@ public GetFileResponse GetFile(string id) return null; } - public (bool, string) VerifyPackage(string packageId) + (bool, string) IIndexReadService.VerifyPackage(string packageId) { - Manifest manifest = this.GetManifest(packageId); + Manifest manifest = ((IIndexReadService)this).GetManifest(packageId); if (manifest == null) throw new PackageNotFoundException(packageId); @@ -230,12 +230,12 @@ public GetFileResponse GetFile(string id) return (true, string.Empty); } - public virtual void DeletePackage(string packageId) + void IIndexReadService.DeletePackage(string packageId) { if (!_settings.AllowPackageDelete) throw new OperationNowAllowedException(); - Manifest manifest = this.GetManifest(packageId); + Manifest manifest = ((IIndexReadService)this).GetManifest(packageId); if (manifest == null) throw new PackageNotFoundException(packageId); @@ -290,7 +290,7 @@ public virtual void DeletePackage(string packageId) } } - public DiskUseStats GetDiskUseSats() + DiskUseStats IIndexReadService.GetDiskUseSats() { string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); DriveInfo drive = new DriveInfo(path); @@ -302,7 +302,7 @@ public DiskUseStats GetDiskUseSats() return stats; } - public PartialPackageLookupResult FindExisting(PartialPackageLookupArguments newPackage) + PartialPackageLookupResult IIndexReadService.FindExisting(PartialPackageLookupArguments newPackage) { IList existing = new List(); diff --git a/src/Tetrifact.Core/LinqExtensions.cs b/src/Tetrifact.Core/LinqExtensions.cs index f0fbb80..e59bf59 100644 --- a/src/Tetrifact.Core/LinqExtensions.cs +++ b/src/Tetrifact.Core/LinqExtensions.cs @@ -14,8 +14,7 @@ public static class LINQExtension /// public static bool IsSubsetOf(this ICollection mainCollection, ICollection subCollection) { - bool isSubset = !mainCollection.Except(subCollection).Any(); - return isSubset; + return !mainCollection.Except(subCollection).Any(); } } diff --git a/src/Tetrifact.Tests/Core/PackagePrune/Prune.cs b/src/Tetrifact.Tests/Core/PackagePrune/Prune.cs index fa613f8..7b7a5f7 100644 --- a/src/Tetrifact.Tests/Core/PackagePrune/Prune.cs +++ b/src/Tetrifact.Tests/Core/PackagePrune/Prune.cs @@ -159,8 +159,7 @@ public void Prune_Disabled() public void Prune_Missing_Manifest() { Settings.PruneWeeklyKeep = 0; - - Mock mockedIndexReader = base.MockRepository.Create(Settings, TagService, IndexReaderLogger, FileSystem, HashServiceHelper.Instance(), LockProvider); + Mock mockedIndexReader = MoqHelper.CreateMockWithDependencies(new object[]{ Settings, TagService, IndexReaderLogger, FileSystem, HashServiceHelper.Instance(), LockProvider }); mockedIndexReader .Setup(r => r.GetManifest(It.IsAny())) .Returns(null); @@ -180,7 +179,7 @@ public void Prune_Missing_Manifest() [Fact] public void Prune_Delete_Exception() { - Mock mockedIndexReader = base.MockRepository.Create(Settings, TagService, IndexReaderLogger, FileSystem, HashServiceHelper.Instance(), LockProvider); + Mock mockedIndexReader = MoqHelper.CreateMockWithDependencies(new object[]{ Settings, TagService, IndexReaderLogger, FileSystem, HashServiceHelper.Instance(), LockProvider }).As(); mockedIndexReader .Setup(r => r.DeletePackage(It.IsAny())) .Callback(() => { diff --git a/src/Tetrifact.Tests/Helpers/MoqHelper.cs b/src/Tetrifact.Tests/Helpers/MoqHelper.cs index 9737b19..2abd0c0 100644 --- a/src/Tetrifact.Tests/Helpers/MoqHelper.cs +++ b/src/Tetrifact.Tests/Helpers/MoqHelper.cs @@ -113,5 +113,22 @@ public static T CreateInstanceWithDependencies(object[] dependencies) where T { return repo.Create(CtorArgs(typeof(T), dependencies, false).ToArray()).Object; } + + public static Mock CreateMockWithDependencies(object[] dependencies) where T : class + { + return repo.Create(CtorArgs(typeof(T), dependencies, false).ToArray()); + } + + /// + /// Creates a mock of some concrete type, force casting it to an interface + /// + /// + /// + /// + /// + public static Mock CreateMockWithDependencies(object[] dependencies) where TConcrete : class where TInterfaceOut : class + { + return repo.Create(CtorArgs(typeof(TConcrete), dependencies, false).ToArray()).As(); + } } }