Skip to content

Commit 058b782

Browse files
authored
Fix a bug where data files are deleted if GetAsync fails when updating (#787)
1 parent a3eee4b commit 058b782

File tree

2 files changed

+46
-21
lines changed

2 files changed

+46
-21
lines changed

src/Serval/src/Serval.DataFiles/Services/DataFileService.cs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public async Task CreateAsync(DataFile dataFile, Stream stream, CancellationToke
4040
string path = GetDataFilePath(filename);
4141
try
4242
{
43-
using Stream fileStream = _fileSystem.OpenWrite(path);
43+
await using Stream fileStream = _fileSystem.OpenWrite(path);
4444
await stream.CopyToAsync(fileStream, cancellationToken);
4545
await Entities.InsertAsync(dataFile with { Filename = filename }, cancellationToken);
4646
}
@@ -67,10 +67,10 @@ public async Task<DataFile> UpdateAsync(string id, Stream stream, CancellationTo
6767
bool deleteFile = false;
6868
try
6969
{
70-
using (Stream fileStream = _fileSystem.OpenWrite(path))
70+
await using (Stream fileStream = _fileSystem.OpenWrite(path))
7171
await stream.CopyToAsync(fileStream, cancellationToken);
7272
await _dataAccessContext.WithTransactionAsync(
73-
async (ct) =>
73+
async ct =>
7474
{
7575
DataFile? originalDataFile = await Entities.UpdateAsync(
7676
id,
@@ -79,21 +79,16 @@ await _dataAccessContext.WithTransactionAsync(
7979
cancellationToken: ct
8080
);
8181
if (originalDataFile is null)
82-
{
8382
throw new EntityNotFoundException($"Could not find the DataFile '{id}'.");
84-
}
85-
else
86-
{
87-
await _deletedFiles.InsertAsync(
88-
new DeletedFile { Filename = originalDataFile.Filename, DeletedAt = DateTime.UtcNow },
89-
cancellationToken: ct
90-
);
91-
}
83+
84+
await _deletedFiles.InsertAsync(
85+
new DeletedFile { Filename = originalDataFile.Filename, DeletedAt = DateTime.UtcNow },
86+
cancellationToken: ct
87+
);
9288
await _mediator.Publish(new DataFileUpdated { DataFileId = id, Filename = filename }, ct);
9389
},
9490
cancellationToken: cancellationToken
9591
);
96-
return await GetAsync(id, cancellationToken);
9792
}
9893
catch
9994
{
@@ -105,12 +100,13 @@ await _deletedFiles.InsertAsync(
105100
if (deleteFile)
106101
_fileSystem.DeleteFile(path);
107102
}
103+
104+
return await GetAsync(id, cancellationToken);
108105
}
109106

110-
public override async Task DeleteAsync(string id, CancellationToken cancellationToken = default)
111-
{
107+
public override async Task DeleteAsync(string id, CancellationToken cancellationToken = default) =>
112108
await _dataAccessContext.WithTransactionAsync(
113-
async (ct) =>
109+
async ct =>
114110
{
115111
DataFile? dataFile = await Entities.DeleteAsync(id, ct);
116112
if (dataFile is null)
@@ -124,10 +120,6 @@ await _deletedFiles.InsertAsync(
124120
},
125121
cancellationToken: cancellationToken
126122
);
127-
}
128123

129-
private string GetDataFilePath(string filename)
130-
{
131-
return Path.Combine(_options.CurrentValue.FilesDirectory, filename);
132-
}
124+
private string GetDataFilePath(string filename) => Path.Combine(_options.CurrentValue.FilesDirectory, filename);
133125
}

src/Serval/test/Serval.DataFiles.Tests/Services/DataFileServiceTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,39 @@ public async Task UpdateAsync_Exists()
8282
Assert.That(deletedFile.Filename, Is.EqualTo("file1.txt"));
8383
}
8484

85+
[Test]
86+
public void UpdateAsync_GetAsyncFails()
87+
{
88+
var env = new TestEnvironment();
89+
90+
// We will use the mediator to cancel the token, which will cause GetAsync() to fail
91+
// What we are testing for is GetAsync() failing due to network or other connectivity issues, token cancellation being one source
92+
var cts = new CancellationTokenSource();
93+
env.Mediator.When(x => x.Publish(Arg.Any<DataFileUpdated>(), Arg.Any<CancellationToken>()))
94+
.Do(_ => cts.Cancel());
95+
96+
// Set up a valid existing file
97+
env.DataFiles.Add(DefaultDataFile with { });
98+
using var fileStream = new MemoryStream();
99+
env.FileSystem.OpenWrite(Arg.Any<string>()).Returns(fileStream);
100+
string content = "This is a file.";
101+
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
102+
{
103+
Assert.ThrowsAsync<OperationCanceledException>(
104+
() => env.Service.UpdateAsync(DataFileId, stream, cts.Token)
105+
);
106+
}
107+
108+
// Verify the file was updated
109+
DataFile dataFile = env.DataFiles.Get(DataFileId);
110+
Assert.That(dataFile.Revision, Is.EqualTo(2));
111+
Assert.That(Encoding.UTF8.GetString(fileStream.ToArray()), Is.EqualTo(content));
112+
DeletedFile deletedFile = env.DeletedFiles.Entities.Single();
113+
Assert.That(deletedFile.Filename, Is.EqualTo("file1.txt"));
114+
115+
env.FileSystem.DidNotReceive().DeleteFile(Arg.Any<string>());
116+
}
117+
85118
[Test]
86119
public void UpdateAsync_DoesNotExist()
87120
{

0 commit comments

Comments
 (0)