mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-09 18:36:27 +00:00
* Core - Fix default headset layouts and the z-index of devices placed by the auto-arrange fallback - Headsets with 2 or 4 LEDs get their default layout, the files are named "... LEDs headset.xml" - Devices placed by the auto-arrange fallback get the z-index of their type * Core - Fall back to the default value passed to GetSetting when a stored setting can't be read * Core - Fix storing, cloning and migrating profiles - New profiles are stored at the current migration version, so they're no longer migrated on every start and can be duplicated right away - A category created at the top is listed first right away, so its profiles render on top - CloneProfileConfiguration makes a copy of the stored data, saving a clone stores its profile through the original - Importing a profile raises ProfileAdded - A profile configuration that isn't valid JSON is logged and skipped instead of stopping all migrations - A stored profile without a version is migrated from version 0, like an imported one - ProfileCategoryRepository.IsUnique returns true when the name is free - M0001 and M0004 leave a missing node type or storage out instead of writing null - M0003 also unwraps a list that is an item of another list
190 lines
8.4 KiB
C#
190 lines
8.4 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using Artemis.Core.Tests.Infrastructure.Storage;
|
|
using Artemis.Storage.Entities.Profile;
|
|
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
|
using Artemis.Storage.Entities.Profile.Conditions;
|
|
using Artemis.Storage.Migrations.Profile;
|
|
|
|
namespace Artemis.Core.Tests.Storage.Migrations;
|
|
|
|
public class M0003SystemTextJsonTests
|
|
{
|
|
private readonly M0003SystemTextJson _migration = new();
|
|
|
|
[Fact]
|
|
public void Version_IsThree()
|
|
{
|
|
_migration.Version.ShouldBe(3);
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_ConvertsEverythingButNodeStorage()
|
|
{
|
|
(JsonObject configuration, JsonObject profile) = Version2Profile();
|
|
|
|
_migration.Migrate(configuration, profile);
|
|
|
|
// Node storage is a JSON string inside the JSON and is left for M0004, copy what M0004 makes of it
|
|
CopyNodeStorage(OldProfileJson.MigratedConfiguration, configuration);
|
|
CopyNodeStorage(OldProfileJson.MigratedProfile, profile);
|
|
// Only MigrateProfile sets the version
|
|
configuration["Version"] = 4;
|
|
configuration.ShouldBeJson(OldProfileJson.MigratedConfiguration);
|
|
profile.ShouldBeJson(OldProfileJson.MigratedProfile);
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_LeavesNodeStorageAlone()
|
|
{
|
|
(JsonObject configuration, JsonObject profile) = Version2Profile();
|
|
|
|
_migration.Migrate(configuration, profile);
|
|
|
|
configuration.FindNode(OldProfileJson.ActivationDataModelNodeId)["Storage"]!.GetValue<string>()
|
|
.ShouldStartWith("{\"$type\":\"Artemis.Storage.Entities.Profile.DataModelPathEntity, Artemis.Storage\"");
|
|
profile.FindNode(OldProfileJson.LayerBrushNodeId)["Storage"]!.GetValue<string>()
|
|
.ShouldStartWith("{\"$type\":\"Artemis.Core.ColorGradient, Artemis.Core\",\"$values\":[");
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_ProfileCanBeReadAfterwards()
|
|
{
|
|
(JsonObject configuration, JsonObject profile) = Version2Profile();
|
|
|
|
_migration.Migrate(configuration, profile);
|
|
|
|
ProfileEntity entity = profile.Deserialize<ProfileEntity>(Constants.JsonConvertSettings)!;
|
|
entity.Folders.ShouldHaveSingleItem().DisplayCondition.ShouldBeOfType<StaticConditionEntity>();
|
|
LayerEntity layer = entity.Layers.ShouldHaveSingleItem();
|
|
layer.DisplayCondition.ShouldBeOfType<EventConditionEntity>();
|
|
layer.AdaptionHints.Count.ShouldBe(2);
|
|
layer.AdaptionHints[0].ShouldBeOfType<CategoryAdaptionHintEntity>();
|
|
layer.AdaptionHints[1].ShouldBeOfType<KeyboardSectionAdaptionHintEntity>();
|
|
layer.Leds.Count.ShouldBe(2);
|
|
|
|
ProfileConfigurationEntity configurationEntity = configuration.Deserialize<ProfileConfigurationEntity>(Constants.JsonConvertSettings)!;
|
|
configurationEntity.ActivationCondition!.Nodes.Count.ShouldBe(2);
|
|
configurationEntity.EnableHotkey!.Key.ShouldBe(44);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Artemis.Storage.Entities.Profile.AdaptionHints.CategoryAdaptionHintEntity, Artemis.Storage", "Category")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.AdaptionHints.DeviceAdaptionHintEntity, Artemis.Storage", "Device")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.AdaptionHints.KeyboardSectionAdaptionHintEntity, Artemis.Storage", "KeyboardSection")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.AdaptionHints.SingleLedAdaptionHintEntity, Artemis.Storage", "SingleLed")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.Conditions.AlwaysOnConditionEntity, Artemis.Storage", "AlwaysOn")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.Conditions.EventConditionEntity, Artemis.Storage", "Event")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.Conditions.PlayOnceConditionEntity, Artemis.Storage", "PlayOnce")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.Conditions.StaticConditionEntity, Artemis.Storage", "Static")]
|
|
public void Migrate_GivesAdaptionHintsAndConditionsTheirShortTypeName(string oldType, string newType)
|
|
{
|
|
JsonObject profile = new() {["Element"] = new JsonObject {["$type"] = oldType, ["Value"] = 1}};
|
|
|
|
_migration.Migrate(new JsonObject(), profile);
|
|
|
|
profile.ShouldBeJson($$$"""{"Element": {"$type": "{{{newType}}}", "Value": 1}}""");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Artemis.Storage.Entities.Profile.LayerEntity, Artemis.Storage")]
|
|
[InlineData("Artemis.Storage.Entities.Profile.Conditions.SomeNewConditionEntity, Artemis.Storage")]
|
|
[InlineData("Static")]
|
|
public void Migrate_RemovesAnyOtherTypeName(string type)
|
|
{
|
|
JsonObject profile = new() {["$type"] = type, ["Element"] = new JsonObject {["$type"] = type, ["Value"] = 1}};
|
|
|
|
_migration.Migrate(new JsonObject(), profile);
|
|
|
|
profile.ShouldBeJson("""{"Element": {"Value": 1}}""");
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_ReplacesListsByTheirValues_WhateverTheirType()
|
|
{
|
|
JsonObject profile = OldProfileJson.Parse("""
|
|
{
|
|
"Numbers": {"$type": "System.Int32[], System.Private.CoreLib", "$values": [1, 2]},
|
|
"Elements": {"$type": "Something else entirely", "$values": [{"$type": "Element", "Children": {"$type": "List", "$values": []}}]}
|
|
}
|
|
""");
|
|
|
|
_migration.Migrate(new JsonObject(), profile);
|
|
|
|
profile.ShouldBeJson("""{"Numbers": [1, 2], "Elements": [{"Children": []}]}""");
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_ListDirectlyInsideAList_LosesItsValuesWrapperToo()
|
|
{
|
|
JsonObject profile = OldProfileJson.Parse("""
|
|
{"Rows": {"$type": "List", "$values": [{"$type": "List", "$values": [1, 2]}]}}
|
|
""");
|
|
|
|
_migration.Migrate(new JsonObject(), profile);
|
|
|
|
profile.ShouldBeJson("""{"Rows": [[1, 2]]}""");
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_LeavesObjectsInPlainArraysAlone()
|
|
{
|
|
// JSON.NET wrapped every list when it wrote type names, so old profiles have no plain arrays
|
|
const string json = """{"Elements": [{"$type": "Element", "Children": {"$type": "List", "$values": []}}]}""";
|
|
JsonObject profile = OldProfileJson.Parse(json);
|
|
|
|
_migration.Migrate(new JsonObject(), profile);
|
|
|
|
profile.ShouldBeJson(json);
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_ValuesThatAreNull_AreTreatedAsAnObject()
|
|
{
|
|
JsonObject profile = OldProfileJson.Parse("""{"Elements": {"$type": "List", "$values": null}}""");
|
|
|
|
_migration.Migrate(new JsonObject(), profile);
|
|
|
|
profile.ShouldBeJson("""{"Elements": {"$values": null}}""");
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_ValuesThatAreNotAList_Throws()
|
|
{
|
|
JsonObject profile = OldProfileJson.Parse("""{"Elements": {"$type": "List", "$values": 5}}""");
|
|
|
|
Should.Throw<InvalidOperationException>(() => _migration.Migrate(new JsonObject(), profile));
|
|
}
|
|
|
|
[Fact]
|
|
public void Migrate_RunningItOnAMigratedProfile_ChangesNothing()
|
|
{
|
|
JsonObject configuration = OldProfileJson.Parse(OldProfileJson.MigratedConfiguration);
|
|
JsonObject profile = OldProfileJson.Parse(OldProfileJson.MigratedProfile);
|
|
|
|
_migration.Migrate(configuration, profile);
|
|
|
|
// The short type names would be removed (see Migrate_RemovesAnyOtherTypeName), but in a migrated profile they
|
|
// are all inside plain arrays, which this migration doesn't look into
|
|
configuration.ShouldBeJson(OldProfileJson.MigratedConfiguration);
|
|
profile.ShouldBeJson(OldProfileJson.MigratedProfile);
|
|
profile.FindTypeNames().ShouldBe(["Static", "Category", "KeyboardSection", "Event"]);
|
|
}
|
|
|
|
private (JsonObject Configuration, JsonObject Profile) Version2Profile()
|
|
{
|
|
JsonObject configuration = OldProfileJson.Parse(OldProfileJson.Version0Configuration);
|
|
JsonObject profile = OldProfileJson.Parse(OldProfileJson.Version0Profile);
|
|
new M0001NodeProviders().Migrate(configuration, profile);
|
|
new M0002NodeProvidersProfileConfig().Migrate(configuration, profile);
|
|
return (configuration, profile);
|
|
}
|
|
|
|
private static void CopyNodeStorage(string fromJson, JsonNode to)
|
|
{
|
|
JsonNode from = JsonNode.Parse(fromJson)!;
|
|
foreach (JsonObject node in to.FindNodes())
|
|
node["Storage"] = from.FindNode(node["Id"]!.GetValue<string>())["Storage"]!.DeepClone();
|
|
}
|
|
}
|