Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrations #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Lager.Tests/Lager.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="RemoveIntegerMigration.cs" />
<Compile Include="MigrationTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RenameIntegerMigration.cs" />
<Compile Include="SettingsStorageProxy.cs" />
<Compile Include="SettingsStorageTest.cs" />
<Compile Include="TransformationMigration.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
102 changes: 102 additions & 0 deletions Lager.Tests/MigrationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Akavache;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Xunit;

namespace Lager.Tests
{
public class MigrationTest
{
public async static Task<T> ThrowsAsync<T>(Func<Task> testCode) where T : Exception
{
try
{
await testCode();
Assert.Throws<T>(() => { }); // Use xUnit's default behavior.
}

catch (T exception)
{
return exception;
}

return null;
}

[Fact]
public async Task MigrateAsyncThrowsOnEmptyEnumerable()
{
var storage = new SettingsStorageProxy();

await ThrowsAsync<ArgumentException>(() => storage.MigrateAsync(Enumerable.Empty<SettingsMigration>()));
}

[Fact]
public async Task RemoveAsyncSmokeTest()
{
var storage = new SettingsStorageProxy();
storage.SetOrCreateProxy(1, "Setting1");

await storage.MigrateAsync(new[] { new RemoveIntegerMigration("Setting1") });

int value = storage.GetOrCreateProxy(42, "Setting1");

Assert.Equal(42, value);
}

[Fact]
public async Task RenameSmokeTest()
{
var storage = new SettingsStorageProxy();
storage.SetOrCreateProxy(42, "Setting1");

await storage.MigrateAsync(new[] { new RenameIntegerMigration(1, "Setting1", "Setting2") });

int value = storage.GetOrCreateProxy(1, "Setting2");

Assert.Equal(42, value);
}

[Fact]
public async Task TransformationRemovesOldObject()
{
var blobCache = new TestBlobCache();
var storage = new SettingsStorageProxy(blobCache);
storage.SetOrCreateProxy(42, "Setting");

await storage.MigrateAsync(new[] { new TransformationMigration<int, string>(1, "Setting", x => x.ToString()) });

Assert.Throws<KeyNotFoundException>(() => blobCache.GetObjectAsync<int>("Setting").Wait());
}

[Fact]
public async Task TransformationSmokeTest()
{
var storage = new SettingsStorageProxy();
storage.SetOrCreateProxy(42, "Setting");

await storage.MigrateAsync(new[] { new TransformationMigration<int, string>(1, "Setting", x => x.ToString()) });

string value = storage.GetOrCreateProxy("Bla", "Setting");

Assert.Equal("42", value);
}

[Fact]
public async Task TransformationsShouldHaveDistinctRevisions()
{
var storage = new SettingsStorageProxy();

var migration1 = new Mock<SettingsMigration>(1);
var migration2 = new Mock<SettingsMigration>(1);

var migrations = new[] { migration1.Object, migration2.Object };

await ThrowsAsync<ArgumentException>(() => storage.MigrateAsync(migrations));
}
}
}
20 changes: 20 additions & 0 deletions Lager.Tests/RemoveIntegerMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading.Tasks;

namespace Lager.Tests
{
public class RemoveIntegerMigration : SettingsMigration
{
private readonly string key;

public RemoveIntegerMigration(string key)
: base(1)
{
this.key = key;
}

public override async Task MigrateAsync()
{
await this.RemoveAsync<int>(key);
}
}
}
22 changes: 22 additions & 0 deletions Lager.Tests/RenameIntegerMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Threading.Tasks;

namespace Lager.Tests
{
public class RenameIntegerMigration : SettingsMigration
{
private readonly string newKey;
private readonly string previousKey;

public RenameIntegerMigration(int revision, string previousKey, string newKey)
: base(revision)
{
this.previousKey = previousKey;
this.newKey = newKey;
}

public override async Task MigrateAsync()
{
await this.RenameAsync<int>(this.previousKey, this.newKey);
}
}
}
1 change: 0 additions & 1 deletion Lager.Tests/SettingsStorageProxy.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Akavache;
using Lager;

namespace Lager.Tests
{
Expand Down
23 changes: 23 additions & 0 deletions Lager.Tests/TransformationMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Threading.Tasks;

namespace Lager.Tests
{
public class TransformationMigration<TBefore, TAfter> : SettingsMigration
{
private readonly string key;
private readonly Func<TBefore, TAfter> transformation;

public TransformationMigration(int revision, string key, Func<TBefore, TAfter> transformation)
: base(revision)
{
this.key = key;
this.transformation = transformation;
}

public override async Task MigrateAsync()
{
await this.TransformAsync(key, transformation);
}
}
}
1 change: 1 addition & 0 deletions Lager/Lager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SettingsMigration.cs" />
<Compile Include="SettingsStorage.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
63 changes: 63 additions & 0 deletions Lager/SettingsMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Akavache;
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

namespace Lager
{
public abstract class SettingsMigration
{
private IBlobCache blobCache;
private string keyPrefix;

protected SettingsMigration(int revision)
{
if (revision < 0)
throw new ArgumentOutOfRangeException("revision", "Revision has to be greater or equal 0");

this.Revision = revision;
}

internal int Revision { get; private set; }

public abstract Task MigrateAsync();

internal void Initialize(string keyPrefix, IBlobCache blobCache)
{
this.keyPrefix = keyPrefix;
this.blobCache = blobCache;
}

protected async Task RemoveAsync<T>(string key)
{
await this.blobCache.InvalidateObject<T>(this.CreateKey(key));
}

protected async Task RenameAsync<T>(string previousKey, string newKey)
{
T value = await this.blobCache.GetObjectAsync<T>(this.CreateKey(previousKey));

await this.blobCache.InvalidateObject<T>(this.CreateKey(previousKey));

await this.blobCache.InsertObject(this.CreateKey(newKey), value);
}

protected async Task TransformAsync<TBefore, TAfter>(string key, Func<TBefore, TAfter> transformation)
{
key = this.CreateKey(key);

TBefore before = await this.blobCache.GetObjectAsync<TBefore>(key);

TAfter after = transformation(before);

await this.blobCache.InvalidateObject<TBefore>(key);

await this.blobCache.InsertObject(key, after);
}

private string CreateKey(string key)
{
return string.Format("{0}:{1}", this.keyPrefix, key);
}
}
}
25 changes: 25 additions & 0 deletions Lager/SettingsStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace Lager
{
Expand Down Expand Up @@ -38,6 +40,29 @@ protected SettingsStorage(string keyPrefix, IBlobCache cache)

public event PropertyChangedEventHandler PropertyChanged;

public async Task MigrateAsync(IEnumerable<SettingsMigration> migrations)
{
List<SettingsMigration> migrationsList = migrations.ToList();

if (!migrationsList.Any())
throw new ArgumentException("Migration list is empty.", "migrations");

bool areRevisionsUnique = migrationsList
.GroupBy(x => x.Revision)
.All(x => x.Count() == 1);

if (!areRevisionsUnique)
throw new ArgumentException("Migration revisions aren't unique.", "migrations");

foreach (SettingsMigration migration in migrationsList.OrderBy(x => x.Revision))
{
migration.Initialize(this.keyPrefix, this.blobCache);
await migration.MigrateAsync();
}

this.cache.Clear();
}

/// <summary>
/// Gets the value for the specified key, or, if the value doesn't exist, saves the <paramref name="defaultValue"/> and returns it.
/// </summary>
Expand Down