diff --git a/ImageDescriber.Test/ImportTests.cs b/ImageDescriber.Test/ImportTests.cs index aa93966..402f17a 100644 --- a/ImageDescriber.Test/ImportTests.cs +++ b/ImageDescriber.Test/ImportTests.cs @@ -288,4 +288,36 @@ public void ParseCsvRecordsKeepsLineBreaksInsideQuotedFields() Assert.AreSequenceEqual(ExpectedEscapedQuoteRecord, records[2].Fields); Assert.AreEqual(5, records[2].LineNumber); } + + [TestMethod] + [DataRow("{\"not\":\"array\"}")] + [DataRow("[{\"Hash\":\"aaaa")] + [DataRow("[{\"Hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"FileSizeBytes\":\"abc\"}]")] + [DataRow("[{\"Hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"KnownPaths\":[\"relative/photo.jpg\"]}]")] + public void RunReportsAMalformedJsonFileWithoutThrowingOrChangingTheStore(string json) + { + PersistentState originalSettings = Program.Settings; + TextWriter originalOut = Console.Out; + string file = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".json"); + File.WriteAllText(file, json); + try + { + Program.Settings = new PersistentState(); + using StringWriter output = new(); + Console.SetOut(output); + + Import import = new(); + import.Run(new Import { InputPath = file }); + + Assert.IsTrue(import.Failed); + Assert.IsEmpty(Program.Settings.Descriptions); + Assert.Contains(file, output.ToString()); + } + finally + { + Console.SetOut(originalOut); + Program.Settings = originalSettings; + File.Delete(file); + } + } } diff --git a/ImageDescriber/Verbs/Import.cs b/ImageDescriber/Verbs/Import.cs index af94f88..522d12f 100644 --- a/ImageDescriber/Verbs/Import.cs +++ b/ImageDescriber/Verbs/Import.cs @@ -162,10 +162,26 @@ internal static bool MergeKnownPaths(ImageDescription source, ImageDescription t return pathsAdded; } - private static List ImportJson(AbsoluteFilePath inputPath) + private static List? ImportJson(AbsoluteFilePath inputPath) { string json = File.ReadAllText(inputPath.WeakString); - return JsonSerializer.Deserialize>(json, JsonOptions) ?? []; + + // A truncated or hand-edited file, or one that isn't an export at all, has to end the + // import before anything is merged or saved, not end the process with a stack trace. + try + { + return JsonSerializer.Deserialize>(json, JsonOptions) ?? []; + } + catch (JsonException ex) + { + Console.WriteLine($"Error: {inputPath} is not a valid ImageDescriber JSON export: {ex.Message}"); + return null; + } + catch (ArgumentException ex) + { + Console.WriteLine($"Error: {inputPath} is not a valid ImageDescriber JSON export: {ex.Message}"); + return null; + } } private static List ImportCsv(AbsoluteFilePath inputPath) =>