refactor: Use raw string literals for multi-line strings

json-serializing-nullable-fields-issue
Robert Dailey 10 months ago
parent c3b7e973b2
commit c190d5e6d6

@ -2,162 +2,167 @@ namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Api.Schemas;
public static class SonarrReleaseProfileSchema public static class SonarrReleaseProfileSchema
{ {
public static string V1 => @"{ public static string V1 =>
'definitions': { """
'SonarrPreferredTerm': { {
'type': [ "definitions": {
'object', "SonarrPreferredTerm": {
'null' "type": [
], "object",
'properties': { "null"
'key': { ],
'type': [ "properties": {
'string', "key": {
'null' "type": [
] "string",
}, "null"
'value': { ]
'type': 'integer' },
"value": {
"type": "integer"
}
}
}
},
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"enabled": {
"type": "boolean"
},
"name": {
"type": [
"string",
"null"
]
},
"required": {
"type": [
"string",
"null"
]
},
"ignored": {
"type": [
"string",
"null"
]
},
"preferred": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/SonarrPreferredTerm"
}
},
"includePreferredWhenRenaming": {
"type": "boolean"
},
"indexerId": {
"type": "integer"
},
"tags": {
"type": [
"array",
"null"
],
"items": {
"type": "integer"
}
}
}
} }
} """;
}
},
'type': 'object',
'properties': {
'id': {
'type': 'integer'
},
'enabled': {
'type': 'boolean'
},
'name': {
'type': [
'string',
'null'
]
},
'required': {
'type': [
'string',
'null'
]
},
'ignored': {
'type': [
'string',
'null'
]
},
'preferred': {
'type': [
'array',
'null'
],
'items': {
'$ref': '#/definitions/SonarrPreferredTerm'
}
},
'includePreferredWhenRenaming': {
'type': 'boolean'
},
'indexerId': {
'type': 'integer'
},
'tags': {
'type': [
'array',
'null'
],
'items': {
'type': 'integer'
}
}
}
}";
public static string V2 => @"{ public static string V2 =>
'definitions': { """
'SonarrPreferredTerm': { {
'type': [ "definitions": {
'object', "SonarrPreferredTerm": {
'null' "type": [
], "object",
'properties': { "null"
'key': { ],
'type': [ "properties": {
'string', "key": {
'null' "type": [
] "string",
}, "null"
'value': { ]
'type': 'integer' },
"value": {
"type": "integer"
}
}
}
},
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"enabled": {
"type": "boolean"
},
"name": {
"type": [
"string",
"null"
]
},
"required": {
"type": [
"array",
"null"
],
"items": {
"type": [
"string",
"null"
]
}
},
"ignored": {
"type": [
"array",
"null"
],
"items": {
"type": [
"string",
"null"
]
}
},
"preferred": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/SonarrPreferredTerm"
}
},
"includePreferredWhenRenaming": {
"type": "boolean"
},
"indexerId": {
"type": "integer"
},
"tags": {
"type": [
"array",
"null"
],
"items": {
"type": "integer"
}
}
}
} }
} """;
}
},
'type': 'object',
'properties': {
'id': {
'type': 'integer'
},
'enabled': {
'type': 'boolean'
},
'name': {
'type': [
'string',
'null'
]
},
'required': {
'type': [
'array',
'null'
],
'items': {
'type': [
'string',
'null'
]
}
},
'ignored': {
'type': [
'array',
'null'
],
'items': {
'type': [
'string',
'null'
]
}
},
'preferred': {
'type': [
'array',
'null'
],
'items': {
'$ref': '#/definitions/SonarrPreferredTerm'
}
},
'includePreferredWhenRenaming': {
'type': 'boolean'
},
'indexerId': {
'type': 'integer'
},
'tags': {
'type': [
'array',
'null'
],
'items': {
'type': 'integer'
}
}
}
}
";
} }

@ -26,10 +26,11 @@ public class BaseCommandSetupIntegrationTest : CliIntegrationFixture
{ {
const int maxFiles = 25; const int maxFiles = 25;
Fs.AddFile(Paths.AppDataDirectory.File("settings.yml").FullName, new MockFileData($@" Fs.AddFile(Paths.AppDataDirectory.File("settings.yml").FullName, new MockFileData(
log_janitor: $"""
max_files: {maxFiles} log_janitor:
")); max_files: {maxFiles}
"""));
for (var i = 0; i < maxFiles + 20; ++i) for (var i = 0; i < maxFiles + 20; ++i)
{ {

@ -113,9 +113,12 @@ public class ServiceCacheTest
var expectedFile = fs.GetFile(testJsonPath); var expectedFile = fs.GetFile(testJsonPath);
expectedFile.Should().NotBeNull(); expectedFile.Should().NotBeNull();
expectedFile.TextContents.Should().Be(@"{ expectedFile.TextContents.Should().Be(
""test_value"": ""Foo"" """
}"); {
"test_value": "Foo"
}
""");
} }
[Test, AutoMockData] [Test, AutoMockData]
@ -184,18 +187,19 @@ public class ServiceCacheTest
IServiceConfiguration config, IServiceConfiguration config,
ServiceCache sut) ServiceCache sut)
{ {
const string cacheJson = @" const string cacheJson =
{ """
'version': 1, {
'trash_id_mappings': [ 'version': 1,
{ 'trash_id_mappings': [
'custom_format_name': '4K Remaster', {
'trash_id': 'eca37840c13c6ef2dd0262b141a5482f', 'custom_format_name': '4K Remaster',
'custom_format_id': 4 'trash_id': 'eca37840c13c6ef2dd0262b141a5482f',
} 'custom_format_id': 4
] }
} ]
"; }
""";
fs.AddFile("cacheFile.json", new MockFileData(cacheJson)); fs.AddFile("cacheFile.json", new MockFileData(cacheJson));
storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New("cacheFile.json")); storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New("cacheFile.json"));

@ -13,32 +13,33 @@ public class FieldsArrayJsonConverterTest
{ {
var serializer = new NewtonsoftJsonSerializer(ServiceJsonSerializerFactory.Settings); var serializer = new NewtonsoftJsonSerializer(ServiceJsonSerializerFactory.Settings);
const string json = @" const string json =
{ """
'fields': [ {
{ "fields": [
'order': 0, {
'name': 'min', "order": 0,
'label': 'Minimum Size', "name": "min",
'unit': 'GB', "label": "Minimum Size",
'helpText': 'Release must be greater than this size', "unit": "GB",
'value': 25, "helpText": "Release must be greater than this size",
'type': 'number', "value": 25,
'advanced': false "type": "number",
}, "advanced": false
{ },
'order': 1, {
'name': 'max', "order": 1,
'label': 'Maximum Size', "name": "max",
'unit': 'GB', "label": "Maximum Size",
'helpText': 'Release must be less than or equal to this size', "unit": "GB",
'value': 40, "helpText": "Release must be less than or equal to this size",
'type': 'number', "value": 40,
'advanced': false "type": "number",
} "advanced": false
] }
} ]
"; }
""";
var result = serializer.Deserialize<CustomFormatSpecificationData>(json); var result = serializer.Deserialize<CustomFormatSpecificationData>(json);
result.Fields.Should().BeEquivalentTo(new[] result.Fields.Should().BeEquivalentTo(new[]
@ -59,20 +60,21 @@ public class FieldsArrayJsonConverterTest
{ {
var serializer = new NewtonsoftJsonSerializer(ServiceJsonSerializerFactory.Settings); var serializer = new NewtonsoftJsonSerializer(ServiceJsonSerializerFactory.Settings);
const string json = @" const string json =
{ """
'fields': { {
'order': 0, "fields": {
'name': 'min', "order": 0,
'label': 'Minimum Size', "name": "min",
'unit': 'GB', "label": "Minimum Size",
'helpText': 'Release must be greater than this size', "unit": "GB",
'value': 25, "helpText": "Release must be greater than this size",
'type': 'number', "value": 25,
'advanced': false "type": "number",
} "advanced": false
} }
"; }
""";
var result = serializer.Deserialize<CustomFormatSpecificationData>(json); var result = serializer.Deserialize<CustomFormatSpecificationData>(json);
result.Fields.Should().BeEquivalentTo(new[] result.Fields.Should().BeEquivalentTo(new[]
@ -89,11 +91,12 @@ public class FieldsArrayJsonConverterTest
{ {
var serializer = new NewtonsoftJsonSerializer(ServiceJsonSerializerFactory.Settings); var serializer = new NewtonsoftJsonSerializer(ServiceJsonSerializerFactory.Settings);
const string json = @" const string json =
{ """
'fields': 0 {
} "fields": 0
"; }
""";
var act = () => serializer.Deserialize<CustomFormatSpecificationData>(json); var act = () => serializer.Deserialize<CustomFormatSpecificationData>(json);
act.Should().Throw<InvalidOperationException>(); act.Should().Throw<InvalidOperationException>();

@ -17,17 +17,19 @@ public class QualityGuideServiceTest : CliIntegrationFixture
public void Get_data_for_service(SupportedServices service, string serviceDir) public void Get_data_for_service(SupportedServices service, string serviceDir)
{ {
var repo = Resolve<ITrashGuidesRepo>(); var repo = Resolve<ITrashGuidesRepo>();
const string metadataJson = @" const string metadataJson =
{ """
'json_paths': { {
'radarr': { "json_paths": {
'qualities': ['docs/json/radarr/quality-size'] "radarr": {
}, "qualities": ["docs/json/radarr/quality-size"]
'sonarr': { },
'qualities': ['docs/json/sonarr/quality-size'] "sonarr": {
} "qualities": ["docs/json/sonarr/quality-size"]
} }
}"; }
}
""";
Fs.AddFile(repo.Path.File("metadata.json"), new MockFileData(metadataJson)); Fs.AddFile(repo.Path.File("metadata.json"), new MockFileData(metadataJson));

@ -17,12 +17,13 @@ public class ConfigManipulatorTest : CliIntegrationFixture
var src = Fs.CurrentDirectory().File("template.yml"); var src = Fs.CurrentDirectory().File("template.yml");
var dst = Fs.CurrentDirectory().SubDir("one", "two", "three").File("config.yml"); var dst = Fs.CurrentDirectory().SubDir("one", "two", "three").File("config.yml");
const string yamlData = @" const string yamlData =
sonarr: """
instance1: sonarr:
base_url: http://localhost:80 instance1:
api_key: 123abc base_url: http://localhost:80
"; api_key: 123abc
""";
Fs.AddFile(src, new MockFileData(yamlData)); Fs.AddFile(src, new MockFileData(yamlData));
@ -38,11 +39,12 @@ sonarr:
var src = Fs.CurrentDirectory().File("template.yml"); var src = Fs.CurrentDirectory().File("template.yml");
var dst = Fs.CurrentDirectory().File("config.yml"); var dst = Fs.CurrentDirectory().File("config.yml");
const string yamlData = @" const string yamlData =
sonarr: """
instance1: sonarr:
invalid: yaml instance1:
"; invalid: yaml
""";
Fs.AddFile(src, new MockFileData(yamlData)); Fs.AddFile(src, new MockFileData(yamlData));

@ -93,25 +93,27 @@ public class TemplateConfigCreatorTest : CliIntegrationFixture
[Test] [Test]
public async Task Template_id_matching_works() public async Task Template_id_matching_works()
{ {
const string templatesJson = @" const string templatesJson =
{ """
'radarr': [ {
{ "radarr": [
'template': 'template-file1.yml', {
'id': 'template1' "template": "template-file1.yml",
} "id": "template1"
], }
'sonarr': [ ],
{ "sonarr": [
'template': 'template-file2.yml', {
'id': 'template2' "template": "template-file2.yml",
}, "id": "template2"
{ },
'template': 'template-file3.yml', {
'id': 'template3' "template": "template-file3.yml",
} "id": "template3"
] }
}"; ]
}
""";
var repo = Resolve<IConfigTemplatesRepo>(); var repo = Resolve<IConfigTemplatesRepo>();
Fs.AddFile(repo.Path.File("templates.json"), new MockFileData(templatesJson)); Fs.AddFile(repo.Path.File("templates.json"), new MockFileData(templatesJson));

@ -14,11 +14,12 @@ public class ServiceCompatibilityIntegrationTest : CliIntegrationFixture
var sut = Resolve<SettingsProvider>(); var sut = Resolve<SettingsProvider>();
// For this test, it doesn't really matter if the YAML data matches what SettingsValue expects. // For this test, it doesn't really matter if the YAML data matches what SettingsValue expects.
// This test only ensures that the data deserialized is from the actual correct file. // This test only ensures that the data deserialized is from the actual correct file.
const string yamlData = @" const string yamlData =
repositories: """
trash_guides: repositories:
clone_url: http://the_url.com trash_guides:
"; clone_url: http://the_url.com
""";
Fs.AddFile(Paths.AppDataDirectory.File("settings.yml"), new MockFileData(yamlData)); Fs.AddFile(Paths.AppDataDirectory.File("settings.yml"), new MockFileData(yamlData));

@ -58,12 +58,13 @@ public class ConfigSaverTest : TrashLibIntegrationFixture
sut.Save(config, destFile); sut.Save(config, destFile);
var expectedYaml = @" var expectedYaml =
radarr: """
instance1: radarr:
api_key: apikey instance1:
base_url: http://baseurl.com api_key: apikey
".TrimStart(); base_url: http://baseurl.com
""";
var expectedFile = Fs.GetFile(destFile); var expectedFile = Fs.GetFile(destFile);
expectedFile.Should().NotBeNull(); expectedFile.Should().NotBeNull();

@ -17,12 +17,13 @@ public class ConfigurationLoaderEnvVarTest : TrashLibIntegrationFixture
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance: sonarr:
api_key: !env_var SONARR_API_KEY instance:
base_url: !env_var SONARR_URL http://sonarr:1233 api_key: !env_var SONARR_API_KEY
"; base_url: !env_var SONARR_URL http://sonarr:1233
""";
var config = sut.Load(testYml); var config = sut.Load(testYml);
@ -41,12 +42,13 @@ sonarr:
{ {
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance: sonarr:
base_url: !env_var SONARR_URL http://sonarr:1233 instance:
api_key: value base_url: !env_var SONARR_URL http://sonarr:1233
"; api_key: value
""";
var config = sut.Load(testYml); var config = sut.Load(testYml);
config.Should().BeEquivalentTo(new[] config.Should().BeEquivalentTo(new[]
@ -66,12 +68,13 @@ sonarr:
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance: sonarr:
base_url: !env_var SONARR_URL http://somevalue instance:
api_key: value base_url: !env_var SONARR_URL http://somevalue
"; api_key: value
""";
var config = sut.Load(testYml); var config = sut.Load(testYml);
config.Should().BeEquivalentTo(new[] config.Should().BeEquivalentTo(new[]
@ -91,12 +94,13 @@ sonarr:
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance: sonarr:
base_url: !env_var SONARR_URL ""http://theurl"" instance:
api_key: !env_var SONARR_API 'the key' base_url: !env_var SONARR_URL "http://theurl"
"; api_key: !env_var SONARR_API 'the key'
""";
var config = sut.Load(testYml); var config = sut.Load(testYml);
config.Should().BeEquivalentTo(new[] config.Should().BeEquivalentTo(new[]
@ -114,12 +118,13 @@ sonarr:
{ {
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance: sonarr:
base_url: !env_var SONARR_URL http://somevalue instance:
api_key: value base_url: !env_var SONARR_URL http://somevalue
"; api_key: value
""";
var config = sut.Load(testYml); var config = sut.Load(testYml);
config.Should().BeEquivalentTo(new[] config.Should().BeEquivalentTo(new[]
@ -136,12 +141,13 @@ sonarr:
{ {
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = $@" const string testYml =
sonarr: $"""
instance: sonarr:
base_url: !env_var SONARR_URL {"\t"}http://somevalue instance:
api_key: value base_url: !env_var SONARR_URL {"\t"}http://somevalue
"; api_key: value
""";
var config = sut.Load(testYml); var config = sut.Load(testYml);
config.Should().BeEquivalentTo(new[] config.Should().BeEquivalentTo(new[]
@ -158,12 +164,13 @@ sonarr:
{ {
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance: sonarr:
base_url: !env_var SONARR_URL instance:
api_key: value base_url: !env_var SONARR_URL
"; api_key: value
""";
var result = sut.Load(testYml); var result = sut.Load(testYml);
result.Should().BeEmpty(); result.Should().BeEmpty();

@ -15,21 +15,23 @@ public class ConfigurationLoaderSecretsTest : TrashLibIntegrationFixture
{ {
var configLoader = Resolve<ConfigurationLoader>(); var configLoader = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance1: sonarr:
api_key: !secret api_key instance1:
base_url: !secret 123GARBAGE_ api_key: !secret api_key
release_profiles: base_url: !secret 123GARBAGE_
- trash_ids: release_profiles:
- !secret secret_rp - trash_ids:
"; - !secret secret_rp
""";
const string secretsYml = @"
api_key: 95283e6b156c42f3af8a9b16173f876b const string secretsYml =
123GARBAGE_: 'https://radarr:7878' """
secret_rp: 1234567 api_key: 95283e6b156c42f3af8a9b16173f876b
"; 123GARBAGE_: 'https://radarr:7878'
secret_rp: 1234567
""";
Fs.AddFile(Paths.AppDataDirectory.File("secrets.yml").FullName, new MockFileData(secretsYml)); Fs.AddFile(Paths.AppDataDirectory.File("secrets.yml").FullName, new MockFileData(secretsYml));
var expected = new[] var expected = new[]
@ -59,12 +61,13 @@ secret_rp: 1234567
using var logContext = TestCorrelator.CreateContext(); using var logContext = TestCorrelator.CreateContext();
var configLoader = Resolve<ConfigurationLoader>(); var configLoader = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance2: sonarr:
api_key: !secret api_key instance2:
base_url: fake_url api_key: !secret api_key
"; base_url: fake_url
""";
const string secretsYml = "no_api_key: 95283e6b156c42f3af8a9b16173f876b"; const string secretsYml = "no_api_key: 95283e6b156c42f3af8a9b16173f876b";
@ -79,12 +82,13 @@ sonarr:
{ {
var configLoader = Resolve<ConfigurationLoader>(); var configLoader = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance3: sonarr:
api_key: !secret api_key instance3:
base_url: fake_url api_key: !secret api_key
"; base_url: fake_url
""";
var result = configLoader.Load(() => new StringReader(testYml), SupportedServices.Sonarr); var result = configLoader.Load(() => new StringReader(testYml), SupportedServices.Sonarr);
result.Should().BeEmpty(); result.Should().BeEmpty();
@ -95,12 +99,13 @@ sonarr:
{ {
var configLoader = Resolve<ConfigurationLoader>(); var configLoader = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance4: sonarr:
api_key: !secret { property: value } instance4:
base_url: fake_url api_key: !secret { property: value }
"; base_url: fake_url
""";
var result = configLoader.Load(() => new StringReader(testYml), SupportedServices.Sonarr); var result = configLoader.Load(() => new StringReader(testYml), SupportedServices.Sonarr);
result.Should().BeEmpty(); result.Should().BeEmpty();
@ -111,13 +116,14 @@ sonarr:
{ {
var configLoader = Resolve<ConfigurationLoader>(); var configLoader = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
sonarr: """
instance5: sonarr:
api_key: fake_key instance5:
base_url: fake_url api_key: fake_key
release_profiles: !secret bogus_profile base_url: fake_url
"; release_profiles: !secret bogus_profile
""";
const string secretsYml = @"bogus_profile: 95283e6b156c42f3af8a9b16173f876b"; const string secretsYml = @"bogus_profile: 95283e6b156c42f3af8a9b16173f876b";

@ -37,10 +37,12 @@ public class ConfigurationLoaderTest : TrashLibIntegrationFixture
static string MockYaml(string sectionName, params object[] args) static string MockYaml(string sectionName, params object[] args)
{ {
var str = new StringBuilder($"{sectionName}:"); var str = new StringBuilder($"{sectionName}:");
const string templateYaml = @" const string templateYaml =
instance{1}: """
base_url: http://{0} instance{1}:
api_key: abc"; base_url: http://{0}
api_key: abc
""";
var counter = 0; var counter = 0;
str.Append(args.Aggregate("", (current, p) => current + templateYaml.FormatWith(p, counter++))); str.Append(args.Aggregate("", (current, p) => current + templateYaml.FormatWith(p, counter++)));
@ -124,12 +126,13 @@ public class ConfigurationLoaderTest : TrashLibIntegrationFixture
using var logContext = TestCorrelator.CreateContext(); using var logContext = TestCorrelator.CreateContext();
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = @" const string testYml =
not_wanted: """
instance: not_wanted:
base_url: abc instance:
api_key: xyz base_url: abc
"; api_key: xyz
""";
sut.Load(testYml, SupportedServices.Sonarr); sut.Load(testYml, SupportedServices.Sonarr);

Loading…
Cancel
Save