185 lines
6.4 KiB
C#
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.
|
|
}
|
|
}
|
|
}
|
|
}
|