using System.Data; using MBN_STOCK_WEBVIEW.LegacyApplication; using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests; public sealed class LegacyParityStockSearchServiceTests { [Fact] public async Task Search_PreservesProviderAndRowOrderWithoutLimitOrSort() { var executor = new RecordingExecutor(new Dictionary { ["LEGACY_PARITY_KOSPI_MASTER"] = Table(("중복", "000001")), ["LEGACY_PARITY_KOSDAQ_MASTER"] = Table(("중복", "100001")), ["LEGACY_PARITY_NXT_KOSPI_MASTER"] = Table(("중복(NXT)", "200001")), ["LEGACY_PARITY_NXT_KOSDAQ_MASTER"] = Table(("중복", "300001")), ["LEGACY_PARITY_KOSPI"] = Table(("가-2", "000002"), ("가-1", "000001")), ["LEGACY_PARITY_KOSDAQ"] = Table(("나", "100001")), ["LEGACY_PARITY_NXT_KOSPI"] = Table(("다(NXT)", "200001")), ["LEGACY_PARITY_NXT_KOSDAQ"] = Table(("라(NXT)", "300001")) }); var service = new LegacyParityStockSearchService(executor); var result = await service.SearchAsync(" a%_ "); Assert.Equal(["가-2", "가-1", "나", "다(NXT)", "라(NXT)"], result.DisplayNames); Assert.Equal("300001", result.ResolveLastByDisplayName("중복")?.StockCode); Assert.Equal(LegacyDomesticStockMarket.NxtKosdaq, result.ResolveLastByDisplayName("중복")?.Market); Assert.Equal(8, executor.Calls.Count); foreach (var call in executor.Calls.Skip(4)) { Assert.DoesNotContain("ORDER BY", call.Spec.Sql, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("LIMIT", call.Spec.Sql, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("ROWNUM", call.Spec.Sql, StringComparison.OrdinalIgnoreCase); Assert.Single(call.Spec.Parameters); Assert.Equal(" A%_ ", call.Spec.Parameters[0].Value); } } [Fact] public async Task EmptySearch_IsAllowedAndMasterSnapshotIsCached() { var tables = new Dictionary(); foreach (var name in new[] { "LEGACY_PARITY_KOSPI", "LEGACY_PARITY_KOSDAQ", "LEGACY_PARITY_NXT_KOSPI", "LEGACY_PARITY_NXT_KOSDAQ" }) { tables[name] = Table(); tables[$"{name}_MASTER"] = Table(); } var executor = new RecordingExecutor(tables); var service = new LegacyParityStockSearchService(executor); await service.SearchAsync(string.Empty); await service.SearchAsync(string.Empty); Assert.Equal(12, executor.Calls.Count); Assert.Equal(4, executor.Calls.Count(call => call.TableName.EndsWith("_MASTER", StringComparison.Ordinal))); Assert.All(executor.Calls.Where(call => !call.TableName.EndsWith("_MASTER", StringComparison.Ordinal)), call => Assert.Equal(string.Empty, call.Spec.Parameters[0].Value)); } private static DataTable Table(params (string Name, string Code)[] rows) { var table = new DataTable(); table.Columns.Add("STOCK_NAME", typeof(string)); table.Columns.Add("STOCK_CODE", typeof(string)); foreach (var row in rows) { table.Rows.Add(row.Name, row.Code); } return table; } private sealed class RecordingExecutor : IDataQueryExecutor { private readonly IReadOnlyDictionary _tables; public RecordingExecutor(IReadOnlyDictionary tables) { _tables = tables; } public List Calls { get; } = []; public DataTable Execute(DataSourceKind source, string tableName, string query) => throw new NotSupportedException(); public Task ExecuteAsync( DataSourceKind source, string tableName, string query, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task ExecuteAsync( DataSourceKind source, string tableName, DataQuerySpec query, CancellationToken cancellationToken = default) { Calls.Add(new Call(source, tableName, query)); return Task.FromResult(_tables[tableName].Copy()); } } private sealed record Call(DataSourceKind Source, string TableName, DataQuerySpec Spec); }