Running Integration Tests against Postgres Database (#838)

* Allow configuring postgres with environment variables

(cherry picked from commit 8439df78fea25656a9a1275d2a2fe3f0df0528c7)

* Fix process provider when environment variables alread exist

[common]

(cherry picked from commit 66e5b4025974e081c1406f01a860b1ac52949c22)

* First bash at running integration tests on postgres

(cherry picked from commit f950e80c7e4f9b088ec6a149386160eab83b61c3)

* Postgres integration tests running as part of the build pipeline

(cherry picked from commit 9ca8616f5098778e9b5e6ce09d2aa11224018fab)

* Fixed: Register PostgresOptions when running in utility mode

* fixup!

* fixup!

* fixup!

* fixup!

* fixup!

Co-authored-by: ta264 <ta264@users.noreply.github.com>
Co-authored-by: Qstick <qstick@gmail.com>
pull/1051/head
Robin Dadswell 2 years ago committed by GitHub
parent 9b1f9abfac
commit cac2729230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -506,6 +506,58 @@ stages:
testResultsFiles: '**/TestResult.xml' testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Unit Tests' testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true 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: 'Prowlarr.*.linux-core-x64.tar.gz'
artifactName: LinuxCoreTests
Prowlarr__Postgres__Host: 'localhost'
Prowlarr__Postgres__Port: '5432'
Prowlarr__Postgres__User: 'prowlarr'
Prowlarr__Postgres__Password: 'prowlarr'
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: find ${TESTSFOLDER} -name "Prowlarr.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=prowlarr \
-e POSTGRES_USER=prowlarr \
-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 - stage: Integration
displayName: Integration displayName: Integration
@ -590,6 +642,67 @@ stages:
failTaskOnFailedTests: true failTaskOnFailedTests: true
displayName: Publish Test Results 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: 'Prowlarr.*.linux-core-x64.tar.gz'
Prowlarr__Postgres__Host: 'localhost'
Prowlarr__Postgres__Port: '5432'
Prowlarr__Postgres__User: 'prowlarr'
Prowlarr__Postgres__Password: 'prowlarr'
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/Prowlarr/. ./bin/
displayName: Move Package Contents
- bash: |
docker run -d --name=postgres14 \
-e POSTGRES_PASSWORD=prowlarr \
-e POSTGRES_USER=prowlarr \
-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 - job: Integration_FreeBSD
displayName: Integration Native FreeBSD displayName: Integration Native FreeBSD
dependsOn: Prepare dependsOn: Prepare

@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080); driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger()); _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll(); _runner.KillAll();
_runner.Start(); _runner.Start();

@ -170,7 +170,7 @@ namespace NzbDrone.Common.Test
var processStarted = new ManualResetEventSlim(); var processStarted = new ManualResetEventSlim();
string suffix; string suffix;
if (OsInfo.IsWindows || PlatformInfo.IsMono) if (OsInfo.IsWindows)
{ {
suffix = ".exe"; suffix = ".exe";
} }

@ -4,11 +4,13 @@ using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -29,7 +31,8 @@ namespace NzbDrone.Common.Test
.AddDummyDatabase() .AddDummyDatabase()
.AddStartupContext(new StartupContext("first", "second")); .AddStartupContext(new StartupContext("first", "second"));
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object); container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
var serviceProvider = container.GetServiceProvider(); var serviceProvider = container.GetServiceProvider();

@ -127,7 +127,18 @@ namespace NzbDrone.Common.Processes
try try
{ {
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value); _logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
var key = environmentVariable.Key.ToString();
var value = environmentVariable.Value?.ToString();
if (startInfo.EnvironmentVariables.ContainsKey(key))
{
startInfo.EnvironmentVariables[key] = value;
}
else
{
startInfo.EnvironmentVariables.Add(key, value);
}
} }
catch (Exception e) catch (Exception e)
{ {

@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore
public void SingleOrDefault_should_return_null_on_empty_db() public void SingleOrDefault_should_return_null_on_empty_db()
{ {
Mocker.Resolve<IDatabase>() Mocker.Resolve<IDatabase>()
.OpenConnection().Query<IndexerDefinition>("SELECT * FROM Indexers") .OpenConnection().Query<IndexerDefinition>("SELECT * FROM \"Indexers\"")
.SingleOrDefault() .SingleOrDefault()
.Should() .Should()
.BeNull(); .BeNull();

@ -5,10 +5,14 @@ using System.IO;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Npgsql;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Test.Common.Datastore;
namespace NzbDrone.Core.Test.Framework namespace NzbDrone.Core.Test.Framework
{ {
@ -49,6 +53,7 @@ namespace NzbDrone.Core.Test.Framework
public abstract class DbTest : CoreTest public abstract class DbTest : CoreTest
{ {
private ITestDatabase _db; private ITestDatabase _db;
private DatabaseType _databaseType;
protected virtual MigrationType MigrationType => MigrationType.Main; protected virtual MigrationType MigrationType => MigrationType.Main;
@ -101,17 +106,39 @@ namespace NzbDrone.Core.Test.Framework
private IDatabase CreateDatabase(MigrationContext migrationContext) private IDatabase CreateDatabase(MigrationContext migrationContext)
{ {
if (_databaseType == DatabaseType.PostgreSQL)
{
CreatePostgresDb();
}
var factory = Mocker.Resolve<DbFactory>(); var factory = Mocker.Resolve<DbFactory>();
// If a special migration test or log migration then create new // 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 factory.Create(migrationContext);
} }
return CreateSqliteDatabase(factory, migrationContext);
}
private void CreatePostgresDb()
{
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
PostgresDatabase.Create(options, MigrationType);
}
private void DropPostgresDb()
{
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
PostgresDatabase.Drop(options, MigrationType);
}
private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext)
{
// Otherwise try to use a cached migrated db // Otherwise try to use a cached migrated db
var cachedDb = GetCachedDatabase(migrationContext.MigrationType); var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType);
var testDb = GetTestDb(migrationContext.MigrationType); var testDb = GetTestSqliteDb(migrationContext.MigrationType);
if (File.Exists(cachedDb)) if (File.Exists(cachedDb))
{ {
TestLogger.Info($"Using cached initial database {cachedDb}"); TestLogger.Info($"Using cached initial database {cachedDb}");
@ -131,12 +158,7 @@ namespace NzbDrone.Core.Test.Framework
} }
} }
private string GetCachedDatabase(MigrationType type) private string GetTestSqliteDb(MigrationType type)
{
return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db");
}
private string GetTestDb(MigrationType type)
{ {
return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase(); return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase();
} }
@ -151,6 +173,13 @@ namespace NzbDrone.Core.Test.Framework
WithTempAsAppPath(); WithTempAsAppPath();
SetupLogging(); 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<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>()); Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>()); Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
@ -171,11 +200,17 @@ namespace NzbDrone.Core.Test.Framework
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
SQLiteConnection.ClearAllPools(); SQLiteConnection.ClearAllPools();
NpgsqlConnection.ClearAllPools();
if (TestFolderInfo != null) if (TestFolderInfo != null)
{ {
DeleteTempFolder(TestFolderInfo.AppDataFolder); DeleteTempFolder(TestFolderInfo.AppDataFolder);
} }
if (_databaseType == DatabaseType.PostgreSQL)
{
DropPostgresDb();
}
} }
} }
} }

@ -1,5 +1,7 @@
using System.IO; using System.IO;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Test.Common.Datastore;
namespace NzbDrone.Core.Test namespace NzbDrone.Core.Test
{ {
@ -10,13 +12,13 @@ namespace NzbDrone.Core.Test
[OneTimeTearDown] [OneTimeTearDown]
public void ClearCachedDatabase() public void ClearCachedDatabase()
{ {
var mainCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Main.db"); var mainCache = SqliteDatabase.GetCachedDb(MigrationType.Main);
if (File.Exists(mainCache)) if (File.Exists(mainCache))
{ {
File.Delete(mainCache); File.Delete(mainCache);
} }
var logCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Log.db"); var logCache = SqliteDatabase.GetCachedDb(MigrationType.Log);
if (File.Exists(logCache)) if (File.Exists(logCache))
{ {
File.Delete(logCache); File.Delete(logCache);

@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Framework
where T : ModelBase, new(); where T : ModelBase, new();
IDirectDataMapper GetDirectDataMapper(); IDirectDataMapper GetDirectDataMapper();
IDbConnection OpenConnection(); IDbConnection OpenConnection();
DatabaseType DatabaseType { get; }
} }
public class TestDatabase : ITestDatabase public class TestDatabase : ITestDatabase
@ -30,6 +31,8 @@ namespace NzbDrone.Core.Test.Framework
private readonly IDatabase _dbConnection; private readonly IDatabase _dbConnection;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
public DatabaseType DatabaseType => _dbConnection.DatabaseType;
public TestDatabase(IDatabase dbConnection) public TestDatabase(IDatabase dbConnection)
{ {
_eventAggregator = new Mock<IEventAggregator>().Object; _eventAggregator = new Mock<IEventAggregator>().Object;

@ -41,7 +41,8 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
Subject.Clean(); Subject.Clean();
AllStoredModels.ToList().ForEach(t => t.LastExecution.Should().Be(expectedTime)); // BeCloseTo handles Postgres rounding times
AllStoredModels.ToList().ForEach(t => t.LastExecution.Should().BeCloseTo(expectedTime));
} }
} }
} }

@ -5,12 +5,14 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -67,6 +69,7 @@ namespace NzbDrone.Core.Configuration
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly ICached<string> _cache; private readonly ICached<string> _cache;
private readonly PostgresOptions _postgresOptions;
private readonly string _configFile; private readonly string _configFile;
private static readonly Regex HiddenCharacterRegex = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex HiddenCharacterRegex = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@ -76,12 +79,14 @@ namespace NzbDrone.Core.Configuration
public ConfigFileProvider(IAppFolderInfo appFolderInfo, public ConfigFileProvider(IAppFolderInfo appFolderInfo,
ICacheManager cacheManager, ICacheManager cacheManager,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IDiskProvider diskProvider) IDiskProvider diskProvider,
IOptions<PostgresOptions> postgresOptions)
{ {
_cache = cacheManager.GetCache<string>(GetType()); _cache = cacheManager.GetCache<string>(GetType());
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_configFile = appFolderInfo.GetConfigPath(); _configFile = appFolderInfo.GetConfigPath();
_postgresOptions = postgresOptions.Value;
} }
public Dictionary<string, object> GetConfigDictionary() public Dictionary<string, object> GetConfigDictionary()
@ -195,13 +200,13 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant(); public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string PostgresHost => GetValue("PostgresHost", string.Empty, persist: false); public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => GetValue("PostgresUser", string.Empty, persist: false); public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
public string PostgresPassword => GetValue("PostgresPassword", string.Empty, persist: false); public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
public string PostgresMainDb => GetValue("PostgresMainDb", "prowlarr-main", persist: false); public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "prowlarr-main", persist: false);
public string PostgresLogDb => GetValue("PostgresLogDb", "prowlarr-log", persist: false); public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "prowlarr-log", persist: false);
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
public string Theme => GetValue("Theme", "light", persist: false); public string Theme => GetValue("Theme", "light", persist: false);
public int PostgresPort => GetValueInt("PostgresPort", 5432, persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);

@ -167,14 +167,12 @@ namespace NzbDrone.Core.Datastore
} }
} }
if (_database.DatabaseType == DatabaseType.SQLite) if (_database.DatabaseType == DatabaseType.PostgreSQL)
{
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
}
else
{ {
return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\""; return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\"";
} }
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
} }
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model) private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)

@ -0,0 +1,26 @@
using Microsoft.Extensions.Configuration;
namespace NzbDrone.Core.Datastore
{
public class PostgresOptions
{
public string Host { get; set; }
public int Port { get; set; }
public string User { get; set; }
public string Password { get; set; }
public string MainDb { get; set; }
public string LogDb { get; set; }
public static PostgresOptions GetOptions()
{
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
var postgresOptions = new PostgresOptions();
config.GetSection("Prowlarr:Postgres").Bind(postgresOptions);
return postgresOptions;
}
}
}

@ -313,7 +313,20 @@ namespace NzbDrone.Core.Datastore
_sb.Append(" = ANY ("); _sb.Append(" = ANY (");
Visit(list); // hardcode the integer list if it exists to bypass parameter limit
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
{
var items = (IEnumerable<int>)value;
_sb.Append("('{");
_sb.Append(string.Join(", ", items));
_sb.Append("}')");
_gotConcreteValue = true;
}
else
{
Visit(list);
}
_sb.Append("))"); _sb.Append("))");
} }
@ -324,7 +337,7 @@ namespace NzbDrone.Core.Datastore
Visit(body.Object); Visit(body.Object);
_sb.Append(" LIKE '%' || "); _sb.Append(" ILIKE '%' || ");
Visit(body.Arguments[0]); Visit(body.Arguments[0]);
@ -337,7 +350,7 @@ namespace NzbDrone.Core.Datastore
Visit(body.Object); Visit(body.Object);
_sb.Append(" LIKE "); _sb.Append(" ILIKE ");
Visit(body.Arguments[0]); Visit(body.Arguments[0]);
@ -350,7 +363,7 @@ namespace NzbDrone.Core.Datastore
Visit(body.Object); Visit(body.Object);
_sb.Append(" LIKE '%' || "); _sb.Append(" ILIKE '%' || ");
Visit(body.Arguments[0]); Visit(body.Arguments[0]);

@ -5,13 +5,16 @@ using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Jobs; using NzbDrone.Core.Jobs;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
@ -43,6 +46,7 @@ namespace NzbDrone.App.Test
// dummy lifetime and broadcaster so tests resolve // dummy lifetime and broadcaster so tests resolve
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object); container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
container.RegisterInstance<IBroadcastSignalRMessage>(new Mock<IBroadcastSignalRMessage>().Object); container.RegisterInstance<IBroadcastSignalRMessage>(new Mock<IBroadcastSignalRMessage>().Object);
container.RegisterInstance<IOptions<PostgresOptions>>(new Mock<IOptions<PostgresOptions>>().Object);
_container = container.GetServiceProvider(); _container = container.GetServiceProvider();
} }
@ -53,6 +57,12 @@ namespace NzbDrone.App.Test
_container.GetRequiredService<IEnumerable<IIndexer>>().Should().NotBeEmpty(); _container.GetRequiredService<IEnumerable<IIndexer>>().Should().NotBeEmpty();
} }
[Test]
public void should_be_able_to_resolve_downloadclients()
{
_container.GetRequiredService<IEnumerable<IDownloadClient>>().Should().NotBeEmpty();
}
[Test] [Test]
public void container_should_inject_itself() public void container_should_inject_itself()
{ {

@ -9,8 +9,11 @@ using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using DryIoc; using DryIoc;
using DryIoc.Microsoft.DependencyInjection; using DryIoc.Microsoft.DependencyInjection;
using FluentMigrator.Runner.Processors.Postgres;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices; using Microsoft.Extensions.Hosting.WindowsServices;
using NLog; using NLog;
@ -23,6 +26,8 @@ using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Host;
using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions;
namespace NzbDrone.Host namespace NzbDrone.Host
{ {
@ -52,6 +57,7 @@ namespace NzbDrone.Host
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var appMode = GetApplicationMode(startupContext); var appMode = GetApplicationMode(startupContext);
var config = GetConfiguration(startupContext);
switch (appMode) switch (appMode)
{ {
@ -80,12 +86,22 @@ namespace NzbDrone.Host
// Utility mode // Utility mode
default: default:
{ {
new Container(rules => rules.WithNzbDroneRules()) new HostBuilder()
.AutoAddServices(ASSEMBLIES) .UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.AddNzbDroneLogger() .ConfigureContainer<IContainer>(c =>
.AddStartupContext(startupContext) {
.Resolve<UtilityModeRouter>() c.AutoAddServices(Bootstrap.ASSEMBLIES)
.Route(appMode); .AddNzbDroneLogger()
.AddDatabase()
.AddStartupContext(startupContext)
.Resolve<UtilityModeRouter>()
.Route(appMode);
})
.ConfigureServices(services =>
{
services.Configure<PostgresOptions>(config.GetSection("Prowlarr:Postgres"));
}).Build();
break; break;
} }
} }
@ -135,6 +151,10 @@ namespace NzbDrone.Host
.AddDatabase() .AddDatabase()
.AddStartupContext(context); .AddStartupContext(context);
}) })
.ConfigureServices(services =>
{
services.Configure<PostgresOptions>(config.GetSection("Prowlarr:Postgres"));
})
.ConfigureWebHost(builder => .ConfigureWebHost(builder =>
{ {
builder.UseConfiguration(config); builder.UseConfiguration(config);
@ -205,6 +225,7 @@ namespace NzbDrone.Host
return new ConfigurationBuilder() return new ConfigurationBuilder()
.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false) .AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false)
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) }) .AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
.AddEnvironmentVariables()
.Build(); .Build();
} }

@ -1,9 +1,15 @@
using System.Collections.Generic;
using System.Threading; using System.Threading;
using Microsoft.Extensions.Configuration;
using NLog; using NLog;
using Npgsql;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Indexers.FileList; using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Datastore;
using Prowlarr.Http.ClientSchema; using Prowlarr.Http.ClientSchema;
namespace NzbDrone.Integration.Test namespace NzbDrone.Integration.Test
@ -19,6 +25,8 @@ namespace NzbDrone.Integration.Test
protected int Port { get; private set; } protected int Port { get; private set; }
protected PostgresOptions PostgresOptions { get; set; } = new ();
protected override string RootUrl => $"http://localhost:{Port}/"; protected override string RootUrl => $"http://localhost:{Port}/";
protected override string ApiKey => _runner.ApiKey; protected override string ApiKey => _runner.ApiKey;
@ -27,7 +35,14 @@ namespace NzbDrone.Integration.Test
{ {
Port = Interlocked.Increment(ref StaticPort); 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.Kill();
_runner.Start(); _runner.Start();
@ -56,6 +71,22 @@ namespace NzbDrone.Integration.Test
protected override void StopTestTarget() protected override void StopTestTarget()
{ {
_runner.Kill(); _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);
} }
} }
} }

@ -8,14 +8,9 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Mono.Test.DiskProviderTests namespace NzbDrone.Mono.Test.DiskProviderTests
{ {
[TestFixture] [TestFixture]
[Platform("Mono")] [Platform(Exclude = "Win")]
public class SymbolicLinkResolverFixture : TestBase<SymbolicLinkResolver> public class SymbolicLinkResolverFixture : TestBase<SymbolicLinkResolver>
{ {
public SymbolicLinkResolverFixture()
{
MonoOnly();
}
[Test] [Test]
public void should_follow_nested_symlinks() public void should_follow_nested_symlinks()
{ {

@ -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")
};
}
}
}

@ -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");
}
}
}

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -9,7 +10,9 @@ using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using RestSharp; using RestSharp;
namespace NzbDrone.Test.Common namespace NzbDrone.Test.Common
@ -22,13 +25,15 @@ namespace NzbDrone.Test.Common
public string AppData { get; private set; } public string AppData { get; private set; }
public string ApiKey { get; private set; } public string ApiKey { get; private set; }
public PostgresOptions PostgresOptions { get; private set; }
public int Port { get; private set; } public int Port { get; private set; }
public NzbDroneRunner(Logger logger, int port = 9696) public NzbDroneRunner(Logger logger, PostgresOptions postgresOptions, int port = 9696)
{ {
_processProvider = new ProcessProvider(logger); _processProvider = new ProcessProvider(logger);
_restClient = new RestClient($"http://localhost:{port}/api/v1"); _restClient = new RestClient($"http://localhost:{port}/api/v1");
PostgresOptions = postgresOptions;
Port = port; Port = port;
} }
@ -133,9 +138,23 @@ namespace NzbDrone.Test.Common
private void Start(string outputProwlarrConsoleExe) private void Start(string outputProwlarrConsoleExe)
{ {
StringDictionary envVars = new ();
if (PostgresOptions?.Host != null)
{
envVars.Add("Prowlarr__Postgres__Host", PostgresOptions.Host);
envVars.Add("Prowlarr__Postgres__Port", PostgresOptions.Port.ToString());
envVars.Add("Prowlarr__Postgres__User", PostgresOptions.User);
envVars.Add("Prowlarr__Postgres__Password", PostgresOptions.Password);
envVars.Add("Prowlarr__Postgres__MainDb", PostgresOptions.MainDb);
envVars.Add("Prowlarr__Postgres__LogDb", PostgresOptions.LogDb);
TestContext.Progress.WriteLine("Using env vars:\n{0}", envVars.ToJson());
}
TestContext.Progress.WriteLine("Starting instance from {0} on port {1}", outputProwlarrConsoleExe, Port); TestContext.Progress.WriteLine("Starting instance from {0} on port {1}", outputProwlarrConsoleExe, Port);
var args = "-nobrowser -nosingleinstancecheck -data=\"" + AppData + "\""; var args = "-nobrowser -nosingleinstancecheck -data=\"" + AppData + "\"";
_nzbDroneProcess = _processProvider.Start(outputProwlarrConsoleExe, args, null, OnOutputDataReceived, OnOutputDataReceived); _nzbDroneProcess = _processProvider.Start(outputProwlarrConsoleExe, args, envVars, OnOutputDataReceived, OnOutputDataReceived);
} }
private void OnOutputDataReceived(string data) private void OnOutputDataReceived(string data)

@ -166,14 +166,6 @@ namespace NzbDrone.Test.Common
} }
} }
protected void MonoOnly()
{
if (!PlatformInfo.IsMono)
{
throw new IgnoreException("mono specific test");
}
}
protected void NotBsd() protected void NotBsd()
{ {
if (OsInfo.Os == Os.Bsd) if (OsInfo.Os == Os.Bsd)

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<Prowlarr__Postgres__Host>192.168.100.5</Prowlarr__Postgres__Host>
<Prowlarr__Postgres__Port>5432</Prowlarr__Postgres__Port>
<Prowlarr__Postgres__User>abc</Prowlarr__Postgres__User>
<Prowlarr__Postgres__Password>abc</Prowlarr__Postgres__Password>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>
Loading…
Cancel
Save