From bd44ec7c5fd23849fc22417226bd83352f707635 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Thu, 2 Jul 2026 10:51:58 +0900 Subject: [PATCH] fix(common-code): enforce storage policy --- .../CommonCodeServiceTests.cs | 76 +++++++++++++++++++ .../Services/CommonCodeService.cs | 27 ++++++- 2 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 TaxBaik.Application.Tests/CommonCodeServiceTests.cs diff --git a/TaxBaik.Application.Tests/CommonCodeServiceTests.cs b/TaxBaik.Application.Tests/CommonCodeServiceTests.cs new file mode 100644 index 0000000..7d10037 --- /dev/null +++ b/TaxBaik.Application.Tests/CommonCodeServiceTests.cs @@ -0,0 +1,76 @@ +namespace TaxBaik.Application.Tests; + +using TaxBaik.Application.Services; +using TaxBaik.Domain.Entities; +using TaxBaik.Domain.Interfaces; +using Xunit; + +public class CommonCodeServiceTests +{ + [Fact] + public async Task UpsertAsync_TrimsAndRejectsWhitespaceInCodeValue() + { + var repository = new FakeCommonCodeRepository(); + var service = new CommonCodeService(repository); + + await Assert.ThrowsAsync(() => service.UpsertAsync(new CommonCode + { + CodeGroup = " CLIENT_STATUS ", + CodeValue = "active code", + CodeName = " 활성 " + })); + } + + [Fact] + public async Task UpsertAsync_TrimsAndPersistsNormalizedValues() + { + var repository = new FakeCommonCodeRepository(); + var service = new CommonCodeService(repository); + + await service.UpsertAsync(new CommonCode + { + CodeGroup = " CLIENT_STATUS ", + CodeValue = "active", + CodeName = " 활성 ", + SortOrder = 10 + }); + + var saved = Assert.Single(repository.SavedCodes); + Assert.Equal("CLIENT_STATUS", saved.CodeGroup); + Assert.Equal("active", saved.CodeValue); + Assert.Equal("활성", saved.CodeName); + } + + private sealed class FakeCommonCodeRepository : ICommonCodeRepository + { + public List SavedCodes { get; } = []; + + public Task> GetAllGroupsAsync(CancellationToken ct = default) => + Task.FromResult>([]); + + public Task> GetByGroupAsync(string codeGroup, CancellationToken ct = default) => + Task.FromResult>([]); + + public Task> GetAllActiveAsync(CancellationToken ct = default) => + Task.FromResult>([]); + + public Task GetAsync(string codeGroup, string codeValue, CancellationToken ct = default) => + Task.FromResult(null); + + public Task UpsertAsync(CommonCode code, CancellationToken ct = default) + { + SavedCodes.Add(new CommonCode + { + CodeGroup = code.CodeGroup, + CodeValue = code.CodeValue, + CodeName = code.CodeName, + SortOrder = code.SortOrder, + IsActive = code.IsActive + }); + return Task.CompletedTask; + } + + public Task DeleteAsync(string codeGroup, string codeValue, CancellationToken ct = default) => + Task.CompletedTask; + } +} diff --git a/TaxBaik.Application/Services/CommonCodeService.cs b/TaxBaik.Application/Services/CommonCodeService.cs index 7101f2c..6c1545c 100644 --- a/TaxBaik.Application/Services/CommonCodeService.cs +++ b/TaxBaik.Application/Services/CommonCodeService.cs @@ -8,6 +8,10 @@ namespace TaxBaik.Application.Services; public class CommonCodeService(ICommonCodeRepository commonCodeRepository) { + private const int MaxCodeGroupLength = 80; + private const int MaxCodeValueLength = 120; + private const int MaxCodeNameLength = 200; + public async Task> GetAllGroupsAsync(CancellationToken ct = default) { return await commonCodeRepository.GetAllGroupsAsync(ct); @@ -36,13 +40,28 @@ public class CommonCodeService(ICommonCodeRepository commonCodeRepository) public async Task DeleteAsync(string codeGroup, string codeValue, CancellationToken ct = default) { - await commonCodeRepository.DeleteAsync(codeGroup.Trim(), codeValue.Trim(), ct); + await commonCodeRepository.DeleteAsync(NormalizeToken(codeGroup, nameof(codeGroup), MaxCodeGroupLength), NormalizeToken(codeValue, nameof(codeValue), MaxCodeValueLength), ct); } private static void Normalize(CommonCode code) { - code.CodeGroup = code.CodeGroup.Trim(); - code.CodeValue = code.CodeValue.Trim(); - code.CodeName = code.CodeName.Trim(); + code.CodeGroup = NormalizeToken(code.CodeGroup, nameof(code.CodeGroup), MaxCodeGroupLength); + code.CodeValue = NormalizeToken(code.CodeValue, nameof(code.CodeValue), MaxCodeValueLength, disallowWhitespace: true); + code.CodeName = NormalizeToken(code.CodeName, nameof(code.CodeName), MaxCodeNameLength); + } + + private static string NormalizeToken(string value, string fieldName, int maxLength, bool disallowWhitespace = false) + { + var normalized = (value ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(normalized)) + throw new ValidationException($"{fieldName}은(는) 필수입니다."); + + if (normalized.Length > maxLength) + throw new ValidationException($"{fieldName}은(는) 최대 {maxLength}자까지 입력할 수 있습니다."); + + if (disallowWhitespace && normalized.Any(char.IsWhiteSpace)) + throw new ValidationException($"{fieldName}에는 공백을 사용할 수 없습니다."); + + return normalized; } }