From 94402e0efa7ee3747416f1c206bd084cdd59ea76 Mon Sep 17 00:00:00 2001 From: Roshan Piyush Date: Fri, 3 Apr 2026 01:40:25 +0530 Subject: [PATCH 1/3] Add password handling AI-Session-Id: e0a63942-b7e7-4fb6-a156-db594d222e39 AI-Tool: claude-code AI-Model: unknown --- .../chatbot/src/chatbot/langgraph_agent.py | 10 +++- services/chatbot/src/mcpserver/server.py | 50 ++++++++++------- .../java/com/crapi/CRAPIBootApplication.java | 2 + .../java/com/crapi/constant/UserMessage.java | 2 +- .../src/main/java/com/crapi/entity/User.java | 2 + .../crapi/service/Impl/OtpServiceImpl.java | 6 +- .../crapi/service/Impl/UserServiceImpl.java | 3 + .../java/com/crapi/service/UserResetJob.java | 55 +++++++++++++++++++ .../service/Impl/OtpServiceImplTest.java | 4 +- 9 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 services/identity/src/main/java/com/crapi/service/UserResetJob.java diff --git a/services/chatbot/src/chatbot/langgraph_agent.py b/services/chatbot/src/chatbot/langgraph_agent.py index 3f7a96a9..4c2babb7 100644 --- a/services/chatbot/src/chatbot/langgraph_agent.py +++ b/services/chatbot/src/chatbot/langgraph_agent.py @@ -226,9 +226,13 @@ async def build_langgraph_agent(api_key, model_name, user_jwt): toolkit = SQLDatabaseToolkit(db=postgresdb, llm=llm) logger.debug("SQL Database toolkit created") - mcp_client = get_mcp_client(user_jwt) - mcp_tools = await mcp_client.get_tools() - logger.debug("MCP tools loaded: %d tools", len(mcp_tools)) + mcp_tools = [] + try: + mcp_client = get_mcp_client(user_jwt) + mcp_tools = await mcp_client.get_tools() + logger.debug("MCP tools loaded: %d tools", len(mcp_tools)) + except Exception as e: + logger.error("Failed to load MCP tools, continuing without them: %s", e) db_tools = toolkit.get_tools() logger.debug("Database tools loaded: %d tools", len(db_tools)) diff --git a/services/chatbot/src/mcpserver/server.py b/services/chatbot/src/mcpserver/server.py index 3c0fd114..ed78565d 100644 --- a/services/chatbot/src/mcpserver/server.py +++ b/services/chatbot/src/mcpserver/server.py @@ -30,16 +30,18 @@ def get_api_key(): global API_KEY - # Try 5 times to get client auth - MAX_ATTEMPTS = 5 + if API_KEY is not None: + return API_KEY + # Try multiple times to get client auth (identity service may not be ready yet) + MAX_ATTEMPTS = 10 for i in range(MAX_ATTEMPTS): logger.info(f"Attempt {i+1} to get API key...") - if API_KEY is None: - login_body = {"email": Config.API_USER, "password": Config.API_PASSWORD} - auth_url = f"{BASE_IDENTITY_URL}/identity/management/user/apikey" - headers = { - "Content-Type": "application/json", - } + login_body = {"email": Config.API_USER, "password": Config.API_PASSWORD} + auth_url = f"{BASE_IDENTITY_URL}/identity/management/user/apikey" + headers = { + "Content-Type": "application/json", + } + try: with httpx.Client( base_url=BASE_URL, headers=headers, @@ -47,23 +49,33 @@ def get_api_key(): ) as client: response = client.post(auth_url, json=login_body) if response.status_code != 200: + logger.error( + f"Failed to get API key in attempt {i+1}: {response.status_code} {response.text}. Sleeping for {i+1} seconds..." + ) + # Reset test users if credentials are rejected + try: + reset_url = f"{BASE_IDENTITY_URL}/identity/api/auth/reset-test-users" + reset_resp = client.post(reset_url) + logger.info(f"Reset test users response: {reset_resp.status_code}") + except Exception as reset_err: + logger.error(f"Failed to reset test users: {reset_err}") if i == MAX_ATTEMPTS - 1: - logger.error( - f"Failed to get API key after {i+1} attempts: {response.status_code} {response.text}" - ) raise Exception( - f"Failed to get API key after {i+1} attempts: {response.status_code} {response.text}" + f"Failed to get API key after {MAX_ATTEMPTS} attempts: {response.status_code} {response.text}" ) - logger.error( - f"Failed to get API key in attempt {i+1}: {response.status_code} {response.text}. Sleeping for {i} seconds..." - ) - time.sleep(i) + time.sleep(i + 1) + continue response_json = response.json() - logger.info(f"Response: {response_json}") API_KEY = response_json.get("apiKey") if API_KEY: - logger.debug("MCP Server API Key obtained successfully.") - return API_KEY + logger.info("MCP Server API Key obtained successfully.") + return API_KEY + logger.error(f"API key not found in response: {response_json}") + except httpx.HTTPError as e: + logger.error(f"HTTP error in attempt {i+1}: {e}") + if i == MAX_ATTEMPTS - 1: + raise + time.sleep(i + 1) return API_KEY diff --git a/services/identity/src/main/java/com/crapi/CRAPIBootApplication.java b/services/identity/src/main/java/com/crapi/CRAPIBootApplication.java index 8208f742..7ee99160 100644 --- a/services/identity/src/main/java/com/crapi/CRAPIBootApplication.java +++ b/services/identity/src/main/java/com/crapi/CRAPIBootApplication.java @@ -16,6 +16,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; /* * Need to give path for application.properties file @@ -25,6 +26,7 @@ @PropertySource(value = "file:/home/hasher/Music/resources/application.properties", ignoreResourceNotFound = true) })*/ @SpringBootApplication(scanBasePackages = {"com.crapi"}) +@EnableScheduling public class CRAPIBootApplication { public static void main(String[] args) { diff --git a/services/identity/src/main/java/com/crapi/constant/UserMessage.java b/services/identity/src/main/java/com/crapi/constant/UserMessage.java index fa1c5c83..4b9f0f65 100644 --- a/services/identity/src/main/java/com/crapi/constant/UserMessage.java +++ b/services/identity/src/main/java/com/crapi/constant/UserMessage.java @@ -49,7 +49,7 @@ public class UserMessage { public static final String EMAIL_NOT_REGISTERED = "Given Email is not registered! "; public static final String INVALID_OTP = "Invalid OTP! Please try again.."; public static final String ERROR = "ERROR.."; - public static final String OTP_VARIFIED_SUCCESS = "OTP verified"; + public static final String OTP_VERIFIED_SUCCESS = "OTP verified"; public static final String OTP_SEND_SUCCESS_ON_EMAIL = "OTP Sent on the provided email, "; public static final String EXCEED_NUMBER_OF_ATTEMPS = "You've exceeded the number of attempts."; public static final String PASSWORD_GOT_RESET = "Password reset successful."; diff --git a/services/identity/src/main/java/com/crapi/entity/User.java b/services/identity/src/main/java/com/crapi/entity/User.java index 58b0a922..2dcf8e61 100644 --- a/services/identity/src/main/java/com/crapi/entity/User.java +++ b/services/identity/src/main/java/com/crapi/entity/User.java @@ -44,6 +44,8 @@ public class User { private LocalDate createdOn = LocalDate.now(); + private LocalDate passwordUpdatedAt = LocalDate.of(2000, 1, 1); + private String code; // @OneToOne diff --git a/services/identity/src/main/java/com/crapi/service/Impl/OtpServiceImpl.java b/services/identity/src/main/java/com/crapi/service/Impl/OtpServiceImpl.java index d204ee34..3c941b5c 100644 --- a/services/identity/src/main/java/com/crapi/service/Impl/OtpServiceImpl.java +++ b/services/identity/src/main/java/com/crapi/service/Impl/OtpServiceImpl.java @@ -79,9 +79,10 @@ public CRAPIResponse validateOtp(OtpForm otpForm) { otp = otpRepository.findByUser(user); if (validateOTPAndEmail(otp, otpForm)) { user.setPassword(encoder.encode(otpForm.getPassword())); + user.setPasswordUpdatedAt(java.time.LocalDate.now()); userRepository.save(user); otp.setStatus(EStatus.INACTIVE.toString()); - validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VARIFIED_SUCCESS, 200); + validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VERIFIED_SUCCESS, 200); } else { otp.setCount(otp.getCount() + 1); validateOTPResponse = new CRAPIResponse(UserMessage.INVALID_OTP, 500); @@ -103,9 +104,10 @@ public CRAPIResponse secureValidateOtp(OtpForm otpForm) { otp = otpRepository.findByUser(user); if (validateOTPAndEmail(otp, otpForm)) { user.setPassword(encoder.encode(otpForm.getPassword())); + user.setPasswordUpdatedAt(java.time.LocalDate.now()); userRepository.save(user); otp.setStatus(EStatus.INACTIVE.toString()); - validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VARIFIED_SUCCESS, 200); + validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VERIFIED_SUCCESS, 200); } else if (otp.getCount() == 9) { otp.setCount(otp.getCount() + 1); invalidateOtp(otp); diff --git a/services/identity/src/main/java/com/crapi/service/Impl/UserServiceImpl.java b/services/identity/src/main/java/com/crapi/service/Impl/UserServiceImpl.java index 40ef7016..858f0040 100644 --- a/services/identity/src/main/java/com/crapi/service/Impl/UserServiceImpl.java +++ b/services/identity/src/main/java/com/crapi/service/Impl/UserServiceImpl.java @@ -33,6 +33,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.transaction.Transactional; import java.text.ParseException; +import java.time.LocalDate; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.impl.Log4jContextFactory; @@ -172,6 +173,7 @@ public User updateUserPassword(String password, String email) { User user = userRepository.findByEmail(email); if (user != null) { user.setPassword(encoder.encode(password)); + user.setPasswordUpdatedAt(LocalDate.now()); userRepository.saveAndFlush(user); } return user; @@ -188,6 +190,7 @@ public CRAPIResponse resetPassword(LoginForm loginForm, HttpServletRequest reque User user = getUserFromToken(request); if (user != null) { user.setPassword(encoder.encode(loginForm.getPassword())); + user.setPasswordUpdatedAt(LocalDate.now()); userRepository.saveAndFlush(user); return new CRAPIResponse(UserMessage.PASSWORD_GOT_RESET, 200); } diff --git a/services/identity/src/main/java/com/crapi/service/UserResetJob.java b/services/identity/src/main/java/com/crapi/service/UserResetJob.java new file mode 100644 index 00000000..ee58f643 --- /dev/null +++ b/services/identity/src/main/java/com/crapi/service/UserResetJob.java @@ -0,0 +1,55 @@ +package com.crapi.service; + +import com.crapi.constant.TestUsers; +import com.crapi.entity.User; +import com.crapi.model.SeedUser; +import com.crapi.repository.UserRepository; +import java.time.LocalDate; +import java.util.ArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class UserResetJob { + + private static final Logger logger = LoggerFactory.getLogger(UserResetJob.class); + + private static final LocalDate DEFAULT_PASSWORD_DATE = LocalDate.of(2000, 1, 1); + + @Autowired private UserRepository userRepository; + + @Autowired private PasswordEncoder encoder; + + @Scheduled(fixedRate = 3600000) // every hour + @Transactional + public void resetTestUserCredsIfChanged() { + ArrayList testUsers = new TestUsers().getUsers(); + int resetCount = 0; + + for (SeedUser seedUser : testUsers) { + User user = userRepository.findByEmail(seedUser.getEmail()); + if (user == null) { + continue; + } + LocalDate updatedAt = user.getPasswordUpdatedAt(); + if (updatedAt != null && !updatedAt.equals(DEFAULT_PASSWORD_DATE)) { + user.setPassword(encoder.encode(seedUser.getPassword())); + user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE); + userRepository.saveAndFlush(user); + resetCount++; + logger.info("Reset password for test user: {}", seedUser.getEmail()); + } + } + + if (resetCount > 0) { + logger.info("Reset credentials for {} test user(s)", resetCount); + } else { + logger.debug("All test user credentials are unchanged, no reset needed"); + } + } +} diff --git a/services/identity/src/test/java/com/crapi/service/Impl/OtpServiceImplTest.java b/services/identity/src/test/java/com/crapi/service/Impl/OtpServiceImplTest.java index a6b2b4cf..aefea6f6 100644 --- a/services/identity/src/test/java/com/crapi/service/Impl/OtpServiceImplTest.java +++ b/services/identity/src/test/java/com/crapi/service/Impl/OtpServiceImplTest.java @@ -97,7 +97,7 @@ public void validateOtpSuccess() { CRAPIResponse crapiAPIResponse = otpService.validateOtp(otpForm); Mockito.verify(userRepository, Mockito.times(1)).save(Mockito.any()); Mockito.verify(otpRepository, Mockito.times(1)).save(Mockito.any()); - Assertions.assertEquals(UserMessage.OTP_VARIFIED_SUCCESS, crapiAPIResponse.getMessage()); + Assertions.assertEquals(UserMessage.OTP_VERIFIED_SUCCESS, crapiAPIResponse.getMessage()); Assertions.assertEquals(HttpStatus.OK.value(), crapiAPIResponse.getStatus()); } @@ -128,7 +128,7 @@ public void secureValidateOtpSuccess() { CRAPIResponse crapiAPIResponse = otpService.secureValidateOtp(otpForm); Mockito.verify(userRepository, Mockito.times(1)).save(Mockito.any()); Mockito.verify(otpRepository, Mockito.times(1)).save(Mockito.any()); - Assertions.assertEquals(UserMessage.OTP_VARIFIED_SUCCESS, crapiAPIResponse.getMessage()); + Assertions.assertEquals(UserMessage.OTP_VERIFIED_SUCCESS, crapiAPIResponse.getMessage()); Assertions.assertEquals(HttpStatus.OK.value(), crapiAPIResponse.getStatus()); } From 901af4ee3926a41f2511a8f11b773528d8bf689c Mon Sep 17 00:00:00 2001 From: Roshan Piyush Date: Fri, 3 Apr 2026 01:46:26 +0530 Subject: [PATCH 2/3] Add tests AI-Session-Id: e0a63942-b7e7-4fb6-a156-db594d222e39 AI-Tool: claude-code AI-Model: unknown --- .../com/crapi/service/UserResetJobTest.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 services/identity/src/test/java/com/crapi/service/UserResetJobTest.java diff --git a/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java b/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java new file mode 100644 index 00000000..9d31bfac --- /dev/null +++ b/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java @@ -0,0 +1,112 @@ +package com.crapi.service; + +import com.crapi.constant.TestUsers; +import com.crapi.entity.User; +import com.crapi.enums.ERole; +import com.crapi.model.SeedUser; +import com.crapi.repository.UserRepository; +import java.time.LocalDate; +import java.util.ArrayList; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.security.crypto.password.PasswordEncoder; + +@RunWith(MockitoJUnitRunner.class) +public class UserResetJobTest { + + private static final LocalDate DEFAULT_PASSWORD_DATE = LocalDate.of(2000, 1, 1); + + @InjectMocks private UserResetJob userResetJob; + + @Mock private UserRepository userRepository; + + @Mock private PasswordEncoder encoder; + + @Test + public void resetSkipsWhenPasswordNotChanged() { + ArrayList testUsers = new TestUsers().getUsers(); + for (SeedUser seedUser : testUsers) { + User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE); + Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); + } + + userResetJob.resetTestUserCredsIfChanged(); + + Mockito.verify(userRepository, Mockito.never()).saveAndFlush(Mockito.any()); + } + + @Test + public void resetResetsWhenPasswordChanged() { + ArrayList testUsers = new TestUsers().getUsers(); + SeedUser firstUser = testUsers.get(0); + + // First user has changed password (non-default date) + User changedUser = new User(firstUser.getEmail(), firstUser.getNumber(), "encoded", firstUser.getRole()); + changedUser.setPasswordUpdatedAt(LocalDate.now()); + Mockito.when(userRepository.findByEmail(firstUser.getEmail())).thenReturn(changedUser); + Mockito.when(encoder.encode(firstUser.getPassword())).thenReturn("resetEncoded"); + Mockito.when(userRepository.saveAndFlush(Mockito.any())).thenReturn(changedUser); + + // Remaining users have default date + for (int i = 1; i < testUsers.size(); i++) { + SeedUser seedUser = testUsers.get(i); + User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE); + Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); + } + + userResetJob.resetTestUserCredsIfChanged(); + + Mockito.verify(userRepository, Mockito.times(1)).saveAndFlush(Mockito.any()); + Assertions.assertEquals("resetEncoded", changedUser.getPassword()); + Assertions.assertEquals(DEFAULT_PASSWORD_DATE, changedUser.getPasswordUpdatedAt()); + } + + @Test + public void resetResetsAllWhenAllPasswordsChanged() { + ArrayList testUsers = new TestUsers().getUsers(); + for (SeedUser seedUser : testUsers) { + User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + user.setPasswordUpdatedAt(LocalDate.now()); + Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); + Mockito.when(userRepository.saveAndFlush(Mockito.any())).thenReturn(user); + } + Mockito.when(encoder.encode(Mockito.anyString())).thenReturn("resetEncoded"); + + userResetJob.resetTestUserCredsIfChanged(); + + Mockito.verify(userRepository, Mockito.times(testUsers.size())).saveAndFlush(Mockito.any()); + } + + @Test + public void resetSkipsNullUser() { + ArrayList testUsers = new TestUsers().getUsers(); + for (SeedUser seedUser : testUsers) { + Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(null); + } + + userResetJob.resetTestUserCredsIfChanged(); + + Mockito.verify(userRepository, Mockito.never()).saveAndFlush(Mockito.any()); + } + + @Test + public void resetSkipsWhenPasswordUpdatedAtNull() { + ArrayList testUsers = new TestUsers().getUsers(); + for (SeedUser seedUser : testUsers) { + User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + user.setPasswordUpdatedAt(null); + Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); + } + + userResetJob.resetTestUserCredsIfChanged(); + + Mockito.verify(userRepository, Mockito.never()).saveAndFlush(Mockito.any()); + } +} From 87bf34f17238a1176117df540e8a05d97bad49ed Mon Sep 17 00:00:00 2001 From: Roshan Piyush Date: Fri, 3 Apr 2026 01:53:09 +0530 Subject: [PATCH 3/3] Spotless AI-Session-Id: e0a63942-b7e7-4fb6-a156-db594d222e39 AI-Tool: claude-code AI-Model: unknown --- .../java/com/crapi/service/UserResetJobTest.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java b/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java index 9d31bfac..99508607 100644 --- a/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java +++ b/services/identity/src/test/java/com/crapi/service/UserResetJobTest.java @@ -2,7 +2,6 @@ import com.crapi.constant.TestUsers; import com.crapi.entity.User; -import com.crapi.enums.ERole; import com.crapi.model.SeedUser; import com.crapi.repository.UserRepository; import java.time.LocalDate; @@ -31,7 +30,8 @@ public class UserResetJobTest { public void resetSkipsWhenPasswordNotChanged() { ArrayList testUsers = new TestUsers().getUsers(); for (SeedUser seedUser : testUsers) { - User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + User user = + new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE); Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); } @@ -47,7 +47,8 @@ public void resetResetsWhenPasswordChanged() { SeedUser firstUser = testUsers.get(0); // First user has changed password (non-default date) - User changedUser = new User(firstUser.getEmail(), firstUser.getNumber(), "encoded", firstUser.getRole()); + User changedUser = + new User(firstUser.getEmail(), firstUser.getNumber(), "encoded", firstUser.getRole()); changedUser.setPasswordUpdatedAt(LocalDate.now()); Mockito.when(userRepository.findByEmail(firstUser.getEmail())).thenReturn(changedUser); Mockito.when(encoder.encode(firstUser.getPassword())).thenReturn("resetEncoded"); @@ -56,7 +57,8 @@ public void resetResetsWhenPasswordChanged() { // Remaining users have default date for (int i = 1; i < testUsers.size(); i++) { SeedUser seedUser = testUsers.get(i); - User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + User user = + new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE); Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); } @@ -72,7 +74,8 @@ public void resetResetsWhenPasswordChanged() { public void resetResetsAllWhenAllPasswordsChanged() { ArrayList testUsers = new TestUsers().getUsers(); for (SeedUser seedUser : testUsers) { - User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + User user = + new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); user.setPasswordUpdatedAt(LocalDate.now()); Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); Mockito.when(userRepository.saveAndFlush(Mockito.any())).thenReturn(user); @@ -100,7 +103,8 @@ public void resetSkipsNullUser() { public void resetSkipsWhenPasswordUpdatedAtNull() { ArrayList testUsers = new TestUsers().getUsers(); for (SeedUser seedUser : testUsers) { - User user = new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); + User user = + new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole()); user.setPasswordUpdatedAt(null); Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user); }