diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/datasource/DatasourceEndpointsIntegrationTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/datasource/DatasourceEndpointsIntegrationTest.java new file mode 100644 index 0000000000..52199724d3 --- /dev/null +++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/datasource/DatasourceEndpointsIntegrationTest.java @@ -0,0 +1,514 @@ +package org.lowcoder.api.datasource; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.lowcoder.api.common.InitData; +import org.lowcoder.api.common.mockuser.WithMockUser; +import org.lowcoder.api.framework.view.PageResponseView; +import org.lowcoder.api.framework.view.ResponseView; +import org.lowcoder.api.permission.view.CommonPermissionView; +import org.lowcoder.domain.datasource.model.Datasource; +import org.lowcoder.domain.datasource.model.DatasourceStatus; +import org.lowcoder.domain.permission.model.ResourceRole; +import org.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO; +import org.lowcoder.sdk.exception.BizException; +import org.lowcoder.sdk.models.DatasourceStructure; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.test.context.ActiveProfiles; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DatasourceEndpointsIntegrationTest { + + @Autowired + private DatasourceController datasourceController; + + @Autowired + private InitData initData; + + private static final String TEST_ORGANIZATION_ID = "org01"; + private static final String TEST_APPLICATION_ID = "app01"; + private static final String TEST_PERMISSION_ID = "permission01"; + + @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; + } + } + } + + @Test + @WithMockUser(id = "user01") + void testCreateDatasource_Integration_Success() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + + // Act + Mono> result = datasourceController.create(request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertEquals("Test REST API Datasource", response.getData().getName()); + assertEquals("restapi", response.getData().getType()); + assertEquals(TEST_ORGANIZATION_ID, response.getData().getOrganizationId()); + assertNotNull(response.getData().getId()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGetDatasourceById_Integration_Success() { + // Arrange - First create a datasource, then retrieve it + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Test Datasource for Get"); + + // Act - Create then get + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.getById(createdId); + }); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertEquals("Test Datasource for Get", response.getData().getName()); + assertEquals("restapi", response.getData().getType()); + assertEquals(TEST_ORGANIZATION_ID, response.getData().getOrganizationId()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testDeleteDatasource_Integration_Success() { + // Arrange - First create a datasource to delete + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Datasource to Delete"); + + // Act - Create then delete + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.delete(createdId); + }); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertTrue(response.getData()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGetDatasourceStructure_Integration_Success() { + // Arrange - First create a datasource, then get its structure + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Datasource for Structure Test"); + + boolean ignoreCache = false; + + // Act - Create then get structure + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.getStructure(createdId, ignoreCache); + }); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + // Note: Structure might be null in test environment + // So we just verify the response structure + assertNotNull(response); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGetDatasourceStructure_Integration_WithCache() { + // Arrange - First create a datasource, then get its structure with cache + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Datasource for Cache Structure Test"); + + boolean ignoreCache = true; + + // Act - Create then get structure + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.getStructure(createdId, ignoreCache); + }); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + // Note: Structure might be null in test environment + // So we just verify the response structure + assertNotNull(response); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListJsDatasourcePlugins_Integration_Success() { + // Arrange + String applicationId = TEST_APPLICATION_ID; + String name = null; + String type = null; + int pageNum = 1; + int pageSize = 10; + + // Act + Mono> result = datasourceController.listJsDatasourcePlugins( + applicationId, name, type, pageNum, pageSize); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getTotal()); + assertNotNull(response.getPageNum()); + assertNotNull(response.getPageSize()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListJsDatasourcePlugins_Integration_WithFilters() { + // Arrange + String applicationId = TEST_APPLICATION_ID; + String name = "restapi"; + String type = "restapi"; + int pageNum = 1; + int pageSize = 5; + + // Act + Mono> result = datasourceController.listJsDatasourcePlugins( + applicationId, name, type, pageNum, pageSize); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getTotal()); + assertNotNull(response.getPageNum()); + assertNotNull(response.getPageSize()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGetPluginDynamicConfig_Integration_EmptyList() { + // Arrange + List emptyList = List.of(); + + // Act + Mono>> result = datasourceController.getPluginDynamicConfig(emptyList); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertTrue(response.getData().isEmpty()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListOrgDataSources_Integration_Success() { + // Arrange + String orgId = TEST_ORGANIZATION_ID; + String name = null; + String type = null; + int pageNum = 1; + int pageSize = 10; + + // Act + Mono> result = datasourceController.listOrgDataSources( + orgId, name, type, pageNum, pageSize); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getTotal()); + assertNotNull(response.getPageNum()); + assertNotNull(response.getPageSize()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListOrgDataSources_Integration_WithFilters() { + // Arrange + String orgId = TEST_ORGANIZATION_ID; + String name = "restapi"; + String type = "restapi"; + int pageNum = 1; + int pageSize = 5; + + // Act + Mono> result = datasourceController.listOrgDataSources( + orgId, name, type, pageNum, pageSize); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getTotal()); + assertNotNull(response.getPageNum()); + assertNotNull(response.getPageSize()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListOrgDataSources_Integration_EmptyOrgId() { + // Arrange + String orgId = ""; + String name = null; + String type = null; + int pageNum = 1; + int pageSize = 10; + + // Act + Mono> result = datasourceController.listOrgDataSources( + orgId, name, type, pageNum, pageSize); + + // Assert - This method uses ofError() directly, so it throws BizException + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + @WithMockUser(id = "user01") + void testListAppDataSources_Integration_Success() { + // Arrange + String appId = TEST_APPLICATION_ID; + String name = null; + String type = null; + int pageNum = 1; + int pageSize = 10; + + // Act + Mono> result = datasourceController.listAppDataSources( + appId, name, type, pageNum, pageSize); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getTotal()); + assertNotNull(response.getPageNum()); + assertNotNull(response.getPageSize()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListAppDataSources_Integration_WithFilters() { + // Arrange + String appId = TEST_APPLICATION_ID; + String name = "restapi"; + String type = "restapi"; + int pageNum = 1; + int pageSize = 5; + + // Act + Mono> result = datasourceController.listAppDataSources( + appId, name, type, pageNum, pageSize); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getTotal()); + assertNotNull(response.getPageNum()); + assertNotNull(response.getPageSize()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testListAppDataSources_Integration_EmptyAppId() { + // Arrange + String appId = ""; + String name = null; + String type = null; + int pageNum = 1; + int pageSize = 10; + + // Act + Mono> result = datasourceController.listAppDataSources( + appId, name, type, pageNum, pageSize); + + // Assert - This method uses ofError() directly, so it throws BizException + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + @WithMockUser(id = "user01") + void testGetDatasourcePermissions_Integration_Success() { + // Arrange - First create a datasource, then get its permissions + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Datasource for Permissions Test"); + + // Act - Create then get permissions + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.getPermissions(createdId); + }); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getData().getUserPermissions()); + assertNotNull(response.getData().getGroupPermissions()); + }) + .verifyComplete(); + } + + @Test + @WithMockUser(id = "user01") + void testGrantDatasourcePermission_Integration_InvalidRole() { + // Arrange - First create a datasource, then try to grant invalid permissions + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Datasource for Invalid Permission Test"); + + Set userIds = Set.of("user02"); + Set groupIds = Set.of(); + String invalidRole = "INVALID_ROLE"; + + DatasourceEndpoints.BatchAddPermissionRequest request = + new DatasourceEndpoints.BatchAddPermissionRequest(invalidRole, userIds, groupIds); + + // Act - Create then try to grant invalid permission + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.grantPermission(createdId, request); + }); + + // Assert - This method uses ofError() directly, so it throws BizException + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + @WithMockUser(id = "user01") + void testUpdateDatasourcePermission_Integration_InvalidRole() { + // Arrange + String permissionId = TEST_PERMISSION_ID; + String invalidRole = "INVALID_ROLE"; + + DatasourceEndpoints.UpdatePermissionRequest request = + new DatasourceEndpoints.UpdatePermissionRequest(invalidRole); + + // Act + Mono> result = datasourceController.updatePermission(permissionId, request); + + // Assert - This method uses ofError() directly, so it throws BizException + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + @WithMockUser(id = "user01") + void testGetDatasourceInfo_Integration_Success() { + // Arrange - First create a datasource, then get its info + UpsertDatasourceRequest createRequest = createTestUpsertDatasourceRequest(); + createRequest.setName("Datasource for Info Test"); + + // Act - Create then get info + Mono> result = datasourceController.create(createRequest) + .flatMap(createResponse -> { + String createdId = createResponse.getData().getId(); + return datasourceController.info(createdId); + }); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + }) + .verifyComplete(); + } + + // Helper method to create a test UpsertDatasourceRequest + private UpsertDatasourceRequest createTestUpsertDatasourceRequest() { + UpsertDatasourceRequest request = new UpsertDatasourceRequest(); + request.setName("Test REST API Datasource"); + request.setType("restapi"); // Use REST_API instead of mysql + request.setOrganizationId(TEST_ORGANIZATION_ID); + request.setStatus(DatasourceStatus.NORMAL); + + // Create REST API datasource config + Map config = new HashMap<>(); + config.put("url", "https://api.example.com"); + config.put("method", "GET"); + config.put("headers", new HashMap()); + config.put("params", new HashMap()); + + request.setDatasourceConfig(config); + return request; + } +} diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/datasource/DatasourceEndpointsTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/datasource/DatasourceEndpointsTest.java new file mode 100644 index 0000000000..c22e39ca00 --- /dev/null +++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/datasource/DatasourceEndpointsTest.java @@ -0,0 +1,546 @@ +package org.lowcoder.api.datasource; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lowcoder.api.framework.view.PageResponseView; +import org.lowcoder.api.framework.view.ResponseView; +import org.lowcoder.api.permission.view.CommonPermissionView; +import org.lowcoder.api.util.BusinessEventPublisher; +import org.lowcoder.api.util.GidService; +import org.lowcoder.domain.datasource.model.Datasource; +import org.lowcoder.domain.datasource.model.DatasourceStatus; +import org.lowcoder.domain.datasource.service.DatasourceService; +import org.lowcoder.domain.datasource.service.DatasourceStructureService; +import org.lowcoder.domain.permission.model.ResourcePermission; +import org.lowcoder.domain.permission.model.ResourceRole; +import org.lowcoder.domain.permission.service.ResourcePermissionService; +import org.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO; +import org.lowcoder.sdk.exception.BizError; +import org.lowcoder.sdk.exception.BizException; +import org.lowcoder.sdk.models.DatasourceStructure; +import org.lowcoder.sdk.models.DatasourceTestResult; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import reactor.core.publisher.Flux; +import java.util.ArrayList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import org.lowcoder.api.datasource.DatasourceView; + +class DatasourceEndpointsTest { + + private DatasourceStructureService datasourceStructureService; + private DatasourceApiService datasourceApiService; + private UpsertDatasourceRequestMapper upsertDatasourceRequestMapper; + private BusinessEventPublisher businessEventPublisher; + private DatasourceService datasourceService; + private GidService gidService; + private ResourcePermissionService resourcePermissionService; + private DatasourceController controller; + + private static final String TEST_DATASOURCE_ID = "test-datasource-id"; + private static final String TEST_ORGANIZATION_ID = "test-org-id"; + private static final String TEST_PERMISSION_ID = "test-permission-id"; + + @BeforeEach + void setUp() { + // Create mocks manually + datasourceStructureService = Mockito.mock(DatasourceStructureService.class); + datasourceApiService = Mockito.mock(DatasourceApiService.class); + upsertDatasourceRequestMapper = Mockito.mock(UpsertDatasourceRequestMapper.class); + businessEventPublisher = Mockito.mock(BusinessEventPublisher.class); + datasourceService = Mockito.mock(DatasourceService.class); + gidService = Mockito.mock(GidService.class); + resourcePermissionService = Mockito.mock(ResourcePermissionService.class); + + // Setup common mocks + when(businessEventPublisher.publishDatasourceEvent(any(Datasource.class), any(), any())).thenReturn(Mono.empty()); + when(businessEventPublisher.publishDatasourceEvent(any(String.class), any(), any())).thenReturn(Mono.empty()); + when(businessEventPublisher.publishDatasourcePermissionEvent(any(), any(), any(), any(), any(), any(), any())).thenReturn(Mono.empty()); + when(businessEventPublisher.publishDatasourceResourcePermissionEvent(any(), any(), any())).thenReturn(Mono.empty()); + when(datasourceService.removePasswordTypeKeysFromJsDatasourcePluginConfig(any())).thenReturn(Mono.empty()); + when(gidService.convertDatasourceIdToObjectId(any())).thenAnswer(invocation -> { + String datasourceId = invocation.getArgument(0); + return datasourceId != null ? Mono.just(datasourceId) : Mono.empty(); + }); + when(gidService.convertApplicationIdToObjectId(any())).thenAnswer(invocation -> { + String applicationId = invocation.getArgument(0); + return applicationId != null ? Mono.just(applicationId) : Mono.empty(); + }); + when(gidService.convertOrganizationIdToObjectId(any())).thenAnswer(invocation -> { + String organizationId = invocation.getArgument(0); + return organizationId != null ? Mono.just(organizationId) : Mono.empty(); + }); + when(resourcePermissionService.getById(any())).thenAnswer(invocation -> { + String permissionId = invocation.getArgument(0); + ResourcePermission mockPermission = Mockito.mock(ResourcePermission.class); + return permissionId != null ? Mono.just(mockPermission) : Mono.empty(); + }); + + // Create controller with all required dependencies + controller = new DatasourceController( + datasourceStructureService, + datasourceApiService, + upsertDatasourceRequestMapper, + businessEventPublisher, + datasourceService, + gidService, + resourcePermissionService + ); + } + + @Test + void testCreateDatasource_Success() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + Datasource mockDatasource = createMockDatasource(); + + when(upsertDatasourceRequestMapper.resolve(request)).thenReturn(mockDatasource); + when(datasourceApiService.create(mockDatasource)).thenReturn(Mono.just(mockDatasource)); + + // Act + Mono> result = controller.create(request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockDatasource); + }) + .verifyComplete(); + } + + @Test + void testCreateDatasource_ServiceError() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + Datasource mockDatasource = createMockDatasource(); + + when(upsertDatasourceRequestMapper.resolve(request)).thenReturn(mockDatasource); + when(datasourceApiService.create(mockDatasource)).thenReturn(Mono.error(new BizException(BizError.INVALID_PARAMETER, "INVALID_PARAMETER"))); + + // Act + Mono> result = controller.create(request); + + // Assert + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + void testGetDatasourceById_Success() { + // Arrange + Datasource mockDatasource = createMockDatasource(); + when(datasourceApiService.findByIdWithPermission(TEST_DATASOURCE_ID)).thenReturn(Mono.just(mockDatasource)); + + // Act + Mono> result = controller.getById(TEST_DATASOURCE_ID); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockDatasource); + }) + .verifyComplete(); + } + + @Test + void testGetDatasourceById_NotFound() { + // Arrange + when(datasourceApiService.findByIdWithPermission(TEST_DATASOURCE_ID)).thenReturn(Mono.error(new BizException(BizError.DATASOURCE_NOT_FOUND, "DATASOURCE_NOT_FOUND"))); + + // Act + Mono> result = controller.getById(TEST_DATASOURCE_ID); + + // Assert + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + void testUpdateDatasource_Success() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + Datasource mockDatasource = createMockDatasource(); + Datasource originalDatasource = createMockDatasource(); + originalDatasource.setName("Original Name"); + + when(upsertDatasourceRequestMapper.resolve(request)).thenReturn(mockDatasource); + when(datasourceService.getById(TEST_DATASOURCE_ID)).thenReturn(Mono.just(originalDatasource)); + when(datasourceApiService.update(TEST_DATASOURCE_ID, mockDatasource)).thenReturn(Mono.just(mockDatasource)); + + // Act + Mono> result = controller.update(TEST_DATASOURCE_ID, request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockDatasource); + }) + .verifyComplete(); + } + + @Test + void testUpdateDatasource_ServiceError() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + Datasource mockDatasource = createMockDatasource(); + + when(upsertDatasourceRequestMapper.resolve(request)).thenReturn(mockDatasource); + when(datasourceService.getById(TEST_DATASOURCE_ID)).thenReturn(Mono.just(mockDatasource)); + when(datasourceApiService.update(TEST_DATASOURCE_ID, mockDatasource)).thenReturn(Mono.error(new BizException(BizError.INVALID_PARAMETER, "INVALID_PARAMETER"))); + + // Act + Mono> result = controller.update(TEST_DATASOURCE_ID, request); + + // Assert + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + void testDeleteDatasource_Success() { + // Arrange + Datasource mockDatasource = createMockDatasource(); + when(datasourceService.getById(TEST_DATASOURCE_ID)).thenReturn(Mono.just(mockDatasource)); + when(datasourceApiService.delete(TEST_DATASOURCE_ID)).thenReturn(Mono.just(true)); + + // Act + Mono> result = controller.delete(TEST_DATASOURCE_ID); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(true); + }) + .verifyComplete(); + } + + @Test + void testDeleteDatasource_ServiceError() { + // Arrange + Datasource mockDatasource = createMockDatasource(); + when(datasourceService.getById(TEST_DATASOURCE_ID)).thenReturn(Mono.just(mockDatasource)); + when(datasourceApiService.delete(TEST_DATASOURCE_ID)).thenReturn(Mono.error(new BizException(BizError.DATASOURCE_NOT_FOUND, "DATASOURCE_NOT_FOUND"))); + + // Act + Mono> result = controller.delete(TEST_DATASOURCE_ID); + + // Assert + StepVerifier.create(result) + .expectError(BizException.class) + .verify(); + } + + @Test + void testTestDatasource_Success() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + Datasource mockDatasource = createMockDatasource(); + DatasourceTestResult testResult = DatasourceTestResult.testSuccess(); + + when(upsertDatasourceRequestMapper.resolve(request)).thenReturn(mockDatasource); + when(datasourceApiService.testDatasource(mockDatasource)).thenReturn(Mono.just(testResult)); + + // Act + Mono> result = controller.testDatasource(request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(true); + }) + .verifyComplete(); + } + + @Test + void testTestDatasource_Failure() { + // Arrange + UpsertDatasourceRequest request = createTestUpsertDatasourceRequest(); + Datasource mockDatasource = createMockDatasource(); + DatasourceTestResult testResult = DatasourceTestResult.testFail("Connection failed"); + + when(upsertDatasourceRequestMapper.resolve(request)).thenReturn(mockDatasource); + when(datasourceApiService.testDatasource(mockDatasource)).thenReturn(Mono.just(testResult)); + + // Act + Mono> result = controller.testDatasource(request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert !response.isSuccess(); + assert response.getCode() == 500; + }) + .verifyComplete(); + } + + @Test + void testGetStructure_Success() { + // Arrange + DatasourceStructure mockStructure = new DatasourceStructure(); + when(datasourceStructureService.getStructure(TEST_DATASOURCE_ID, false)).thenReturn(Mono.just(mockStructure)); + + // Act + Mono> result = controller.getStructure(TEST_DATASOURCE_ID, false); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockStructure); + }) + .verifyComplete(); + } + + @Test + void testGetStructure_WithIgnoreCache() { + // Arrange + DatasourceStructure mockStructure = new DatasourceStructure(); + when(datasourceStructureService.getStructure(TEST_DATASOURCE_ID, true)).thenReturn(Mono.just(mockStructure)); + + // Act + Mono> result = controller.getStructure(TEST_DATASOURCE_ID, true); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockStructure); + }) + .verifyComplete(); + } + + @Test + void testListJsDatasourcePlugins_Success() { + // Arrange + Flux mockDatasourceFlux = Flux.just(createMockDatasource()); + when(datasourceApiService.listJsDatasourcePlugins(anyString(), anyString(), anyString())) + .thenReturn(mockDatasourceFlux); + + // Act + Mono> result = controller.listJsDatasourcePlugins("appId", "name", "type", 1, 10); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + }) + .verifyComplete(); + } + + @Test + void testGetPluginDynamicConfig_Success() { + // Arrange + GetPluginDynamicConfigRequestDTO requestDTO = GetPluginDynamicConfigRequestDTO.builder() + .dataSourceId("test-id") + .pluginName("test-plugin") + .path("/test") + .dataSourceConfig(new HashMap<>()) + .build(); + List requestDTOs = List.of(requestDTO); + List mockConfigs = List.of(new HashMap<>()); + when(datasourceApiService.getPluginDynamicConfig(requestDTOs)).thenReturn(Mono.just(mockConfigs)); + + // Act + Mono>> result = controller.getPluginDynamicConfig(requestDTOs); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockConfigs); + }) + .verifyComplete(); + } + + @Test + void testListOrgDataSources_Success() { + // Arrange + Flux mockDatasourceViewFlux = Flux.just(Mockito.mock(DatasourceView.class)); + when(datasourceApiService.listOrgDataSources(anyString(), anyString(), anyString())) + .thenReturn(mockDatasourceViewFlux); + + // Act + Mono> result = controller.listOrgDataSources(TEST_ORGANIZATION_ID, "name", "type", 1, 10); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + }) + .verifyComplete(); + } + + @Test + void testListAppDataSources_Success() { + // Arrange + Flux mockDatasourceViewFlux = Flux.just(Mockito.mock(DatasourceView.class)); + when(datasourceApiService.listAppDataSources(anyString(), anyString(), anyString())) + .thenReturn(mockDatasourceViewFlux); + + // Act + Mono> result = controller.listAppDataSources("appId", "name", "type", 1, 10); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + }) + .verifyComplete(); + } + + @Test + void testGetPermissions_Success() { + // Arrange + CommonPermissionView mockPermissionView = CommonPermissionView.builder() + .orgName("Test Org") + .creatorId("user01") + .groupPermissions(new ArrayList<>()) + .userPermissions(new ArrayList<>()) + .build(); + when(datasourceApiService.getPermissions(TEST_DATASOURCE_ID)).thenReturn(Mono.just(mockPermissionView)); + + // Act + Mono> result = controller.getPermissions(TEST_DATASOURCE_ID); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockPermissionView); + }) + .verifyComplete(); + } + + @Test + void testGrantPermission_Success() { + // Arrange + DatasourceEndpoints.BatchAddPermissionRequest request = new DatasourceEndpoints.BatchAddPermissionRequest("owner", Set.of("user1"), Set.of("group1")); + when(datasourceApiService.grantPermission(eq(TEST_DATASOURCE_ID), any(), any(), any())).thenReturn(Mono.just(true)); + when(datasourceApiService.getPermissions(TEST_DATASOURCE_ID)).thenReturn(Mono.just(CommonPermissionView.builder() + .orgName("Test Org") + .creatorId("user01") + .groupPermissions(new ArrayList<>()) + .userPermissions(new ArrayList<>()) + .build())); + + // Act + Mono> result = controller.grantPermission(TEST_DATASOURCE_ID, request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(true); + }) + .verifyComplete(); + } + + @Test + void testUpdatePermission_Success() { + // Arrange + DatasourceEndpoints.UpdatePermissionRequest request = new DatasourceEndpoints.UpdatePermissionRequest("viewer"); + ResourcePermission mockPermission = Mockito.mock(ResourcePermission.class); + when(resourcePermissionService.getById(TEST_PERMISSION_ID)).thenReturn(Mono.just(mockPermission)); + when(datasourceApiService.updatePermission(TEST_PERMISSION_ID, ResourceRole.VIEWER)).thenReturn(Mono.just(true)); + + // Act + Mono> result = controller.updatePermission(TEST_PERMISSION_ID, request); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(true); + }) + .verifyComplete(); + } + + @Test + void testDeletePermission_Success() { + // Arrange + when(datasourceApiService.deletePermission(TEST_PERMISSION_ID)).thenReturn(Mono.just(true)); + when(resourcePermissionService.getById(TEST_PERMISSION_ID)).thenReturn(Mono.just(Mockito.mock(ResourcePermission.class))); + + // Act + Mono> result = controller.deletePermission(TEST_PERMISSION_ID); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(true); + }) + .verifyComplete(); + } + + @Test + void testInfo_Success() { + // Arrange + Object mockInfo = new HashMap<>(); + when(datasourceApiService.info(TEST_DATASOURCE_ID)).thenReturn(mockInfo); + + // Act + Mono> result = controller.info(TEST_DATASOURCE_ID); + + // Assert + StepVerifier.create(result) + .assertNext(response -> { + assert response.isSuccess(); + assert response.getData().equals(mockInfo); + }) + .verifyComplete(); + } + + @Test + void testInfo_WithoutDatasourceId() { + // Arrange + Object mockInfo = new HashMap<>(); + when(datasourceApiService.info(null)).thenReturn(mockInfo); + when(gidService.convertDatasourceIdToObjectId(null)).thenReturn(Mono.empty()); + + // Act + Mono> result = controller.info(null); + + // Assert + StepVerifier.create(result) + .verifyComplete(); + } + + // Helper methods + private UpsertDatasourceRequest createTestUpsertDatasourceRequest() { + UpsertDatasourceRequest request = new UpsertDatasourceRequest(); + request.setId(TEST_DATASOURCE_ID); + request.setName("Test Datasource"); + request.setType("mysql"); + request.setOrganizationId(TEST_ORGANIZATION_ID); + request.setStatus(DatasourceStatus.NORMAL); + request.setDatasourceConfig(new HashMap<>()); + return request; + } + + private Datasource createMockDatasource() { + Datasource datasource = new Datasource(); + datasource.setId(TEST_DATASOURCE_ID); + datasource.setName("Test Datasource"); + datasource.setType("mysql"); + datasource.setOrganizationId(TEST_ORGANIZATION_ID); + // Note: Datasource doesn't have a setStatus method, it uses setDatasourceStatus + return datasource; + } +} \ No newline at end of file