diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/authentication/AuthenticationEndpointsIntegrationTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/authentication/AuthenticationEndpointsIntegrationTest.java new file mode 100644 index 000000000..3c103dd74 --- /dev/null +++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/authentication/AuthenticationEndpointsIntegrationTest.java @@ -0,0 +1,647 @@ +package org.lowcoder.api.authentication; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.lowcoder.api.authentication.dto.APIKeyRequest; +import org.lowcoder.api.authentication.dto.AuthConfigRequest; +import org.lowcoder.api.common.InitData; +import org.lowcoder.api.common.mockuser.WithMockUser; +import org.lowcoder.api.framework.view.ResponseView; +import org.lowcoder.api.usermanagement.view.APIKeyVO; +import org.lowcoder.domain.authentication.AuthenticationService; +import org.lowcoder.domain.authentication.FindAuthConfig; +import org.lowcoder.domain.user.model.APIKey; +import org.lowcoder.domain.user.repository.UserRepository; +import org.lowcoder.sdk.auth.AbstractAuthConfig; +import org.lowcoder.sdk.auth.EmailAuthConfig; +import org.lowcoder.sdk.constants.AuthSourceConstants; +import org.lowcoder.sdk.exception.BizException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.http.ResponseCookie; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.lowcoder.sdk.exception.BizError.INVALID_PASSWORD; +import org.lowcoder.domain.user.model.Connection; + +@SpringBootTest +@ActiveProfiles("test") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AuthenticationEndpointsIntegrationTest { + + @Autowired + private AuthenticationController authenticationController; + + @Autowired + private UserRepository userRepository; + + @Autowired + private AuthenticationService authenticationService; + + @Autowired + private InitData initData; + + private ServerWebExchange mockExchange; + + @BeforeEach + void setUp() { + try { + initData.init(); + } catch (RuntimeException e) { + // Handle duplicate key errors gracefully - this happens when test data already exists + if (e.getCause() instanceof DuplicateKeyException) { + // Data already exists, continue with test + System.out.println("Test data already exists, continuing with test..."); + } else { + // Re-throw other exceptions + throw e; + } + } + MockServerHttpRequest request = MockServerHttpRequest.post("").build(); + mockExchange = MockServerWebExchange.builder(request).build(); + } + + @Test + @WithMockUser(id = "user01") + void testFormLogin_Integration_Success() { + // Arrange + String email = "integration_test@example.com"; + String password = "testPassword123"; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + email, password, true, source, authId + ); + + // Act + Mono> result = authenticationController.formLogin( + formLoginRequest, null, null, mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + + // Verify user was created in database + StepVerifier.create(userRepository.findByConnections_SourceAndConnections_RawId(source, email)) + .assertNext(user -> { + assertNotNull(user); + // Since connections is a Set, we need to find the connection by source + // Fixed: Changed from get(0) to stream().filter().findFirst() approach + Connection connection = user.getConnections().stream() + .filter(conn -> source.equals(conn.getSource())) + .findFirst() + .orElse(null); + assertNotNull(connection); + assertEquals(email, connection.getRawId()); + assertEquals(source, connection.getSource()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testFormLogin_Integration_LoginExistingUser() { + // Arrange - First register a user + String email = "existing_user@example.com"; + String password = "testPassword123"; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + + AuthenticationEndpoints.FormLoginRequest registerRequest = new AuthenticationEndpoints.FormLoginRequest( + email, password, true, source, authId + ); + + // Register the user first + authenticationController.formLogin(registerRequest, null, null, mockExchange).block(); + + // Now try to login with the same credentials + AuthenticationEndpoints.FormLoginRequest loginRequest = new AuthenticationEndpoints.FormLoginRequest( + email, password, false, source, authId + ); + + // Act + Mono> result = authenticationController.formLogin( + loginRequest, null, null, mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testFormLogin_Integration_InvalidCredentials() { + // Arrange + String email = "nonexistent@example.com"; + String password = "wrongPassword"; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + email, password, false, source, authId + ); + + // Act & Assert + StepVerifier.create(authenticationController.formLogin(formLoginRequest, null, null, mockExchange)) + .verifyErrorMatches(throwable -> { + assertTrue(throwable instanceof BizException); + BizException bizException = (BizException) throwable; + assertEquals(INVALID_PASSWORD, bizException.getError()); + return true; + }); + } + + @Test + @WithMockUser(id = "user01") + void testLogout_Integration_Success() { + // Arrange - Set up a mock session token + MockServerHttpRequest request = MockServerHttpRequest.post("") + .cookie(ResponseCookie.from("token", "test-session-token").build()) + .build(); + ServerWebExchange exchangeWithCookie = MockServerWebExchange.builder(request).build(); + + // Act + Mono> result = authenticationController.logout(exchangeWithCookie); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testEnableAuthConfig_Integration_Success() { + // Arrange + AuthConfigRequest authConfigRequest = new AuthConfigRequest(); + authConfigRequest.put("authType", "FORM"); + authConfigRequest.put("source", "test-email"); + authConfigRequest.put("sourceName", "Test Email Auth"); + authConfigRequest.put("enableRegister", true); + + // Act + Mono> result = authenticationController.enableAuthConfig(authConfigRequest); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNull(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGetAllConfigs_Integration_Success() { + // Act + Mono>> result = authenticationController.getAllConfigs(); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + // Should have at least the default email config + assertTrue(response.getData().size() >= 1); + + // Verify at least one config is an EmailAuthConfig + boolean hasEmailConfig = response.getData().stream() + .anyMatch(config -> config instanceof EmailAuthConfig); + assertTrue(hasEmailConfig); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testCreateAPIKey_Integration_Success() { + // Arrange + APIKeyRequest apiKeyRequest = new APIKeyRequest(); + apiKeyRequest.put("name", "Integration Test API Key"); + apiKeyRequest.put("description", "API Key created during integration test"); + + // Act + Mono> result = authenticationController.createAPIKey(apiKeyRequest); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getData().getId()); + assertNotNull(response.getData().getToken()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGetAllAPIKeys_Integration_Success() { + // Arrange - Create an API key first + APIKeyRequest apiKeyRequest = new APIKeyRequest(); + apiKeyRequest.put("name", "Test API Key for List"); + apiKeyRequest.put("description", "Test Description"); + + authenticationController.createAPIKey(apiKeyRequest).block(); + + // Act + Mono>> result = authenticationController.getAllAPIKeys(); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertTrue(response.getData().size() >= 1); + + // Verify the created API key is in the list + boolean foundCreatedKey = response.getData().stream() + .anyMatch(key -> "Test API Key for List".equals(key.getName())); + assertTrue(foundCreatedKey); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testDeleteAPIKey_Integration_Success() { + // Arrange - Create an API key first + APIKeyRequest apiKeyRequest = new APIKeyRequest(); + apiKeyRequest.put("name", "API Key to Delete"); + apiKeyRequest.put("description", "This key will be deleted"); + + APIKeyVO createdKey = authenticationController.createAPIKey(apiKeyRequest).block().getData(); + + // Act + Mono> result = authenticationController.deleteAPIKey(createdKey.getId()); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNull(response.getData()); + }) + .verifyComplete(); + + // Verify the key was actually deleted + StepVerifier.create(authenticationController.getAllAPIKeys()) + .assertNext(response -> { + assertTrue(response.isSuccess()); + boolean keyStillExists = response.getData().stream() + .anyMatch(key -> createdKey.getId().equals(key.getId())); + assertFalse(keyStillExists); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testBindEmail_Integration_Success() { + // Arrange + String emailToBind = "bound_email@example.com"; + + // Act + Mono> result = authenticationController.bindEmail(emailToBind); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testFormLogin_Integration_WithInvitationId() { + // Arrange - Test registration without invitation ID (invitation ID is optional) + String email = "invited_user@example.com"; + String password = "testPassword123"; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + String invitationId = null; // No invitation ID - should work fine + + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + email, password, true, source, authId + ); + + // Act + Mono> result = authenticationController.formLogin( + formLoginRequest, invitationId, null, mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testFormLogin_Integration_WithOrgId() { + // Arrange + String email = "org_user@example.com"; + String password = "testPassword123"; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + String orgId = null; // Use null to get default EMAIL auth config + + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + email, password, true, source, authId + ); + + // Act + Mono> result = authenticationController.formLogin( + formLoginRequest, null, orgId, mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testDisableAuthConfig_Integration_Success() { + // Arrange - First enable an auth config + AuthConfigRequest authConfigRequest = new AuthConfigRequest(); + authConfigRequest.put("authType", "FORM"); + authConfigRequest.put("source", "disable-test"); + authConfigRequest.put("sourceName", "Test Auth to Disable"); + authConfigRequest.put("enableRegister", true); + + authenticationController.enableAuthConfig(authConfigRequest).block(); + + // Get the config ID (this is a simplified approach - in real scenario you'd get it from the response) + String configId = "disable-test"; // Simplified for test + + // Act + Mono> result = authenticationController.disableAuthConfig(configId, false); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNull(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testLoginWithThirdParty_Integration_Success() { + // Arrange - Use the existing Google OAuth config from test data + String authId = "106e4f4a4f6a48e5aa23cca6757c29e4"; // Google OAuth config ID from organization.json + String source = "GOOGLE"; + String code = "mock-oauth-code"; + String redirectUrl = "http://localhost:8080/auth/callback"; + String orgId = "org01"; + + // Act & Assert - Expect network error since auth.google.com doesn't exist + StepVerifier.create(authenticationController.loginWithThirdParty( + authId, source, code, null, redirectUrl, orgId, mockExchange + )) + .verifyErrorMatches(throwable -> { + assertTrue(throwable instanceof BizException); + BizException bizException = (BizException) throwable; + // Should fail due to network error when trying to reach auth.google.com + assertTrue(bizException.getMessage().contains("Failed to get OIDC information") || + bizException.getMessage().contains("Failed to resolve")); + return true; + }); + } + + @Test + @WithMockUser(id = "user01") + void testLoginWithThirdParty_Integration_WithInvitationId() { + // Arrange - Test with invitation ID + String authId = "106e4f4a4f6a48e5aa23cca6757c29e4"; + String source = "GOOGLE"; + String code = "mock-oauth-code"; + String redirectUrl = "http://localhost:8080/auth/callback"; + String orgId = "org01"; + String invitationId = "test-invitation-id"; + + // Act & Assert - Expect network error since auth.google.com doesn't exist + StepVerifier.create(authenticationController.loginWithThirdParty( + authId, source, code, invitationId, redirectUrl, orgId, mockExchange + )) + .verifyErrorMatches(throwable -> { + assertTrue(throwable instanceof BizException); + BizException bizException = (BizException) throwable; + // Should fail due to network error when trying to reach auth.google.com + assertTrue(bizException.getMessage().contains("Failed to get OIDC information") || + bizException.getMessage().contains("Failed to resolve")); + return true; + }); + } + + @Test + @WithMockUser(id = "user01") + void testLinkAccountWithThirdParty_Integration_Success() { + // Arrange - Use the existing Google OAuth config from test data + String authId = "106e4f4a4f6a48e5aa23cca6757c29e4"; // Google OAuth config ID from organization.json + String source = "GOOGLE"; + String code = "mock-oauth-code"; + String redirectUrl = "http://localhost:8080/auth/callback"; + String orgId = "org01"; + + // Act & Assert - Expect network error since auth.google.com doesn't exist + StepVerifier.create(authenticationController.linkAccountWithThirdParty( + authId, source, code, redirectUrl, orgId, mockExchange + )) + .verifyErrorMatches(throwable -> { + assertTrue(throwable instanceof BizException); + BizException bizException = (BizException) throwable; + // Should fail due to network error when trying to reach auth.google.com + assertTrue(bizException.getMessage().contains("Failed to get OIDC information") || + bizException.getMessage().contains("Failed to resolve")); + return true; + }); + } + + @Test + @WithMockUser(id = "user01") + void testLoginWithThirdParty_Integration_InvalidAuthConfig() { + // Arrange - Test with non-existent auth config + String authId = "non-existent-auth-id"; + String source = "GOOGLE"; + String code = "mock-oauth-code"; + String redirectUrl = "http://localhost:8080/auth/callback"; + String orgId = "org01"; + + // Act & Assert + StepVerifier.create(authenticationController.loginWithThirdParty( + authId, source, code, null, redirectUrl, orgId, mockExchange + )) + .verifyErrorMatches(throwable -> { + assertTrue(throwable instanceof BizException); + // Should fail due to invalid auth config + return true; + }); + } + + @Test + @WithMockUser(id = "user01") + void testLinkAccountWithThirdParty_Integration_InvalidAuthConfig() { + // Arrange - Test with non-existent auth config + String authId = "non-existent-auth-id"; + String source = "GOOGLE"; + String code = "mock-oauth-code"; + String redirectUrl = "http://localhost:8080/auth/callback"; + String orgId = "org01"; + + // Act & Assert + StepVerifier.create(authenticationController.linkAccountWithThirdParty( + authId, source, code, redirectUrl, orgId, mockExchange + )) + .verifyErrorMatches(throwable -> { + assertTrue(throwable instanceof BizException); + // Should fail due to invalid auth config + return true; + }); + } + + @Test + @WithMockUser(id = "user01") + void testFormLoginRequest_Record_Integration() { + // Arrange - Test FormLoginRequest record creation and validation + String loginId = "test@example.com"; + String password = "testPassword123"; + boolean register = true; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + + // Act - Create FormLoginRequest record + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, register, source, authId + ); + + // Assert - Verify record properties + assertEquals(loginId, formLoginRequest.loginId()); + assertEquals(password, formLoginRequest.password()); + assertEquals(register, formLoginRequest.register()); + assertEquals(source, formLoginRequest.source()); + assertEquals(authId, formLoginRequest.authId()); + + // Test record immutability and equality + AuthenticationEndpoints.FormLoginRequest sameRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, register, source, authId + ); + assertEquals(formLoginRequest, sameRequest); + assertEquals(formLoginRequest.hashCode(), sameRequest.hashCode()); + + // Test different request + AuthenticationEndpoints.FormLoginRequest differentRequest = new AuthenticationEndpoints.FormLoginRequest( + "different@example.com", password, register, source, authId + ); + assertNotEquals(formLoginRequest, differentRequest); + + // Test toString method + String toString = formLoginRequest.toString(); + assertTrue(toString.contains(loginId)); + assertTrue(toString.contains(source)); + assertTrue(toString.contains(String.valueOf(register))); + + // Test with null values (should work for optional fields) + AuthenticationEndpoints.FormLoginRequest nullAuthIdRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, register, source, null + ); + assertNull(nullAuthIdRequest.authId()); + assertEquals(loginId, nullAuthIdRequest.loginId()); + } + + @Test + @WithMockUser(id = "user01") + void testFormLoginRequest_Record_WithDifferentSources() { + // Arrange - Test FormLoginRequest with different auth sources + String loginId = "test@example.com"; + String password = "testPassword123"; + boolean register = false; + String authId = getEmailAuthConfigId(); + + // Test with EMAIL source + AuthenticationEndpoints.FormLoginRequest emailRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, register, AuthSourceConstants.EMAIL, authId + ); + assertEquals(AuthSourceConstants.EMAIL, emailRequest.source()); + + // Test with PHONE source + AuthenticationEndpoints.FormLoginRequest phoneRequest = new AuthenticationEndpoints.FormLoginRequest( + "1234567890", password, register, AuthSourceConstants.PHONE, authId + ); + assertEquals(AuthSourceConstants.PHONE, phoneRequest.source()); + assertEquals("1234567890", phoneRequest.loginId()); + + // Test with GOOGLE source + AuthenticationEndpoints.FormLoginRequest googleRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, register, "GOOGLE", authId + ); + assertEquals("GOOGLE", googleRequest.source()); + } + + @Test + @WithMockUser(id = "user01") + void testFormLoginRequest_Record_WithDifferentRegisterModes() { + // Arrange - Test FormLoginRequest with different register modes + String loginId = "test@example.com"; + String password = "testPassword123"; + String source = AuthSourceConstants.EMAIL; + String authId = getEmailAuthConfigId(); + + // Test register mode (true) + AuthenticationEndpoints.FormLoginRequest registerRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, true, source, authId + ); + assertTrue(registerRequest.register()); + + // Test login mode (false) + AuthenticationEndpoints.FormLoginRequest loginRequest = new AuthenticationEndpoints.FormLoginRequest( + loginId, password, false, source, authId + ); + assertFalse(loginRequest.register()); + + // Verify they are different + assertNotEquals(registerRequest, loginRequest); + } + + private String getEmailAuthConfigId() { + return authenticationService.findAuthConfigBySource(null, AuthSourceConstants.EMAIL) + .map(FindAuthConfig::authConfig) + .map(AbstractAuthConfig::getId) + .block(); + } +} \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/authentication/AuthenticationEndpointsUnitTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/authentication/AuthenticationEndpointsUnitTest.java new file mode 100644 index 000000000..b74483684 --- /dev/null +++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/authentication/AuthenticationEndpointsUnitTest.java @@ -0,0 +1,384 @@ +package org.lowcoder.api.authentication; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lowcoder.api.authentication.dto.APIKeyRequest; +import org.lowcoder.api.authentication.dto.AuthConfigRequest; +import org.lowcoder.api.authentication.service.AuthenticationApiService; +import org.lowcoder.api.framework.view.ResponseView; +import org.lowcoder.api.home.SessionUserService; +import org.lowcoder.api.usermanagement.view.APIKeyVO; +import org.lowcoder.api.util.BusinessEventPublisher; +import org.lowcoder.domain.user.model.APIKey; +import org.lowcoder.domain.user.model.AuthUser; +import org.lowcoder.domain.user.model.User; +import org.lowcoder.domain.user.service.UserService; +import org.lowcoder.sdk.auth.AbstractAuthConfig; +import org.lowcoder.sdk.auth.EmailAuthConfig; +import org.lowcoder.sdk.util.CookieHelper; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AuthenticationEndpointsUnitTest { + + @Mock + private AuthenticationApiService authenticationApiService; + + @Mock + private SessionUserService sessionUserService; + + @Mock + private CookieHelper cookieHelper; + + @Mock + private BusinessEventPublisher businessEventPublisher; + + @Mock + private UserService userService; + + private AuthenticationController authenticationController; + private ServerWebExchange mockExchange; + + @BeforeEach + void setUp() { + authenticationController = new AuthenticationController( + authenticationApiService, + sessionUserService, + cookieHelper, + businessEventPublisher, + userService + ); + + MockServerHttpRequest request = MockServerHttpRequest.post("").build(); + mockExchange = MockServerWebExchange.builder(request).build(); + } + + @Test + void testFormLogin_Success() { + // Arrange + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + "test@example.com", "password", false, "email", "authId" + ); + AuthUser mockAuthUser = mock(AuthUser.class); + + when(authenticationApiService.authenticateByForm( + eq("test@example.com"), eq("password"), eq("email"), + eq(false), eq("authId"), eq("orgId") + )).thenReturn(Mono.just(mockAuthUser)); + + when(authenticationApiService.loginOrRegister( + eq(mockAuthUser), eq(mockExchange), eq("invitationId"), eq(false) + )).thenReturn(Mono.empty()); + + // Act + Mono> result = authenticationController.formLogin( + formLoginRequest, "invitationId", "orgId", mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + + verify(authenticationApiService).authenticateByForm( + "test@example.com", "password", "email", false, "authId", "orgId" + ); + verify(authenticationApiService).loginOrRegister(mockAuthUser, mockExchange, "invitationId", false); + } + + @Test + void testFormLogin_RegisterMode() { + // Arrange + AuthenticationEndpoints.FormLoginRequest formLoginRequest = new AuthenticationEndpoints.FormLoginRequest( + "new@example.com", "password", true, "email", "authId" + ); + AuthUser mockAuthUser = mock(AuthUser.class); + + when(authenticationApiService.authenticateByForm( + eq("new@example.com"), eq("password"), eq("email"), + eq(true), eq("authId"), isNull() + )).thenReturn(Mono.just(mockAuthUser)); + + when(authenticationApiService.loginOrRegister( + eq(mockAuthUser), eq(mockExchange), isNull(), eq(false) + )).thenReturn(Mono.empty()); + + // Act + Mono> result = authenticationController.formLogin( + formLoginRequest, null, null, mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + void testLoginWithThirdParty_Success() { + // Arrange + AuthUser mockAuthUser = mock(AuthUser.class); + + when(authenticationApiService.authenticateByOauth2( + eq("authId"), eq("google"), eq("code"), eq("redirectUrl"), eq("orgId") + )).thenReturn(Mono.just(mockAuthUser)); + + when(authenticationApiService.loginOrRegister( + eq(mockAuthUser), eq(mockExchange), eq("invitationId"), eq(false) + )).thenReturn(Mono.empty()); + + // Act + Mono> result = authenticationController.loginWithThirdParty( + "authId", "google", "code", "invitationId", "redirectUrl", "orgId", mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + void testLinkAccountWithThirdParty_Success() { + // Arrange + AuthUser mockAuthUser = mock(AuthUser.class); + + when(authenticationApiService.authenticateByOauth2( + eq("authId"), eq("github"), eq("code"), eq("redirectUrl"), eq("orgId") + )).thenReturn(Mono.just(mockAuthUser)); + + when(authenticationApiService.loginOrRegister( + eq(mockAuthUser), eq(mockExchange), isNull(), eq(true) + )).thenReturn(Mono.empty()); + + // Act + Mono> result = authenticationController.linkAccountWithThirdParty( + "authId", "github", "code", "redirectUrl", "orgId", mockExchange + ); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + void testLogout_Success() { + // Arrange + when(cookieHelper.getCookieToken(mockExchange)).thenReturn("sessionToken"); + when(sessionUserService.removeUserSession("sessionToken")).thenReturn(Mono.empty()); + when(businessEventPublisher.publishUserLogoutEvent()).thenReturn(Mono.empty()); + + // Act + Mono> result = authenticationController.logout(mockExchange); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + + verify(cookieHelper).getCookieToken(mockExchange); + verify(sessionUserService).removeUserSession("sessionToken"); + verify(businessEventPublisher).publishUserLogoutEvent(); + } + + @Test + void testEnableAuthConfig_Success() { + // Arrange + AuthConfigRequest authConfigRequest = new AuthConfigRequest(); + authConfigRequest.put("authType", "FORM"); + authConfigRequest.put("source", "email"); + + when(authenticationApiService.enableAuthConfig(authConfigRequest)).thenReturn(Mono.just(true)); + + // Act + Mono> result = authenticationController.enableAuthConfig(authConfigRequest); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNull(response.getData()); + }) + .verifyComplete(); + + verify(authenticationApiService).enableAuthConfig(authConfigRequest); + } + + @Test + void testDisableAuthConfig_Success() { + // Arrange + when(authenticationApiService.disableAuthConfig("authId", true)).thenReturn(Mono.just(true)); + + // Act + Mono> result = authenticationController.disableAuthConfig("authId", true); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNull(response.getData()); + }) + .verifyComplete(); + + verify(authenticationApiService).disableAuthConfig("authId", true); + } + + @Test + void testGetAllConfigs_Success() { + // Arrange + EmailAuthConfig emailConfig = new EmailAuthConfig("email", true, true); + List configs = List.of(emailConfig); + + when(authenticationApiService.findAuthConfigs(false)) + .thenReturn(reactor.core.publisher.Flux.fromIterable( + configs.stream().map(config -> new org.lowcoder.domain.authentication.FindAuthConfig(config, null)).toList() + )); + + // Act + Mono>> result = authenticationController.getAllConfigs(); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertEquals(1, response.getData().size()); + assertEquals(emailConfig, response.getData().get(0)); + }) + .verifyComplete(); + } + + @Test + void testCreateAPIKey_Success() { + // Arrange + APIKeyRequest apiKeyRequest = new APIKeyRequest(); + apiKeyRequest.put("name", "Test API Key"); + apiKeyRequest.put("description", "Test Description"); + + APIKeyVO mockApiKeyVO = mock(APIKeyVO.class); + when(authenticationApiService.createAPIKey(apiKeyRequest)).thenReturn(Mono.just(mockApiKeyVO)); + + // Act + Mono> result = authenticationController.createAPIKey(apiKeyRequest); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertEquals(mockApiKeyVO, response.getData()); + }) + .verifyComplete(); + + verify(authenticationApiService).createAPIKey(apiKeyRequest); + } + + @Test + void testDeleteAPIKey_Success() { + // Arrange + when(authenticationApiService.deleteAPIKey("apiKeyId")).thenReturn(Mono.empty()); + + // Act + Mono> result = authenticationController.deleteAPIKey("apiKeyId"); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNull(response.getData()); + }) + .verifyComplete(); + + verify(authenticationApiService).deleteAPIKey("apiKeyId"); + } + + @Test + void testGetAllAPIKeys_Success() { + // Arrange + APIKey apiKey1 = APIKey.builder().id("key1").name("Key 1").build(); + APIKey apiKey2 = APIKey.builder().id("key2").name("Key 2").build(); + List apiKeys = List.of(apiKey1, apiKey2); + + when(authenticationApiService.findAPIKeys()) + .thenReturn(reactor.core.publisher.Flux.fromIterable(apiKeys)); + + // Act + Mono>> result = authenticationController.getAllAPIKeys(); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertEquals(2, response.getData().size()); + assertEquals(apiKey1, response.getData().get(0)); + assertEquals(apiKey2, response.getData().get(1)); + }) + .verifyComplete(); + } + + @Test + void testBindEmail_Success() { + // Arrange + User mockUser = mock(User.class); + when(sessionUserService.getVisitor()).thenReturn(Mono.just(mockUser)); + when(userService.bindEmail(mockUser, "test@example.com")).thenReturn(Mono.just(true)); + + // Act + Mono> result = authenticationController.bindEmail("test@example.com"); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertEquals(true, response.getData()); + }) + .verifyComplete(); + + verify(sessionUserService).getVisitor(); + verify(userService).bindEmail(mockUser, "test@example.com"); + } + + @Test + void testFormLoginRequest_Record() { + // Arrange & Act + AuthenticationEndpoints.FormLoginRequest request = new AuthenticationEndpoints.FormLoginRequest( + "test@example.com", "password", false, "email", "authId" + ); + + // Assert + assertEquals("test@example.com", request.loginId()); + assertEquals("password", request.password()); + assertFalse(request.register()); + assertEquals("email", request.source()); + assertEquals("authId", request.authId()); + } +} \ No newline at end of file