Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.Infrastructure.Tests/S5025TrustedManualFileStoreTests.cs

185 lines
6.4 KiB
C#

using System.Text;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
using MBN_STOCK_WEBVIEW.Infrastructure;
namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;
public sealed class S5025TrustedManualFileStoreTests
{
[Theory]
[InlineData(S5025ManualAudience.Individual, "개인.dat")]
[InlineData(S5025ManualAudience.Foreign, "외국인.dat")]
[InlineData(S5025ManualAudience.Institution, "기관.dat")]
public async Task WriteAsync_round_trips_the_closed_audience_file(
S5025ManualAudience audience,
string expectedFileName)
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
await store.WriteAsync(audience, Rows());
Assert.True(File.Exists(Path.Combine(directory.Path, expectedFileName)));
var actual = await store.ReadAsync(audience);
Assert.Equal(Rows(), actual);
Assert.Empty(Directory.EnumerateFiles(directory.Path, "*.tmp"));
}
[Fact]
public async Task WriteAsync_preserves_the_legacy_cp949_terminal_record()
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
await store.WriteAsync(S5025ManualAudience.Individual, Rows());
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var cp949 = Encoding.GetEncoding(949);
var bytes = await File.ReadAllBytesAsync(Path.Combine(directory.Path, "개인.dat"));
var text = cp949.GetString(bytes);
Assert.EndsWith("\r\n\r\n", text, StringComparison.Ordinal);
Assert.Equal(5, text.Split("\r\n", StringSplitOptions.None).Length - 2);
}
[Theory]
[InlineData(0)]
[InlineData(4)]
[InlineData(6)]
public async Task WriteAsync_requires_exactly_five_rows(int rowCount)
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
await Assert.ThrowsAsync<S5025TrustedManualDataException>(() =>
store.WriteAsync(
S5025ManualAudience.Individual,
Rows().Take(rowCount).Concat(Enumerable.Repeat(Rows()[0], Math.Max(0, rowCount - 5)))
.Take(rowCount)
.ToArray()));
}
[Theory]
[InlineData("bad^name")]
[InlineData("bad\nname")]
public async Task WriteAsync_rejects_delimiter_and_control_characters(string value)
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
var rows = Rows().ToArray();
rows[0] = rows[0] with { LeftName = value };
await Assert.ThrowsAsync<S5025TrustedManualDataException>(() =>
store.WriteAsync(S5025ManualAudience.Individual, rows));
}
[Fact]
public async Task WriteAsync_rejects_text_that_cp949_cannot_encode()
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
var rows = Rows().ToArray();
rows[0] = rows[0] with { LeftName = "😀" };
await Assert.ThrowsAsync<S5025TrustedManualDataException>(() =>
store.WriteAsync(S5025ManualAudience.Individual, rows));
}
[Fact]
public async Task WriteAsync_honors_pre_cancelled_token_without_creating_a_file()
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
using var cancellation = new CancellationTokenSource();
cancellation.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
store.WriteAsync(S5025ManualAudience.Individual, Rows(), cancellation.Token));
Assert.Empty(Directory.EnumerateFiles(directory.Path));
}
[Fact]
public async Task WriteAsync_rejects_unknown_audience_before_creating_a_file()
{
using var directory = new TemporaryDirectory();
using var store = CreateStore(directory.Path);
await Assert.ThrowsAsync<S5025TrustedManualDataException>(() =>
store.WriteAsync((S5025ManualAudience)99, Rows()));
Assert.Empty(Directory.EnumerateFiles(directory.Path));
}
[Fact]
public async Task WriteAsync_rejects_an_existing_reparse_destination()
{
using var directory = new TemporaryDirectory();
using var outside = new TemporaryDirectory();
var target = Path.Combine(outside.Path, "outside.dat");
await File.WriteAllTextAsync(target, "outside");
var link = Path.Combine(directory.Path, "개인.dat");
if (!TryCreateFileSymbolicLink(link, target))
{
return;
}
using var store = CreateStore(directory.Path);
await Assert.ThrowsAsync<S5025TrustedManualDataException>(() =>
store.WriteAsync(S5025ManualAudience.Individual, Rows()));
Assert.Equal("outside", await File.ReadAllTextAsync(target));
}
private static IReadOnlyList<S5025TrustedManualRow> Rows() =>
[
new("삼성전자", "1,234", "SK하이닉스", "-987"),
new("현대차", "2", "기아", "-2"),
new("NAVER", "3", "카카오", "-3"),
new("LG화학", "4", "포스코", "-4"),
new("한화", "5", "셀트리온", "-5")
];
private static S5025TrustedManualFileStore CreateStore(string directory)
{
TrustedManualDirectory.PreparePrivateWritableDirectory(directory);
return new S5025TrustedManualFileStore(directory);
}
private static bool TryCreateFileSymbolicLink(string link, string target)
{
try
{
File.CreateSymbolicLink(link, target);
return true;
}
catch (Exception exception) when (exception is
UnauthorizedAccessException or IOException or PlatformNotSupportedException)
{
return false;
}
}
private sealed class TemporaryDirectory : IDisposable
{
public TemporaryDirectory()
{
Path = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(Path);
}
public string Path { get; }
public void Dispose()
{
try
{
Directory.Delete(Path, recursive: true);
}
catch (Exception exception) when (exception is
IOException or UnauthorizedAccessException)
{
// Cleanup must not hide the test result.
}
}
}
}