feat: complete legacy parity and modernize operator UI
This commit is contained in:
@@ -77,6 +77,10 @@ public sealed class LegacyThemeWorkflowTests
|
||||
property => Assert.False(property.CanWrite));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
workflow.CreateInitial(ThemeSession.NotApplicable));
|
||||
Assert.Equal(5_000, LegacyThemeWorkflow.MaximumSearchResults);
|
||||
Assert.Equal(
|
||||
LegacyThemeSelectionService.MaximumResults,
|
||||
LegacyThemeWorkflow.MaximumSearchResults);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -229,8 +233,43 @@ public sealed class LegacyThemeWorkflowTests
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Nxt, session, "AI(NXT)", "00000001"));
|
||||
await Assert.ThrowsAsync<LegacyThemeWorkflowDataException>(() =>
|
||||
workflow.SearchAsync(snapshot, "AI", ThemeSession.PreMarket));
|
||||
var storedSuffix = await workflow.SearchAsync(
|
||||
snapshot,
|
||||
"AI",
|
||||
ThemeSession.PreMarket);
|
||||
Assert.Equal("AI(NXT)", Assert.Single(storedSuffix.SearchResults).DisplayTitle);
|
||||
|
||||
service.SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Nxt, session, "AI(NXT)(NXT)", "00000001"));
|
||||
var repeatedNxtSuffix = await workflow.SearchAsync(
|
||||
snapshot,
|
||||
"AI",
|
||||
ThemeSession.PreMarket);
|
||||
Assert.Equal(
|
||||
"AI(NXT)(NXT)",
|
||||
Assert.Single(repeatedNxtSuffix.SearchResults).Identity.ThemeTitle);
|
||||
|
||||
service.SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "AI(NXT)", "00000001"));
|
||||
var krxStoredSuffix = await workflow.SearchAsync(
|
||||
snapshot,
|
||||
"AI",
|
||||
ThemeSession.PreMarket);
|
||||
Assert.Equal("AI(NXT)", Assert.Single(krxStoredSuffix.SearchResults).DisplayTitle);
|
||||
|
||||
service.SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, "AI(NXT)(NXT)", "00000001"));
|
||||
var repeatedKrxSuffix = await workflow.SearchAsync(
|
||||
snapshot,
|
||||
"AI",
|
||||
ThemeSession.PreMarket);
|
||||
Assert.Equal(ThemeMarket.Krx, Assert.Single(repeatedKrxSuffix.SearchResults).Market);
|
||||
|
||||
service.SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
@@ -263,6 +302,70 @@ public sealed class LegacyThemeWorkflowTests
|
||||
Assert.Contains("search provider order", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_PreservesExactLegacyStoredTitlesWithEdgeSpaces()
|
||||
{
|
||||
var service = new FakeThemeSelectionService
|
||||
{
|
||||
SearchHandler = (query, session) => SearchResult(
|
||||
query,
|
||||
session,
|
||||
Selector(ThemeMarket.Krx, ThemeSession.NotApplicable, " KRX Theme ", "00000001"),
|
||||
Selector(ThemeMarket.Nxt, session, " NXT Theme ", "00000002"))
|
||||
};
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
|
||||
var result = await workflow.SearchAsync(
|
||||
workflow.CreateInitial(),
|
||||
string.Empty,
|
||||
ThemeSession.PreMarket);
|
||||
|
||||
Assert.Collection(
|
||||
result.SearchResults,
|
||||
row =>
|
||||
{
|
||||
Assert.Equal(" KRX Theme ", row.Identity.ThemeTitle);
|
||||
Assert.Equal(" KRX Theme ", row.DisplayTitle);
|
||||
Assert.Equal(ThemeMarket.Krx, row.Market);
|
||||
},
|
||||
row =>
|
||||
{
|
||||
Assert.Equal(" NXT Theme ", row.Identity.ThemeTitle);
|
||||
Assert.Equal(" NXT Theme (NXT)", row.DisplayTitle);
|
||||
Assert.Equal(ThemeMarket.Nxt, row.Market);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_AcceptsFiniteFiveThousandRowTruncationBoundary()
|
||||
{
|
||||
var service = new FakeThemeSelectionService
|
||||
{
|
||||
SearchHandler = (query, session) => new ThemeSearchResult(
|
||||
query,
|
||||
session,
|
||||
DateTimeOffset.UtcNow,
|
||||
Enumerable.Range(0, LegacyThemeWorkflow.MaximumSearchResults)
|
||||
.Select(index => Selector(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
$"Theme {index}",
|
||||
index.ToString("D8", System.Globalization.CultureInfo.InvariantCulture)))
|
||||
.ToArray(),
|
||||
IsTruncated: true)
|
||||
};
|
||||
var workflow = new LegacyThemeWorkflow(service);
|
||||
|
||||
var snapshot = await workflow.SearchAsync(
|
||||
workflow.CreateInitial(),
|
||||
string.Empty,
|
||||
ThemeSession.PreMarket);
|
||||
|
||||
Assert.Equal(5_000, snapshot.SearchResults.Count);
|
||||
Assert.True(snapshot.SearchIsTruncated);
|
||||
Assert.Equal("theme-result-5000", snapshot.SearchResults[^1].ResultId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_UsesOpaqueResultIdAndCopiesValidatedPreview()
|
||||
{
|
||||
@@ -515,6 +618,7 @@ public sealed class LegacyThemeWorkflowTests
|
||||
action.ValueKind == LegacyThemeValueKind.Expected ? "EXPECTED" : "CURRENT",
|
||||
draft.Selection.Subtype);
|
||||
Assert.Equal("00000001", draft.Selection.DataCode);
|
||||
Assert.Equal("AI", draft.Selection.ExactSubject);
|
||||
Assert.Equal(action.PageSize, draft.PageSize);
|
||||
Assert.Equal($"1/{draft.PagePlan.PageCount}", draft.PagePlan.InitialPageText);
|
||||
Assert.InRange(draft.PagePlan.PageCount, 1, 20);
|
||||
@@ -554,7 +658,8 @@ public sealed class LegacyThemeWorkflowTests
|
||||
"Robotics(NXT)",
|
||||
"AFTER_MARKET",
|
||||
sortId,
|
||||
"00000002"),
|
||||
"00000002",
|
||||
"Robotics"),
|
||||
draft.Selection);
|
||||
Assert.True(workflow.GetActionState(selected, actionId).IsAvailable);
|
||||
}
|
||||
@@ -567,6 +672,69 @@ public sealed class LegacyThemeWorkflowTests
|
||||
Assert.Equal("NXT는 데이터가 없습니다.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaterializePlaylistDraft_NxtStoredSuffixIsNotDuplicated()
|
||||
{
|
||||
var identity = Identity(
|
||||
ThemeMarket.Nxt,
|
||||
ThemeSession.PreMarket,
|
||||
"Robotics(NXT)",
|
||||
"00000002");
|
||||
var workflow = new LegacyThemeWorkflow(
|
||||
ServiceWithTheme(identity, CreateItems(1, ThemeMarket.Nxt)));
|
||||
var selected = await SearchAndSelectAsync(workflow, ThemeSession.PreMarket);
|
||||
|
||||
var draft = workflow.MaterializePlaylistDraft(selected, "five-row-current");
|
||||
|
||||
Assert.Equal("Robotics(NXT)", draft.Title);
|
||||
Assert.Equal("Robotics(NXT)", draft.Selection.Subject);
|
||||
Assert.Equal("Robotics(NXT)", draft.Selection.ExactSubject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaterializePlaylistDraft_KrxStoredNxtSuffixKeepsExplicitKrxRoute()
|
||||
{
|
||||
var identity = Identity(
|
||||
ThemeMarket.Krx,
|
||||
ThemeSession.NotApplicable,
|
||||
"Robotics(NXT)",
|
||||
"00000002");
|
||||
var workflow = new LegacyThemeWorkflow(
|
||||
ServiceWithTheme(identity, CreateItems(1, ThemeMarket.Krx)));
|
||||
var selected = await SearchAndSelectAsync(workflow);
|
||||
|
||||
var draft = workflow.MaterializePlaylistDraft(selected, "five-row-current");
|
||||
|
||||
Assert.Equal("Robotics(NXT)", draft.Title);
|
||||
Assert.Equal("PAGED_THEME", draft.Selection.GroupCode);
|
||||
Assert.Equal("Robotics(NXT)", draft.Selection.Subject);
|
||||
Assert.Equal("Robotics(NXT)", draft.Selection.ExactSubject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_NxtEmptyActivePreviewRemainsSelectedButCannotAddOrPlayout()
|
||||
{
|
||||
var identity = Identity(
|
||||
ThemeMarket.Nxt,
|
||||
ThemeSession.PreMarket,
|
||||
" NXT Theme",
|
||||
"00000004");
|
||||
var workflow = new LegacyThemeWorkflow(ServiceWithTheme(identity, []));
|
||||
|
||||
var selected = await SearchAndSelectAsync(workflow, ThemeSession.PreMarket);
|
||||
|
||||
Assert.Equal(ThemeMarket.Nxt, selected.SelectedTheme!.Market);
|
||||
Assert.Equal(" NXT Theme(NXT)", selected.SelectedTheme.DisplayTitle);
|
||||
Assert.Empty(selected.PreviewItems);
|
||||
Assert.All(selected.ActionStates, state =>
|
||||
{
|
||||
Assert.False(state.IsAvailable);
|
||||
Assert.Equal("theme-preview-is-empty", state.UnavailableReason);
|
||||
});
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
workflow.MaterializePlaylistDraft(selected, "five-row-current"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaterializePlaylistDraft_NxtReevaluatesTheNativeHourAtActionTime()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user