Skip to content

Commit

Permalink
- configuration of method and class name of AssemblyScript
Browse files Browse the repository at this point in the history
- support diffrent signatures of Execute method in AssemblyScript
  • Loading branch information
max-ieremenko committed Oct 21, 2018
1 parent 3dcb65c commit 3600cac
Show file tree
Hide file tree
Showing 26 changed files with 679 additions and 38 deletions.
42 changes: 34 additions & 8 deletions Examples/CSharpMirationStep/readme.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
The project contains an example of database migration step implementation.
# .NET assembly with a script implementation
The project contains an example of database migration step implementation.

Build output is 2.1_2.2.dll with taget framework 4.5.2
Build output is 2.1_2.2.dll with target framework 4.5.2

Assembly must contain only one "public class SqlDatabaseScript", namespace name doesn't matter.

SqlDatabaseScript must contain "public void Execute(IDbCommand command, IReadOnlyDictionary<string, string> variables)".

Method Execute implements the logic of your migration step. See [code example](https://github.com/max-ieremenko/SqlDatabase/blob/master/Examples/CSharpMirationStep/SqlDatabaseScript.cs).
Method [SqlDatabaseScript.Execute](https://github.com/max-ieremenko/SqlDatabase/blob/master/Examples/CSharpMirationStep/SqlDatabaseScript.cs) implements the logic of your migration step.

Use parameter "IDbCommand command" to affect database.
Use Console.WriteLine() to write something into migration log.

## Runtime
At runtime the assembly will be loaded into private application domain with
* ApplicationBase: location of SqlDatabase.exe
* ConfigurationFile: SqlDatabase.exe.config

Instance of migration step will be resolved via reflection: Activator.CreateInstance(typeof(SqlDatabaseScript))

After the migration step is finished or failed
- instance of SqlDatabaseScript will be disposed (id IDisposable)
- instance of SqlDatabaseScript will be disposed (if IDisposable)
- the domain will be unloaded

## Reflection
The assembly must contain only one "public class SqlDatabaseScript", namespace doesn't matter.
Class SqlDatabaseScript must contain method "public void Execute(...)".

Supported signatures of Execute method
* void Execute(IDbCommand command, IReadOnlyDictionary<string, string> variables)
* void Execute(IReadOnlyDictionary<string, string> variables, IDbCommand command)
* void Execute(IDbCommand command)
* void Execute(IDbConnection connection)

## Configuration
name of class SqlDatabaseScript and method Execute can be changed in the [SqlDatabase.exe.config](https://github.com/max-ieremenko/SqlDatabase/blob/master/Sources/SqlDatabase/App.config):
```xml
<configuration>
<configSections>
<section name="sqlDatabase"
type="SqlDatabase.Configuration.AppConfiguration, SqlDatabase"/>
</configSections>

<sqlDatabase>
<assemblyScript className="SqlDatabaseScript"
methodName="Execute" />
</sqlDatabase>
</configuration>
```
4 changes: 2 additions & 2 deletions Sources/GlobalAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

[assembly: AssemblyVersion("1.4.2.0")]
[assembly: AssemblyFileVersion("1.4.2.0")]
[assembly: AssemblyVersion("1.5.0.0")]
[assembly: AssemblyFileVersion("1.5.0.0")]
62 changes: 62 additions & 0 deletions Sources/SqlDatabase.Test/Configuration/AppConfigurationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Configuration;
using NUnit.Framework;
using SqlDatabase.TestApi;

namespace SqlDatabase.Configuration
{
[TestFixture]
public class AppConfigurationTest
{
private TempDirectory _temp;

[SetUp]
public void BeforeEachTest()
{
_temp = new TempDirectory();
}

[TearDown]
public void AfterEachTest()
{
_temp?.Dispose();
}

[Test]
public void LoadEmpty()
{
var configuration = LoadFromResource("AppConfiguration.empty.xml");
Assert.IsNull(configuration);
}

[Test]
public void LoadDefault()
{
var configuration = LoadFromResource("AppConfiguration.default.xml");
Assert.IsNotNull(configuration);

Assert.That(configuration.GetCurrentVersionScript, Is.Not.Null.And.Not.Empty);
Assert.That(configuration.SetCurrentVersionScript, Is.Not.Null.And.Not.Empty);
Assert.That(configuration.AssemblyScript.ClassName, Is.Not.Null.And.Not.Empty);
Assert.That(configuration.AssemblyScript.MethodName, Is.Not.Null.And.Not.Empty);
}

[Test]
public void LoadFull()
{
var configuration = LoadFromResource("AppConfiguration.full.xml");
Assert.IsNotNull(configuration);

Assert.AreEqual("get-version", configuration.GetCurrentVersionScript);
Assert.AreEqual("set-version", configuration.SetCurrentVersionScript);
Assert.AreEqual("method-name", configuration.AssemblyScript.MethodName);
Assert.AreEqual("class-name", configuration.AssemblyScript.ClassName);
}

private AppConfiguration LoadFromResource(string resourceName)
{
var fileName = _temp.CopyFileFromResources(resourceName);
var configuration = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap { ExeConfigFilename = fileName }, ConfigurationUserLevel.None);
return (AppConfiguration)configuration.GetSection(AppConfiguration.SectionName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="sqlDatabase"
type="SqlDatabase.Configuration.AppConfiguration, SqlDatabase"/>
</configSections>

<sqlDatabase />
</configuration>
3 changes: 3 additions & 0 deletions Sources/SqlDatabase.Test/Resources/AppConfiguration.empty.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>
13 changes: 13 additions & 0 deletions Sources/SqlDatabase.Test/Resources/AppConfiguration.full.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="sqlDatabase"
type="SqlDatabase.Configuration.AppConfiguration, SqlDatabase"/>
</configSections>

<sqlDatabase getCurrentVersion="get-version"
setCurrentVersion="set-version">
<assemblyScript methodName="method-name"
className="class-name"/>
</sqlDatabase>
</configuration>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace SqlDatabase.Scripts.AssemblyInternal
{
Expand All @@ -26,5 +27,21 @@ public void Execute(IDbCommand command, IReadOnlyDictionary<string, string> vari
throw new NotImplementedException();
}
}

public sealed class DatabaseScriptWithOneParameter
{
public void ExecuteCommand(IDbCommand command)
{
throw new NotImplementedException();
}
}

public sealed class DatabaseScriptWithConnection
{
public void Run(SqlConnection connection)
{
throw new NotImplementedException();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,37 @@ public void FailToCreateInstance()
Assert.IsNull(_sut.Resolve(GetType().Assembly));
CollectionAssert.IsNotEmpty(_logErrorOutput);
}

[Test]
public void ResolveExecuteWithCommandOnly()
{
_sut.ExecutorClassName = nameof(DatabaseScriptWithOneParameter);
_sut.ExecutorMethodName = nameof(DatabaseScriptWithOneParameter.ExecuteCommand);

var actual = _sut.Resolve(GetType().Assembly);
CollectionAssert.IsEmpty(_logErrorOutput);
Assert.IsInstanceOf<DefaultEntryPoint>(actual);

var entryPoint = (DefaultEntryPoint)actual;
Assert.IsNotNull(entryPoint.Log);
Assert.IsInstanceOf<DatabaseScriptWithOneParameter>(entryPoint.ScriptInstance);
Assert.IsNotNull(entryPoint.Method);
}

[Test]
public void ResolveExecuteWithConnection()
{
_sut.ExecutorClassName = nameof(DatabaseScriptWithConnection);
_sut.ExecutorMethodName = nameof(DatabaseScriptWithConnection.Run);

var actual = _sut.Resolve(GetType().Assembly);
CollectionAssert.IsEmpty(_logErrorOutput);
Assert.IsInstanceOf<DefaultEntryPoint>(actual);

var entryPoint = (DefaultEntryPoint)actual;
Assert.IsNotNull(entryPoint.Log);
Assert.IsInstanceOf<DatabaseScriptWithConnection>(entryPoint.ScriptInstance);
Assert.IsNotNull(entryPoint.Method);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using Moq;
using NUnit.Framework;

namespace SqlDatabase.Scripts.AssemblyInternal
{
[TestFixture]
public class ExecuteMethodResolverCommandDictionaryTest
{
private ExecuteMethodResolverCommandDictionary _sut;
private IDbCommand _executeCommand;
private IReadOnlyDictionary<string, string> _executeVariables;

[SetUp]
public void BeforeEachTest()
{
_sut = new ExecuteMethodResolverCommandDictionary();
}

[Test]
public void IsMatch()
{
var method = GetType().GetMethod(nameof(Execute), BindingFlags.Instance | BindingFlags.NonPublic);
Assert.IsTrue(_sut.IsMatch(method));
}

[Test]
public void CreateDelegate()
{
var method = GetType().GetMethod(nameof(Execute), BindingFlags.Instance | BindingFlags.NonPublic);
var actual = _sut.CreateDelegate(this, method);
Assert.IsNotNull(actual);

var command = new Mock<IDbCommand>(MockBehavior.Strict);
var variables = new Mock<IReadOnlyDictionary<string, string>>(MockBehavior.Strict);

actual(command.Object, variables.Object);

Assert.AreEqual(_executeCommand, command.Object);
Assert.AreEqual(_executeVariables, variables.Object);
}

private void Execute(IDbCommand command, IReadOnlyDictionary<string, string> variables)
{
Assert.IsNull(_executeCommand);
_executeCommand = command;
_executeVariables = variables;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Data;
using System.Reflection;
using Moq;
using NUnit.Framework;

namespace SqlDatabase.Scripts.AssemblyInternal
{
[TestFixture]
public class ExecuteMethodResolverCommandTest
{
private ExecuteMethodResolverCommand _sut;
private IDbCommand _executeCommand;

[SetUp]
public void BeforeEachTest()
{
_sut = new ExecuteMethodResolverCommand();
}

[Test]
public void IsMatch()
{
var method = GetType().GetMethod(nameof(Execute), BindingFlags.Instance | BindingFlags.NonPublic);
Assert.IsTrue(_sut.IsMatch(method));
}

[Test]
public void CreateDelegate()
{
var method = GetType().GetMethod(nameof(Execute), BindingFlags.Instance | BindingFlags.NonPublic);
var actual = _sut.CreateDelegate(this, method);
Assert.IsNotNull(actual);

var command = new Mock<IDbCommand>(MockBehavior.Strict);
actual(command.Object, null);
Assert.AreEqual(_executeCommand, command.Object);
}

private void Execute(IDbCommand command)
{
Assert.IsNull(_executeCommand);
_executeCommand = command;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using Moq;
using NUnit.Framework;

namespace SqlDatabase.Scripts.AssemblyInternal
{
[TestFixture]
public class ExecuteMethodResolverDbConnectionTest
{
private ExecuteMethodResolverDbConnection _sut;
private IDbConnection _executeConnection;

[SetUp]
public void BeforeEachTest()
{
_sut = new ExecuteMethodResolverDbConnection();
}

[Test]
public void IsMatch()
{
var method = GetType().GetMethod(nameof(Execute), BindingFlags.Instance | BindingFlags.NonPublic);
Assert.IsTrue(_sut.IsMatch(method));
}

[Test]
public void CreateDelegate()
{
var method = GetType().GetMethod(nameof(Execute), BindingFlags.Instance | BindingFlags.NonPublic);
var actual = _sut.CreateDelegate(this, method);
Assert.IsNotNull(actual);

var connection = new Mock<IDbConnection>(MockBehavior.Strict);

var command = new Mock<IDbCommand>(MockBehavior.Strict);
command
.SetupGet(c => c.Connection)
.Returns(connection.Object);

actual(command.Object, null);
Assert.AreEqual(_executeConnection, connection.Object);
}

private void Execute(IDbConnection connection)
{
Assert.IsNull(_executeConnection);
_executeConnection = connection;
}
}
}
Loading

0 comments on commit 3600cac

Please sign in to comment.