Browse Source

Temp

pull/311/head
Sebastian 7 years ago
parent
commit
65c3cfbd88
  1. 20
      src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  2. 21
      src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs
  3. 64
      src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs
  4. 41
      src/Squidex.Domain.Apps.Entities/Backup/BackupSerializer.cs
  5. 37
      src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs
  6. 136
      src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs
  7. 2
      src/Squidex.Domain.Apps.Entities/Backup/IRestoreGrain.cs
  8. 10
      src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  9. 3
      src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs
  10. 17
      src/Squidex.Domain.Apps.Entities/ICleanableAppGrain.cs
  11. 23
      src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs
  12. 2
      src/Squidex.Infrastructure/States/IStreamNameResolver.cs
  13. 7
      src/Squidex/Areas/Api/Controllers/Backups/Models/RestoreRequest.cs
  14. 2
      src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs
  15. 47
      tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs
  16. 131
      tests/Squidex.Domain.Apps.Entities.Tests/Backup/GuidMapperTests.cs
  17. 30
      tests/Squidex.Infrastructure.Tests/States/DefaultStreamNameResolverTests.cs

20
src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup;
@ -17,7 +18,6 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps
@ -28,8 +28,8 @@ namespace Squidex.Domain.Apps.Entities.Apps
private readonly IGrainFactory grainFactory;
private readonly IUserResolver userResolver;
private readonly HashSet<string> activeUsers = new HashSet<string>();
private readonly Dictionary<string, string> usersWithEmail = new Dictionary<string, string>();
private readonly Dictionary<string, RefToken> userMapping = new Dictionary<string, RefToken>();
private Dictionary<string, string> usersWithEmail = new Dictionary<string, string>();
private Dictionary<string, RefToken> userMapping = new Dictionary<string, RefToken>();
private bool isReserved;
private bool isActorAssigned;
private AppCreated appCreated;
@ -152,22 +152,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadUsersAsync(BackupReader reader)
{
await reader.ReadAttachmentAsync(UsersFile, stream =>
{
stream.SerializeAsJson(usersWithEmail);
var json = await reader.ReadJsonAttachmentAsync(UsersFile);
return TaskHelper.Done;
});
usersWithEmail = json.ToObject<Dictionary<string, string>>();
}
private Task WriterUsersAsync(BackupWriter writer)
{
return writer.WriteAttachmentAsync(UsersFile, stream =>
{
stream.SerializeAsJson(usersWithEmail);
var json = JObject.FromObject(usersWithEmail);
return TaskHelper.Done;
});
return writer.WriteJsonAsync(UsersFile, json);
}
public override async Task CompleteRestoreAsync(Guid appId, BackupReader reader)

21
src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Backup;
@ -84,29 +85,23 @@ namespace Squidex.Domain.Apps.Entities.Assets
await RebuildManyAsync(assetIds, id => RebuildAsync<AssetState, AssetGrain>(id, (e, s) => s.Apply(e)));
}
private Task RestoreTagsAsync(Guid appId, BackupReader reader)
private async Task RestoreTagsAsync(Guid appId, BackupReader reader)
{
return reader.ReadAttachmentAsync(TagsFile, stream =>
{
var tags = stream.DeserializeAsJson<TagSet>();
var tags = await reader.ReadJsonAttachmentAsync(TagsFile);
return tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags);
});
await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags.ToObject<TagSet>());
}
private Task BackupTagsAsync(Guid appId, BackupWriter writer)
{
return writer.WriteAttachmentAsync(TagsFile, async stream =>
private async Task BackupTagsAsync(Guid appId, BackupWriter writer)
{
var tags = await tagService.GetExportableTagsAsync(appId, TagGroups.Assets);
stream.SerializeAsJson(tags);
});
await writer.WriteJsonAsync(TagsFile, JObject.FromObject(tags));
}
private Task WriteAssetAsync(Guid assetId, long fileVersion, BackupWriter writer)
{
return writer.WriteAttachmentAsync(GetName(assetId, fileVersion), stream =>
return writer.WriteBlobAsync(GetName(assetId, fileVersion), stream =>
{
return assetStore.DownloadAsync(assetId.ToString(), fileVersion, null, stream);
});
@ -116,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
assetIds.Add(assetId);
return reader.ReadAttachmentAsync(GetName(assetId, fileVersion), async stream =>
return reader.ReadBlobAsync(GetName(reader.OldGuid(assetId), fileVersion), async stream =>
{
try
{

64
src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -9,14 +9,19 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Archive;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupReader : DisposableObjectBase
{
private static readonly JsonSerializer Serializer = new JsonSerializer();
private readonly GuidMapper guidMapper = new GuidMapper();
private readonly ZipArchive archive;
private int readEvents;
private int readAttachments;
@ -44,7 +49,43 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
}
public async Task ReadAttachmentAsync(string name, Func<Stream, Task> handler)
public Guid OldGuid(Guid newId)
{
return guidMapper.OldGuid(newId);
}
public async Task<JToken> ReadJsonAttachmentAsync(string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
var attachmentEntry = archive.GetEntry(ArchiveHelper.GetAttachmentPath(name));
if (attachmentEntry == null)
{
throw new FileNotFoundException("Cannot find attachment.", name);
}
JToken result;
using (var stream = attachmentEntry.Open())
{
using (var textReader = new StreamReader(stream))
{
using (var jsonReader = new JsonTextReader(textReader))
{
result = await JToken.ReadFromAsync(jsonReader);
guidMapper.NewGuids(result);
}
}
}
readAttachments++;
return result;
}
public async Task ReadBlobAsync(string name, Func<Stream, Task> handler)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(handler, nameof(handler));
@ -64,9 +105,10 @@ namespace Squidex.Domain.Apps.Entities.Backup
readAttachments++;
}
public async Task ReadEventsAsync(Func<StoredEvent, Task> handler)
public async Task ReadEventsAsync(IStreamNameResolver streamNameResolver, Func<StoredEvent, Task> handler)
{
Guard.NotNull(handler, nameof(handler));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
while (true)
{
@ -79,10 +121,26 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open())
{
var storedEvent = stream.DeserializeAsJson<StoredEvent>();
using (var textReader = new StreamReader(stream))
{
using (var jsonReader = new JsonTextReader(textReader))
{
var storedEvent = Serializer.Deserialize<StoredEvent>(jsonReader);
storedEvent.Data.Payload = guidMapper.NewGuids(storedEvent.Data.Payload);
storedEvent.Data.Metadata = guidMapper.NewGuids(storedEvent.Data.Metadata);
var streamName = streamNameResolver.WithNewId(storedEvent.StreamName, guidMapper.NewGuidString);
storedEvent = new StoredEvent(streamName,
storedEvent.EventPosition,
storedEvent.EventStreamNumber,
storedEvent.Data);
await handler(storedEvent);
}
}
}
readEvents++;
}

41
src/Squidex.Domain.Apps.Entities/Backup/BackupSerializer.cs

@ -1,41 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.IO;
using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Entities.Backup
{
public static class BackupSerializer
{
private static readonly JsonSerializer JsonSerializer = JsonSerializer.CreateDefault();
public static void SerializeAsJson<T>(this Stream stream, T value)
{
using (var writer = new StreamWriter(stream))
{
JsonSerializer.Serialize(writer, value);
}
}
public static T DeserializeAsJson<T>(this Stream stream)
{
using (var reader = new StreamReader(stream))
{
return (T)JsonSerializer.Deserialize(reader, typeof(T));
}
}
public static void DeserializeAsJson<T>(this Stream stream, T result)
{
using (var reader = new StreamReader(stream))
{
JsonSerializer.Populate(reader, result);
}
}
}
}

37
src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs

@ -9,6 +9,8 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Archive;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -17,6 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupWriter : DisposableObjectBase
{
private static readonly JsonSerializer Serializer = new JsonSerializer();
private readonly ZipArchive archive;
private int writtenEvents;
private int writtenAttachments;
@ -31,9 +34,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
get { return writtenAttachments; }
}
public BackupWriter(Stream stream)
public BackupWriter(Stream stream, bool keepOpen = false)
{
archive = new ZipArchive(stream, ZipArchiveMode.Create, false);
archive = new ZipArchive(stream, ZipArchiveMode.Create, keepOpen);
}
protected override void DisposeObject(bool disposing)
@ -44,7 +47,27 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
}
public async Task WriteAttachmentAsync(string name, Func<Stream, Task> handler)
public async Task WriteJsonAsync(string name, JToken value)
{
Guard.NotNullOrEmpty(name, nameof(name));
var attachmentEntry = archive.CreateEntry(ArchiveHelper.GetAttachmentPath(name));
using (var stream = attachmentEntry.Open())
{
using (var textWriter = new StreamWriter(stream))
{
using (var jsonWriter = new JsonTextWriter(textWriter))
{
await value.WriteToAsync(jsonWriter);
}
}
}
writtenAttachments++;
}
public async Task WriteBlobAsync(string name, Func<Stream, Task> handler)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(handler, nameof(handler));
@ -67,7 +90,13 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open())
{
stream.SerializeAsJson(storedEvent);
using (var textWriter = new StreamWriter(stream))
{
using (var jsonWriter = new JsonTextWriter(textWriter))
{
Serializer.Serialize(jsonWriter, storedEvent);
}
}
}
writtenEvents++;

136
src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs

@ -12,71 +12,159 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Backup
{
public static class GuidMapper
public sealed class GuidMapper
{
private static readonly int GuidLength = Guid.Empty.ToString().Length;
private readonly List<(JObject Source, string NewKey, string OldKey)> mappings = new List<(JObject Source, string NewKey, string OldKey)>();
private readonly Dictionary<Guid, Guid> oldToNewGuid = new Dictionary<Guid, Guid>();
private readonly Dictionary<Guid, Guid> newToOldGuid = new Dictionary<Guid, Guid>();
public static void GenerateNewGuid(JToken jToken, Dictionary<Guid, Guid> guids)
public Guid NewGuid(Guid oldGuid)
{
if (jToken.Type == JTokenType.Object)
return oldToNewGuid.GetOrDefault(oldGuid);
}
public Guid OldGuid(Guid newGuid)
{
GenerateNewGuid((JObject)jToken, guids);
return newToOldGuid.GetOrDefault(newGuid);
}
public string NewGuidString(string key)
{
if (Guid.TryParse(key, out var guid))
{
return GenerateNewGuid(guid).ToString();
}
return null;
}
private static void GenerateNewGuid(JObject jObject, Dictionary<Guid, Guid> guids)
public JToken NewGuids(JToken jToken)
{
foreach (var kvp in jObject)
var result = NewGuidsCore(jToken);
if (mappings.Count > 0)
{
foreach (var mapping in mappings)
{
switch (kvp.Value.Type)
if (mapping.Source.TryGetValue(mapping.OldKey, out var value))
{
mapping.Source.Remove(mapping.OldKey);
mapping.Source[mapping.NewKey] = value;
}
}
mappings.Clear();
}
return result;
}
private JToken NewGuidsCore(JToken jToken)
{
switch (jToken.Type)
{
case JTokenType.String:
ReplaceGuidString(jObject, guids, kvp);
if (TryConvertString(jToken.ToString(), out var result))
{
return result;
}
break;
case JTokenType.Guid:
ReplaceGuid(jObject, guids, kvp);
break;
return GenerateNewGuid((Guid)jToken);
case JTokenType.Object:
GenerateNewGuid((JObject)kvp.Value, guids);
NewGuidsCore((JObject)jToken);
break;
case JTokenType.Array:
NewGuidsCore((JArray)jToken);
break;
}
return jToken;
}
private void NewGuidsCore(JArray jArray)
{
for (var i = 0; i < jArray.Count; i++)
{
jArray[i] = NewGuidsCore(jArray[i]);
}
}
private void NewGuidsCore(JObject jObject)
{
foreach (var jProperty in jObject.Properties())
{
var newValue = NewGuidsCore(jProperty.Value);
if (!ReferenceEquals(newValue, jProperty.Value))
{
jProperty.Value = newValue;
}
if (TryConvertString(jProperty.Name, out var newKey))
{
mappings.Add((jObject, newKey, jProperty.Name));
}
}
}
private bool TryConvertString(string value, out string result)
{
return TryGenerateNewGuidString(value, out result) || TryGenerateNewNamedId(value, out result);
}
private static void ReplaceGuidString(JObject jObject, Dictionary<Guid, Guid> guids, KeyValuePair<string, JToken> kvp)
private bool TryGenerateNewGuidString(string value, out string result)
{
var value = kvp.Value.ToString();
result = null;
if (value.Length == GuidLength)
{
if (Guid.TryParse(value, out var guid))
{
var newGuid = guids.GetOrAdd(guid, GuidGenerator);
var newGuid = GenerateNewGuid(guid);
jObject.Property(kvp.Key).Value = newGuid.ToString();
result = newGuid.ToString();
return true;
}
}
else if (value.Length > GuidLength && value[GuidLength] == ',')
return false;
}
private bool TryGenerateNewNamedId(string value, out string result)
{
result = null;
if (value.Length > GuidLength && value[GuidLength] == ',')
{
if (Guid.TryParse(value.Substring(0, GuidLength), out var guid))
{
var newGuid = guids.GetOrAdd(guid, GuidGenerator);
var newGuid = GenerateNewGuid(guid);
result = newGuid + value.Substring(GuidLength);
jObject.Property(kvp.Key).Value = newGuid + value.Substring(GuidLength);
return true;
}
}
return false;
}
private static void ReplaceGuid(JObject jObject, Dictionary<Guid, Guid> guids, KeyValuePair<string, JToken> kvp)
private Guid GenerateNewGuid(Guid oldGuid)
{
var newGuid = guids.GetOrAdd((Guid)kvp.Value, GuidGenerator);
jObject.Property(kvp.Key).Value = newGuid;
return oldToNewGuid.GetOrAdd(oldGuid, GuidGenerator);
}
private static Guid GuidGenerator(Guid key)
private Guid GuidGenerator(Guid oldGuid)
{
return Guid.NewGuid();
var newGuid = Guid.NewGuid();
newToOldGuid[newGuid] = oldGuid;
return newGuid;
}
}
}

2
src/Squidex.Domain.Apps.Entities/Backup/IRestoreGrain.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public interface IRestoreGrain : IGrainWithStringKey
{
Task RestoreAsync(Uri url);
Task RestoreAsync(string appName, Uri url);
Task<J<IRestoreJob>> GetJobAsync();
}

10
src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -34,6 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IEventDataFormatter eventDataFormatter;
private readonly IGrainFactory grainFactory;
private readonly ISemanticLog log;
private readonly IStreamNameResolver streamNameResolver;
private readonly IStore<string> store;
private RefToken actor;
private RestoreState state = new RestoreState();
@ -53,6 +54,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IGrainFactory grainFactory,
IEnumerable<BackupHandler> handlers,
ISemanticLog log,
IStreamNameResolver streamNameResolver,
IStore<string> store)
{
Guard.NotNull(assetStore, nameof(assetStore));
@ -63,6 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(store, nameof(store));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
Guard.NotNull(log, nameof(log));
this.assetStore = assetStore;
@ -73,6 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.grainFactory = grainFactory;
this.handlers = handlers;
this.store = store;
this.streamNameResolver = streamNameResolver;
this.log = log;
}
@ -105,9 +109,10 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
}
public Task RestoreAsync(Uri url)
public Task RestoreAsync(string appName, Uri url)
{
Guard.NotNull(url, nameof(url));
Guard.NotNullOrEmpty(appName, nameof(appName));
if (CurrentJob?.Status == JobStatus.Started)
{
@ -117,6 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
state.Job = new RestoreStateJob
{
Id = Guid.NewGuid(),
AppName = appName,
Started = clock.GetCurrentInstant(),
Status = JobStatus.Started,
Url = url
@ -255,7 +261,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ReadEventsAsync(BackupReader reader)
{
await reader.ReadEventsAsync(async (storedEvent) =>
await reader.ReadEventsAsync(streamNameResolver, async (storedEvent) =>
{
var @event = eventDataFormatter.Parse(storedEvent.Data);

3
src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs

@ -14,6 +14,9 @@ namespace Squidex.Domain.Apps.Entities.Backup.State
{
public sealed class RestoreStateJob : IRestoreJob
{
[JsonProperty]
public string AppName { get; set; }
[JsonProperty]
public Guid Id { get; set; }

17
src/Squidex.Domain.Apps.Entities/ICleanableAppGrain.cs

@ -1,17 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Orleans;
namespace Squidex.Domain.Apps.Entities
{
public interface ICleanableAppGrain : IGrainWithGuidKey
{
Task ClearAsync();
}
}

23
src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs

@ -15,6 +15,9 @@ namespace Squidex.Infrastructure.States
public string GetStreamName(Type aggregateType, string id)
{
Guard.NotNullOrEmpty(id, nameof(id));
Guard.NotNull(aggregateType, nameof(aggregateType));
var typeName = char.ToLower(aggregateType.Name[0]) + aggregateType.Name.Substring(1);
foreach (var suffix in Suffixes)
@ -29,5 +32,25 @@ namespace Squidex.Infrastructure.States
return $"{typeName}-{id}";
}
public string WithNewId(string streamName, Func<string, string> idGenerator)
{
Guard.NotNullOrEmpty(streamName, nameof(streamName));
Guard.NotNull(idGenerator, nameof(idGenerator));
var positionOfDash = streamName.LastIndexOf('-');
if (positionOfDash >= 0)
{
var newId = idGenerator(streamName.Substring(positionOfDash + 1));
if (!string.IsNullOrWhiteSpace(newId))
{
streamName = $"{streamName.Substring(0, positionOfDash)}-{newId}";
}
}
return streamName;
}
}
}

2
src/Squidex.Infrastructure/States/IStreamNameResolver.cs

@ -12,5 +12,7 @@ namespace Squidex.Infrastructure.States
public interface IStreamNameResolver
{
string GetStreamName(Type aggregateType, string id);
string WithNewId(string streamName, Func<string, string> idGenerator);
}
}

7
src/Squidex/Areas/Api/Controllers/Backups/Models/RestoreRequest.cs

@ -12,6 +12,13 @@ namespace Squidex.Areas.Api.Controllers.Backups.Models
{
public sealed class RestoreRequest
{
/// <summary>
/// The name of the app.
/// </summary>
[Required]
[RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")]
public string Name { get; set; }
/// <summary>
/// The url to the restore file.
/// </summary>

2
src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs

@ -63,7 +63,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
{
var restoreGrain = grainFactory.GetGrain<IRestoreGrain>(User.OpenIdSubject());
await restoreGrain.RestoreAsync(request.Url);
await restoreGrain.RestoreAsync(request.Name, request.Url);
return NoContent();
}

47
tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs

@ -5,11 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
using Xunit;
@ -17,6 +21,14 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public class BackupReaderWriterTests
{
private readonly IStreamNameResolver streamNameResolver = A.Fake<IStreamNameResolver>();
public BackupReaderWriterTests()
{
A.CallTo(() => streamNameResolver.WithNewId(A<string>.Ignored, A<Func<string, string>>.Ignored))
.ReturnsLazily(new Func<string, Func<string, string>, string>((stream, idGenerator) => stream + "^2"));
}
[Fact]
public async Task Should_write_and_read_events()
{
@ -24,20 +36,26 @@ namespace Squidex.Domain.Apps.Entities.Backup
var sourceEvents = new List<StoredEvent>();
using (var writer = new BackupWriter(stream))
using (var writer = new BackupWriter(stream, true))
{
for (var i = 0; i < 1000; i++)
{
var eventData = new EventData { Type = i.ToString(), Metadata = i, Payload = i };
var eventStored = new StoredEvent("S", "1", 2, eventData);
if (i % 10 == 0)
if (i % 17 == 0)
{
await writer.WriteAttachmentAsync(eventData.Type, innerStream =>
await writer.WriteBlobAsync(eventData.Type, innerStream =>
{
return innerStream.WriteAsync(new byte[] { (byte)i }, 0, 1);
innerStream.WriteByte((byte)i);
return TaskHelper.Done;
});
}
else if (i % 37 == 0)
{
await writer.WriteJsonAsync(eventData.Type, $"JSON_{i}");
}
writer.WriteEvent(eventStored);
@ -51,13 +69,13 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var reader = new BackupReader(stream))
{
await reader.ReadEventsAsync(async @event =>
await reader.ReadEventsAsync(streamNameResolver, async @event =>
{
var i = int.Parse(@event.Data.Type);
if (i % 10 == 0)
if (i % 17 == 0)
{
await reader.ReadAttachmentAsync(@event.Data.Type, innerStream =>
await reader.ReadBlobAsync(@event.Data.Type, innerStream =>
{
var b = innerStream.ReadByte();
@ -66,12 +84,25 @@ namespace Squidex.Domain.Apps.Entities.Backup
return TaskHelper.Done;
});
}
else if (i % 37 == 0)
{
var j = await reader.ReadJsonAttachmentAsync(@event.Data.Type);
Assert.Equal($"JSON_{i}", j.ToString());
}
readEvents.Add(@event);
});
}
readEvents.Should().BeEquivalentTo(sourceEvents);
var sourceEventsWithNewStreamName =
sourceEvents.Select(x =>
new StoredEvent(streamNameResolver.WithNewId(x.StreamName, null),
x.EventPosition,
x.EventStreamNumber,
x.Data)).ToList();
readEvents.Should().BeEquivalentTo(sourceEventsWithNewStreamName);
}
}
}

131
tests/Squidex.Domain.Apps.Entities.Tests/Backup/GuidMapperTests.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Xunit;
@ -14,14 +13,100 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public class GuidMapperTests
{
private readonly Guid id1 = Guid.NewGuid();
private readonly Guid id2 = Guid.NewGuid();
private readonly GuidMapper map = new GuidMapper();
[Fact]
public void Should_map_guid_string_if_valid()
{
var result = map.NewGuidString(id1.ToString());
Assert.Equal(map.NewGuid(id1).ToString(), result);
}
[Fact]
public void Should_return_null_if_mapping_invalid_guid_string()
{
var result = map.NewGuidString("invalid");
Assert.Null(result);
}
[Fact]
public void Should_return_null_if_mapping_null_guid_string()
{
var result = map.NewGuidString(null);
Assert.Null(result);
}
[Fact]
public void Should_map_guid()
{
var m = new Dictionary<Guid, Guid>();
var result = map.NewGuids(id1);
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
Assert.Equal(map.NewGuid(id1), result.Value<Guid>());
}
[Fact]
public void Should_return_old_guid()
{
var newGuid = map.NewGuids(id1).Value<Guid>();
Assert.Equal(id1, map.OldGuid(newGuid));
}
[Fact]
public void Should_map_guid_string()
{
var result = map.NewGuids(id1.ToString());
Assert.Equal(map.NewGuid(id1).ToString(), result.Value<string>());
}
[Fact]
public void Should_map_named_id()
{
var result = map.NewGuids($"{id1},name");
Assert.Equal($"{map.NewGuid(id1)},name", result.Value<string>());
}
[Fact]
public void Should_map_array_with_guid()
{
var obj =
new JObject(
new JProperty("k",
new JArray(id1, id1, id2)));
map.NewGuids(obj);
Assert.Equal(map.NewGuid(id1), obj["k"][0].Value<Guid>());
Assert.Equal(map.NewGuid(id1), obj["k"][1].Value<Guid>());
Assert.Equal(map.NewGuid(id2), obj["k"][2].Value<Guid>());
}
[Fact]
public void Should_map_objects_with_guid_keys()
{
var obj =
new JObject(
new JProperty("k",
new JObject(
new JProperty(id1.ToString(), id1),
new JProperty(id2.ToString(), id2))));
map.NewGuids(obj);
Assert.Equal(map.NewGuid(id1), obj["k"].Value<Guid>(map.NewGuid(id1).ToString()));
Assert.Equal(map.NewGuid(id2), obj["k"].Value<Guid>(map.NewGuid(id2).ToString()));
}
[Fact]
public void Should_map_objects_with_guid()
{
var obj =
new JObject(
new JProperty("k",
@ -30,21 +115,16 @@ namespace Squidex.Domain.Apps.Entities.Backup
new JProperty("v2", id1),
new JProperty("v3", id2))));
GuidMapper.GenerateNewGuid(obj, m);
map.NewGuids(obj);
Assert.Equal(m[id1], obj["k"].Value<Guid>("v1"));
Assert.Equal(m[id1], obj["k"].Value<Guid>("v2"));
Assert.Equal(m[id2], obj["k"].Value<Guid>("v3"));
Assert.Equal(map.NewGuid(id1), obj["k"].Value<Guid>("v1"));
Assert.Equal(map.NewGuid(id1), obj["k"].Value<Guid>("v2"));
Assert.Equal(map.NewGuid(id2), obj["k"].Value<Guid>("v3"));
}
[Fact]
public void Should_map_guid_string()
public void Should_map_objects_with_guid_string()
{
var m = new Dictionary<Guid, Guid>();
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var obj =
new JObject(
new JProperty("k",
@ -53,21 +133,16 @@ namespace Squidex.Domain.Apps.Entities.Backup
new JProperty("v2", id1.ToString()),
new JProperty("v3", id2.ToString()))));
GuidMapper.GenerateNewGuid(obj, m);
map.NewGuids(obj);
Assert.Equal(m[id1].ToString(), obj["k"].Value<string>("v1"));
Assert.Equal(m[id1].ToString(), obj["k"].Value<string>("v2"));
Assert.Equal(m[id2].ToString(), obj["k"].Value<string>("v3"));
Assert.Equal(map.NewGuid(id1).ToString(), obj["k"].Value<string>("v1"));
Assert.Equal(map.NewGuid(id1).ToString(), obj["k"].Value<string>("v2"));
Assert.Equal(map.NewGuid(id2).ToString(), obj["k"].Value<string>("v3"));
}
[Fact]
public void Should_map_named_id()
public void Should_map_objects_with_named_id()
{
var m = new Dictionary<Guid, Guid>();
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var obj =
new JObject(
new JProperty("k",
@ -76,11 +151,11 @@ namespace Squidex.Domain.Apps.Entities.Backup
new JProperty("v2", $"{id1},v2"),
new JProperty("v3", $"{id2},v3"))));
GuidMapper.GenerateNewGuid(obj, m);
map.NewGuids(obj);
Assert.Equal($"{m[id1].ToString()},v1", obj["k"].Value<string>("v1"));
Assert.Equal($"{m[id1].ToString()},v2", obj["k"].Value<string>("v2"));
Assert.Equal($"{m[id2].ToString()},v3", obj["k"].Value<string>("v3"));
Assert.Equal($"{map.NewGuid(id1).ToString()},v1", obj["k"].Value<string>("v1"));
Assert.Equal($"{map.NewGuid(id1).ToString()},v2", obj["k"].Value<string>("v2"));
Assert.Equal($"{map.NewGuid(id2).ToString()},v3", obj["k"].Value<string>("v3"));
}
}
}

30
tests/Squidex.Infrastructure.Tests/States/DefaultStreamNameResolverTests.cs

@ -43,5 +43,35 @@ namespace Squidex.Infrastructure.States
Assert.Equal($"myUser-{id}", name);
}
[Fact]
public void Should_calculate_new_stream_if_valid()
{
var oldStream = "myUser-123";
var newStream = sut.WithNewId(oldStream, x => "456");
Assert.Equal("myUser-456", newStream);
}
[Fact]
public void Should_return_old_stream_if_format_not_valid()
{
var oldStream = "myUser|123";
var newStream = sut.WithNewId(oldStream, x => "456");
Assert.Equal(oldStream, newStream);
}
[Fact]
public void Should_return_old_stream_if_new_id_not_valid()
{
var oldStream = "myUser-123";
var newStream = sut.WithNewId(oldStream, x => null);
Assert.Equal(oldStream, newStream);
}
}
}

Loading…
Cancel
Save