From f3694a7fd88fe65ae12932f368a00329f9c5c917 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Fri, 13 Mar 2026 22:23:14 +0900 Subject: [PATCH 1/4] feat(chat): add LLM access type tracking in search log for RAG chat Set the LLM type name (from rag.llm.name system property) as the access type in search logs when using RAG chat endpoints. This enables distinguishing chat-based searches from web/API searches in analytics. Also update SearchLogHelper to handle custom access type strings beyond the predefined constants, and fix import ordering across test files. Co-Authored-By: Claude Opus 4.6 --- .../fess/api/chat/ChatApiManager.java | 10 + .../codelibs/fess/helper/SearchLogHelper.java | 2 + .../fess/api/chat/ChatApiManagerTest.java | 111 ++++++++ .../fess/helper/SearchLogHelperTest.java | 260 +++++++++++++++++- 4 files changed, 382 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/codelibs/fess/api/chat/ChatApiManager.java b/src/main/java/org/codelibs/fess/api/chat/ChatApiManager.java index eb7b39f006..a5b4b17615 100644 --- a/src/main/java/org/codelibs/fess/api/chat/ChatApiManager.java +++ b/src/main/java/org/codelibs/fess/api/chat/ChatApiManager.java @@ -27,6 +27,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.Constants; import org.codelibs.fess.api.BaseApiManager; import org.codelibs.fess.chat.ChatClient.ChatResult; import org.codelibs.fess.chat.ChatPhaseCallback; @@ -183,6 +184,11 @@ protected void processChatRequest(final HttpServletRequest request, final HttpSe } final String userId = getUserId(request); + + // Set LLM type name as Access Type for search log + request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, + ComponentUtil.getFessConfig().getSystemProperty("rag.llm.name", "ollama")); + final Map fields = parseFieldFilters(request); final String[] extraQueries = parseExtraQueries(request); final ChatResult result; @@ -257,6 +263,10 @@ protected void processStreamRequest(final HttpServletRequest request, final Http try (final PrintWriter writer = response.getWriter()) { final String userId = getUserId(request); + // Set LLM type name as Access Type for search log + request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, + ComponentUtil.getFessConfig().getSystemProperty("rag.llm.name", "ollama")); + // Create phase callback for SSE events final ChatPhaseCallback phaseCallback = new ChatPhaseCallback() { @Override diff --git a/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java b/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java index f7b359e8db..fbc20a46ff 100644 --- a/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java +++ b/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java @@ -183,6 +183,8 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER); } else if (Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN.equals(accessType)) { searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN); + } else if (accessType instanceof String && StringUtil.isNotBlank((String) accessType)) { + searchLog.setAccessType((String) accessType); } else { searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_WEB); } diff --git a/src/test/java/org/codelibs/fess/api/chat/ChatApiManagerTest.java b/src/test/java/org/codelibs/fess/api/chat/ChatApiManagerTest.java index 4d2c13514d..1d1e378110 100644 --- a/src/test/java/org/codelibs/fess/api/chat/ChatApiManagerTest.java +++ b/src/test/java/org/codelibs/fess/api/chat/ChatApiManagerTest.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import org.codelibs.fess.Constants; import org.codelibs.fess.entity.ChatMessage.ChatSource; import org.codelibs.fess.entity.FacetQueryView; import org.codelibs.fess.helper.ViewHelper; @@ -371,6 +372,105 @@ public void test_createSuccessResponse_emptySources() { assertEquals(sources, response.get("sources")); } + // ===== getMaxMessageLength tests ===== + + @Test + public void test_getMaxMessageLength_default() { + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + private static final long serialVersionUID = 1L; + + @Override + public String getOrDefault(final String key, final String defaultValue) { + if ("rag.chat.message.max.length".equals(key)) { + return defaultValue; + } + return defaultValue; + } + }); + + final int result = chatApiManager.getMaxMessageLength(ComponentUtil.getFessConfig()); + assertEquals(4000, result); + } + + @Test + public void test_getMaxMessageLength_customValue() { + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + private static final long serialVersionUID = 1L; + + @Override + public String getOrDefault(final String key, final String defaultValue) { + if ("rag.chat.message.max.length".equals(key)) { + return "8000"; + } + return defaultValue; + } + }); + + final int result = chatApiManager.getMaxMessageLength(ComponentUtil.getFessConfig()); + assertEquals(8000, result); + } + + @Test + public void test_getMaxMessageLength_invalidValue() { + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + private static final long serialVersionUID = 1L; + + @Override + public String getOrDefault(final String key, final String defaultValue) { + if ("rag.chat.message.max.length".equals(key)) { + return "not-a-number"; + } + return defaultValue; + } + }); + + final int result = chatApiManager.getMaxMessageLength(ComponentUtil.getFessConfig()); + assertEquals(4000, result); + } + + // ===== Access Type attribute tests ===== + + @Test + public void test_mockRequest_attributeStorage() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + assertNull(request.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE)); + + request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, "ollama"); + assertEquals("ollama", request.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE)); + } + + @Test + public void test_mockRequest_attributeOverwrite() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + + request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, "ollama"); + assertEquals("ollama", request.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE)); + + request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, "openai"); + assertEquals("openai", request.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE)); + } + + @Test + public void test_accessType_constantValues() { + assertEquals("json", Constants.SEARCH_LOG_ACCESS_TYPE_JSON); + assertEquals("gsa", Constants.SEARCH_LOG_ACCESS_TYPE_GSA); + assertEquals("web", Constants.SEARCH_LOG_ACCESS_TYPE_WEB); + assertEquals("admin", Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN); + assertEquals("other", Constants.SEARCH_LOG_ACCESS_TYPE_OTHER); + } + + @Test + public void test_accessType_llmNamesAreDifferentFromBuiltinTypes() { + final String[] llmNames = { "ollama", "openai", "gemini" }; + for (final String llmName : llmNames) { + assertFalse(Constants.SEARCH_LOG_ACCESS_TYPE_JSON.equals(llmName)); + assertFalse(Constants.SEARCH_LOG_ACCESS_TYPE_GSA.equals(llmName)); + assertFalse(Constants.SEARCH_LOG_ACCESS_TYPE_WEB.equals(llmName)); + assertFalse(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN.equals(llmName)); + assertFalse(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER.equals(llmName)); + } + } + // ===== parseExtraQueries tests ===== private void setupViewHelperWithFacetGroups(FacetQueryView... views) { @@ -549,6 +649,7 @@ private static class MockHttpServletRequest extends jakarta.servlet.http.HttpSer private String servletPath; private String method = "POST"; private final Map parameterValuesMap = new HashMap<>(); + private final Map attributeMap = new HashMap<>(); public MockHttpServletRequest() { super(new MockServletRequest()); @@ -580,6 +681,16 @@ public String[] getParameterValues(String name) { public void setParameterValues(String name, String[] values) { parameterValuesMap.put(name, values); } + + @Override + public Object getAttribute(String name) { + return attributeMap.get(name); + } + + @Override + public void setAttribute(String name, Object o) { + attributeMap.put(name, o); + } } /** diff --git a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java index d9e998a3dc..a857f7cb2b 100644 --- a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java +++ b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java @@ -15,12 +15,23 @@ */ package org.codelibs.fess.helper; +import java.time.LocalDateTime; +import java.util.Collections; import java.util.HashMap; import java.util.Map; - +import java.util.Set; + +import org.codelibs.fess.Constants; +import org.codelibs.fess.entity.FacetInfo; +import org.codelibs.fess.entity.GeoInfo; +import org.codelibs.fess.entity.HighlightInfo; +import org.codelibs.fess.entity.SearchRequestParams; +import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.opensearch.log.exentity.SearchLog; import org.codelibs.fess.unit.UnitFessTestCase; import org.codelibs.fess.util.ComponentUtil; +import org.codelibs.fess.util.QueryResponseList; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -146,6 +157,253 @@ public void test_clickLogQueue_initialization() { assertTrue(searchLogHelper.clickLogQueue.isEmpty()); } + // ===== addSearchLog Access Type tests ===== + + private void setupAddSearchLogComponents() { + final MockRoleQueryHelper roleQueryHelper = new MockRoleQueryHelper(); + ComponentUtil.register(roleQueryHelper, "roleQueryHelper"); + + final MockUserInfoHelper userInfoHelper = new MockUserInfoHelper(); + ComponentUtil.register(userInfoHelper, "userInfoHelper"); + + final MockViewHelper viewHelper = new MockViewHelper(); + ComponentUtil.register(viewHelper, "viewHelper"); + + final MockVirtualHostHelper virtualHostHelper = new MockVirtualHostHelper(); + ComponentUtil.register(virtualHostHelper, "virtualHostHelper"); + } + + private SearchLog callAddSearchLogAndGetResult(final String accessTypeAttribute) { + setupAddSearchLogComponents(); + + if (accessTypeAttribute != null) { + setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, accessTypeAttribute); + } + + final MockSearchRequestParams params = new MockSearchRequestParams(); + final LocalDateTime now = LocalDateTime.now(); + final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); + + searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + + assertFalse(searchLogHelper.searchLogQueue.isEmpty()); + return searchLogHelper.searchLogQueue.poll(); + } + + @Test + public void test_addSearchLog_accessType_json() { + final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_JSON); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_JSON, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_gsa() { + final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_GSA); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_GSA, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_other() { + final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_admin() { + final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_defaultWeb() { + final SearchLog searchLog = callAddSearchLogAndGetResult(null); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_ollama() { + final SearchLog searchLog = callAddSearchLogAndGetResult("ollama"); + assertEquals("ollama", searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_openai() { + final SearchLog searchLog = callAddSearchLogAndGetResult("openai"); + assertEquals("openai", searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_gemini() { + final SearchLog searchLog = callAddSearchLogAndGetResult("gemini"); + assertEquals("gemini", searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_customLlmName() { + final SearchLog searchLog = callAddSearchLogAndGetResult("my-custom-llm"); + assertEquals("my-custom-llm", searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_blankStringDefaultsToWeb() { + setupAddSearchLogComponents(); + setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, ""); + + final MockSearchRequestParams params = new MockSearchRequestParams(); + final LocalDateTime now = LocalDateTime.now(); + final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); + + searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + + assertFalse(searchLogHelper.searchLogQueue.isEmpty()); + final SearchLog searchLog = searchLogHelper.searchLogQueue.poll(); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_whitespaceOnlyDefaultsToWeb() { + setupAddSearchLogComponents(); + setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, " "); + + final MockSearchRequestParams params = new MockSearchRequestParams(); + final LocalDateTime now = LocalDateTime.now(); + final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); + + searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + + assertFalse(searchLogHelper.searchLogQueue.isEmpty()); + final SearchLog searchLog = searchLogHelper.searchLogQueue.poll(); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_nonStringObjectDefaultsToWeb() { + setupAddSearchLogComponents(); + setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Integer.valueOf(123)); + + final MockSearchRequestParams params = new MockSearchRequestParams(); + final LocalDateTime now = LocalDateTime.now(); + final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); + + searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + + assertFalse(searchLogHelper.searchLogQueue.isEmpty()); + final SearchLog searchLog = searchLogHelper.searchLogQueue.poll(); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + } + + // Mock classes for addSearchLog tests + + private static class MockSearchRequestParams extends SearchRequestParams { + @Override + public String getQuery() { + return "test query"; + } + + @Override + public Map getFields() { + return Collections.emptyMap(); + } + + @Override + public Map getConditions() { + return Collections.emptyMap(); + } + + @Override + public String[] getLanguages() { + return new String[0]; + } + + @Override + public GeoInfo getGeoInfo() { + return null; + } + + @Override + public FacetInfo getFacetInfo() { + return null; + } + + @Override + public HighlightInfo getHighlightInfo() { + return null; + } + + @Override + public String getSort() { + return null; + } + + @Override + public int getStartPosition() { + return 0; + } + + @Override + public int getPageSize() { + return 10; + } + + @Override + public int getOffset() { + return 0; + } + + @Override + public String[] getExtraQueries() { + return new String[0]; + } + + @Override + public Object getAttribute(final String name) { + return null; + } + + @Override + public SearchRequestType getType() { + return SearchRequestType.SEARCH; + } + + @Override + public String getSimilarDocHash() { + return null; + } + + @Override + public java.util.Locale getLocale() { + return java.util.Locale.ROOT; + } + } + + private static class MockRoleQueryHelper extends RoleQueryHelper { + @Override + public Set build(final SearchRequestType searchRequestType) { + return Collections.emptySet(); + } + } + + private static class MockUserInfoHelper extends UserInfoHelper { + @Override + public String getUserCode() { + return null; + } + } + + private static class MockViewHelper extends ViewHelper { + @Override + public String getClientIp(final jakarta.servlet.http.HttpServletRequest req) { + return "127.0.0.1"; + } + } + + private static class MockVirtualHostHelper extends VirtualHostHelper { + @Override + public String getVirtualHostKey() { + return ""; + } + } + // Mock classes private static class MockFessConfig extends FessConfig.SimpleImpl { private static final long serialVersionUID = 1L; From d53724110e541110aabb081aa3f3549907f87222 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Fri, 13 Mar 2026 22:43:49 +0900 Subject: [PATCH 2/4] test(search-log): fix parallel test failure by registering mock RequestManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register MockRequestManager in setUp to avoid FessLoginAssist → UserBhv DI chain failure when tests run in parallel. Co-Authored-By: Claude Sonnet 4.6 --- .../codelibs/fess/helper/SearchLogHelperTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java index a857f7cb2b..a5c3a5fc10 100644 --- a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java +++ b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java @@ -32,8 +32,12 @@ import org.codelibs.fess.unit.UnitFessTestCase; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.util.QueryResponseList; +import org.dbflute.optional.OptionalThing; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; +import org.lastaflute.web.login.UserBean; +import org.lastaflute.web.servlet.request.RequestManager; +import org.lastaflute.web.servlet.request.SimpleRequestManager; public class SearchLogHelperTest extends UnitFessTestCase { @@ -171,6 +175,9 @@ private void setupAddSearchLogComponents() { final MockVirtualHostHelper virtualHostHelper = new MockVirtualHostHelper(); ComponentUtil.register(virtualHostHelper, "virtualHostHelper"); + + // Register mock RequestManager to avoid FessLoginAssist → UserBhv DI chain failure in parallel tests + ComponentUtil.register(new MockRequestManager(), RequestManager.class.getCanonicalName()); } private SearchLog callAddSearchLogAndGetResult(final String accessTypeAttribute) { @@ -404,6 +411,13 @@ public String getVirtualHostKey() { } } + private static class MockRequestManager extends SimpleRequestManager { + @Override + public , ID> OptionalThing findUserBean(final Class userBeanType) { + return OptionalThing.empty(); + } + } + // Mock classes private static class MockFessConfig extends FessConfig.SimpleImpl { private static final long serialVersionUID = 1L; From 0e9e1c93f5c923797429ca0f9ce279213737cb19 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Sat, 14 Mar 2026 09:12:54 +0900 Subject: [PATCH 3/4] refactor(search-log): extract SearchLogContext and createSearchLog for testability Extract SearchLogContext inner class to decouple resolved dependencies from ComponentUtil, split addSearchLog into a wrapper and createSearchLog(context) for direct unit testing, and extract determineAccessType as a protected method. Update tests to call createSearchLog and determineAccessType directly, removing the need to register mock components for most access-type tests. Co-Authored-By: Claude Sonnet 4.6 --- .../codelibs/fess/helper/SearchLogHelper.java | 134 ++++++++---- .../fess/helper/SearchLogHelperTest.java | 207 +++++++++++------- 2 files changed, 221 insertions(+), 120 deletions(-) diff --git a/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java b/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java index fbc20a46ff..a2bb629b32 100644 --- a/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java +++ b/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java @@ -56,6 +56,8 @@ import org.dbflute.optional.OptionalThing; import org.lastaflute.web.util.LaRequestUtil; import org.opensearch.action.update.UpdateRequest; + +import jakarta.servlet.http.HttpServletRequest; import org.opensearch.script.Script; import com.fasterxml.jackson.core.JsonProcessingException; @@ -124,6 +126,28 @@ public UserInfo load(final String key) throws Exception { searchLogLogger = LogManager.getLogger(loggerName); } + /** Holds resolved dependencies for search log creation, decoupled from ComponentUtil. */ + protected static class SearchLogContext { + final FessConfig fessConfig; + final String[] roles; + final String userCode; + final String userId; + final HttpServletRequest request; + final String clientIp; + final String virtualHostKey; + + SearchLogContext(final FessConfig fessConfig, final String[] roles, final String userCode, final String userId, + final HttpServletRequest request, final String clientIp, final String virtualHostKey) { + this.fessConfig = fessConfig; + this.roles = roles; + this.userCode = userCode; + this.userId = userId; + this.request = request; + this.clientIp = clientIp; + this.virtualHostKey = virtualHostKey; + } + } + /** * Adds a search log to the queue. * @@ -143,19 +167,40 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r return; } - final RoleQueryHelper roleQueryHelper = ComponentUtil.getRoleQueryHelper(); - final UserInfoHelper userInfoHelper = ComponentUtil.getUserInfoHelper(); + final String[] roles = ComponentUtil.getRoleQueryHelper().build(params.getType()).stream().toArray(n -> new String[n]); + final String userCode = fessConfig.isUserInfo() ? ComponentUtil.getUserInfoHelper().getUserCode() : null; + final String userId = ComponentUtil.getRequestManager().findUserBean(FessUserBean.class).map(FessUserBean::getUserId).orElse(null); + final HttpServletRequest request = LaRequestUtil.getOptionalRequest().orElse(null); + final String clientIp = request != null ? ComponentUtil.getViewHelper().getClientIp(request) : null; + final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); + + final SearchLogContext context = new SearchLogContext(fessConfig, roles, userCode, userId, request, clientIp, virtualHostKey); + createSearchLog(params, requestedTime, queryId, query, pageStart, pageSize, queryResponseList, context); + } + + /** + * Builds a SearchLog from the given parameters and context, then adds it to the queue. + * + * @param params The search request parameters. + * @param requestedTime The time the search was requested. + * @param queryId The ID of the search query. + * @param query The search query string. + * @param pageStart The start position of the page. + * @param pageSize The size of the page. + * @param queryResponseList The list of query responses. + * @param context The search log context holding resolved dependencies. + */ + protected void createSearchLog(final SearchRequestParams params, final LocalDateTime requestedTime, final String queryId, + final String query, final int pageStart, final int pageSize, final QueryResponseList queryResponseList, + final SearchLogContext context) { final SearchLog searchLog = new SearchLog(); - if (fessConfig.isUserInfo()) { - final String userCode = userInfoHelper.getUserCode(); - if (userCode != null) { - searchLog.setUserSessionId(userCode); - searchLog.setUserInfo(getUserInfo(userCode)); - } + if (context.userCode != null) { + searchLog.setUserSessionId(context.userCode); + searchLog.setUserInfo(getUserInfo(context.userCode)); } - searchLog.setRoles(roleQueryHelper.build(params.getType()).stream().toArray(n -> new String[n])); + searchLog.setRoles(context.roles); searchLog.setQueryId(queryId); searchLog.setHitCount(queryResponseList.getAllRecordCount()); searchLog.setHitCountRelation(queryResponseList.getAllRecordCountRelation()); @@ -166,29 +211,19 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r searchLog.setSearchQuery(StringUtils.abbreviate(queryResponseList.getSearchQuery(), 1000)); searchLog.setQueryOffset(pageStart); searchLog.setQueryPageSize(pageSize); - ComponentUtil.getRequestManager().findUserBean(FessUserBean.class).ifPresent(user -> { - searchLog.setUser(user.getUserId()); - }); - LaRequestUtil.getOptionalRequest().ifPresent(req -> { - searchLog.setClientIp(StringUtils.abbreviate(ComponentUtil.getViewHelper().getClientIp(req), 100)); - searchLog.setReferer(StringUtils.abbreviate(req.getHeader("referer"), 1000)); - searchLog.setUserAgent(StringUtils.abbreviate(req.getHeader("user-agent"), 255)); - final Object accessType = req.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE); - if (Constants.SEARCH_LOG_ACCESS_TYPE_JSON.equals(accessType)) { - searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_JSON); - } else if (Constants.SEARCH_LOG_ACCESS_TYPE_GSA.equals(accessType)) { - searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_GSA); - } else if (Constants.SEARCH_LOG_ACCESS_TYPE_OTHER.equals(accessType)) { - searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER); - } else if (Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN.equals(accessType)) { - searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN); - } else if (accessType instanceof String && StringUtil.isNotBlank((String) accessType)) { - searchLog.setAccessType((String) accessType); - } else { - searchLog.setAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_WEB); - } - final Object languages = req.getAttribute(Constants.REQUEST_LANGUAGES); + if (context.userId != null) { + searchLog.setUser(context.userId); + } + + if (context.request != null) { + searchLog.setClientIp(StringUtils.abbreviate(context.clientIp, 100)); + searchLog.setReferer(StringUtils.abbreviate(context.request.getHeader("referer"), 1000)); + searchLog.setUserAgent(StringUtils.abbreviate(context.request.getHeader("user-agent"), 255)); + + searchLog.setAccessType(determineAccessType(context.request.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE))); + + final Object languages = context.request.getAttribute(Constants.REQUEST_LANGUAGES); if (languages != null) { searchLog.setLanguages(StringUtils.join((String[]) languages, ",")); } else { @@ -196,9 +231,9 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r } @SuppressWarnings("unchecked") - final Map> fieldLogMap = (Map>) req.getAttribute(Constants.FIELD_LOGS); + final Map> fieldLogMap = (Map>) context.request.getAttribute(Constants.FIELD_LOGS); if (fieldLogMap != null) { - final int queryMaxLength = fessConfig.getQueryMaxLengthAsInteger(); + final int queryMaxLength = context.fessConfig.getQueryMaxLengthAsInteger(); for (final Map.Entry> logEntry : fieldLogMap.entrySet()) { for (final String value : logEntry.getValue()) { searchLog.addSearchFieldLogValue(logEntry.getKey(), StringUtils.abbreviate(value, queryMaxLength)); @@ -206,26 +241,45 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r } } - for (final String s : fessConfig.getSearchlogRequestHeadersAsArray()) { + for (final String s : context.fessConfig.getSearchlogRequestHeadersAsArray()) { final String key = s.replace('-', '_').toLowerCase(Locale.ENGLISH); - Collections.list(req.getHeaders(s)).stream().forEach(v -> { + Collections.list(context.request.getHeaders(s)).stream().forEach(v -> { searchLog.addRequestHeaderValue(key, v); }); } - }); + } - final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); - if (StringUtil.isNotBlank(virtualHostKey)) { - searchLog.setVirtualHost(virtualHostKey); + if (StringUtil.isNotBlank(context.virtualHostKey)) { + searchLog.setVirtualHost(context.virtualHostKey); } else { searchLog.setVirtualHost(StringUtil.EMPTY); } addDocumentsInResponse(queryResponseList, searchLog); - searchLogQueue.add(searchLog); } + /** + * Returns the access type string from the given request attribute value, defaulting to web. + * + * @param accessType The access type attribute value from the request. + * @return The access type string. + */ + protected String determineAccessType(final Object accessType) { + if (Constants.SEARCH_LOG_ACCESS_TYPE_JSON.equals(accessType)) { + return Constants.SEARCH_LOG_ACCESS_TYPE_JSON; + } else if (Constants.SEARCH_LOG_ACCESS_TYPE_GSA.equals(accessType)) { + return Constants.SEARCH_LOG_ACCESS_TYPE_GSA; + } else if (Constants.SEARCH_LOG_ACCESS_TYPE_OTHER.equals(accessType)) { + return Constants.SEARCH_LOG_ACCESS_TYPE_OTHER; + } else if (Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN.equals(accessType)) { + return Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN; + } else if (accessType instanceof String && StringUtil.isNotBlank((String) accessType)) { + return (String) accessType; + } + return Constants.SEARCH_LOG_ACCESS_TYPE_WEB; + } + /** * Adds documents in the response to the search log. * diff --git a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java index a5c3a5fc10..835b4b0478 100644 --- a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java +++ b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java @@ -163,35 +163,29 @@ public void test_clickLogQueue_initialization() { // ===== addSearchLog Access Type tests ===== - private void setupAddSearchLogComponents() { - final MockRoleQueryHelper roleQueryHelper = new MockRoleQueryHelper(); - ComponentUtil.register(roleQueryHelper, "roleQueryHelper"); - - final MockUserInfoHelper userInfoHelper = new MockUserInfoHelper(); - ComponentUtil.register(userInfoHelper, "userInfoHelper"); - - final MockViewHelper viewHelper = new MockViewHelper(); - ComponentUtil.register(viewHelper, "viewHelper"); - - final MockVirtualHostHelper virtualHostHelper = new MockVirtualHostHelper(); - ComponentUtil.register(virtualHostHelper, "virtualHostHelper"); - - // Register mock RequestManager to avoid FessLoginAssist → UserBhv DI chain failure in parallel tests - ComponentUtil.register(new MockRequestManager(), RequestManager.class.getCanonicalName()); + private SearchLogHelper.SearchLogContext createTestContext(final jakarta.servlet.http.HttpServletRequest request) { + return new SearchLogHelper.SearchLogContext((FessConfig) ComponentUtil.getFessConfig(), new String[0], // roles + null, // userCode + null, // userId + request, // request + "127.0.0.1", // clientIp + "" // virtualHostKey + ); } - private SearchLog callAddSearchLogAndGetResult(final String accessTypeAttribute) { - setupAddSearchLogComponents(); - + private SearchLog callCreateSearchLogAndGetResult(final String accessTypeAttribute) { if (accessTypeAttribute != null) { setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, accessTypeAttribute); } + final jakarta.servlet.http.HttpServletRequest request = org.lastaflute.web.util.LaRequestUtil.getOptionalRequest().orElse(null); + final SearchLogHelper.SearchLogContext context = createTestContext(request); + final MockSearchRequestParams params = new MockSearchRequestParams(); final LocalDateTime now = LocalDateTime.now(); final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); - searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + searchLogHelper.createSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList, context); assertFalse(searchLogHelper.searchLogQueue.isEmpty()); return searchLogHelper.searchLogQueue.poll(); @@ -199,94 +193,141 @@ private SearchLog callAddSearchLogAndGetResult(final String accessTypeAttribute) @Test public void test_addSearchLog_accessType_json() { - final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_JSON); + final SearchLog searchLog = callCreateSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_JSON); assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_JSON, searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_gsa() { - final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_GSA); + final SearchLog searchLog = callCreateSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_GSA); assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_GSA, searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_other() { - final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER); + final SearchLog searchLog = callCreateSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER); assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER, searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_admin() { - final SearchLog searchLog = callAddSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN); + final SearchLog searchLog = callCreateSearchLogAndGetResult(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN); assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN, searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_defaultWeb() { - final SearchLog searchLog = callAddSearchLogAndGetResult(null); + final SearchLog searchLog = callCreateSearchLogAndGetResult(null); assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_ollama() { - final SearchLog searchLog = callAddSearchLogAndGetResult("ollama"); + final SearchLog searchLog = callCreateSearchLogAndGetResult("ollama"); assertEquals("ollama", searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_openai() { - final SearchLog searchLog = callAddSearchLogAndGetResult("openai"); + final SearchLog searchLog = callCreateSearchLogAndGetResult("openai"); assertEquals("openai", searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_gemini() { - final SearchLog searchLog = callAddSearchLogAndGetResult("gemini"); + final SearchLog searchLog = callCreateSearchLogAndGetResult("gemini"); assertEquals("gemini", searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_customLlmName() { - final SearchLog searchLog = callAddSearchLogAndGetResult("my-custom-llm"); + final SearchLog searchLog = callCreateSearchLogAndGetResult("my-custom-llm"); assertEquals("my-custom-llm", searchLog.getAccessType()); } @Test public void test_addSearchLog_accessType_blankStringDefaultsToWeb() { - setupAddSearchLogComponents(); - setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, ""); + final SearchLog searchLog = callCreateSearchLogAndGetResult(""); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_whitespaceOnlyDefaultsToWeb() { + final SearchLog searchLog = callCreateSearchLogAndGetResult(" "); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + } + + @Test + public void test_addSearchLog_accessType_nonStringObjectDefaultsToWeb() { + setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Integer.valueOf(123)); + + final jakarta.servlet.http.HttpServletRequest request = org.lastaflute.web.util.LaRequestUtil.getOptionalRequest().orElse(null); + final SearchLogHelper.SearchLogContext context = createTestContext(request); final MockSearchRequestParams params = new MockSearchRequestParams(); final LocalDateTime now = LocalDateTime.now(); final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); - searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + searchLogHelper.createSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList, context); assertFalse(searchLogHelper.searchLogQueue.isEmpty()); final SearchLog searchLog = searchLogHelper.searchLogQueue.poll(); assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); } + // ===== determineAccessType direct tests ===== + @Test - public void test_addSearchLog_accessType_whitespaceOnlyDefaultsToWeb() { - setupAddSearchLogComponents(); - setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, " "); + public void test_determineAccessType_json() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_JSON, searchLogHelper.determineAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_JSON)); + } - final MockSearchRequestParams params = new MockSearchRequestParams(); - final LocalDateTime now = LocalDateTime.now(); - final QueryResponseList queryResponseList = new QueryResponseList(Collections.emptyList(), 0L, "eq", 0L, false, null, 0, 10, 0); + @Test + public void test_determineAccessType_gsa() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_GSA, searchLogHelper.determineAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_GSA)); + } - searchLogHelper.addSearchLog(params, now, "test-query-id", "test query", 0, 10, queryResponseList); + @Test + public void test_determineAccessType_other() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER, searchLogHelper.determineAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_OTHER)); + } - assertFalse(searchLogHelper.searchLogQueue.isEmpty()); - final SearchLog searchLog = searchLogHelper.searchLogQueue.poll(); - assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + @Test + public void test_determineAccessType_admin() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN, searchLogHelper.determineAccessType(Constants.SEARCH_LOG_ACCESS_TYPE_ADMIN)); } @Test - public void test_addSearchLog_accessType_nonStringObjectDefaultsToWeb() { - setupAddSearchLogComponents(); - setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Integer.valueOf(123)); + public void test_determineAccessType_customString() { + assertEquals("my-custom-llm", searchLogHelper.determineAccessType("my-custom-llm")); + } + + @Test + public void test_determineAccessType_blankDefaultsToWeb() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLogHelper.determineAccessType("")); + } + + @Test + public void test_determineAccessType_nullDefaultsToWeb() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLogHelper.determineAccessType(null)); + } + + @Test + public void test_determineAccessType_nonStringDefaultsToWeb() { + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLogHelper.determineAccessType(Integer.valueOf(123))); + } + + // ===== addSearchLog integration test (exercises wrapper wiring) ===== + + @Test + public void test_addSearchLog_wiring() { + ComponentUtil.register(new MockRoleQueryHelper(), "roleQueryHelper"); + ComponentUtil.register(new MockUserInfoHelper(), "userInfoHelper"); + ComponentUtil.register(new MockViewHelper(), "viewHelper"); + ComponentUtil.register(new MockVirtualHostHelper(), "virtualHostHelper"); + ComponentUtil.register(new MockRequestManager(), RequestManager.class.getCanonicalName()); + + setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Constants.SEARCH_LOG_ACCESS_TYPE_JSON); final MockSearchRequestParams params = new MockSearchRequestParams(); final LocalDateTime now = LocalDateTime.now(); @@ -296,7 +337,48 @@ public void test_addSearchLog_accessType_nonStringObjectDefaultsToWeb() { assertFalse(searchLogHelper.searchLogQueue.isEmpty()); final SearchLog searchLog = searchLogHelper.searchLogQueue.poll(); - assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_WEB, searchLog.getAccessType()); + assertEquals(Constants.SEARCH_LOG_ACCESS_TYPE_JSON, searchLog.getAccessType()); + assertEquals("test query", searchLog.getSearchWord()); + assertEquals("test-query-id", searchLog.getQueryId()); + assertEquals("127.0.0.1", searchLog.getClientIp()); + assertNull(searchLog.getVirtualHost()); + } + + // Mock classes for addSearchLog wiring test + + private static class MockRoleQueryHelper extends RoleQueryHelper { + @Override + public Set build(final SearchRequestType searchRequestType) { + return Collections.emptySet(); + } + } + + private static class MockUserInfoHelper extends UserInfoHelper { + @Override + public String getUserCode() { + return null; + } + } + + private static class MockViewHelper extends ViewHelper { + @Override + public String getClientIp(final jakarta.servlet.http.HttpServletRequest req) { + return "127.0.0.1"; + } + } + + private static class MockVirtualHostHelper extends VirtualHostHelper { + @Override + public String getVirtualHostKey() { + return ""; + } + } + + private static class MockRequestManager extends SimpleRequestManager { + @Override + public , ID> OptionalThing findUserBean(final Class userBeanType) { + return OptionalThing.empty(); + } } // Mock classes for addSearchLog tests @@ -383,41 +465,6 @@ public java.util.Locale getLocale() { } } - private static class MockRoleQueryHelper extends RoleQueryHelper { - @Override - public Set build(final SearchRequestType searchRequestType) { - return Collections.emptySet(); - } - } - - private static class MockUserInfoHelper extends UserInfoHelper { - @Override - public String getUserCode() { - return null; - } - } - - private static class MockViewHelper extends ViewHelper { - @Override - public String getClientIp(final jakarta.servlet.http.HttpServletRequest req) { - return "127.0.0.1"; - } - } - - private static class MockVirtualHostHelper extends VirtualHostHelper { - @Override - public String getVirtualHostKey() { - return ""; - } - } - - private static class MockRequestManager extends SimpleRequestManager { - @Override - public , ID> OptionalThing findUserBean(final Class userBeanType) { - return OptionalThing.empty(); - } - } - // Mock classes private static class MockFessConfig extends FessConfig.SimpleImpl { private static final long serialVersionUID = 1L; From f11df76ba6f75d7c3236a2a6368c0cea00f8b39c Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Sat, 14 Mar 2026 10:30:37 +0900 Subject: [PATCH 4/4] fix(test): stabilize SearchLogHelper wiring test --- .../codelibs/fess/helper/SearchLogHelper.java | 15 +++++- .../fess/helper/SearchLogHelperTest.java | 54 ++++--------------- 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java b/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java index a2bb629b32..ed51710c0d 100644 --- a/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java +++ b/src/main/java/org/codelibs/fess/helper/SearchLogHelper.java @@ -167,6 +167,18 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r return; } + final SearchLogContext context = createSearchLogContext(params, fessConfig); + createSearchLog(params, requestedTime, queryId, query, pageStart, pageSize, queryResponseList, context); + } + + /** + * Resolves the runtime dependencies needed to build a SearchLog. + * + * @param params The search request parameters. + * @param fessConfig The Fess configuration. + * @return The resolved search log context. + */ + protected SearchLogContext createSearchLogContext(final SearchRequestParams params, final FessConfig fessConfig) { final String[] roles = ComponentUtil.getRoleQueryHelper().build(params.getType()).stream().toArray(n -> new String[n]); final String userCode = fessConfig.isUserInfo() ? ComponentUtil.getUserInfoHelper().getUserCode() : null; final String userId = ComponentUtil.getRequestManager().findUserBean(FessUserBean.class).map(FessUserBean::getUserId).orElse(null); @@ -174,8 +186,7 @@ public void addSearchLog(final SearchRequestParams params, final LocalDateTime r final String clientIp = request != null ? ComponentUtil.getViewHelper().getClientIp(request) : null; final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); - final SearchLogContext context = new SearchLogContext(fessConfig, roles, userCode, userId, request, clientIp, virtualHostKey); - createSearchLog(params, requestedTime, queryId, query, pageStart, pageSize, queryResponseList, context); + return new SearchLogContext(fessConfig, roles, userCode, userId, request, clientIp, virtualHostKey); } /** diff --git a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java index 835b4b0478..6dd9e92ee0 100644 --- a/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java +++ b/src/test/java/org/codelibs/fess/helper/SearchLogHelperTest.java @@ -32,21 +32,17 @@ import org.codelibs.fess.unit.UnitFessTestCase; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.util.QueryResponseList; -import org.dbflute.optional.OptionalThing; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -import org.lastaflute.web.login.UserBean; -import org.lastaflute.web.servlet.request.RequestManager; -import org.lastaflute.web.servlet.request.SimpleRequestManager; public class SearchLogHelperTest extends UnitFessTestCase { - private SearchLogHelper searchLogHelper; + private TestableSearchLogHelper searchLogHelper; @Override protected void setUp(TestInfo testInfo) throws Exception { super.setUp(testInfo); - searchLogHelper = new SearchLogHelper(); + searchLogHelper = new TestableSearchLogHelper(); setupMockComponents(); searchLogHelper.init(); } @@ -321,13 +317,9 @@ public void test_determineAccessType_nonStringDefaultsToWeb() { @Test public void test_addSearchLog_wiring() { - ComponentUtil.register(new MockRoleQueryHelper(), "roleQueryHelper"); - ComponentUtil.register(new MockUserInfoHelper(), "userInfoHelper"); - ComponentUtil.register(new MockViewHelper(), "viewHelper"); - ComponentUtil.register(new MockVirtualHostHelper(), "virtualHostHelper"); - ComponentUtil.register(new MockRequestManager(), RequestManager.class.getCanonicalName()); - setMockRequestAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Constants.SEARCH_LOG_ACCESS_TYPE_JSON); + final jakarta.servlet.http.HttpServletRequest request = org.lastaflute.web.util.LaRequestUtil.getOptionalRequest().orElse(null); + searchLogHelper.testContext = createTestContext(request); final MockSearchRequestParams params = new MockSearchRequestParams(); final LocalDateTime now = LocalDateTime.now(); @@ -344,40 +336,12 @@ public void test_addSearchLog_wiring() { assertNull(searchLog.getVirtualHost()); } - // Mock classes for addSearchLog wiring test - - private static class MockRoleQueryHelper extends RoleQueryHelper { - @Override - public Set build(final SearchRequestType searchRequestType) { - return Collections.emptySet(); - } - } - - private static class MockUserInfoHelper extends UserInfoHelper { - @Override - public String getUserCode() { - return null; - } - } - - private static class MockViewHelper extends ViewHelper { - @Override - public String getClientIp(final jakarta.servlet.http.HttpServletRequest req) { - return "127.0.0.1"; - } - } - - private static class MockVirtualHostHelper extends VirtualHostHelper { - @Override - public String getVirtualHostKey() { - return ""; - } - } + private static class TestableSearchLogHelper extends SearchLogHelper { + private SearchLogContext testContext; - private static class MockRequestManager extends SimpleRequestManager { @Override - public , ID> OptionalThing findUserBean(final Class userBeanType) { - return OptionalThing.empty(); + protected SearchLogContext createSearchLogContext(final SearchRequestParams params, final FessConfig fessConfig) { + return testContext != null ? testContext : super.createSearchLogContext(params, fessConfig); } } @@ -531,4 +495,4 @@ public java.time.LocalDateTime getCurrentTimeAsLocalDateTime() { return java.time.LocalDateTime.now(); } } -} \ No newline at end of file +}