Compare commits

...

15 Commits

Author SHA1 Message Date
Robert McRackan
2eaac6acc2 Bug fix #210 : if attempting to paginate more than 10,000 titles : "Implied library size is unsupported" 2022-03-17 16:14:12 -04:00
Robert McRackan
03b458765c Bug fix: getting library had errors for libraries with over 10k titles 2022-03-17 13:06:55 -04:00
Robert McRackan
c8b4bc6361 Address issue #199 : Rare users can be unable to handle library scan batch size of 250. When timeout occurs, retry with batch size of 50 2022-03-06 14:21:21 -05:00
rmcrackan
d9b5725ff1 Update README.md
CLI manual change to settings file no longer needed
2022-03-03 16:59:31 -05:00
Robert McRackan
0a0f60192b Better exception logging with Serilog.Exceptions library 2022-03-03 16:54:43 -05:00
Robert McRackan
424d939c15 Update dependencies 2022-03-03 13:02:15 -05:00
Robert McRackan
87f13ff8ed Incr. ver. 2022-02-21 10:26:58 -05:00
Robert McRackan
1e24df626a Add error recovery around FileLocations.json to handle file corruption 2022-02-21 10:24:56 -05:00
Robert McRackan
0312786721 Add description to exports #197 2022-02-14 13:44:54 -05:00
Robert McRackan
1f8a5b256e Bug fix: Defensive FirstOrDefault. #194 2022-02-03 08:53:41 -05:00
rmcrackan
426391f01c Update README.md
Forgot to remove .nfo from readme
2022-01-30 09:55:34 -05:00
Robert McRackan
c296bff47f Bug fix #181 : if audible gives a null picture id then once that image is searched for, no further cover art was downloaded 2022-01-12 22:20:58 -05:00
Robert McRackan
6b649cf4ca Rare duplicates are making their way into the db. Defensive FirstOrDefault added to address bug #184 2022-01-11 07:34:42 -05:00
Robert McRackan
5103240a76 Bugfix: Latest AudibleAPI addresses #175 . Thank you again @mkb79 2022-01-04 11:25:49 -05:00
rmcrackan
c2418b10f6 Update README.md 2022-01-04 10:53:53 -05:00
23 changed files with 249 additions and 94 deletions

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>6.6.2.1</Version>
<Version>6.7.2.1</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -56,7 +56,7 @@ namespace AppScaffolding
// migrations go below here
//
Migrations.migrate_to_v6_5_2(config);
Migrations.migrate_to_v6_6_9(config);
}
public static void PopulateMissingConfigValues(Configuration config)
@@ -107,29 +107,12 @@ namespace AppScaffolding
if (config.GetObject("Serilog") != null)
return;
// "Serilog": {
// "MinimumLevel": "Information"
// "WriteTo": [
// {
// "Name": "Console"
// },
// {
// "Name": "File",
// "Args": {
// "rollingInterval": "Day",
// "outputTemplate": ...
// }
// }
// ],
// "Using": [ "Dinah.Core" ],
// "Enrich": [ "WithCaller" ]
// }
var serilogObj = new JObject
{
{ "MinimumLevel", "Information" },
{ "WriteTo", new JArray
{
new JObject { {"Name", "Console" } },
// new JObject { {"Name", "Console" } }, // this has caused more problems than it's solved
new JObject
{
{ "Name", "File" },
@@ -144,14 +127,16 @@ namespace AppScaffolding
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation
// - with class and method info: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}" }
// {Properties:j} needed for expanded exception logging
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}" }
}
}
}
}
},
{ "Using", new JArray{ "Dinah.Core" } }, // dll's name, NOT namespace
{ "Enrich", new JArray{ "WithCaller" } },
// better exception logging with: Serilog.Exceptions library -- WithExceptionDetails
{ "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace
{ "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } },
};
config.SetObject("Serilog", serilogObj);
}
@@ -166,9 +151,9 @@ namespace AppScaffolding
// capture most Console.WriteLine() and write to serilog. See below tests for details.
// Some dependencies print helpful info via Console.WriteLine. We'd like to log it.
//
// Serilog also writes to Console so this might be asking for trouble. ie: infinite loops.
// SerilogTextWriter needs to be more robust and tested. Esp the Write() methods.
// Empirical testing so far has shown no issues.
// If Serilog also writes to Console, this might be asking for trouble. ie: infinite loops.
// To use that way, SerilogTextWriter needs to be more robust and tested. Esp the Write() methods.
// However, empirical testing so far has shown no issues.
Console.SetOut(new MultiTextWriter(origOut, new SerilogTextWriter()));
#region Console => Serilog tests
@@ -346,9 +331,51 @@ namespace AppScaffolding
};
#endregion
public static void migrate_to_v6_5_2(Configuration config)
public static void migrate_to_v6_6_9(Configuration config)
{
// example
var writeToPath = $"Serilog.WriteTo";
// remove WriteTo[].Name == Console
{
if (UNSAFE_MigrationHelper.Settings_TryGetArrayLength(writeToPath, out var length1))
{
for (var i = length1 - 1; i >= 0; i--)
{
var exists = UNSAFE_MigrationHelper.Settings_TryGetFromJsonPath($"{writeToPath}[{i}].Name", out var value);
if (exists && value == "Console")
UNSAFE_MigrationHelper.Settings_RemoveFromArray(writeToPath, i);
}
}
}
// add Serilog.Exceptions -- WithExceptionDetails
{
// outputTemplate should contain "{Properties:j}"
{
// re-calculate. previous loop may have changed the length
if (UNSAFE_MigrationHelper.Settings_TryGetArrayLength(writeToPath, out var length2))
{
var propertyName = "outputTemplate";
for (var i = 0; i < length2; i++)
{
var jsonPath = $"{writeToPath}[{i}].Args";
var exists = UNSAFE_MigrationHelper.Settings_TryGetFromJsonPath($"{jsonPath}.{propertyName}", out var value);
var newValue = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}";
if (exists && value != newValue)
UNSAFE_MigrationHelper.Settings_SetWithJsonPath(jsonPath, propertyName, newValue);
}
}
}
// Serilog.Using must include "Serilog.Exceptions"
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Using", "Serilog.Exceptions");
// Serilog.Enrich must include "WithExceptionDetails"
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails");
}
}
}
}

View File

@@ -113,6 +113,108 @@ namespace AppScaffolding
return success;
}
public static bool Settings_JsonPathIsType(string jsonPath, JTokenType jTokenType)
{
JToken val = null;
process_SettingsJson(jObj => val = jObj.SelectToken(jsonPath), false);
return val?.Type == jTokenType;
}
public static bool Settings_TryGetFromJsonPath(string jsonPath, out string value)
{
JToken val = null;
process_SettingsJson(jObj => val = jObj.SelectToken(jsonPath), false);
if (val?.Type == JTokenType.String)
{
value = val.Value<string>();
return true;
}
else
{
value = null;
return false;
}
}
public static void Settings_SetWithJsonPath(string jsonPath, string propertyName, string newValue)
{
if (!Settings_TryGetFromJsonPath($"{jsonPath}.{propertyName}", out _))
return;
process_SettingsJson(jObj =>
{
var token = jObj.SelectToken(jsonPath);
if (token is null
|| token is not JObject o
|| o[propertyName] is null)
return;
var oldValue = token.Value<string>(propertyName);
if (oldValue != newValue)
token[propertyName] = newValue;
});
}
public static bool Settings_TryGetArrayLength(string jsonPath, out int length)
{
length = 0;
if (!Settings_JsonPathIsType(jsonPath, JTokenType.Array))
return false;
JArray array = null;
process_SettingsJson(jObj => array = (JArray)jObj.SelectToken(jsonPath));
length = array.Count;
return true;
}
public static void Settings_AddToArray(string jsonPath, string newValue)
{
if (!Settings_JsonPathIsType(jsonPath, JTokenType.Array))
return;
process_SettingsJson(jObj =>
{
var array = (JArray)jObj.SelectToken(jsonPath);
array.Add(newValue);
});
}
/// <summary>Do not add if already exists</summary>
public static void Settings_AddUniqueToArray(string arrayPath, string newValue)
{
if (!Settings_TryGetArrayLength(arrayPath, out var qty))
return;
for (var i = 0; i < qty; i++)
{
var exists = Settings_TryGetFromJsonPath($"{arrayPath}[{i}]", out var value);
if (exists && value == newValue)
return;
}
Settings_AddToArray(arrayPath, newValue);
}
/// <summary>only remove if not exists</summary>
public static void Settings_RemoveFromArray(string jsonPath, int position)
{
if (!Settings_JsonPathIsType(jsonPath, JTokenType.Array))
return;
process_SettingsJson(jObj =>
{
var array = (JArray)jObj.SelectToken(jsonPath);
if (position < array.Count)
array.RemoveAt(position);
});
}
/// <summary>only insert if not exists</summary>
public static void Settings_Insert(string key, string value)
=> process_SettingsJson(jObj => jObj.TryAdd(key, value));

View File

@@ -44,6 +44,9 @@ namespace ApplicationServices
[Name("Length In Minutes")]
public int LengthInMinutes { get; set; }
[Name("Description")]
public string Description { get; set; }
[Name("Publisher")]
public string Publisher { get; set; }
@@ -111,6 +114,7 @@ namespace ApplicationServices
AuthorNames = a.Book.AuthorNames,
NarratorNames = a.Book.NarratorNames,
LengthInMinutes = a.Book.LengthInMinutes,
Description = a.Book.Description,
Publisher = a.Book.Publisher,
HasPdf = a.Book.HasPdf,
SeriesNames = a.Book.SeriesNames,
@@ -180,6 +184,7 @@ namespace ApplicationServices
nameof (ExportDto.AuthorNames),
nameof (ExportDto.NarratorNames),
nameof (ExportDto.LengthInMinutes),
nameof (ExportDto.Description),
nameof (ExportDto.Publisher),
nameof (ExportDto.HasPdf),
nameof (ExportDto.SeriesNames),
@@ -233,6 +238,7 @@ namespace ApplicationServices
row.CreateCell(col++).SetCellValue(dto.AuthorNames);
row.CreateCell(col++).SetCellValue(dto.NarratorNames);
row.CreateCell(col++).SetCellValue(dto.LengthInMinutes);
row.CreateCell(col++).SetCellValue(dto.Description);
row.CreateCell(col++).SetCellValue(dto.Publisher);
row.CreateCell(col++).SetCellValue(dto.HasPdf);
row.CreateCell(col++).SetCellValue(dto.SeriesNames);

View File

@@ -127,11 +127,18 @@ namespace AudibleUtilities
// items = AudibleApi.Common.Converter.FromJson<List<Item>>(System.IO.File.ReadAllText(library_json));
//}
#endif
Serilog.Log.Logger.Debug("Begin initial library scan");
if (!items.Any())
items = await Api.GetAllLibraryItemsAsync(responseGroups);
Serilog.Log.Logger.Debug("Initial library scan complete. Begin episode scan");
await manageEpisodesAsync(items, importEpisodes);
Serilog.Log.Logger.Debug("Episode scan complete");
#if DEBUG
//System.IO.File.WriteAllText(library_json, AudibleApi.Common.Converter.ToJson(items));
#endif
@@ -209,7 +216,8 @@ namespace AudibleUtilities
new Series
{
Asin = parent.Asin,
Sequence = parent.Relationships.Single(r => r.Asin == child.Asin).Sort.ToString(),
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive for malformed data from audible
Sequence = parent.Relationships.FirstOrDefault(r => r.Asin == child.Asin).Sort.ToString(),
Title = parent.TitleWithSubtitle
}
};

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AudibleApi" Version="2.5.1.1" />
<PackageReference Include="AudibleApi" Version="2.7.2.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,12 +13,12 @@
<ItemGroup>
<PackageReference Include="Dinah.EntityFrameworkCore" Version="4.0.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -94,7 +94,8 @@ namespace DtoImporterService
// nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db
var authors = item
.Authors
.Select(a => DbContext.Contributors.Local.Single(c => a.Name == c.Name))
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive
.Select(a => DbContext.Contributors.Local.FirstOrDefault(c => a.Name == c.Name))
.ToList();
var narrators
@@ -104,7 +105,8 @@ namespace DtoImporterService
// nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db
: item
.Narrators
.Select(n => DbContext.Contributors.Local.Single(c => n.Name == c.Name))
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive
.Select(n => DbContext.Contributors.Local.FirstOrDefault(c => n.Name == c.Name))
.ToList();
// categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd
@@ -118,7 +120,8 @@ namespace DtoImporterService
// 2+
: item.Categories[1].CategoryId;
var category = DbContext.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == lastCategory);
// This should properly be SingleOrDefault() not FirstOrDefault(), but FirstOrDefault is defensive
var category = DbContext.Categories.Local.FirstOrDefault(c => c.AudibleCategoryId == lastCategory);
Book book;
try
@@ -154,7 +157,8 @@ namespace DtoImporterService
var publisherName = item.Publisher;
if (!string.IsNullOrWhiteSpace(publisherName))
{
var publisher = DbContext.Contributors.Local.Single(c => publisherName == c.Name);
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive
var publisher = DbContext.Contributors.Local.FirstOrDefault(c => publisherName == c.Name);
book.ReplacePublisher(publisher);
}
@@ -171,7 +175,9 @@ namespace DtoImporterService
var item = importItem.DtoItem;
// set/update book-specific info which may have changed
book.PictureId = item.PictureId;
if (item.PictureId is not null)
book.PictureId = item.PictureId;
book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars);
// important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import

View File

@@ -82,7 +82,8 @@ namespace DtoImporterService
);
foreach (var name in newPeople)
{
var p = groupby.Single(g => g.Name == name).People.First();
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive
var p = groupby.FirstOrDefault(g => g.Name == name).People.First();
try
{

View File

@@ -53,7 +53,8 @@ namespace DtoImporterService
var newItem = gb.ImportItems.First();
var libraryBook = new LibraryBook(
DbContext.Books.Local.Single(b => b.AudibleProductId == newItem.DtoItem.ProductId),
// This should properly be Single() not FirstOrDefault(), but FirstOrDefault is defensive
DbContext.Books.Local.FirstOrDefault(b => b.AudibleProductId == newItem.DtoItem.ProductId),
newItem.DtoItem.DateAdded,
newItem.AccountId);
try

View File

@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="4.0.3.1" />
<PackageReference Include="Polly" Version="7.2.2" />
<PackageReference Include="Dinah.Core" Version="4.0.4.1" />
<PackageReference Include="Polly" Version="7.2.3" />
</ItemGroup>
</Project>

View File

@@ -208,7 +208,7 @@ namespace FileManager
{
var msg = "Unrecoverable error. Settings file cannot be found";
var ex = new FileNotFoundException(msg, Filepath);
Serilog.Log.Logger.Error(msg, ex);
Serilog.Log.Logger.Error(ex, msg);
throw ex;
}
@@ -226,7 +226,7 @@ namespace FileManager
{
var msg = "Unrecoverable error. Unable to read settings from Settings file";
var ex = new NullReferenceException(msg);
Serilog.Log.Logger.Error(msg, ex);
Serilog.Log.Logger.Error(ex, msg);
throw ex;
}

View File

@@ -270,7 +270,7 @@ namespace LibationFileManager
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
if (!valueWasChanged)
{
Log.Logger.Information("LogLevel.set attempt. No change");
Log.Logger.Debug("LogLevel.set attempt. No change");
return;
}

View File

@@ -26,6 +26,18 @@ namespace LibationFileManager
if (File.Exists(jsonFile))
{
var list = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(jsonFile));
// file exists but deser is null. this will never happen when file is healthy
if (list is null)
{
lock (locker)
{
Serilog.Log.Logger.Error("Error deserializing file. Wrong format. Possibly corrupt. Deleting file. {@DebugInfo}", new { jsonFile });
File.Delete(jsonFile);
return;
}
}
cache = new Cache<CacheEntry>(list);
}
}

View File

@@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -73,16 +73,10 @@ namespace LibationFileManager
if (!cache.ContainsKey(def) || cache[def] == null)
{
var path = getPath(def);
byte[] bytes;
if (File.Exists(path))
bytes = File.ReadAllBytes(path);
else
{
bytes = downloadBytes(def);
saveFile(def, bytes);
}
var bytes
= File.Exists(path)
? File.ReadAllBytes(path)
: downloadBytes(def);
cache[def] = bytes;
}
return cache[def];
@@ -104,7 +98,6 @@ namespace LibationFileManager
continue;
var bytes = downloadBytes(def);
saveFile(def, bytes);
lock (cacheLocker)
cache[def] = bytes;
@@ -115,14 +108,24 @@ namespace LibationFileManager
private static HttpClient imageDownloadClient { get; } = new HttpClient();
private static byte[] downloadBytes(PictureDefinition def)
{
var sz = (int)def.Size;
return imageDownloadClient.GetByteArrayAsync("ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}._SL{sz}_.jpg").Result;
}
if (def.PictureId is null)
return getDefaultImage(def.Size);
private static void saveFile(PictureDefinition def, byte[] bytes)
{
var path = getPath(def);
File.WriteAllBytes(path, bytes);
try
{
var sz = (int)def.Size;
var bytes = imageDownloadClient.GetByteArrayAsync("ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}._SL{sz}_.jpg").Result;
// save image file. make sure to not save default image
var path = getPath(def);
File.WriteAllBytes(path, bytes);
return bytes;
}
catch
{
return getDefaultImage(def.Size);
}
}
}
}

View File

@@ -28,7 +28,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="4.0.3.1" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="4.0.4.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -156,7 +156,6 @@ When you set up Libation, you'll specify a Books directory. Libation looks insid
* .m4b: your audiobook in m4b format. This is the most pure version of your audiobook and retains the highest quality. Now that it's decrypted, you can play it on any audio player and put it on any device. If you'd like, you can also use 3rd party tools to turn it into an mp3. The freedom to do what you want with your files was the original inspiration for Libation.
* .cue: this is a file which logs where chapter breaks occur. Many tools are able to use this if you want to split your book into files along chapter lines.
* .nfo: This is just some general info about the book and includes some technical stats about the audiofile.
### Export your library
@@ -244,7 +243,7 @@ To make upgrades and reinstalls easier, Libation separates all of its responsibi
### Linux and Mac
Although Libation only currently officially supports Windows, [some users](https://github.com/rmcrackan/Libation/issues/28#issuecomment-890594158) have had success with WINE.
Although Libation only currently officially supports Windows, some users have had success with WINE. ([Linux](https://github.com/rmcrackan/Libation/issues/28#issuecomment-890594158), [OSX Crossover and WINE](https://github.com/rmcrackan/Libation/issues/150#issuecomment-1004918592))
### Settings
@@ -294,14 +293,3 @@ export library to file
libationcli export -p "C:\foo\bar\my.xlsx" --xlsx
libationcli export -p "C:\foo\bar\my.xlsx" -x
```
Currently logs are written to Console and to file. This means they'll be printed in the CLI. To disable, find this in Settings.json and delete the 3 lines after `"WriteTo": [`
```
"Serilog": {
"MinimumLevel": "Information",
"WriteTo": [
{
"Name": "Console"
},
```

View File

@@ -7,12 +7,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.17.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -7,12 +7,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.17.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>