Files
ExperionCrawler/ExperionCrawler.Tests/KoreanTimeRangeExtractorTests.cs

383 lines
11 KiB
C#

using ExperionCrawler.Core.Application.Services;
using Xunit;
namespace ExperionCrawler.Tests;
/// <summary>
/// KoreanTimeRangeExtractor 단위 테스트
/// 새로운 한글 시간 패턴 파싱 로직을 검증합니다.
/// </summary>
public class KoreanTimeRangeExtractorTests
{
private readonly KoreanTimeRangeExtractor _extractor;
private readonly KstClock _kstClock;
public KoreanTimeRangeExtractorTests()
{
// 테스트용 고정 시계: KST 2026-04-23 12:00:00 = UTC 2026-04-23 03:00:00
var fixedClock = new FixedClock(new DateTimeOffset(2026, 4, 23, 3, 0, 0, TimeSpan.Zero));
_kstClock = new KstClock(fixedClock);
_extractor = new KoreanTimeRangeExtractor(_kstClock);
}
#region : ~
[Fact]
public void AbsoluteRange_FromTo_KoreanDate()
{
// Arrange & Act
var result = _extractor.Extract("4월 3일 부터 4월 5일 까지");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
Assert.Null(result.PostgresInterval);
// KST 2026-04-03 00:00 ~ 2026-04-06 00:00 (까지의 다음날)
Assert.Equal(2026, result.KstFrom!.Value.Year);
Assert.Equal(4, result.KstFrom.Value.Month);
Assert.Equal(3, result.KstFrom.Value.Day);
Assert.Equal(2026, result.KstTo!.Value.Year);
Assert.Equal(4, result.KstTo.Value.Month);
Assert.Equal(6, result.KstTo.Value.Day); // 다음날 00:00
}
[Fact]
public void AbsoluteRange_FromTo_IsoDate()
{
// Arrange & Act
var result = _extractor.Extract("2026-04-03 부터 2026-04-05 까지");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
}
[Fact]
public void AbsoluteRange_FromTo_WithTime()
{
// Arrange & Act
var result = _extractor.Extract("어제 09:00 부터 오늘 18:00 까지");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
// KST 2026-04-22 09:00 ~ 2026-04-23 18:00
Assert.Equal(2026, result.KstFrom!.Value.Year);
Assert.Equal(4, result.KstFrom.Value.Month);
Assert.Equal(22, result.KstFrom.Value.Day);
Assert.Equal(9, result.KstFrom.Value.Hour);
Assert.Equal(2026, result.KstTo!.Value.Year);
Assert.Equal(4, result.KstTo.Value.Month);
Assert.Equal(23, result.KstTo.Value.Day);
Assert.Equal(18, result.KstTo.Value.Hour);
}
[Fact]
public void AbsoluteRange_FromTo_WithAmPm()
{
// Arrange & Act
var result = _extractor.Extract("오전 9시부터 오후 6시까지");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
// KST 2026-04-23 09:00 ~ 18:00
Assert.Equal(9, result.KstFrom!.Value.Hour);
Assert.Equal(18, result.KstTo!.Value.Hour);
}
[Fact]
public void AbsoluteRange_WithTimeComponent_PreservesTime()
{
// Arrange & Act
var result = _extractor.Extract("2026-03-01 14:00 부터 2026-03-01 16:30 까지");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
Assert.Equal(14, result.KstFrom!.Value.Hour);
Assert.Equal(0, result.KstFrom.Value.Minute);
Assert.Equal(16, result.KstTo!.Value.Hour);
Assert.Equal(30, result.KstTo.Value.Minute);
}
#endregion
#region : /
[Fact]
public void OneDirection_After()
{
// Arrange & Act
var result = _extractor.Extract("오늘 오후 2시 이후");
// Assert
Assert.NotNull(result.KstFrom);
Assert.Null(result.KstTo);
Assert.Null(result.PostgresInterval);
// KST 2026-04-23 14:00
Assert.Equal(14, result.KstFrom!.Value.Hour);
}
[Fact]
public void OneDirection_Before()
{
// Arrange & Act
var result = _extractor.Extract("2026-05-05 이전");
// Assert
Assert.Null(result.KstFrom);
Assert.NotNull(result.KstTo);
Assert.Null(result.PostgresInterval);
// KST 2026-05-05
Assert.Equal(2026, result.KstTo!.Value.Year);
Assert.Equal(5, result.KstTo.Value.Month);
Assert.Equal(5, result.KstTo.Value.Day);
}
[Fact]
public void OneDirection_FromOnly()
{
// Arrange & Act
var result = _extractor.Extract("4월 3일 부터");
// Assert
Assert.NotNull(result.KstFrom);
Assert.Null(result.KstTo);
Assert.Null(result.PostgresInterval);
}
#endregion
#region : / N시간||
[Theory]
[InlineData("최근 3시간", "3 hours")]
[InlineData("지난 2시간", "2 hours")]
[InlineData("최근 30분", "30 minutes")]
[InlineData("지난 15분", "15 minutes")]
[InlineData("최근 7일", "7 days")]
[InlineData("지난 30일", "30 days")]
[InlineData("최근 2주", "14 days")]
public void RelativeRange_CorrectInterval(string input, string expectedInterval)
{
// Arrange & Act
var result = _extractor.Extract(input);
// Assert
Assert.Equal(expectedInterval, result.PostgresInterval);
Assert.Null(result.KstFrom);
Assert.Null(result.KstTo);
}
#endregion
#region : //
[Fact]
public void NamedDay_Today()
{
// Arrange & Act
var result = _extractor.Extract("오늘");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
// KST 2026-04-23 00:00 ~ 2026-04-24 00:00
Assert.Equal(2026, result.KstFrom!.Value.Year);
Assert.Equal(4, result.KstFrom.Value.Month);
Assert.Equal(23, result.KstFrom.Value.Day);
Assert.Equal(2026, result.KstTo!.Value.Year);
Assert.Equal(4, result.KstTo.Value.Month);
Assert.Equal(24, result.KstTo.Value.Day);
}
[Fact]
public void NamedDay_Yesterday()
{
// Arrange & Act
var result = _extractor.Extract("어제");
// Assert
Assert.NotNull(result.KstFrom);
Assert.NotNull(result.KstTo);
// KST 2026-04-22 00:00 ~ 2026-04-23 00:00
Assert.Equal(2026, result.KstFrom!.Value.Year);
Assert.Equal(4, result.KstFrom.Value.Month);
Assert.Equal(22, result.KstFrom.Value.Day);
Assert.Equal(2026, result.KstTo!.Value.Year);
Assert.Equal(4, result.KstTo.Value.Month);
Assert.Equal(23, result.KstTo.Value.Day);
}
[Fact]
public void NamedDay_ThisWeek()
{
// Arrange & Act
var result = _extractor.Extract("이번 주");
// Assert
Assert.NotNull(result.KstFrom);
Assert.Null(result.KstTo);
// KST 2026-04-20 (월요일 - 2026-04-23은 목요일이므로 3일 전)
Assert.Equal(2026, result.KstFrom!.Value.Year);
Assert.Equal(4, result.KstFrom.Value.Month);
// 2026-04-23은 목요일(Thursday = 4), 월요일은 4-3 = 4-20
Assert.Equal(20, result.KstFrom.Value.Day);
}
#endregion
#region
[Fact]
public void Default_InvalidInput_ReturnsOneHourInterval()
{
// Arrange & Act
var result = _extractor.Extract("PV_101.PV 평균값");
// Assert
Assert.Equal("1 hour", result.PostgresInterval);
Assert.Null(result.KstFrom);
Assert.Null(result.KstTo);
}
[Fact]
public void Default_EmptyInput_ReturnsOneHourInterval()
{
// Arrange & Act
var result = _extractor.Extract("");
// Assert
Assert.Equal("1 hour", result.PostgresInterval);
Assert.Null(result.KstFrom);
Assert.Null(result.KstTo);
}
#endregion
#region ToSqlCondition Tests
[Fact]
public void ToSqlCondition_RelativeRange_UsesInterval()
{
// Arrange
var result = _extractor.Extract("최근 3시간");
// Act
var sql = result.ToSqlCondition("\"time\"", _kstClock);
// Assert
Assert.Equal("\"time\" >= NOW() - INTERVAL '3 hours'", sql);
}
[Fact]
public void ToSqlCondition_AbsoluteRange_BothSides()
{
// Arrange
var result = _extractor.Extract("오늘");
// Act
var sql = result.ToSqlCondition("\"time\"", _kstClock);
// Assert
// KST 2026-04-23 00:00 = UTC 2026-04-22 15:00
// KST 2026-04-24 00:00 = UTC 2026-04-23 15:00
Assert.Contains("\"time\" >=", sql);
Assert.Contains("AND \"time\" <", sql);
Assert.Contains("2026-04-22 15:00:00+00", sql);
Assert.Contains("2026-04-23 15:00:00+00", sql);
}
[Fact]
public void ToSqlCondition_FromOnly()
{
// Arrange
var result = _extractor.Extract("오늘 오후 2시 이후");
// Act
var sql = result.ToSqlCondition("\"time\"", _kstClock);
// Assert
// KST 2026-04-23 14:00 = UTC 2026-04-23 05:00
Assert.Contains("\"time\" >=", sql);
Assert.DoesNotContain("AND", sql);
Assert.Contains("2026-04-23 05:00:00+00", sql);
}
[Fact]
public void ToSqlCondition_ToOnly()
{
// Arrange
var result = _extractor.Extract("2026-05-05 이전");
// Act
var sql = result.ToSqlCondition("\"time\"", _kstClock);
// Assert
// KST 2026-05-05 00:00 = UTC 2026-05-04 15:00
Assert.Contains("\"time\" <", sql);
Assert.DoesNotContain("AND", sql);
Assert.Contains("2026-05-04 15:00:00+00", sql);
}
#endregion
#region ParseKstDateTime Tests (via Extract)
// ParseKstDateTime은 internal이므로 Extract를 통해 간접 테스트합니다.
[Theory]
[InlineData("오늘", "2026-04-23")]
[InlineData("어제", "2026-04-22")]
[InlineData("4월 3일", "2026-04-03")]
public void Extract_KoreanDate_ParsesCorrectly(string input, string expectedDate)
{
// Arrange & Act
var result = _extractor.Extract(input);
// Assert
Assert.NotNull(result.KstFrom);
Assert.Equal(expectedDate, result.KstFrom!.Value.ToString("yyyy-MM-dd"));
}
[Fact]
public void Extract_WithTime_PreservesTime()
{
// Arrange & Act
var result = _extractor.Extract("오늘 14:30 이후");
// Assert
Assert.NotNull(result.KstFrom);
Assert.Equal(14, result.KstFrom!.Value.Hour);
Assert.Equal(30, result.KstFrom.Value.Minute);
}
[Fact]
public void Extract_WithAmPm_ConvertsTo24Hour()
{
// Arrange & Act
var result = _extractor.Extract("오후 3시 이후");
// Assert
Assert.NotNull(result.KstFrom);
Assert.Equal(15, result.KstFrom!.Value.Hour);
}
[Fact]
public void Extract_IsoFormat_ParsesCorrectly()
{
// Arrange & Act
var result = _extractor.Extract("2026-03-01 14:00 이후");
// Assert
Assert.NotNull(result.KstFrom);
Assert.Equal(2026, result.KstFrom!.Value.Year);
Assert.Equal(3, result.KstFrom.Value.Month);
Assert.Equal(1, result.KstFrom.Value.Day);
Assert.Equal(14, result.KstFrom.Value.Hour);
}
#endregion
}