567 lines
23 KiB
C#
567 lines
23 KiB
C#
using System.Text.Json;
|
|
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
|
using MBN_STOCK_WEBVIEW.Infrastructure;
|
|
using MMoneyCoderSharp.Data;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;
|
|
|
|
public sealed class LegacyManualOperatorDataImporterTests
|
|
{
|
|
private static readonly string[] DestinationFiles =
|
|
[
|
|
"개인.dat",
|
|
"외국인.dat",
|
|
"기관.dat",
|
|
"VI발동.dat"
|
|
];
|
|
|
|
[Fact]
|
|
public async Task Fixed_source_reads_the_four_original_files_without_modifying_them()
|
|
{
|
|
using var sourceDirectory = new TemporaryDirectory();
|
|
WriteSourceFiles(sourceDirectory.Path);
|
|
var before = DestinationFiles.ToDictionary(
|
|
name => name,
|
|
name => File.ReadAllBytes(Path.Combine(sourceDirectory.Path, name)));
|
|
var source = new FixedLegacyManualOperatorDataSource(
|
|
sourceDirectory.Path,
|
|
TimeSpan.Zero);
|
|
|
|
var snapshot = await source.ReadAsync();
|
|
|
|
Assert.Equal(5, snapshot.IndividualRows.Count);
|
|
Assert.Equal(5, snapshot.ForeignRows.Count);
|
|
Assert.Equal(5, snapshot.InstitutionRows.Count);
|
|
Assert.Equal(3, snapshot.ViItems.Count);
|
|
Assert.Matches("^[a-f0-9]{64}$", snapshot.SourceSha256);
|
|
Assert.All(DestinationFiles, name =>
|
|
Assert.Equal(before[name], File.ReadAllBytes(Path.Combine(sourceDirectory.Path, name))));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Import_commits_all_four_files_and_the_versioned_marker_last()
|
|
{
|
|
using var sourceDirectory = new TemporaryDirectory();
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
WriteSourceFiles(sourceDirectory.Path);
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var now = new DateTimeOffset(2026, 7, 12, 1, 2, 3, TimeSpan.Zero);
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new FixedLegacyManualOperatorDataSource(sourceDirectory.Path, TimeSpan.Zero),
|
|
() => now);
|
|
|
|
var result = await importer.ImportAsync();
|
|
|
|
Assert.Equal(1, result.MarkerVersion);
|
|
Assert.Equal(15, result.NetSellRowCount);
|
|
Assert.Equal(3, result.ViItemCount);
|
|
Assert.Equal(now, result.ImportedAt);
|
|
Assert.All(DestinationFiles, name =>
|
|
Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, name))));
|
|
var markerPath = Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.MarkerFileName);
|
|
Assert.True(File.Exists(markerPath));
|
|
using var marker = JsonDocument.Parse(await File.ReadAllBytesAsync(markerPath));
|
|
Assert.Equal(1, marker.RootElement.GetProperty("schemaVersion").GetInt32());
|
|
Assert.Equal(result.SourceSha256, marker.RootElement.GetProperty("sourceSha256").GetString());
|
|
Assert.Matches(
|
|
"^[a-f0-9]{64}$",
|
|
marker.RootElement.GetProperty("productionSha256").GetString()!);
|
|
Assert.Equal(15, marker.RootElement.GetProperty("netSellRowCount").GetInt32());
|
|
Assert.Equal(3, marker.RootElement.GetProperty("viItemCount").GetInt32());
|
|
Assert.False(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.IntentFileName)));
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.ReadyImportedData,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(destinationDirectory.Path).State);
|
|
|
|
using var netStore = new S5025TrustedManualFileStore(destinationDirectory.Path);
|
|
using var viStore = new ViTrustedManualFileStore(destinationDirectory.Path);
|
|
Assert.Equal(Rows(), await netStore.ReadAsync(S5025ManualAudience.Individual));
|
|
Assert.Equal(3, (await viStore.ReadAsync()).Count);
|
|
|
|
var serializedResult = JsonSerializer.Serialize(result);
|
|
var serializedMarker = await File.ReadAllTextAsync(markerPath);
|
|
Assert.DoesNotContain(sourceDirectory.Path, serializedResult, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain(sourceDirectory.Path, serializedMarker, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("source\\repos", serializedMarker, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Empty_vi_file_keeps_its_distinct_zero_length_contract_after_import_and_restart()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var snapshot = new LegacyManualOperatorSourceSnapshot(
|
|
Rows(),
|
|
Rows(),
|
|
Rows(),
|
|
Array.Empty<LegacyManualViItem>(),
|
|
new string('c', 64));
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new CountingSource(snapshot));
|
|
|
|
var result = await importer.ImportAsync();
|
|
|
|
Assert.Equal(0, result.ViItemCount);
|
|
Assert.Equal(
|
|
0,
|
|
new FileInfo(Path.Combine(destinationDirectory.Path, "VI발동.dat")).Length);
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.ReadyImportedData,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(destinationDirectory.Path).State);
|
|
using var viStore = new ViTrustedManualFileStore(destinationDirectory.Path);
|
|
Assert.Empty(await viStore.ReadAsync());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("개인.dat")]
|
|
[InlineData("외국인.dat")]
|
|
[InlineData("기관.dat")]
|
|
[InlineData("VI발동.dat")]
|
|
public async Task Any_existing_private_data_file_blocks_the_whole_import_without_overwrite(
|
|
string existingFile)
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var path = Path.Combine(destinationDirectory.Path, existingFile);
|
|
var original = new byte[] { 1, 2, 3, 4 };
|
|
await File.WriteAllBytesAsync(path, original);
|
|
var source = new CountingSource(Snapshot());
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
source);
|
|
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorImportFailure.DestinationNotEmpty, error.Failure);
|
|
Assert.False(error.OutcomeUnknown);
|
|
Assert.Equal(0, source.ReadCount);
|
|
Assert.Equal(original, await File.ReadAllBytesAsync(path));
|
|
Assert.Single(Directory.EnumerateFileSystemEntries(destinationDirectory.Path));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Marker_blocks_a_second_import_before_the_source_is_read_again()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var source = new CountingSource(Snapshot());
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
source);
|
|
|
|
_ = await importer.ImportAsync();
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorImportFailure.AlreadyImported, error.Failure);
|
|
Assert.False(error.OutcomeUnknown);
|
|
Assert.Equal(1, source.ReadCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Preexisting_intent_and_partial_data_fail_closed_before_source_or_store_use()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var intentPath = Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.IntentFileName);
|
|
await File.WriteAllTextAsync(intentPath, "{}");
|
|
var partialPath = Path.Combine(destinationDirectory.Path, DestinationFiles[0]);
|
|
var partialBytes = LegacyManualOperatorDataCodec.SerializeNetSell(Rows());
|
|
await File.WriteAllBytesAsync(partialPath, partialBytes);
|
|
|
|
var inspection = LegacyManualOperatorDataRuntimeGuard.Inspect(
|
|
destinationDirectory.Path);
|
|
var source = new CountingSource(Snapshot());
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
source);
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorDataRuntimeState.InterruptedImport, inspection.State);
|
|
Assert.False(inspection.IsReady);
|
|
Assert.Equal(LegacyManualOperatorImportFailure.InterruptedImport, error.Failure);
|
|
Assert.True(error.OutcomeUnknown);
|
|
Assert.Equal(0, source.ReadCount);
|
|
Assert.Equal(partialBytes, await File.ReadAllBytesAsync(partialPath));
|
|
Assert.Equal("{}", await File.ReadAllTextAsync(intentPath));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Existing_legacy_files_without_marker_or_intent_remain_runtime_eligible()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
await File.WriteAllBytesAsync(
|
|
Path.Combine(destinationDirectory.Path, DestinationFiles[0]),
|
|
LegacyManualOperatorDataCodec.SerializeNetSell(Rows()));
|
|
|
|
var inspection = LegacyManualOperatorDataRuntimeGuard.Inspect(
|
|
destinationDirectory.Path);
|
|
|
|
Assert.True(inspection.IsReady);
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.ReadyLegacyData,
|
|
inspection.State);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
public async Task Failure_after_each_data_move_rolls_back_data_and_intent(
|
|
int failAfterMove)
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new CountingSource(Snapshot()),
|
|
utcNow: null,
|
|
afterDataFileMoved: movedCount =>
|
|
{
|
|
if (movedCount == failAfterMove)
|
|
{
|
|
throw new IOException("injected move failure");
|
|
}
|
|
});
|
|
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorImportFailure.ImportFailed, error.Failure);
|
|
Assert.False(error.OutcomeUnknown);
|
|
Assert.All(DestinationFiles, name =>
|
|
Assert.False(File.Exists(Path.Combine(destinationDirectory.Path, name))));
|
|
Assert.False(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.MarkerFileName)));
|
|
Assert.False(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.IntentFileName)));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
public async Task Abrupt_termination_after_each_move_leaves_intent_and_blocks_all_reuse(
|
|
int crashAfterMove)
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
using (var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new CountingSource(Snapshot()),
|
|
utcNow: null,
|
|
afterDataFileMoved: movedCount =>
|
|
{
|
|
if (movedCount == crashAfterMove)
|
|
{
|
|
throw new LegacyManualOperatorImportCrashSimulationException();
|
|
}
|
|
}))
|
|
{
|
|
await Assert.ThrowsAsync<LegacyManualOperatorImportCrashSimulationException>(
|
|
() => importer.ImportAsync());
|
|
}
|
|
|
|
Assert.True(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.IntentFileName)));
|
|
Assert.Equal(
|
|
crashAfterMove,
|
|
DestinationFiles.Count(name => File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
name))));
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.InterruptedImport,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(destinationDirectory.Path).State);
|
|
|
|
var secondSource = new CountingSource(Snapshot());
|
|
using var secondImporter = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
secondSource);
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => secondImporter.ImportAsync());
|
|
Assert.Equal(LegacyManualOperatorImportFailure.InterruptedImport, error.Failure);
|
|
Assert.True(error.OutcomeUnknown);
|
|
Assert.Equal(0, secondSource.ReadCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Abrupt_termination_after_completed_marker_keeps_stale_intent_fail_closed()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
using (var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new CountingSource(Snapshot()),
|
|
utcNow: null,
|
|
afterDataFileMoved: null,
|
|
afterMarkerCommitted: () =>
|
|
throw new LegacyManualOperatorImportCrashSimulationException()))
|
|
{
|
|
await Assert.ThrowsAsync<LegacyManualOperatorImportCrashSimulationException>(
|
|
() => importer.ImportAsync());
|
|
}
|
|
|
|
Assert.All(DestinationFiles, name =>
|
|
Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, name))));
|
|
Assert.True(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.MarkerFileName)));
|
|
Assert.True(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.IntentFileName)));
|
|
var inspection = LegacyManualOperatorDataRuntimeGuard.Inspect(
|
|
destinationDirectory.Path);
|
|
Assert.False(inspection.IsReady);
|
|
Assert.Equal(LegacyManualOperatorDataRuntimeState.InterruptedImport, inspection.State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Completed_marker_with_missing_or_modified_data_is_rejected()
|
|
{
|
|
using var missingDirectory = new TemporaryDirectory();
|
|
PreparePrivate(missingDirectory.Path);
|
|
using (var importer = new LegacyManualOperatorDataImporter(
|
|
missingDirectory.Path,
|
|
new CountingSource(Snapshot())))
|
|
{
|
|
_ = await importer.ImportAsync();
|
|
}
|
|
File.Delete(Path.Combine(missingDirectory.Path, DestinationFiles[0]));
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.InvalidImportedState,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(missingDirectory.Path).State);
|
|
|
|
using var modifiedDirectory = new TemporaryDirectory();
|
|
PreparePrivate(modifiedDirectory.Path);
|
|
using (var importer = new LegacyManualOperatorDataImporter(
|
|
modifiedDirectory.Path,
|
|
new CountingSource(Snapshot())))
|
|
{
|
|
_ = await importer.ImportAsync();
|
|
}
|
|
await File.AppendAllTextAsync(
|
|
Path.Combine(modifiedDirectory.Path, DestinationFiles[0]),
|
|
"tamper");
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.InvalidImportedState,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(modifiedDirectory.Path).State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Completed_marker_allows_later_verified_operator_edits()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
using (var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new CountingSource(Snapshot())))
|
|
{
|
|
_ = await importer.ImportAsync();
|
|
}
|
|
|
|
var editedRows = Rows().ToArray();
|
|
editedRows[0] = editedRows[0] with { LeftAmount = "9,999" };
|
|
using (var store = new S5025TrustedManualFileStore(destinationDirectory.Path))
|
|
{
|
|
await store.WriteAsync(S5025ManualAudience.Individual, editedRows);
|
|
}
|
|
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.ReadyImportedData,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(destinationDirectory.Path).State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Commit_failure_rolls_back_every_file_created_by_the_operation_without_retry()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var source = new CountingSource(Snapshot());
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
source,
|
|
utcNow: null,
|
|
afterDataFileMoved: movedCount =>
|
|
{
|
|
if (movedCount == 1)
|
|
{
|
|
Directory.CreateDirectory(Path.Combine(destinationDirectory.Path, "외국인.dat"));
|
|
}
|
|
});
|
|
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorImportFailure.ImportFailed, error.Failure);
|
|
Assert.False(error.OutcomeUnknown);
|
|
Assert.Equal(1, source.ReadCount);
|
|
Assert.False(File.Exists(Path.Combine(destinationDirectory.Path, "개인.dat")));
|
|
Assert.False(File.Exists(Path.Combine(destinationDirectory.Path, "기관.dat")));
|
|
Assert.False(File.Exists(Path.Combine(destinationDirectory.Path, "VI발동.dat")));
|
|
Assert.False(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.MarkerFileName)));
|
|
Assert.Empty(Directory.EnumerateFiles(destinationDirectory.Path, "*.tmp"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unsafe_rollback_is_reported_as_outcome_unknown_and_is_never_retried()
|
|
{
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
PreparePrivate(destinationDirectory.Path);
|
|
var source = new CountingSource(Snapshot());
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
source,
|
|
utcNow: null,
|
|
afterDataFileMoved: movedCount =>
|
|
{
|
|
if (movedCount != 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
File.WriteAllText(Path.Combine(destinationDirectory.Path, "개인.dat"), "tampered");
|
|
Directory.CreateDirectory(Path.Combine(destinationDirectory.Path, "외국인.dat"));
|
|
});
|
|
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorImportFailure.RollbackFailed, error.Failure);
|
|
Assert.True(error.OutcomeUnknown);
|
|
Assert.Equal(1, source.ReadCount);
|
|
Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, "개인.dat")));
|
|
Assert.False(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.MarkerFileName)));
|
|
Assert.True(File.Exists(Path.Combine(
|
|
destinationDirectory.Path,
|
|
LegacyManualOperatorDataImporter.IntentFileName)));
|
|
Assert.Equal(
|
|
LegacyManualOperatorDataRuntimeState.InterruptedImport,
|
|
LegacyManualOperatorDataRuntimeGuard.Inspect(destinationDirectory.Path).State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Invalid_source_format_creates_no_private_file_and_does_not_disclose_the_path()
|
|
{
|
|
using var sourceDirectory = new TemporaryDirectory();
|
|
using var destinationDirectory = new TemporaryDirectory();
|
|
WriteSourceFiles(sourceDirectory.Path);
|
|
await File.WriteAllTextAsync(Path.Combine(sourceDirectory.Path, "기관.dat"), "invalid");
|
|
PreparePrivate(destinationDirectory.Path);
|
|
using var importer = new LegacyManualOperatorDataImporter(
|
|
destinationDirectory.Path,
|
|
new FixedLegacyManualOperatorDataSource(sourceDirectory.Path, TimeSpan.Zero));
|
|
|
|
var error = await Assert.ThrowsAsync<LegacyManualOperatorImportException>(
|
|
() => importer.ImportAsync());
|
|
|
|
Assert.Equal(LegacyManualOperatorImportFailure.SourceInvalid, error.Failure);
|
|
Assert.DoesNotContain(sourceDirectory.Path, error.Message, StringComparison.OrdinalIgnoreCase);
|
|
Assert.Empty(Directory.EnumerateFileSystemEntries(destinationDirectory.Path));
|
|
}
|
|
|
|
private static void WriteSourceFiles(string directory)
|
|
{
|
|
foreach (var fileName in DestinationFiles.Take(3))
|
|
{
|
|
File.WriteAllBytes(
|
|
Path.Combine(directory, fileName),
|
|
LegacyManualOperatorDataCodec.SerializeNetSell(Rows()));
|
|
}
|
|
|
|
File.WriteAllBytes(
|
|
Path.Combine(directory, "VI발동.dat"),
|
|
LegacyManualOperatorDataCodec.SerializeVi(ViItems()));
|
|
}
|
|
|
|
private static LegacyManualOperatorSourceSnapshot Snapshot() => new(
|
|
Rows(),
|
|
Rows(),
|
|
Rows(),
|
|
ViItems(),
|
|
new string('a', 64));
|
|
|
|
private static IReadOnlyList<S5025TrustedManualRow> Rows() =>
|
|
[
|
|
new("삼성전자", "1,000", "SK하이닉스", "-2,000"),
|
|
new("현대차", "2", "기아", "-2"),
|
|
new("NAVER", "3", "카카오", "-3"),
|
|
new("LG화학", "4", "포스코", "-4"),
|
|
new("한화", "5", "셀트리온", "-5")
|
|
];
|
|
|
|
private static IReadOnlyList<LegacyManualViItem> ViItems() =>
|
|
[
|
|
new("PKR7005930003", "삼성전자"),
|
|
new("PKR7016360000", "삼성증권"),
|
|
new("PKR7005930003", "삼성전자")
|
|
];
|
|
|
|
private static void PreparePrivate(string path) =>
|
|
TrustedManualDirectory.PreparePrivateWritableDirectory(path);
|
|
|
|
private sealed class CountingSource : ILegacyManualOperatorDataSource
|
|
{
|
|
private readonly LegacyManualOperatorSourceSnapshot _snapshot;
|
|
|
|
public CountingSource(LegacyManualOperatorSourceSnapshot snapshot)
|
|
{
|
|
_snapshot = snapshot;
|
|
}
|
|
|
|
public int ReadCount { get; private set; }
|
|
|
|
public Task<LegacyManualOperatorSourceSnapshot> ReadAsync(
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ReadCount++;
|
|
return Task.FromResult(_snapshot);
|
|
}
|
|
}
|
|
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
}
|