Skip to content

Commit 7082f53

Browse files
stefanedwardsbowencode
authored andcommitted
add: appending to FileDataSink
1 parent 6960fa0 commit 7082f53

File tree

6 files changed

+115
-9
lines changed

6 files changed

+115
-9
lines changed

Interfaces/Cosmos.DataTransfer.Common.UnitTests/FileDataSinkTests.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
using System.IO.Compression;
22
using Microsoft.Extensions.Logging.Abstractions;
3+
using Microsoft.Extensions.Configuration;
34
using System.Text;
45
using Microsoft.VisualStudio.TestTools.UnitTesting;
56
using Cosmos.DataTransfer.JsonExtension;
67
using Cosmos.DataTransfer.JsonExtension.UnitTests;
78
using Cosmos.DataTransfer.Interfaces;
89
using Moq;
910

10-
namespace Cosmos.DataTransfer.Common.UnitTest;
11+
namespace Cosmos.DataTransfer.Common.UnitTests;
1112

1213
[TestClass]
1314
public class FileSinkDataSinkTests {
1415

1516
public static IEnumerable<object[]> Test_WriteToTargetAsyncData { get {
16-
1717
yield return new object[] { CompressionEnum.None, "", "" };
1818
yield return new object[] { CompressionEnum.None, ".txt", ".txt" };
1919
yield return new object[] { CompressionEnum.Brotli, "", ".br" };
@@ -26,15 +26,15 @@ public static IEnumerable<object[]> Test_WriteToTargetAsyncData { get {
2626
} }
2727

2828
[TestMethod]
29-
[DoNotParallelize]
3029
[DynamicData(nameof(Test_WriteToTargetAsyncData))]
3130
public async Task Test_WriteToTargetAsync(CompressionEnum compression, string suffix, string expected_ext) {
3231
var source = new Mock<IDataSourceExtension>();
3332
FileDataSink sink = new();
3433
var filePath = Path.GetTempFileName();
3534
var config = TestHelpers.CreateConfig(new Dictionary<string,string>() {
3635
{ "FilePath", filePath + suffix },
37-
{ "Compression", compression.ToString() }
36+
{ "Compression", compression.ToString() },
37+
{ "Append", "false" }
3838
});
3939

4040
var str = Encoding.UTF8.GetBytes("Hello world!");
@@ -57,4 +57,44 @@ public void Test_GetSettings() {
5757
Assert.IsInstanceOfType(response[0], typeof(FileSinkSettings));
5858
}
5959

60+
public static IEnumerable<object[]> Test_WriteToTargetAsyncAppendData { get {
61+
yield return new object[] { "foobar.txt", CompressionEnum.None };
62+
yield return new object[] { "foobar.txt.gz", CompressionEnum.Gzip };
63+
yield return new object[] { "foobar.txt.br", CompressionEnum.Brotli };
64+
yield return new object[] { "foobar.txt.zz", CompressionEnum.Deflate };
65+
} }
66+
67+
[TestMethod]
68+
[DynamicData(nameof(Test_WriteToTargetAsyncAppendData))]
69+
public async Task Test_WriteToTargetAsyncAppend(string filePath, CompressionEnum compression) {
70+
var source = new Mock<IDataSourceExtension>();
71+
FileDataSink sink = new();
72+
var tmpdir = FileSinkDataSourceTests.GetTemporaryDirectory();
73+
var destfile = Path.Combine(tmpdir, filePath);
74+
File.Copy(Path.Combine("Data", filePath), destfile);
75+
var config = TestHelpers.CreateConfig(new Dictionary<string,string>() {
76+
{ "FilePath", destfile },
77+
{ "Compression", compression.ToString() },
78+
{ "Append", "true" }
79+
});
80+
81+
if (compression != CompressionEnum.None) {
82+
var e = Assert.ThrowsException<AggregateException>(() => {
83+
var settings = config.Get<FileSinkSettings>();
84+
settings.Validate();
85+
});
86+
return;
87+
}
88+
var str = Encoding.UTF8.GetBytes("\nIt's Valentines day! Lovely!");
89+
await sink.WriteToTargetAsync(writer => writer.WriteAsync(str).AsTask(),
90+
config, source.Object, NullLogger.Instance);
91+
92+
var dataSource = new FileDataSource();
93+
var stream = dataSource.ReadFile(destfile, CompressionEnum.None, NullLogger.Instance)!;
94+
var buffer = new byte[100];
95+
await stream.ReadAsync(buffer.AsMemory(0, buffer.Length));
96+
var result = Encoding.UTF8.GetString(buffer);
97+
Assert.AreEqual("Hello world!\nIt's Valentines day! Lovely!", result.TrimEnd('\0'));
98+
}
99+
60100
}

Interfaces/Cosmos.DataTransfer.Common.UnitTests/FileDataSourceTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
using System.Text;
22
using Microsoft.VisualStudio.TestTools.UnitTesting;
3-
using Microsoft.Extensions.Logging;
43
using Microsoft.Extensions.Logging.Abstractions;
54
using Cosmos.DataTransfer.JsonExtension.UnitTests;
65

7-
namespace Cosmos.DataTransfer.Common.UnitTest;
6+
namespace Cosmos.DataTransfer.Common.UnitTests;
87

98
[TestClass]
109
public class FileSinkDataSourceTests {
@@ -50,7 +49,7 @@ public async Task Test_ReadSourceAsync_SingleFile(string filePath, CompressionEn
5049
Assert.AreEqual(expected, result.TrimEnd('\0'), $"file: {filePath}, compression: {compression}.");
5150
}
5251

53-
private string GetTemporaryDirectory() {
52+
internal static string GetTemporaryDirectory() {
5453
string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
5554
if (File.Exists(tempDirectory))
5655
{
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using Cosmos.DataTransfer.Interfaces;
3+
4+
namespace Cosmos.DataTransfer.Common.UnitTests;
5+
6+
[TestClass]
7+
public class FileSinkSettingsTests {
8+
9+
[TestMethod]
10+
public void TestValidate_PassMinimum() {
11+
var settings = new FileSinkSettings() {
12+
FilePath = "A file",
13+
};
14+
settings.Validate();
15+
}
16+
17+
[TestMethod]
18+
public void TestValidate_Fails() {
19+
var settings = new FileSinkSettings() {
20+
FilePath = " ",
21+
};
22+
var e = Assert.ThrowsException<AggregateException>(() => settings.Validate());
23+
Assert.AreEqual(e.InnerException!.Message, "The FilePath field is required.");
24+
}
25+
26+
[TestMethod]
27+
[DataRow(false, "None", true)]
28+
[DataRow(false, "Gzip", true)]
29+
[DataRow(false, "Brotli", true)]
30+
[DataRow(false, "Deflate", true)]
31+
[DataRow(true, "None", true)]
32+
[DataRow(true, "Gzip", false)]
33+
[DataRow(true, "Brotli", false)]
34+
[DataRow(true, "Deflate", false)]
35+
public void TestValidate_TestCombinations(bool append, string compression, bool pass) {
36+
var settings = new FileSinkSettings() {
37+
FilePath = "A file",
38+
Append = append,
39+
Compression = Enum.Parse<CompressionEnum>(compression),
40+
};
41+
if (!pass) {
42+
Assert.ThrowsException<AggregateException>(() => settings.Validate());
43+
} else {
44+
settings.Validate();
45+
}
46+
}
47+
48+
49+
}

Interfaces/Cosmos.DataTransfer.Common/FileDataSink.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public async Task WriteToTargetAsync(Func<Stream, Task> writeToStream, IConfigur
1515
{
1616
using var writer = GetCompressor(settings.Compression, settings.FilePath, settings.Append);
1717
await writeToStream(writer);
18+
writer.Close();
1819
}
1920
}
2021

Interfaces/Cosmos.DataTransfer.Common/FileSinkSettings.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@
33

44
namespace Cosmos.DataTransfer.Common;
55

6-
public class FileSinkSettings : IDataExtensionSettings
6+
public class FileSinkSettings : IDataExtensionSettings, IValidatableObject
77
{
88
[Required]
99
public string? FilePath { get; set; }
1010
public bool Append { get; set; } = false;
1111
public CompressionEnum Compression { get; set; } = CompressionEnum.None;
12+
13+
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
14+
{
15+
if (this.Append && Compression != CompressionEnum.None) {
16+
// Technically we can, but the .NET methods here
17+
// cannot read the concatenated, compressed files.
18+
yield return new ValidationResult(
19+
"Cannot Append to any compressed files.",
20+
new string[] { "Append", "Compression" }
21+
);
22+
}
23+
}
1224
}

Interfaces/Cosmos.DataTransfer.Common/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ Gzip, bzip2, and zlib compressed files are automatically detected by file extens
2626
Use parameter `Compression` to enable compression of output file.
2727
The relevant extension is automatically added to the file name, if missing.
2828

29+
It is possible to build further upon a file by setting the `Append` parameter
30+
to `true`. Note however that it is not compatible with any compression methods,
31+
as the result a concatenation of multiple compressed files.
32+
2933
```json
3034
{
3135
"FilePath": "",
32-
"Compression": "None" | "Gzip" | "Brotli" | "Deflate"
36+
"Compression": "None" | "Gzip" | "Brotli" | "Deflate",
37+
"Append": false
3338
}
3439
```

0 commit comments

Comments
 (0)