diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6c99b022e..9b5d2f46f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -515,6 +515,61 @@ stages: testResultsFiles: '**/TestResult.xml' testRunTitle: '$(testName) Unit Tests' failTaskOnFailedTests: true + + - job: Unit_LinuxCore_Postgres + displayName: Unit Native LinuxCore with Postgres Database + dependsOn: Prepare + condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) + variables: + pattern: 'Radarr.*.linux-core-x64.tar.gz' + artifactName: LinuxCoreTests + Radarr__Postgres__Host: 'localhost' + Radarr__Postgres__Port: '5432' + Radarr__Postgres__User: 'radarr' + Radarr__Postgres__Password: 'radarr' + + pool: + vmImage: 'ubuntu-18.04' + + timeoutInMinutes: 10 + + steps: + - task: UseDotNet@2 + displayName: 'Install .net core' + inputs: + version: $(dotnetVersion) + - checkout: none + - task: DownloadPipelineArtifact@2 + displayName: Download Test Artifact + inputs: + buildType: 'current' + artifactName: $(artifactName) + targetPath: $(testsFolder) + - bash: | + chmod a+x _tests/ffprobe + displayName: Make ffprobe Executable + - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; + displayName: Make Test Dummy Executable + condition: and(succeeded(), ne(variables['osName'], 'Windows')) + - bash: | + docker run -d --name=postgres14 \ + -e POSTGRES_PASSWORD=radarr \ + -e POSTGRES_USER=radarr \ + -p 5432:5432/tcp \ + postgres:14 + displayName: Start postgres + - bash: | + chmod a+x ${TESTSFOLDER}/test.sh + ls -lR ${TESTSFOLDER} + ${TESTSFOLDER}/test.sh Linux Unit Test + displayName: Run Tests + - task: PublishTestResults@2 + displayName: Publish Test Results + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '**/TestResult.xml' + testRunTitle: 'LinuxCore Postgres Unit Tests' + failTaskOnFailedTests: true - stage: Integration displayName: Integration @@ -599,6 +654,67 @@ stages: failTaskOnFailedTests: true displayName: Publish Test Results + - job: Integration_LinuxCore_Postgres + displayName: Integration Native LinuxCore with Postgres Database + dependsOn: Prepare + condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) + variables: + pattern: 'Radarr.*.linux-core-x64.tar.gz' + Radarr__Postgres__Host: 'localhost' + Radarr__Postgres__Port: '5432' + Radarr__Postgres__User: 'radarr' + Radarr__Postgres__Password: 'radarr' + + pool: + vmImage: 'ubuntu-18.04' + + steps: + - task: UseDotNet@2 + displayName: 'Install .net core' + inputs: + version: $(dotnetVersion) + - checkout: none + - task: DownloadPipelineArtifact@2 + displayName: Download Test Artifact + inputs: + buildType: 'current' + artifactName: 'LinuxCoreTests' + targetPath: $(testsFolder) + - task: DownloadPipelineArtifact@2 + displayName: Download Build Artifact + inputs: + buildType: 'current' + artifactName: Packages + itemPattern: '**/$(pattern)' + targetPath: $(Build.ArtifactStagingDirectory) + - task: ExtractFiles@1 + inputs: + archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' + destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' + displayName: Extract Package + - bash: | + mkdir -p ./bin/ + cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ + displayName: Move Package Contents + - bash: | + docker run -d --name=postgres14 \ + -e POSTGRES_PASSWORD=radarr \ + -e POSTGRES_USER=radarr \ + -p 5432:5432/tcp \ + postgres:14 + displayName: Start postgres + - bash: | + chmod a+x ${TESTSFOLDER}/test.sh + ${TESTSFOLDER}/test.sh Linux Integration Test + displayName: Run Integration Tests + - task: PublishTestResults@2 + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '**/TestResult.xml' + testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests' + failTaskOnFailedTests: true + displayName: Publish Test Results + - job: Integration_FreeBSD displayName: Integration Native FreeBSD dependsOn: Prepare diff --git a/src/NzbDrone.Automation.Test/AutomationTest.cs b/src/NzbDrone.Automation.Test/AutomationTest.cs index da3ee0855..5091c7142 100644 --- a/src/NzbDrone.Automation.Test/AutomationTest.cs +++ b/src/NzbDrone.Automation.Test/AutomationTest.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080); - _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger()); + _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null); _runner.KillAll(); _runner.Start(); diff --git a/src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs b/src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs new file mode 100644 index 000000000..303862729 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore +{ + [TestFixture] + public class WhereBuilderPostgresFixture : CoreTest + { + private WhereBuilderPostgres _subject; + + [OneTimeSetUp] + public void MapTables() + { + // Generate table mapping + Mocker.Resolve(); + } + + private WhereBuilderPostgres Where(Expression> filter) + { + return new WhereBuilderPostgres(filter, true, 0); + } + + [Test] + public void postgres_where_equal_const() + { + _subject = Where(x => x.Id == 10); + + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(10); + } + + [Test] + public void postgres_where_equal_variable() + { + var id = 10; + _subject = Where(x => x.Id == id); + + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(id); + } + + [Test] + public void postgres_where_equal_property() + { + var movie = new Movie { Id = 10 }; + _subject = Where(x => x.Id == movie.Id); + + _subject.Parameters.ParameterNames.Should().HaveCount(1); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(movie.Id); + } + + [Test] + public void postgres_where_equal_joined_property() + { + _subject = Where(x => x.Profile.Id == 1); + + _subject.Parameters.ParameterNames.Should().HaveCount(1); + _subject.ToString().Should().Be($"(\"Profiles\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(1); + } + + [Test] + public void postgres_where_throws_without_concrete_condition_if_requiresConcreteCondition() + { + Expression> filter = (x, y) => x.Id == y.Id; + _subject = new WhereBuilderPostgres(filter, true, 0); + Assert.Throws(() => _subject.ToString()); + } + + [Test] + public void postgres_where_allows_abstract_condition_if_not_requiresConcreteCondition() + { + Expression> filter = (x, y) => x.Id == y.Id; + _subject = new WhereBuilderPostgres(filter, false, 0); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = \"Movies\".\"Id\")"); + } + + [Test] + public void postgres_where_string_is_null() + { + _subject = Where(x => x.CleanTitle == null); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" IS NULL)"); + } + + [Test] + public void postgres_where_string_is_null_value() + { + string cleanTitle = null; + _subject = Where(x => x.CleanTitle == cleanTitle); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" IS NULL)"); + } + + [Test] + public void postgres_where_equal_null_property() + { + var movie = new Movie { CleanTitle = null }; + _subject = Where(x => x.CleanTitle == movie.CleanTitle); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" IS NULL)"); + } + + [Test] + public void postgres_where_column_contains_string() + { + var test = "small"; + _subject = Where(x => x.CleanTitle.Contains(test)); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" ILIKE '%' || @Clause1_P1 || '%')"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void postgres_where_string_contains_column() + { + var test = "small"; + _subject = Where(x => test.Contains(x.CleanTitle)); + + _subject.ToString().Should().Be($"(@Clause1_P1 ILIKE '%' || \"Movies\".\"CleanTitle\" || '%')"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void postgres_where_column_starts_with_string() + { + var test = "small"; + _subject = Where(x => x.CleanTitle.StartsWith(test)); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" ILIKE @Clause1_P1 || '%')"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void postgres_where_column_ends_with_string() + { + var test = "small"; + _subject = Where(x => x.CleanTitle.EndsWith(test)); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" ILIKE '%' || @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void postgres_where_in_list() + { + var list = new List { 1, 2, 3 }; + _subject = Where(x => list.Contains(x.Id)); + + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = ANY (('{{1, 2, 3}}')))"); + } + + [Test] + public void postgres_where_in_list_2() + { + var list = new List { 1, 2, 3 }; + _subject = Where(x => x.CleanTitle == "test" && list.Contains(x.Id)); + + _subject.ToString().Should().Be($"((\"Movies\".\"CleanTitle\" = @Clause1_P1) AND (\"Movies\".\"Id\" = ANY (('{{1, 2, 3}}'))))"); + } + + [Test] + public void postgres_where_in_string_list() + { + var list = new List { "first", "second", "third" }; + + _subject = Where(x => list.Contains(x.CleanTitle)); + + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" = ANY (@Clause1_P1))"); + } + + [Test] + public void enum_as_int() + { + _subject = Where(x => x.Status == MovieStatusType.Announced); + + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" = @Clause1_P1)"); + } + + [Test] + public void enum_in_list() + { + var allowed = new List { MovieStatusType.Announced, MovieStatusType.InCinemas }; + _subject = Where(x => allowed.Contains(x.Status)); + + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" = ANY (@Clause1_P1))"); + } + + [Test] + public void enum_in_array() + { + var allowed = new MovieStatusType[] { MovieStatusType.Announced, MovieStatusType.InCinemas }; + _subject = Where(x => allowed.Contains(x.Status)); + + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" = ANY (@Clause1_P1))"); + } + } +} diff --git a/src/NzbDrone.Core.Test/Framework/DbTest.cs b/src/NzbDrone.Core.Test/Framework/DbTest.cs index e38336375..fba791b15 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTest.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTest.cs @@ -1,14 +1,18 @@ -using System; +using System; using System.Collections.Generic; using System.Data.SQLite; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Npgsql; using NUnit.Framework; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Test.Common.Datastore; namespace NzbDrone.Core.Test.Framework { @@ -49,6 +53,7 @@ namespace NzbDrone.Core.Test.Framework public abstract class DbTest : CoreTest { private ITestDatabase _db; + private DatabaseType _databaseType; protected virtual MigrationType MigrationType => MigrationType.Main; @@ -101,17 +106,39 @@ namespace NzbDrone.Core.Test.Framework private IDatabase CreateDatabase(MigrationContext migrationContext) { + if (_databaseType == DatabaseType.PostgreSQL) + { + CreatePostgresDb(); + } + var factory = Mocker.Resolve(); // If a special migration test or log migration then create new - if (migrationContext.BeforeMigration != null) + if (migrationContext.BeforeMigration != null || _databaseType == DatabaseType.PostgreSQL) { return factory.Create(migrationContext); } + return CreateSqliteDatabase(factory, migrationContext); + } + + private void CreatePostgresDb() + { + var options = Mocker.Resolve>().Value; + PostgresDatabase.Create(options, MigrationType); + } + + private void DropPostgresDb() + { + var options = Mocker.Resolve>().Value; + PostgresDatabase.Drop(options, MigrationType); + } + + private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext) + { // Otherwise try to use a cached migrated db - var cachedDb = GetCachedDatabase(migrationContext.MigrationType); - var testDb = GetTestDb(migrationContext.MigrationType); + var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType); + var testDb = GetTestSqliteDb(migrationContext.MigrationType); if (File.Exists(cachedDb)) { TestLogger.Info($"Using cached initial database {cachedDb}"); @@ -131,12 +158,7 @@ namespace NzbDrone.Core.Test.Framework } } - private string GetCachedDatabase(MigrationType type) - { - return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db"); - } - - private string GetTestDb(MigrationType type) + private string GetTestSqliteDb(MigrationType type) { return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase(); } @@ -151,6 +173,13 @@ namespace NzbDrone.Core.Test.Framework WithTempAsAppPath(); SetupLogging(); + // populate the possible postgres options + var postgresOptions = PostgresDatabase.GetTestOptions(); + _databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite; + + // Set up remaining container services + Mocker.SetConstant(Options.Create(postgresOptions)); + Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); @@ -170,12 +199,19 @@ namespace NzbDrone.Core.Test.Framework // Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly) GC.Collect(); GC.WaitForPendingFinalizers(); + SQLiteConnection.ClearAllPools(); + NpgsqlConnection.ClearAllPools(); if (TestFolderInfo != null) { DeleteTempFolder(TestFolderInfo.AppDataFolder); } + + if (_databaseType == DatabaseType.PostgreSQL) + { + DropPostgresDb(); + } } } } diff --git a/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs b/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs index 587043e95..ae6b102a7 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs @@ -1,5 +1,7 @@ using System.IO; using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Test.Common.Datastore; namespace NzbDrone.Core.Test { @@ -10,13 +12,13 @@ namespace NzbDrone.Core.Test [OneTimeTearDown] public void ClearCachedDatabase() { - var mainCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Main.db"); + var mainCache = SqliteDatabase.GetCachedDb(MigrationType.Main); if (File.Exists(mainCache)) { File.Delete(mainCache); } - var logCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Log.db"); + var logCache = SqliteDatabase.GetCachedDb(MigrationType.Log); if (File.Exists(logCache)) { File.Delete(logCache); diff --git a/src/NzbDrone.Core.Test/Framework/TestDatabase.cs b/src/NzbDrone.Core.Test/Framework/TestDatabase.cs index 3fbfdf028..5391cfb1f 100644 --- a/src/NzbDrone.Core.Test/Framework/TestDatabase.cs +++ b/src/NzbDrone.Core.Test/Framework/TestDatabase.cs @@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Framework where T : ModelBase, new(); IDirectDataMapper GetDirectDataMapper(); IDbConnection OpenConnection(); + DatabaseType DatabaseType { get; } } public class TestDatabase : ITestDatabase @@ -30,6 +31,8 @@ namespace NzbDrone.Core.Test.Framework private readonly IDatabase _dbConnection; private readonly IEventAggregator _eventAggregator; + public DatabaseType DatabaseType => _dbConnection.DatabaseType; + public TestDatabase(IDatabase dbConnection) { _eventAggregator = new Mock().Object; diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index 3298a7f0d..45c8888cd 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -7,7 +7,6 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Exceptions; using NzbDrone.Common.Instrumentation; -using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore.Migration.Framework; namespace NzbDrone.Core.Datastore diff --git a/src/NzbDrone.Core/Datastore/PostgresOptions.cs b/src/NzbDrone.Core/Datastore/PostgresOptions.cs index 6b6026ece..abe568381 100644 --- a/src/NzbDrone.Core/Datastore/PostgresOptions.cs +++ b/src/NzbDrone.Core/Datastore/PostgresOptions.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Configuration; + namespace NzbDrone.Core.Datastore { public class PostgresOptions @@ -8,5 +10,17 @@ namespace NzbDrone.Core.Datastore public string Password { get; set; } public string MainDb { get; set; } public string LogDb { get; set; } + + public static PostgresOptions GetOptions() + { + var config = new ConfigurationBuilder() + .AddEnvironmentVariables("Radarr__") + .Build(); + + var postgresOptions = new PostgresOptions(); + config.GetSection("Postgres").Bind(postgresOptions); + + return postgresOptions; + } } } diff --git a/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs b/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs index f1854aea6..f3e31e179 100644 --- a/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs +++ b/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs @@ -91,7 +91,7 @@ namespace NzbDrone.Core.Datastore // Only use the SQL condition if the expression didn't resolve to an actual value if (tableName != null && !gotValue) { - _sb.Append($"\"{tableName}\".\"{expression.Member.Name.ToLower()}\""); + _sb.Append($"\"{tableName}\".\"{expression.Member.Name}\""); } else { diff --git a/src/NzbDrone.Integration.Test/IntegrationTest.cs b/src/NzbDrone.Integration.Test/IntegrationTest.cs index 062f07602..6dc3671e5 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTest.cs @@ -1,9 +1,15 @@ +using System.Collections.Generic; using System.Threading; +using Microsoft.Extensions.Configuration; using NLog; +using Npgsql; using NUnit.Framework; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Test.Common; +using NzbDrone.Test.Common.Datastore; using Radarr.Http.ClientSchema; namespace NzbDrone.Integration.Test @@ -19,6 +25,8 @@ namespace NzbDrone.Integration.Test protected int Port { get; private set; } + protected PostgresOptions PostgresOptions { get; set; } = new (); + protected override string RootUrl => $"http://localhost:{Port}/"; protected override string ApiKey => _runner.ApiKey; @@ -27,7 +35,14 @@ namespace NzbDrone.Integration.Test { Port = Interlocked.Increment(ref StaticPort); - _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), Port); + PostgresOptions = PostgresDatabase.GetTestOptions(); + + if (PostgresOptions?.Host != null) + { + CreatePostgresDb(PostgresOptions); + } + + _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), PostgresOptions, Port); _runner.Kill(); _runner.Start(); @@ -59,6 +74,22 @@ namespace NzbDrone.Integration.Test protected override void StopTestTarget() { _runner.Kill(); + if (PostgresOptions?.Host != null) + { + DropPostgresDb(PostgresOptions); + } + } + + private static void CreatePostgresDb(PostgresOptions options) + { + PostgresDatabase.Create(options, MigrationType.Main); + PostgresDatabase.Create(options, MigrationType.Log); + } + + private static void DropPostgresDb(PostgresOptions options) + { + PostgresDatabase.Drop(options, MigrationType.Main); + PostgresDatabase.Drop(options, MigrationType.Log); } } } diff --git a/src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs b/src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs new file mode 100644 index 000000000..940334ca2 --- /dev/null +++ b/src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs @@ -0,0 +1,69 @@ +using System; +using Npgsql; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Test.Common.Datastore +{ + public static class PostgresDatabase + { + public static PostgresOptions GetTestOptions() + { + var options = PostgresOptions.GetOptions(); + + var uid = TestBase.GetUID(); + options.MainDb = uid + "_main"; + options.LogDb = uid + "_log"; + + return options; + } + + public static void Create(PostgresOptions options, MigrationType migrationType) + { + var db = GetDatabaseName(options, migrationType); + var connectionString = GetConnectionString(options); + using var conn = new NpgsqlConnection(connectionString); + conn.Open(); + + using var cmd = conn.CreateCommand(); + cmd.CommandText = $"CREATE DATABASE \"{db}\" WITH OWNER = {options.User} ENCODING = 'UTF8' CONNECTION LIMIT = -1;"; + cmd.ExecuteNonQuery(); + } + + public static void Drop(PostgresOptions options, MigrationType migrationType) + { + var db = GetDatabaseName(options, migrationType); + var connectionString = GetConnectionString(options); + using var conn = new NpgsqlConnection(connectionString); + conn.Open(); + + using var cmd = conn.CreateCommand(); + cmd.CommandText = $"DROP DATABASE \"{db}\" WITH (FORCE);"; + cmd.ExecuteNonQuery(); + } + + private static string GetConnectionString(PostgresOptions options) + { + var builder = new NpgsqlConnectionStringBuilder() + { + Host = options.Host, + Port = options.Port, + Username = options.User, + Password = options.Password, + Enlist = false + }; + + return builder.ConnectionString; + } + + private static string GetDatabaseName(PostgresOptions options, MigrationType migrationType) + { + return migrationType switch + { + MigrationType.Main => options.MainDb, + MigrationType.Log => options.LogDb, + _ => throw new NotImplementedException("Unknown migration type") + }; + } + } +} diff --git a/src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs b/src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs new file mode 100644 index 000000000..151e245fc --- /dev/null +++ b/src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs @@ -0,0 +1,14 @@ +using System.IO; +using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Test.Common.Datastore +{ + public static class SqliteDatabase + { + public static string GetCachedDb(MigrationType type) + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db"); + } + } +} diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index 45c0dc161..4b4531f52 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; @@ -9,7 +10,9 @@ using NUnit.Framework; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Processes; +using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Datastore; using RestSharp; namespace NzbDrone.Test.Common @@ -22,13 +25,15 @@ namespace NzbDrone.Test.Common public string AppData { get; private set; } public string ApiKey { get; private set; } + public PostgresOptions PostgresOptions { get; private set; } public int Port { get; private set; } - public NzbDroneRunner(Logger logger, int port = 7878) + public NzbDroneRunner(Logger logger, PostgresOptions postgresOptions, int port = 7878) { _processProvider = new ProcessProvider(logger); _restClient = new RestClient($"http://localhost:{port}/api/v3"); + PostgresOptions = postgresOptions; Port = port; } @@ -132,9 +137,23 @@ namespace NzbDrone.Test.Common private void Start(string outputRadarrConsoleExe) { + StringDictionary envVars = new (); + if (PostgresOptions?.Host != null) + { + envVars.Add("Radarr__Postgres__Host", PostgresOptions.Host); + envVars.Add("Radarr__Postgres__Port", PostgresOptions.Port.ToString()); + envVars.Add("Radarr__Postgres__User", PostgresOptions.User); + envVars.Add("Radarr__Postgres__Password", PostgresOptions.Password); + envVars.Add("Radarr__Postgres__MainDb", PostgresOptions.MainDb); + envVars.Add("Radarr__Postgres__LogDb", PostgresOptions.LogDb); + + TestContext.Progress.WriteLine("Using env vars:\n{0}", envVars.ToJson()); + } + TestContext.Progress.WriteLine("Starting instance from {0} on port {1}", outputRadarrConsoleExe, Port); + var args = "-nobrowser -nosingleinstancecheck -data=\"" + AppData + "\""; - _nzbDroneProcess = _processProvider.Start(outputRadarrConsoleExe, args, null, OnOutputDataReceived, OnOutputDataReceived); + _nzbDroneProcess = _processProvider.Start(outputRadarrConsoleExe, args, envVars, OnOutputDataReceived, OnOutputDataReceived); } private void OnOutputDataReceived(string data) diff --git a/src/postgres.runsettings b/src/postgres.runsettings new file mode 100644 index 000000000..fceb79c36 --- /dev/null +++ b/src/postgres.runsettings @@ -0,0 +1,11 @@ + + + + + 192.168.100.5 + 5432 + abc + abc + + + \ No newline at end of file