diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9603afc..1dffc23 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -129,6 +129,6 @@ jobs: APP_IMAGE_TAG="${{ github.sha }}" docker compose pull app # 기존 앱을 내리기 전에 후보 컨테이너의 사용자/마운트로 모델 접근을 검사한다. APP_IMAGE_TAG="${{ github.sha }}" docker compose run --rm -T --no-deps --entrypoint sh app -s < check-hair-analysis-model.sh - APP_IMAGE_TAG="${{ github.sha }}" docker compose up -d --remove-orphans + APP_IMAGE_TAG="${{ github.sha }}" docker compose up -d --remove-orphans --wait --wait-timeout 120 docker image prune -f EOF diff --git a/src/main/java/com/heddy/adapter/out/ai/HairMetricCalculator.java b/src/main/java/com/heddy/adapter/out/ai/HairMetricCalculator.java index 1cee8ec..511374b 100644 --- a/src/main/java/com/heddy/adapter/out/ai/HairMetricCalculator.java +++ b/src/main/java/com/heddy/adapter/out/ai/HairMetricCalculator.java @@ -1,7 +1,7 @@ package com.heddy.adapter.out.ai; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; import com.heddy.domain.analysis.model.ConfidenceGrade; import com.heddy.domain.analysis.model.HairAnalysisOutcome; import com.heddy.domain.analysis.model.HairAnalysisPrediction; @@ -366,7 +366,7 @@ private static String summary( private String json(Map value) { try { return objectMapper.writeValueAsString(value); - } catch (JsonProcessingException exception) { + } catch (JacksonException exception) { throw new IllegalStateException("분석 근거를 JSON으로 만들 수 없습니다", exception); } } diff --git a/src/main/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapter.java b/src/main/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapter.java index fba936b..89b0310 100644 --- a/src/main/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapter.java +++ b/src/main/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapter.java @@ -4,7 +4,7 @@ import ai.onnxruntime.OrtEnvironment; import ai.onnxruntime.OrtException; import ai.onnxruntime.OrtSession; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import com.heddy.domain.analysis.model.HairAnalysisOutcome; import com.heddy.domain.analysis.port.out.HairAnalysisEnginePort; import jakarta.annotation.PreDestroy; diff --git a/src/test/java/com/heddy/adapter/out/ai/HairAnalysisEnabledContextTest.java b/src/test/java/com/heddy/adapter/out/ai/HairAnalysisEnabledContextTest.java new file mode 100644 index 0000000..592e7d2 --- /dev/null +++ b/src/test/java/com/heddy/adapter/out/ai/HairAnalysisEnabledContextTest.java @@ -0,0 +1,52 @@ +package com.heddy.adapter.out.ai; + +import ai.onnxruntime.OrtEnvironment; +import ai.onnxruntime.OrtSession; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import tools.jackson.databind.ObjectMapper; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class HairAnalysisEnabledContextTest { + @TempDir Path directory; + + @Test + void enabledEngineStartsWithBootsAutoConfiguredJacksonMapper() throws Exception { + Path model = Files.write(directory.resolve("model.onnx"), new byte[]{1}); + OrtEnvironment environment = mock(OrtEnvironment.class); + OrtSession session = mock(OrtSession.class); + when(environment.createSession(anyString(), any(OrtSession.SessionOptions.class))) + .thenReturn(session); + when(session.getInputNames()).thenReturn(Set.of("pixel_values")); + + // Only the native model is stubbed: Boot's real Jackson auto-configuration must wire the bean. + try (var nativeEnvironment = mockStatic(OrtEnvironment.class); + var options = mockConstruction(OrtSession.SessionOptions.class)) { + nativeEnvironment.when(OrtEnvironment::getEnvironment).thenReturn(environment); + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class)) + .withUserConfiguration(OnnxFaceParsingHairAnalysisAdapter.class) + .withPropertyValues("app.ai.enabled=true", "app.ai.model-path=" + model, + "app.ai.model-version=context-test", "app.ai.input-size=320", + "app.ai.minimum-confidence-score=50") + .run(context -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(ObjectMapper.class); + assertThat(context).hasSingleBean(OnnxFaceParsingHairAnalysisAdapter.class); + assertThat(context.getBean(OnnxFaceParsingHairAnalysisAdapter.class).isReady()).isTrue(); + }); + } + verify(session).close(); + } +} diff --git a/src/test/java/com/heddy/adapter/out/ai/HairMetricCalculatorTest.java b/src/test/java/com/heddy/adapter/out/ai/HairMetricCalculatorTest.java index 1aedf6d..9381eb5 100644 --- a/src/test/java/com/heddy/adapter/out/ai/HairMetricCalculatorTest.java +++ b/src/test/java/com/heddy/adapter/out/ai/HairMetricCalculatorTest.java @@ -1,6 +1,6 @@ package com.heddy.adapter.out.ai; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import com.heddy.domain.analysis.model.HairAnalysisOutcome; import com.heddy.domain.analysis.model.MetricType; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapterModelTest.java b/src/test/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapterModelTest.java index 1dd8e8c..dee107c 100644 --- a/src/test/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapterModelTest.java +++ b/src/test/java/com/heddy/adapter/out/ai/OnnxFaceParsingHairAnalysisAdapterModelTest.java @@ -1,6 +1,6 @@ package com.heddy.adapter.out.ai; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import com.heddy.domain.analysis.model.HairAnalysisOutcome; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;