using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Nodes; using AIStudio.Assistants.SWOTAnalysis; using AIStudio.Assistants.VisualBriefing; using AIStudio.Chat; using AIStudio.Settings; using AIStudio.Settings.DataModel; using AIStudio.Settings.DataModel.PreviousModels; using AIStudio.Tools; using AIStudio.Tools.Databases.ProvenanceStore; using AIStudio.Tools.DataBackup; using Microsoft.Extensions.Logging.Abstractions; using Version = AIStudio.Settings.Version; namespace AIStudio.Tests.Serialization; /// /// Reads and writes stored files of AI Studio, and checks that nothing gets lost and nothing gets written /// otherwise than with the contracts of reflection. /// /// /// The files in the folder Fixtures were written with the contracts of reflection, before AI Studio got /// contracts of its own, and every property holds a value. They are made up: no real chat or setting of /// anybody is among them. A property added later is missing in them, which the tests allow. /// [TestFixture] public sealed class JsonGoldenFileTests { private static IEnumerable GoldenFiles() { TestHost.PrepareStaticState(); yield return new("settings.json", typeof(Data), SettingsManager.JSON_OPTIONS); yield return new("settings-v1.json", typeof(DataV1V3), SettingsManager.JSON_OPTIONS); yield return new("settings-v4.json", typeof(DataV4), SettingsManager.JSON_OPTIONS); yield return new("settings-v5.json", typeof(DataV5), SettingsManager.JSON_OPTIONS); yield return new("chat.json", typeof(ChatThread), WorkspaceBehaviour.JSON_OPTIONS); yield return new("swot-analysis.json", typeof(SwotAnalysisResult), SwotAnalysisContentPresentation.JSON_OPTIONS); yield return new("data-backup-manifest.json", typeof(DataBackupManifest), DataBackupFormat.JSON_OPTIONS); yield return new("data-restore-plan.json", typeof(DataRestorePlan), DataBackupFormat.JSON_OPTIONS); yield return new("data-restore-report.json", typeof(DataRestoreReport), DataBackupFormat.JSON_OPTIONS); yield return new("provenance-surrounding-content.json", typeof(List), SqliteProvenanceStoreClientImplementation.JSON_OPTIONS); // A visual briefing, with the artifacts of its stages, a build, and the header of an exported briefing: yield return new("visual-briefing-manifest.json", typeof(VisualBriefingManifest), VisualBriefingJson.Persistence); yield return new("visual-briefing-evidence-artifact.json", typeof(VisualBriefingEvidenceArtifact), VisualBriefingJson.Persistence); yield return new("visual-briefing-plan-artifact.json", typeof(VisualBriefingPlanArtifact), VisualBriefingJson.Persistence); yield return new("visual-briefing-content-artifact.json", typeof(VisualBriefingContentArtifact), VisualBriefingJson.Persistence); yield return new("visual-briefing-presentation-artifact.json", typeof(VisualBriefingPresentationArtifact), VisualBriefingJson.Persistence); yield return new("visual-briefing-build.json", typeof(VisualBriefingBuildRecord), VisualBriefingJson.Persistence); yield return new("visual-briefing-export-manifest.json", typeof(VisualBriefingExportManifest), VisualBriefingJson.Canonical); } [TestCaseSource(nameof(GoldenFiles))] public void AStoredFileIsWrittenAgainAsWithReflection(JsonGoldenFile goldenFile) { var json = ReadFixture(goldenFile.FileName); var baseline = JsonReflectionBaseline.Of(goldenFile.Options); var writtenAgain = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, goldenFile.Type, goldenFile.Options), goldenFile.Type, goldenFile.Options); var writtenAgainByReflection = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, goldenFile.Type, baseline), goldenFile.Type, baseline); Assert.That(writtenAgain, Is.EqualTo(writtenAgainByReflection)); } [TestCaseSource(nameof(GoldenFiles))] public void AStoredFileKeepsAllItsValues(JsonGoldenFile goldenFile) { var json = ReadFixture(goldenFile.FileName); var writtenAgain = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, goldenFile.Type, goldenFile.Options), goldenFile.Type, goldenFile.Options); Assert.That(JsonContainment.FindMissing(JsonNode.Parse(json), JsonNode.Parse(writtenAgain)), Is.Empty); } [TestCase("settings-v1.json", Version.V1)] [TestCase("settings-v4.json", Version.V4)] [TestCase("settings-v5.json", Version.V5)] public void OldSettingsMigrateAsWithReflection(string fileName, Version version) { var json = ReadFixture(fileName); var baseline = JsonReflectionBaseline.Of(SettingsManager.JSON_OPTIONS); var migrated = SettingsMigrations.Migrate(NullLogger.Instance, version, json, SettingsManager.JSON_OPTIONS); var migratedByReflection = SettingsMigrations.Migrate(NullLogger.Instance, version, json, baseline); Assert.That(JsonSerializer.Serialize(migrated, SettingsManager.JSON_OPTIONS), Is.EqualTo(JsonSerializer.Serialize(migratedByReflection, baseline))); } /// /// Reads a file from the fixtures next to this test. /// /// /// Read from the source tree, the way the other fixtures are, so the file needs no entry in the project file. /// internal static string ReadFixture(string fileName, [CallerFilePath] string sourceFilePath = "") => File.ReadAllText(Path.Combine(Path.GetDirectoryName(sourceFilePath)!, "Fixtures", fileName)); }