Initial commit to new repository

This commit is contained in:
2026-04-03 18:22:19 +09:00
commit 4458bb0f52
7672 changed files with 452440 additions and 0 deletions

View File

@@ -0,0 +1,279 @@
using FluentAssertions;
using AxCopilot.Core;
using Xunit;
namespace AxCopilot.Tests.Core;
public class FuzzyEngineTests
{
// ─── CalculateScore 기본 매칭 ────────────────────────────────────────────
[Fact]
public void CalculateScore_ExactMatch_ReturnsHighestScore()
{
var score = FuzzyEngine.CalculateScore("notepad", "notepad", 0);
score.Should().BeGreaterThanOrEqualTo(1000);
}
[Fact]
public void CalculateScore_PrefixMatch_ReturnsHighScore()
{
var score = FuzzyEngine.CalculateScore("note", "notepad", 0);
score.Should().BeGreaterThanOrEqualTo(800);
score.Should().BeLessThan(1000);
}
[Fact]
public void CalculateScore_ContainsMatch_ReturnsMediumScore()
{
var score = FuzzyEngine.CalculateScore("pad", "notepad", 0);
score.Should().BeGreaterThanOrEqualTo(600);
score.Should().BeLessThan(800);
}
[Fact]
public void CalculateScore_NoMatch_ReturnsZero()
{
var score = FuzzyEngine.CalculateScore("xyz", "notepad", 0);
score.Should().Be(0);
}
[Fact]
public void CalculateScore_EmptyQuery_ReturnsZero()
{
var score = FuzzyEngine.CalculateScore("", "notepad", 0);
score.Should().Be(0);
}
[Fact]
public void CalculateScore_BaseScore_AddsToResult()
{
var scoreWithBase = FuzzyEngine.CalculateScore("notepad", "notepad", 100);
var scoreWithout = FuzzyEngine.CalculateScore("notepad", "notepad", 0);
scoreWithBase.Should().Be(scoreWithout + 100);
}
[Fact]
public void CalculateScore_ExactBeforePrefix()
{
var exact = FuzzyEngine.CalculateScore("note", "note", 0);
var prefix = FuzzyEngine.CalculateScore("not", "note", 0);
exact.Should().BeGreaterThan(prefix);
}
// ─── FuzzyMatch ──────────────────────────────────────────────────────────
[Fact]
public void FuzzyMatch_AllCharsPresent_ReturnsPositive()
{
var score = FuzzyEngine.FuzzyMatch("ntpd", "notepad");
score.Should().BePositive();
}
[Fact]
public void FuzzyMatch_CharsMissing_ReturnsZero()
{
var score = FuzzyEngine.FuzzyMatch("xyz", "notepad");
score.Should().Be(0);
}
[Fact]
public void FuzzyMatch_ConsecutiveCharsScoreHigher()
{
var consecutive = FuzzyEngine.FuzzyMatch("not", "notepad");
var scattered = FuzzyEngine.FuzzyMatch("ntp", "notepad");
consecutive.Should().BeGreaterThan(scattered);
}
[Fact]
public void FuzzyMatch_EmptyQuery_ReturnsPositive()
{
var score = FuzzyEngine.FuzzyMatch("", "notepad");
score.Should().BeGreaterThanOrEqualTo(0);
}
[Fact]
public void FuzzyMatch_FullMatch_ReturnsHighScore()
{
var full = FuzzyEngine.FuzzyMatch("abcde", "abcde");
var partial = FuzzyEngine.FuzzyMatch("ace", "abcde");
full.Should().BeGreaterThan(partial);
}
[Fact]
public void FuzzyMatch_MinimumScoreGuaranteed()
{
var score = FuzzyEngine.FuzzyMatch("ntpd", "notepad");
score.Should().BeGreaterThanOrEqualTo(50);
}
// ─── 한글 자모 분리 ─────────────────────────────────────────────────────
[Theory]
[InlineData("가", "ㄱㅏ")]
[InlineData("한", "ㅎㅏㄴ")]
[InlineData("글", "ㄱㅡㄹ")]
[InlineData("abc", "abc")]
[InlineData("가a나", "ㄱㅏaㄴㅏ")]
public void DecomposeToJamo_ReturnsCorrectJamo(string input, string expected)
{
FuzzyEngine.DecomposeToJamo(input).Should().Be(expected);
}
// ─── 자모 기반 포함 검색 ────────────────────────────────────────────────
[Fact]
public void JamoContainsScore_MiddleWord_ReturnsPositive()
{
// "모장" → "메모장" (자모 분리 후 연속 매칭)
var score = FuzzyEngine.JamoContainsScore("메모장", "모장");
score.Should().BePositive();
}
[Fact]
public void JamoContainsScore_NoMatch_ReturnsZero()
{
var score = FuzzyEngine.JamoContainsScore("메모장", "가나");
score.Should().Be(0);
}
[Fact]
public void JamoContainsScore_SubsequenceMatch_ReturnsPositive()
{
// "메장" → 메-모-장에서 비연속 자모 매칭
var score = FuzzyEngine.JamoContainsScore("메모장", "메장");
score.Should().BePositive();
}
[Fact]
public void JamoContainsScore_NonKorean_ReturnsZero()
{
var score = FuzzyEngine.JamoContainsScore("notepad", "pad");
score.Should().Be(0); // 영어는 Contains에서 이미 처리
}
// ─── 한글 초성 검색 ──────────────────────────────────────────────────────
[Theory]
[InlineData("ㄴ", true)]
[InlineData("ㄴㅌ", true)]
[InlineData("ㄱㄴㄷ", true)]
public void IsChosung_ValidChosung_ReturnsTrue(string text, bool expected)
{
FuzzyEngine.IsChosung(text).Should().Be(expected);
}
[Theory]
[InlineData("notepad", false)]
[InlineData("노트패드", false)]
[InlineData("a", false)]
[InlineData("ㄴa", false)]
public void IsChosung_NonChosung_ReturnsFalse(string text, bool expected)
{
FuzzyEngine.IsChosung(text).Should().Be(expected);
}
[Fact]
public void GetChosung_HangulChar_ReturnsCorrectChosung()
{
FuzzyEngine.GetChosung('나').Should().Be('ㄴ');
FuzzyEngine.GetChosung('가').Should().Be('ㄱ');
FuzzyEngine.GetChosung('하').Should().Be('ㅎ');
}
[Fact]
public void GetChosung_NonHangul_ReturnsNull()
{
FuzzyEngine.GetChosung('a').Should().Be('\0');
FuzzyEngine.GetChosung('1').Should().Be('\0');
}
[Fact]
public void ContainsChosung_ConsecutiveMatch_ReturnsTrue()
{
FuzzyEngine.ContainsChosung("노트패드", "ㄴㅌ").Should().BeTrue();
}
[Fact]
public void ContainsChosung_NonMatchingChosung_ReturnsFalse()
{
FuzzyEngine.ContainsChosung("노트패드", "ㅅ").Should().BeFalse();
}
[Fact]
public void ContainsChosung_PartialMatch_ReturnsTrue()
{
FuzzyEngine.ContainsChosung("계산기", "ㄱㅅ").Should().BeTrue();
}
[Fact]
public void ContainsChosung_QueryLongerThanTarget_ReturnsFalse()
{
FuzzyEngine.ContainsChosung("가", "ㄱㄴㄷㄹ").Should().BeFalse();
}
[Fact]
public void ContainsChosung_NonConsecutive_ReturnsTrue()
{
// "ㅁㅊ" → 메모장(ㅁㅁㅈ) — 안 맞음 (ㅊ가 없으므로)
FuzzyEngine.ContainsChosung("메모장", "ㅁㅊ").Should().BeFalse();
// "ㅁㅈ" → 메모장(ㅁㅁㅈ) — 비연속 매칭
FuzzyEngine.ContainsChosung("메모장", "ㅁㅈ").Should().BeTrue();
}
// ─── 초성 점수 매칭 ─────────────────────────────────────────────────────
[Fact]
public void ChosungMatchScore_PureChosung_Consecutive()
{
var score = FuzzyEngine.ChosungMatchScore("계산기", "ㄱㅅ");
score.Should().BeGreaterThanOrEqualTo(500);
}
[Fact]
public void ChosungMatchScore_PureChosung_Subsequence()
{
// "ㅁㅈ" → 메모장 (ㅁ...ㅈ 비연속)
var score = FuzzyEngine.ChosungMatchScore("메모장", "ㅁㅈ");
score.Should().BePositive();
}
[Fact]
public void ChosungMatchScore_MixedQuery()
{
// "ㅁ장" → 혼합: ㅁ은 초성, 장은 완성형
var score = FuzzyEngine.ChosungMatchScore("메모장", "ㅁ장");
score.Should().BePositive();
}
[Fact]
public void ChosungMatchScore_NoMatch_ReturnsZero()
{
var score = FuzzyEngine.ChosungMatchScore("메모장", "ㅋㅋ");
score.Should().Be(0);
}
// ─── 통합 점수 우선순위 ─────────────────────────────────────────────────
[Fact]
public void CalculateScore_ScoreHierarchy()
{
// 정확 > 시작 > 포함 > 자모포함 > 초성 > fuzzy
var exact = FuzzyEngine.CalculateScore("메모장", "메모장", 0);
var prefix = FuzzyEngine.CalculateScore("메모", "메모장", 0);
var contains = FuzzyEngine.CalculateScore("모장", "메모장", 0);
exact.Should().BeGreaterThan(prefix);
prefix.Should().BeGreaterThan(contains);
}
[Fact]
public void CalculateScore_JamoBeforeChosung()
{
// "모장" (자모 포함) > "ㅁㅈ" (초성 비연속)
var jamo = FuzzyEngine.CalculateScore("모장", "메모장", 0);
var chosung = FuzzyEngine.CalculateScore("ㅁㅈ", "메모장", 0);
jamo.Should().BeGreaterThan(chosung);
}
}