feat: enable initial-consonant search everywhere
This commit is contained in:
@@ -92,6 +92,36 @@ public sealed class LegacyManualFinancialScreenServiceTests
|
||||
Assert.Contains("ESCAPE '!'", escapedExecutor.Spec.Sql, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Initial_consonant_search_filters_the_bounded_full_candidate_list()
|
||||
{
|
||||
var table = EmptyTable(ManualFinancialScreenKind.Sales);
|
||||
AddQuarterRow(table, "가나다");
|
||||
AddQuarterRow(table, "삼성전자");
|
||||
AddQuarterRow(table, "현대차");
|
||||
var executor = new RecordingQueryExecutor(table);
|
||||
var service = new LegacyManualFinancialScreenService(executor);
|
||||
|
||||
var result = await service.SearchAsync(
|
||||
ManualFinancialScreenKind.Sales,
|
||||
" ㅅㅅㅈ ",
|
||||
maximumResults: 1);
|
||||
|
||||
Assert.Equal("ㅅㅅㅈ", result.Query);
|
||||
Assert.Equal(
|
||||
"삼성전자",
|
||||
Assert.Single(result.Items).Record.Identity.StockName);
|
||||
Assert.Equal(
|
||||
"삼성전자",
|
||||
Assert.Single(result.RawItems).Record.Identity.StockName);
|
||||
Assert.False(result.IsTruncated);
|
||||
Assert.Equal(1, executor.Spec!.Parameters[0].Value);
|
||||
Assert.Equal(string.Empty, executor.Spec.Parameters[1].Value);
|
||||
Assert.Equal(
|
||||
LegacyManualFinancialScreenService.MaximumResults + 1,
|
||||
executor.Spec.Parameters[2].Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Screens))]
|
||||
public void Row_version_SQL_is_one_shared_null_tagged_length_prefixed_contract(
|
||||
|
||||
@@ -84,6 +84,30 @@ public sealed class LegacyOverseasIndustryIndexSearchServiceTests
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsFilterTheFullBoundedUsCandidateWindow()
|
||||
{
|
||||
var executor = new RecordingExecutor(_ => ResultTable(
|
||||
("필라델피아 반도체", "US_SEMICONDUCTOR", "SOX"),
|
||||
("다우 운송", "US_DOW_TRANSPORT", "DJT"),
|
||||
("필라델피아 은행", "US_BANK", "BKX")));
|
||||
var service = new LegacyOverseasIndustryIndexSearchService(executor);
|
||||
|
||||
var result = await service.SearchAsync(" ㅍㄹㄷㅍㅇ ", maximumResults: 1);
|
||||
|
||||
Assert.Equal("ㅍㄹㄷㅍㅇ", result.Query);
|
||||
Assert.True(result.IsTruncated);
|
||||
Assert.Equal(
|
||||
"필라델피아 반도체",
|
||||
Assert.Single(result.Items).KoreanName);
|
||||
var call = Assert.Single(executor.Calls);
|
||||
Assert.Contains("F_NATC = 'US'", call.Spec.Sql, StringComparison.Ordinal);
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
Assert.Equal(
|
||||
LegacyOverseasIndustryIndexSearchService.MaximumResults + 1,
|
||||
call.Spec.Parameters[1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_EscapesLikeMetacharactersOnlyInsideBoundValue()
|
||||
{
|
||||
|
||||
@@ -37,6 +37,35 @@ public sealed class LegacyOverseasStockSearchServiceTests
|
||||
Assert.Equal("row_limit", call.Spec.Parameters[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitialConsonantQuery_UsesAllNationSearchAndFiltersTheFullCandidateWindow()
|
||||
{
|
||||
var executor = new RecordingExecutor(WorldStockTable(
|
||||
("삼성전자", "LNS@SAMSUNG", "GB"),
|
||||
("Apple", "NAS@AAPL", "US"),
|
||||
("신성전자", "TSE@SHINSEONG", "JP")));
|
||||
var service = new LegacyOverseasStockSearchService(executor);
|
||||
|
||||
var result = await service.SearchAsync(" ㅅㅅㅈㅈ ", maximumResults: 1);
|
||||
|
||||
Assert.Equal("ㅅㅅㅈㅈ", result.Query);
|
||||
Assert.True(result.IsTruncated);
|
||||
Assert.Equal(
|
||||
new WorldStockSearchItem("삼성전자", "LNS@SAMSUNG", "GB"),
|
||||
Assert.Single(result.Items));
|
||||
var call = Assert.Single(executor.Calls);
|
||||
Assert.DoesNotContain("F_NATC IN", call.Spec.Sql, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"LIKE '%' || :search_term || '%' ESCAPE '!'",
|
||||
call.Spec.Sql,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
Assert.Equal(
|
||||
LegacyOverseasStockSearchService.MaximumResults +
|
||||
LegacyOverseasStockSearchService.MaximumIsolatedRows + 1,
|
||||
call.Spec.Parameters[1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AllowsDuplicateInputNamesWhenTheExactIdentityDiffersButRejectsDuplicateIdentity()
|
||||
{
|
||||
|
||||
@@ -85,6 +85,87 @@ public sealed class LegacyStockSearchServiceTests
|
||||
call.Spec.Parameters[1].Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitialConsonantSearch_UsesFullEmptyBoundCandidateWindowsThenFiltersNames()
|
||||
{
|
||||
var tables = new Queue<DataTable>(
|
||||
[
|
||||
StockTable(
|
||||
("카카오", "035720"),
|
||||
("삼성전자", "005930")),
|
||||
StockTable(("신성델타테크", "065350")),
|
||||
StockTable(("삼성전자(NXT)", "005930")),
|
||||
StockTable(("알테오젠(NXT)", "196170"))
|
||||
]);
|
||||
var executor = new RecordingExecutor(_ => tables.Dequeue());
|
||||
var service = new LegacyStockSearchService(executor);
|
||||
|
||||
var result = await service.SearchAsync(" ㅅㅅ ", maximumResults: 2);
|
||||
|
||||
Assert.Equal("ㅅㅅ", result.Query);
|
||||
Assert.True(result.IsTruncated);
|
||||
Assert.Collection(
|
||||
result.Items,
|
||||
item => Assert.Equal(
|
||||
new StockSearchItem(
|
||||
StockMarket.Kospi,
|
||||
DataSourceKind.Oracle,
|
||||
"삼성전자",
|
||||
"005930"),
|
||||
item),
|
||||
item => Assert.Equal(
|
||||
new StockSearchItem(
|
||||
StockMarket.Kosdaq,
|
||||
DataSourceKind.Oracle,
|
||||
"신성델타테크",
|
||||
"065350"),
|
||||
item));
|
||||
Assert.All(executor.Calls, call =>
|
||||
{
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
Assert.Equal(
|
||||
LegacyStockSearchService.MaximumResults + 1,
|
||||
call.Spec.Parameters[1].Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitialConsonantSearch_ValidatesNonmatchingCandidatesBeforeFiltering()
|
||||
{
|
||||
var malformed = StockTable(("카\n카오", "035720"));
|
||||
var executor = new RecordingExecutor(_ => malformed);
|
||||
var service = new LegacyStockSearchService(executor);
|
||||
|
||||
await Assert.ThrowsAsync<StockSearchDataException>(
|
||||
() => service.SearchAsync("ㅅㅅ"));
|
||||
|
||||
Assert.Single(executor.Calls);
|
||||
Assert.Equal(string.Empty, executor.Calls[0].Spec.Parameters[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitialConsonantSearch_ReportsSaturatedCandidateWindowConservatively()
|
||||
{
|
||||
var callIndex = 0;
|
||||
var executor = new RecordingExecutor(_ =>
|
||||
callIndex++ == 0
|
||||
? StockTable(LegacyStockSearchService.MaximumResults + 1)
|
||||
: StockTable());
|
||||
var service = new LegacyStockSearchService(executor);
|
||||
|
||||
var result = await service.SearchAsync("ㅅㅅ", maximumResults: 25);
|
||||
|
||||
Assert.Empty(result.Items);
|
||||
Assert.True(result.IsTruncated);
|
||||
Assert.All(executor.Calls, call =>
|
||||
{
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
Assert.Equal(
|
||||
LegacyStockSearchService.MaximumResults + 1,
|
||||
call.Spec.Parameters[1].Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperatorCatalogSearchAllAsync_UsesBoundEmptyTermAndFiniteProviderLimits()
|
||||
{
|
||||
|
||||
@@ -109,6 +109,83 @@ public sealed class LegacyThemeSelectionServiceTests
|
||||
Assert.All(executor.Calls, call => Assert.Equal("A!!!_!%", call.Spec.Parameters[0].Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsReadFullProvidersThenFilterInProviderOrder()
|
||||
{
|
||||
var executor = new RecordingExecutor(call => call.Source switch
|
||||
{
|
||||
DataSourceKind.Oracle => SearchTable(
|
||||
("AI", "00000001", "KRX"),
|
||||
("반도체", "00000002", "KRX")),
|
||||
DataSourceKind.MariaDb => SearchTable(
|
||||
("바다차", "00000003", "NXT")),
|
||||
_ => throw new InvalidOperationException()
|
||||
});
|
||||
var service = new LegacyThemeSelectionService(executor);
|
||||
|
||||
var result = await service.SearchAsync(
|
||||
" ㅂㄷㅊ ",
|
||||
ThemeSession.PreMarket,
|
||||
maximumResults: 1);
|
||||
|
||||
Assert.Equal("ㅂㄷㅊ", result.Query);
|
||||
Assert.True(result.IsTruncated);
|
||||
var item = Assert.Single(result.Items);
|
||||
Assert.Equal(ThemeMarket.Krx, item.Identity.Market);
|
||||
Assert.Equal("반도체", item.Identity.ThemeTitle);
|
||||
Assert.All(executor.Calls, call =>
|
||||
{
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
Assert.Equal(
|
||||
LegacyThemeSelectionService.MaximumResults + 1,
|
||||
call.Spec.Parameters[1].Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsValidateNonMatchingCandidatesBeforeFiltering()
|
||||
{
|
||||
var executor = new RecordingExecutor(call => call.Source switch
|
||||
{
|
||||
DataSourceKind.Oracle => SearchTable(("AI", "invalid", "KRX")),
|
||||
DataSourceKind.MariaDb => SearchTable(),
|
||||
_ => throw new InvalidOperationException()
|
||||
});
|
||||
var service = new LegacyThemeSelectionService(executor);
|
||||
|
||||
await Assert.ThrowsAsync<ThemeSelectionDataException>(
|
||||
() => service.SearchAsync("ㅂㄷㅊ", ThemeSession.PreMarket));
|
||||
|
||||
Assert.Single(executor.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsConservativelyReportProviderCandidateCap()
|
||||
{
|
||||
var cappedKrx = SearchTable(
|
||||
Enumerable.Range(0, LegacyThemeSelectionService.MaximumResults + 1)
|
||||
.Select(index => (
|
||||
Title: "알파",
|
||||
Code: index.ToString("D8", System.Globalization.CultureInfo.InvariantCulture),
|
||||
Market: "KRX"))
|
||||
.ToArray());
|
||||
var executor = new RecordingExecutor(call => call.Source switch
|
||||
{
|
||||
DataSourceKind.Oracle => cappedKrx,
|
||||
DataSourceKind.MariaDb => SearchTable(),
|
||||
_ => throw new InvalidOperationException()
|
||||
});
|
||||
var service = new LegacyThemeSelectionService(executor);
|
||||
|
||||
var result = await service.SearchAsync(
|
||||
"ㅅㅅ",
|
||||
ThemeSession.AfterMarket,
|
||||
maximumResults: 1);
|
||||
|
||||
Assert.Empty(result.Items);
|
||||
Assert.True(result.IsTruncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_PreservesLegacyThreeAndFourDigitReadIdentities()
|
||||
{
|
||||
|
||||
@@ -96,6 +96,79 @@ public sealed class LegacyTradingHaltSelectionServiceTests
|
||||
Assert.Equal(0, executor.StringSqlCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsReadFullMarketsThenFilterBeforeExistingSort()
|
||||
{
|
||||
var results = new Queue<DataTable>(
|
||||
[
|
||||
TradingHaltTable(
|
||||
("000001", "Alpha"),
|
||||
("000002", "삼성전자")),
|
||||
TradingHaltTable(("100001", "삼성전기"))
|
||||
]);
|
||||
var executor = new RecordingExecutor(_ => results.Dequeue());
|
||||
var service = new LegacyTradingHaltSelectionService(executor);
|
||||
|
||||
var result = await service.SearchAsync(" ㅅㅅㅈ ", maximumResults: 1);
|
||||
|
||||
Assert.Equal("ㅅㅅㅈ", result.Query);
|
||||
Assert.True(result.IsTruncated);
|
||||
Assert.Equal(
|
||||
new TradingHaltSelection(
|
||||
TradingHaltMarket.Kospi,
|
||||
"000002",
|
||||
"삼성전자"),
|
||||
Assert.Single(result.Items));
|
||||
Assert.All(executor.Calls, call =>
|
||||
{
|
||||
Assert.DoesNotContain("LIKE", call.Spec.Sql, StringComparison.OrdinalIgnoreCase);
|
||||
var parameter = Assert.Single(call.Spec.Parameters);
|
||||
Assert.Equal("row_limit", parameter.Name);
|
||||
Assert.Equal(
|
||||
LegacyTradingHaltSelectionService.MaximumResults + 1,
|
||||
parameter.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsValidateNonMatchingCandidatesBeforeFiltering()
|
||||
{
|
||||
var executor = new RecordingExecutor(_ => TradingHaltTable(
|
||||
("000001", "Alpha"),
|
||||
("000001", "Other")));
|
||||
var service = new LegacyTradingHaltSelectionService(executor);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<TradingHaltSelectionDataException>(
|
||||
() => service.SearchAsync("ㅅㅅ"));
|
||||
|
||||
Assert.Contains("duplicate market/code", exception.Message, StringComparison.Ordinal);
|
||||
Assert.Single(executor.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsConservativelyReportProviderCandidateCap()
|
||||
{
|
||||
var cappedKospi = TradingHaltTable(
|
||||
Enumerable.Range(0, LegacyTradingHaltSelectionService.MaximumResults + 1)
|
||||
.Select(index => (
|
||||
StockCode: index.ToString("D6", System.Globalization.CultureInfo.InvariantCulture),
|
||||
DisplayName: "알파" + index.ToString(
|
||||
System.Globalization.CultureInfo.InvariantCulture)))
|
||||
.ToArray());
|
||||
var results = new Queue<DataTable>(
|
||||
[
|
||||
cappedKospi,
|
||||
TradingHaltTable()
|
||||
]);
|
||||
var executor = new RecordingExecutor(_ => results.Dequeue());
|
||||
var service = new LegacyTradingHaltSelectionService(executor);
|
||||
|
||||
var result = await service.SearchAsync("ㅅㅅ", maximumResults: 1);
|
||||
|
||||
Assert.Empty(result.Items);
|
||||
Assert.True(result.IsTruncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_ReturnsDeterministicGlobalLimitAndTruncation()
|
||||
{
|
||||
|
||||
@@ -102,6 +102,35 @@ public sealed class LegacyWorldStockSearchServiceTests
|
||||
Assert.Equal("row_limit", Assert.Single(call.Spec.Parameters).Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_InitialConsonantsUseTheBoundedUsTwSearchCandidateWindow()
|
||||
{
|
||||
var executor = new RecordingExecutor(_ => WorldStockTable(
|
||||
("삼성전자", "SAMSUNG", "US"),
|
||||
("Apple", "AAPL", "US"),
|
||||
("신성전자", "SHINSEONG", "TW")));
|
||||
var service = new LegacyWorldStockSearchService(executor);
|
||||
|
||||
var result = await service.SearchAsync(" ㅅㅅㅈㅈ ", maximumResults: 1);
|
||||
|
||||
Assert.Equal("ㅅㅅㅈㅈ", result.Query);
|
||||
Assert.True(result.IsTruncated);
|
||||
Assert.Equal(
|
||||
new WorldStockSearchItem("삼성전자", "SAMSUNG", "US"),
|
||||
Assert.Single(result.Items));
|
||||
var call = Assert.Single(executor.Calls);
|
||||
Assert.Contains("F_NATC IN ('US', 'TW')", call.Spec.Sql, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"LIKE '%' || :search_term || '%' ESCAPE '!'",
|
||||
call.Spec.Sql,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal(string.Empty, call.Spec.Parameters[0].Value);
|
||||
Assert.Equal(
|
||||
LegacyWorldStockSearchService.MaximumResults +
|
||||
LegacyWorldStockSearchService.MaximumIsolatedRows + 1,
|
||||
call.Spec.Parameters[1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_ReturnsBoundedRowsAndReportsTruncation()
|
||||
{
|
||||
|
||||
@@ -54,6 +54,36 @@ public sealed class LegacyExpertWorkflowTests
|
||||
() => controller.MaterializeSelectedAction("5074-caller-override"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Search_AcceptsConservativeTruncationAfterInitialFiltering()
|
||||
{
|
||||
var service = new DelegateExpertService
|
||||
{
|
||||
Search = (query, _, _) => Task.FromResult(
|
||||
SearchResult(query, Alpha) with { IsTruncated = true })
|
||||
};
|
||||
var controller = new LegacyExpertWorkflowController(service);
|
||||
|
||||
var searched = await controller.SearchAsync("ㄱㅈㅁ");
|
||||
|
||||
Assert.Equal(Alpha.ExpertName, Assert.Single(searched.Search.Results).ExpertName);
|
||||
Assert.True(searched.Search.IsTruncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Search_StillRejectsImpossiblePartialTruncationForOrdinaryText()
|
||||
{
|
||||
var service = new DelegateExpertService
|
||||
{
|
||||
Search = (query, _, _) => Task.FromResult(
|
||||
SearchResult(query, Alpha) with { IsTruncated = true })
|
||||
};
|
||||
var controller = new LegacyExpertWorkflowController(service);
|
||||
|
||||
await Assert.ThrowsAsync<ExpertSelectionDataException>(
|
||||
() => controller.SearchAsync("Expert"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, false, 1)]
|
||||
[InlineData(5, false, 1)]
|
||||
|
||||
@@ -366,6 +366,62 @@ public sealed class LegacyThemeWorkflowTests
|
||||
Assert.Equal("theme-result-5000", snapshot.SearchResults[^1].ResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_AcceptsConservativeTruncationAfterInitialFiltering()
|
||||
{
|
||||
var service = new FakeThemeSelectionService
|
||||
{
|
||||
SearchHandler = (query, session) => new ThemeSearchResult(
|
||||
query,
|
||||
session,
|
||||
DateTimeOffset.UtcNow,
|
||||
[
|
||||
Selector(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
"삼성 테마",
|
||||
"00000001")
|
||||
],
|
||||
IsTruncated: true)
|
||||
};
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
|
||||
var snapshot = await workflow.SearchAsync(
|
||||
workflow.CreateInitial(),
|
||||
"ㅅㅅ",
|
||||
ThemeSession.PreMarket);
|
||||
|
||||
Assert.Equal("삼성 테마", Assert.Single(snapshot.SearchResults).DisplayTitle);
|
||||
Assert.True(snapshot.SearchIsTruncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_StillRejectsImpossiblePartialTruncationForOrdinaryText()
|
||||
{
|
||||
var service = new FakeThemeSelectionService
|
||||
{
|
||||
SearchHandler = (query, session) => new ThemeSearchResult(
|
||||
query,
|
||||
session,
|
||||
DateTimeOffset.UtcNow,
|
||||
[
|
||||
Selector(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
"AI",
|
||||
"00000001")
|
||||
],
|
||||
IsTruncated: true)
|
||||
};
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
|
||||
await Assert.ThrowsAsync<LegacyThemeWorkflowDataException>(() =>
|
||||
workflow.SearchAsync(
|
||||
workflow.CreateInitial(),
|
||||
"AI",
|
||||
ThemeSession.PreMarket));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_UsesOpaqueResultIdAndCopiesValidatedPreview()
|
||||
{
|
||||
|
||||
@@ -286,6 +286,78 @@ public sealed class LegacyManualFinancialWorkflowTests
|
||||
workflow.ToggleNameSort(state, staleGeneration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Directional_find_matches_Hangul_initial_consonants()
|
||||
{
|
||||
var service = new FakeManualFinancialService(
|
||||
Snapshot(Record(ManualFinancialScreenKind.Sales, "가나다"), 1),
|
||||
Snapshot(Record(ManualFinancialScreenKind.Sales, "삼성전자"), 2),
|
||||
Snapshot(Record(ManualFinancialScreenKind.Sales, "현대차"), 3));
|
||||
var workflow = Workflow(service);
|
||||
var state = await workflow.SearchAsync(
|
||||
workflow.CreateInitial(ManualFinancialScreenKind.Sales));
|
||||
|
||||
state = workflow.FindByName(
|
||||
state,
|
||||
" ㅅㅅㅈ ",
|
||||
LegacyManualFinancialFindDirection.Down,
|
||||
state.Revision);
|
||||
|
||||
Assert.Equal("ㅅㅅㅈ", state.FindQuery);
|
||||
Assert.Equal(
|
||||
"삼성전자",
|
||||
state.Rows.Single(row => row.ResultId == state.SelectedRowId).StockName);
|
||||
|
||||
state = workflow.FindByName(
|
||||
state,
|
||||
"ㄱㄴㄷ",
|
||||
LegacyManualFinancialFindDirection.Up,
|
||||
state.Revision);
|
||||
|
||||
Assert.Equal(
|
||||
"가나다",
|
||||
state.Rows.Single(row => row.ResultId == state.SelectedRowId).StockName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_keeps_a_matching_row_inside_an_initial_consonant_filter()
|
||||
{
|
||||
const string stockName = "삼성전자";
|
||||
var service = new FakeManualFinancialService
|
||||
{
|
||||
SearchOverride = (screen, query) => new ManualFinancialSearchResult(
|
||||
screen,
|
||||
query,
|
||||
DateTimeOffset.UtcNow,
|
||||
Array.Empty<ManualFinancialSnapshot>(),
|
||||
IsTruncated: false)
|
||||
};
|
||||
var workflow = new LegacyManualFinancialWorkflow(
|
||||
service,
|
||||
new FakeStockSearchService(new StockSearchItem(
|
||||
StockMarket.Kospi,
|
||||
DataSourceKind.Oracle,
|
||||
stockName,
|
||||
"005930")),
|
||||
new LegacyManualFinancialWriteSafetyState());
|
||||
var state = await workflow.SearchAsync(
|
||||
workflow.CreateInitial(ManualFinancialScreenKind.Sales),
|
||||
"ㅅㅅㅈ");
|
||||
state = await workflow.SearchStocksAsync(state, stockName);
|
||||
state = workflow.SelectStock(
|
||||
state,
|
||||
Assert.Single(state.StockCandidates).ResultId);
|
||||
|
||||
state = await workflow.ExecuteAsync(
|
||||
state,
|
||||
workflow.CreateCreateRequest(
|
||||
state,
|
||||
Record(ManualFinancialScreenKind.Sales, stockName)));
|
||||
|
||||
Assert.Equal("ㅅㅅㅈ", state.Query);
|
||||
Assert.Equal(stockName, Assert.Single(state.Rows).StockName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Screens))]
|
||||
public async Task Create_save_readback_and_action_id_only_draft_work_for_all_modes(
|
||||
|
||||
@@ -34,6 +34,36 @@ public sealed class LegacyTradingHaltWorkflowTests
|
||||
Assert.Equal("가온전자", current.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Search_AcceptsConservativeTruncationAfterInitialFiltering()
|
||||
{
|
||||
var service = new DelegateTradingHaltService
|
||||
{
|
||||
Search = (query, _, _) => Task.FromResult(
|
||||
SearchResult(query, KospiAlpha) with { IsTruncated = true })
|
||||
};
|
||||
var controller = new LegacyTradingHaltWorkflowController(service);
|
||||
|
||||
var searched = await controller.SearchAsync("ㄱㅈ");
|
||||
|
||||
Assert.Equal(KospiAlpha.DisplayName, Assert.Single(searched.Results).DisplayName);
|
||||
Assert.True(searched.IsTruncated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Search_StillRejectsImpossiblePartialTruncationForOrdinaryText()
|
||||
{
|
||||
var service = new DelegateTradingHaltService
|
||||
{
|
||||
Search = (query, _, _) => Task.FromResult(
|
||||
SearchResult(query, KospiAlpha) with { IsTruncated = true })
|
||||
};
|
||||
var controller = new LegacyTradingHaltWorkflowController(service);
|
||||
|
||||
await Assert.ThrowsAsync<TradingHaltSelectionDataException>(
|
||||
() => controller.SearchAsync("Alpha"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchSelect_MaterializesClosedCurrentAndCandleDraftsFromActionIdOnly()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user