mirror of https://github.com/Squidex/squidex.git
Browse Source
* Repair asset files. * Tests fixed. * Mini fix to prevent errors. * Reverted the event consumer approach.pull/596/head
committed by
GitHub
22 changed files with 338 additions and 119 deletions
@ -0,0 +1,75 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Events.Assets; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Assets; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public sealed class RepairFiles |
||||
|
{ |
||||
|
private static readonly MemoryStream DummyStream = new MemoryStream(Encoding.UTF8.GetBytes("dummy")); |
||||
|
private readonly IAssetFileStore assetFileStore; |
||||
|
private readonly IEventStore eventStore; |
||||
|
private readonly IEventDataFormatter eventDataFormatter; |
||||
|
|
||||
|
public RepairFiles( |
||||
|
IAssetFileStore assetFileStore, |
||||
|
IEventStore eventStore, |
||||
|
IEventDataFormatter eventDataFormatter) |
||||
|
{ |
||||
|
Guard.NotNull(assetFileStore, nameof(assetFileStore)); |
||||
|
Guard.NotNull(eventStore, nameof(eventStore)); |
||||
|
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); |
||||
|
|
||||
|
this.assetFileStore = assetFileStore; |
||||
|
this.eventStore = eventStore; |
||||
|
this.eventDataFormatter = eventDataFormatter; |
||||
|
} |
||||
|
|
||||
|
public async Task RepairAsync(CancellationToken ct = default) |
||||
|
{ |
||||
|
await eventStore.QueryAsync(async storedEvent => |
||||
|
{ |
||||
|
var @event = eventDataFormatter.ParseIfKnown(storedEvent); |
||||
|
|
||||
|
if (@event != null) |
||||
|
{ |
||||
|
switch (@event.Payload) |
||||
|
{ |
||||
|
case AssetCreated assetCreated: |
||||
|
await TryRepairAsync(assetCreated.AppId, assetCreated.AssetId, assetCreated.FileVersion, ct); |
||||
|
break; |
||||
|
case AssetUpdated assetUpdated: |
||||
|
await TryRepairAsync(assetUpdated.AppId, assetUpdated.AssetId, assetUpdated.FileVersion, ct); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
}, "^asset\\-", ct: ct); |
||||
|
} |
||||
|
|
||||
|
private async Task TryRepairAsync(NamedId<DomainId> appId, DomainId id, long fileVersion, CancellationToken ct) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await assetFileStore.GetFileSizeAsync(appId.Id, id, fileVersion, ct); |
||||
|
} |
||||
|
catch (AssetNotFoundException) |
||||
|
{ |
||||
|
DummyStream.Position = 0; |
||||
|
|
||||
|
await assetFileStore.UploadAsync(appId.Id, id, fileVersion, DummyStream, ct); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Events.Assets; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Assets; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class RepairFilesTests |
||||
|
{ |
||||
|
private readonly IEventStore eventStore = A.Fake<IEventStore>(); |
||||
|
private readonly IEventDataFormatter eventDataFormatter = A.Fake<IEventDataFormatter>(); |
||||
|
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>(); |
||||
|
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); |
||||
|
private readonly RepairFiles sut; |
||||
|
|
||||
|
public RepairFilesTests() |
||||
|
{ |
||||
|
sut = new RepairFiles(assetFileStore, eventStore, eventDataFormatter); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_repair_created_asset_if_not_found() |
||||
|
{ |
||||
|
var @event = new AssetCreated { AppId = appId, AssetId = DomainId.NewGuid() }; |
||||
|
|
||||
|
SetupEvent(@event); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 0, default)) |
||||
|
.Throws(new AssetNotFoundException("file")); |
||||
|
|
||||
|
await sut.RepairAsync(); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 0, A<Stream>._, default)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_repair_created_asset_if_found() |
||||
|
{ |
||||
|
var @event = new AssetCreated { AppId = appId, AssetId = DomainId.NewGuid() }; |
||||
|
|
||||
|
SetupEvent(@event); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 0, default)) |
||||
|
.Returns(100); |
||||
|
|
||||
|
await sut.RepairAsync(); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 0, A<Stream>._, default)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_repair_updated_asset_if_not_found() |
||||
|
{ |
||||
|
var @event = new AssetUpdated { AppId = appId, AssetId = DomainId.NewGuid(), FileVersion = 3 }; |
||||
|
|
||||
|
SetupEvent(@event); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 3, default)) |
||||
|
.Throws(new AssetNotFoundException("file")); |
||||
|
|
||||
|
await sut.RepairAsync(); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 3, A<Stream>._, default)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_repair_updated_asset_if_found() |
||||
|
{ |
||||
|
var @event = new AssetUpdated { AppId = appId, AssetId = DomainId.NewGuid(), FileVersion = 3 }; |
||||
|
|
||||
|
SetupEvent(@event); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 3, default)) |
||||
|
.Returns(100); |
||||
|
|
||||
|
await sut.RepairAsync(); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 3, A<Stream>._, default)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_ignore_old_events() |
||||
|
{ |
||||
|
SetupEvent(null); |
||||
|
|
||||
|
await sut.RepairAsync(); |
||||
|
|
||||
|
A.CallTo(() => assetFileStore.GetFileSizeAsync(A<DomainId>._, A<DomainId>._, A<long>._, default)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private void SetupEvent(IEvent? @event) |
||||
|
{ |
||||
|
var storedEvent = new StoredEvent("stream", "0", -1, new EventData("type", new EnvelopeHeaders(), "payload")); |
||||
|
|
||||
|
if (@event != null) |
||||
|
{ |
||||
|
A.CallTo(() => eventDataFormatter.ParseIfKnown(storedEvent)) |
||||
|
.Returns(Envelope.Create(@event)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
A.CallTo(() => eventDataFormatter.ParseIfKnown(storedEvent)) |
||||
|
.Returns(null); |
||||
|
} |
||||
|
|
||||
|
A.CallTo(() => eventStore.QueryAsync(A<Func<StoredEvent, Task>>._, "^asset\\-", null, default)) |
||||
|
.Invokes(x => |
||||
|
{ |
||||
|
var callback = x.GetArgument<Func<StoredEvent, Task>>(0)!; |
||||
|
|
||||
|
callback(storedEvent).Wait(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue