From dbbc5f6f0ce7fe9d5a5b01d09eb1beace4241442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Moitti=C3=A9?= Date: Fri, 18 Oct 2024 16:35:48 +0200 Subject: [PATCH] wip --- build.gradle | 1 + changelog.md | 9 ++ .../controller/AIConfigurationController.java | 31 ++++- .../controller/AIController.java | 57 ++++++++ .../service/AISecretService.java | 15 +++ .../service/AISecretServiceImpl.java | 93 ++++++++++++- .../letomodelizerapi/service/AIService.java | 21 +++ .../service/AIServiceImpl.java | 53 +++++++- .../ai-configuration-description-schema.json | 127 ++++++++++++++++++ .../AIConfigurationControllerTest.java | 17 ++- .../service/AISecretImplTest.java | 7 +- 11 files changed, 414 insertions(+), 17 deletions(-) create mode 100644 src/main/resources/ai-configuration-description-schema.json diff --git a/build.gradle b/build.gradle index 22266a97..a4fb3f0e 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,7 @@ dependencies { implementation 'commons-lang:commons-lang:2.6' implementation 'commons-beanutils:commons-beanutils:1.9.4' implementation 'com.github.erosb:json-sKema:0.18.0' + implementation 'com.hubspot.jinjava:jinjava:2.7.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql:42.7.4' annotationProcessor 'org.projectlombok:lombok' diff --git a/changelog.md b/changelog.md index 6c6cb0d5..ef1d5d74 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Add api endpoints: + * For AI configuration actions on proxy: + * `GET /api/ai/proxy/configuration`, to send configuration on the proxy. + * `GET /api/ai/proxy/descriptions`, to get all configurations descriptions on the proxy. + * For AI configurations: + * `GET /api/ai/configurations`, to get all AI configurations. + * `POST /api/ai/configurations`, to get create a AI configuration. + * `GET /api/ai/configurations/[CONFIGURATION_ID]`, to get an AI configuration. + * `PUT /api/ai/configurations/[CONFIGURATION_ID]`, to update an AI configuration. + * `DELETE /api/ai/configurations/[CONFIGURATION_ID]`, to delete an AI configuration. * For AI secrets: * `GET /api/ai/secrets`, to get all AI secret keys. * `POST /api/ai/secrets`, to create an AI secret. diff --git a/src/main/java/com/ditrit/letomodelizerapi/controller/AIConfigurationController.java b/src/main/java/com/ditrit/letomodelizerapi/controller/AIConfigurationController.java index 24ad2a3b..4e722816 100644 --- a/src/main/java/com/ditrit/letomodelizerapi/controller/AIConfigurationController.java +++ b/src/main/java/com/ditrit/letomodelizerapi/controller/AIConfigurationController.java @@ -8,6 +8,8 @@ import com.ditrit.letomodelizerapi.model.permission.EntityPermission; import com.ditrit.letomodelizerapi.persistence.model.User; import com.ditrit.letomodelizerapi.service.AIConfigurationService; +import com.ditrit.letomodelizerapi.service.AISecretService; +import com.ditrit.letomodelizerapi.service.AIService; import com.ditrit.letomodelizerapi.service.UserPermissionService; import com.ditrit.letomodelizerapi.service.UserService; import jakarta.servlet.http.HttpServletRequest; @@ -50,14 +52,25 @@ public class AIConfigurationController implements DefaultController { /** - * Service to manage ai configuration. + * Service to manage AI configuration. */ private final AIConfigurationService aiConfigurationService; + /** + * Service to manage AI secrets. + */ + private final AISecretService aiSecretService; + + /** + * Service to manage AI configuration. + */ + private final AIService aiService; + /** * Service to manage user. */ private final UserService userService; + /** * Service to manage user permissions. */ @@ -143,6 +156,10 @@ public Response createConfiguration(final @Context HttpServletRequest request, aiConfigurationRecord.key()); var aiConfiguration = aiConfigurationService.create(aiConfigurationRecord); + var configuration = aiSecretService.generateConfiguration(aiConfiguration); + + aiService.sendConfiguration(configuration); + return Response.status(HttpStatus.CREATED.value()) .entity(new BeanMapper<>(AIConfigurationDTO.class).apply(aiConfiguration)) .build(); @@ -172,7 +189,11 @@ public Response updateConfiguration(final @Context HttpServletRequest request, userPermissionService.checkPermission(user, "id", EntityPermission.AI_CONFIGURATION, ActionPermission.UPDATE); log.info("[{}] Received PUT request to update configuration {}", user.getLogin(), id.toString()); + var aiConfiguration = aiConfigurationService.update(id, aiConfigurationRecord); + var configuration = aiSecretService.generateConfiguration(aiConfiguration); + + aiService.sendConfiguration(configuration); return Response.status(HttpStatus.OK.value()) .entity(new BeanMapper<>(AIConfigurationDTO.class).apply(aiConfiguration)) @@ -200,6 +221,14 @@ public Response deleteConfiguration(final @Context HttpServletRequest request, userPermissionService.checkPermission(user, "id", EntityPermission.AI_CONFIGURATION, ActionPermission.DELETE); log.info("[{}] Received DELETE request to delete configuration {}", user.getLogin(), id); + var aiConfiguration = aiConfigurationService.findById(id); + + aiConfiguration.setValue(null); + + var configuration = aiSecretService.generateConfiguration(aiConfiguration); + + aiService.sendConfiguration(configuration); + aiConfigurationService.delete(id); return Response.noContent().build(); diff --git a/src/main/java/com/ditrit/letomodelizerapi/controller/AIController.java b/src/main/java/com/ditrit/letomodelizerapi/controller/AIController.java index 3091d0a2..f3c42794 100644 --- a/src/main/java/com/ditrit/letomodelizerapi/controller/AIController.java +++ b/src/main/java/com/ditrit/letomodelizerapi/controller/AIController.java @@ -1,6 +1,7 @@ package com.ditrit.letomodelizerapi.controller; +import com.ditrit.letomodelizerapi.config.Constants; import com.ditrit.letomodelizerapi.controller.model.QueryFilter; import com.ditrit.letomodelizerapi.model.BeanMapper; import com.ditrit.letomodelizerapi.model.ai.AIConversationDTO; @@ -12,6 +13,7 @@ import com.ditrit.letomodelizerapi.model.permission.ActionPermission; import com.ditrit.letomodelizerapi.model.permission.EntityPermission; import com.ditrit.letomodelizerapi.persistence.model.User; +import com.ditrit.letomodelizerapi.service.AISecretService; import com.ditrit.letomodelizerapi.service.AIService; import com.ditrit.letomodelizerapi.service.UserPermissionService; import com.ditrit.letomodelizerapi.service.UserService; @@ -69,6 +71,11 @@ public class AIController implements DefaultController { */ private AIService aiService; + /** + * Service to manage AI request. + */ + private AISecretService aiSecretService; + /** * Handles a POST request to generate files with an Artificial Intelligence (AI) based on the provided * request details. @@ -287,4 +294,54 @@ public Response findAllMessages(final @Context HttpServletRequest request, return Response.status(this.getStatus(resources)).entity(resources).build(); } + + /** + * Sends the generated configuration to the AI proxy. + *

+ * This method handles GET requests to the endpoint {@code /proxy/configuration}. + * It uses the {@code aiSecretService} to generate a configuration that is then sent to the AI proxy. + * + * @param request the {@link HttpServletRequest} containing the current HTTP request information, used to retrieve + * the session details. + * @return a {@link Response} with a 204 (No Content) status, indicating the configuration was successfully sent + * to the AI proxy. + */ + @GET + @Path("/proxy/configuration") + public Response sendConfigurationToProxy(final @Context HttpServletRequest request) { + HttpSession session = request.getSession(); + log.info("[{}] Received GET request to send configuration to proxy", + session.getAttribute(Constants.DEFAULT_USER_PROPERTY)); + + var configuration = aiSecretService.generateConfiguration(); + + aiService.sendConfiguration(configuration); + + return Response.noContent().build(); + } + + /** + * Retrieves configuration descriptions from the AI proxy. + *

+ * This method handles GET requests to the endpoint {@code /proxy/descriptions}. + * If the user has permission, the method retrieves and returns the configuration descriptions from the AI proxy. + * + * @param request the {@link HttpServletRequest} containing the current HTTP request information, used to retrieve + * the session details and user information. + * @return a {@link Response} containing the configuration descriptions in the response body with a 200 (OK) status. + * If the user lacks permission, an appropriate error response will be returned. + */ + @GET + @Path("/proxy/descriptions") + public Response retrieveConfigurationDescriptions(final @Context HttpServletRequest request) { + HttpSession session = request.getSession(); + User user = userService.getFromSession(session); + userPermissionService.checkPermission(user, "id", EntityPermission.AI_SECRET, ActionPermission.ACCESS); + + log.info("[{}] Received GET request to get configuration descriptions from proxy", user.getLogin()); + + var descriptions = aiService.getConfigurationDescriptions(); + + return Response.ok(descriptions).build(); + } } diff --git a/src/main/java/com/ditrit/letomodelizerapi/service/AISecretService.java b/src/main/java/com/ditrit/letomodelizerapi/service/AISecretService.java index 704e9993..17e37930 100644 --- a/src/main/java/com/ditrit/letomodelizerapi/service/AISecretService.java +++ b/src/main/java/com/ditrit/letomodelizerapi/service/AISecretService.java @@ -1,6 +1,7 @@ package com.ditrit.letomodelizerapi.service; import com.ditrit.letomodelizerapi.model.ai.AISecretRecord; +import com.ditrit.letomodelizerapi.persistence.model.AIConfiguration; import com.ditrit.letomodelizerapi.persistence.model.AISecret; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -54,4 +55,18 @@ public interface AISecretService { * @param id the ID of the AISecret entity to delete. */ void delete(UUID id); + + /** + * Retrieve all configurations, apply secrets in configuration values and return encrypted configuration for AI + * proxy. + * @return Encrypted configuration. + */ + byte[] generateConfiguration(); + + /** + * Apply secret in provided configuration value and return encrypted configuration for AI proxy. + * @param configuration Provided configuration. + * @return Encrypted configuration. + */ + byte[] generateConfiguration(AIConfiguration configuration); } diff --git a/src/main/java/com/ditrit/letomodelizerapi/service/AISecretServiceImpl.java b/src/main/java/com/ditrit/letomodelizerapi/service/AISecretServiceImpl.java index 62240d8d..efa34e58 100644 --- a/src/main/java/com/ditrit/letomodelizerapi/service/AISecretServiceImpl.java +++ b/src/main/java/com/ditrit/letomodelizerapi/service/AISecretServiceImpl.java @@ -5,9 +5,13 @@ import com.ditrit.letomodelizerapi.model.ai.AISecretRecord; import com.ditrit.letomodelizerapi.model.error.ApiException; import com.ditrit.letomodelizerapi.model.error.ErrorType; +import com.ditrit.letomodelizerapi.persistence.model.AIConfiguration; import com.ditrit.letomodelizerapi.persistence.model.AISecret; +import com.ditrit.letomodelizerapi.persistence.repository.AIConfigurationRepository; import com.ditrit.letomodelizerapi.persistence.repository.AISecretRepository; import com.ditrit.letomodelizerapi.persistence.specification.SpecificationHelper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.hubspot.jinjava.Jinjava; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +28,8 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -58,11 +64,23 @@ public class AISecretServiceImpl implements AISecretService { */ private final AISecretRepository aiSecretRepository; + /** + * The AIConfigurationRepository instance is injected by Spring's dependency injection mechanism. + * This repository is used for performing database operations related to AIConfiguration entities, + * such as querying, saving, and updating access control data. + */ + private final AIConfigurationRepository aiConfigurationRepository; + /** * The key to encrypt or decrypt secret value. */ private final String secretEncryptionKey; + /** + * The key to encrypt or decrypt configuration. + */ + private final String configurationEncryptionKey; + /** * Size of IV. */ @@ -81,13 +99,19 @@ public class AISecretServiceImpl implements AISecretService { * Constructor for AISecretServiceImpl. * * @param aiSecretRepository Repository to manage AISecret. + * @param aiConfigurationRepository Repository to manage AIConfiguration. * @param secretEncryptionKey the key to encrypt or decrypt secret value. + * @param configurationEncryptionKey the key to encrypt or decrypt configuration. */ @Autowired public AISecretServiceImpl(final AISecretRepository aiSecretRepository, - @Value("${ai.secrets.encryption.key}") final String secretEncryptionKey) { + final AIConfigurationRepository aiConfigurationRepository, + @Value("${ai.secrets.encryption.key}") final String secretEncryptionKey, + @Value("${ai.configuration.encryption.key}") final String configurationEncryptionKey) { this.aiSecretRepository = aiSecretRepository; + this.aiConfigurationRepository = aiConfigurationRepository; this.secretEncryptionKey = secretEncryptionKey; + this.configurationEncryptionKey = configurationEncryptionKey; } /** @@ -96,10 +120,11 @@ public AISecretServiceImpl(final AISecretRepository aiSecretRepository, * and uses AES encryption to secure the plain text. The resulting byte array contains both the IV and the * encrypted text. * + * @param key key to encrypt the text. * @param plainText the plain text to be encrypted. * @return a byte array containing the IV and the encrypted text. */ - private byte[] encrypt(final String plainText) { + private byte[] encrypt(final String key, final String plainText) { try { byte[] clean = plainText.getBytes(); @@ -111,7 +136,7 @@ private byte[] encrypt(final String plainText) { // Hashing key MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(secretEncryptionKey.getBytes(StandardCharsets.UTF_8)); + digest.update(key.getBytes(StandardCharsets.UTF_8)); byte[] keyBytes = new byte[KEY_SIZE]; System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES"); @@ -137,10 +162,11 @@ private byte[] encrypt(final String plainText) { * This method extracts the IV, hashes the provided secret key using SHA-256, and uses AES decryption * to convert the encrypted bytes back into plain text. * + * @param key key to decrypt the text. * @param encryptedIvTextBytes a byte array containing the IV and the encrypted text. * @return the decrypted plain text. */ - private String decrypt(final byte[] encryptedIvTextBytes) { + private String decrypt(final String key, final byte[] encryptedIvTextBytes) { try { // Extract IV byte[] iv = new byte[IV_SIZE]; @@ -155,7 +181,7 @@ private String decrypt(final byte[] encryptedIvTextBytes) { // Hash key byte[] keyBytes = new byte[KEY_SIZE]; MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(secretEncryptionKey.getBytes(StandardCharsets.UTF_8)); + md.update(key.getBytes(StandardCharsets.UTF_8)); System.arraycopy(md.digest(), 0, keyBytes, 0, keyBytes.length); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES"); @@ -227,7 +253,7 @@ public AISecret create(final AISecretRecord aiSecretRecord) { var aiSecret = new AISecret(); aiSecret.setKey(aiSecretRecord.key()); - aiSecret.setValue(encrypt(aiSecretRecord.value())); + aiSecret.setValue(encrypt(secretEncryptionKey, aiSecretRecord.value())); aiSecret = aiSecretRepository.save(aiSecret); @@ -241,7 +267,7 @@ public AISecret create(final AISecretRecord aiSecretRecord) { public AISecret update(final UUID id, final AISecretRecord aiSecretRecord) { AISecret aiSecret = findById(id); - aiSecret.setValue(encrypt(aiSecretRecord.value())); + aiSecret.setValue(encrypt(secretEncryptionKey, aiSecretRecord.value())); aiSecret = aiSecretRepository.save(aiSecret); @@ -257,4 +283,57 @@ public void delete(final UUID id) { aiSecretRepository.delete(aiSecret); } + + @Override + public byte[] generateConfiguration() { + return generateConfiguration(aiConfigurationRepository.findAll()); + } + + @Override + public byte[] generateConfiguration(final AIConfiguration configuration) { + return generateConfiguration(List.of(configuration)); + } + + /** + * Generates an encrypted configuration file using a list of AI configurations. + *

+ * This method uses the Jinjava templating engine to process configuration values and replace + * placeholders with decrypted secret values. It collects all secrets from the {@code aiSecretRepository}, + * decrypts them, and stores them in a context map. Then, it iterates over the provided configurations, + * applies the secret replacements, and constructs a JSON object containing the processed configuration values. + * Finally, the generated configuration is encrypted before being returned as a byte array. + * + * @param configurations a list of {@link AIConfiguration} objects that define the keys, values, and optional + * handlers for the configuration. The values may contain placeholders for secrets that will + * be replaced using Jinjava. + * @return a byte array representing the encrypted configuration, ready to be sent to the AI proxy. + * @throws ApiException if encryption fails or any other unexpected error occurs during processing. + */ + public byte[] generateConfiguration(final List configurations) { + var jinjava = new Jinjava(); + var secrets = new HashMap(); + var context = new HashMap(); + var json = JsonNodeFactory.instance.objectNode(); + + aiSecretRepository.findAll().forEach(secret -> secrets.put(secret.getKey(), + decrypt(secretEncryptionKey, secret.getValue()))); + + context.put("secrets", secrets); + + configurations.forEach(configuration -> { + String key = null; + + if (configuration.getHandler() == null) { + key = configuration.getKey(); + } else { + key = String.format("%s.%s", configuration.getHandler(), configuration.getKey()); + } + + json.put(key, jinjava.render(configuration.getValue(), context)); + }); + + System.out.println(json.toPrettyString()); + + return encrypt(configurationEncryptionKey, json.toString()); + } } diff --git a/src/main/java/com/ditrit/letomodelizerapi/service/AIService.java b/src/main/java/com/ditrit/letomodelizerapi/service/AIService.java index 1067b37f..756efa2a 100644 --- a/src/main/java/com/ditrit/letomodelizerapi/service/AIService.java +++ b/src/main/java/com/ditrit/letomodelizerapi/service/AIService.java @@ -118,4 +118,25 @@ AIConversation updateConversationById(User user, UUID id, AIConversationRecord a Page findAllConversations(Map immutableFilters, Pageable pageable); + /** + * Sends the encrypted configuration to the AI proxy. + *

+ * This method accepts a byte array representing the encrypted configuration and sends it to the AI proxy for + * further processing or application. The configuration is assumed to have been generated and encrypted by the + * caller before being passed to this method. + * + * @param configuration a byte array containing the encrypted configuration data to be sent to the AI proxy. + */ + void sendConfiguration(byte[] configuration); + + /** + * Retrieves the descriptions of the configurations from the AI proxy. + *

+ * This method communicates with the AI proxy to retrieve a list or summary of configuration descriptions that are + * currently available. The descriptions provide insight into the configurations being used or processed by the + * proxy. The result is returned as a string, typically in a JSON or plain text format. + * + * @return a string representing the configuration descriptions retrieved from the AI proxy. + */ + String getConfigurationDescriptions(); } diff --git a/src/main/java/com/ditrit/letomodelizerapi/service/AIServiceImpl.java b/src/main/java/com/ditrit/letomodelizerapi/service/AIServiceImpl.java index 70a0f3ee..2890fd59 100644 --- a/src/main/java/com/ditrit/letomodelizerapi/service/AIServiceImpl.java +++ b/src/main/java/com/ditrit/letomodelizerapi/service/AIServiceImpl.java @@ -100,17 +100,18 @@ public AIServiceImpl(final AIConversationRepository aiConversationRepository, * Sends a request to the AI service with the specified endpoint and request body. * * @param endpoint the URL of the AI endpoint to which the request is sent. + * @param contentType the content type of the body. * @param body the content to be sent in the body of the request. * @return the response body returned by the AI service. */ - public String sendRequest(final String endpoint, final String body) { + public String sendRequest(final String endpoint, final String contentType, final byte[] body) { try { URI uri = new URI(aiHost).resolve("api/").resolve(endpoint); HttpRequest request = HttpRequest.newBuilder() .uri(uri) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, contentType) .headers(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .POST(HttpRequest.BodyPublishers.ofString(body)) + .POST(HttpRequest.BodyPublishers.ofByteArray(body)) .build(); HttpResponse response = HttpClient @@ -119,7 +120,7 @@ public String sendRequest(final String endpoint, final String body) { .send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == ErrorType.AI_GENERATION_ERROR.getCode()) { - throw new ApiException(ErrorType.AI_GENERATION_ERROR, "body", body); + throw new ApiException(ErrorType.AI_GENERATION_ERROR, "body"); } if (!HttpStatus.valueOf(response.statusCode()).is2xxSuccessful()) { @@ -161,7 +162,8 @@ public String sendFiles(final AIConversation conversation, final List findAllConversations(final Map immut pageable.getSortOr(Sort.by(Sort.Direction.DESC, Constants.DEFAULT_UPDATE_DATE_PROPERTY)) )); } + + @Override + public void sendConfiguration(final byte[] configuration) { + sendRequest("configuration", MediaType.APPLICATION_OCTET_STREAM, configuration); + } + + @Override + public String getConfigurationDescriptions() { + final var endpoint = "api/configuration/descriptions"; + try { + URI uri = new URI(aiHost).resolve(endpoint); + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .headers(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + .GET() + .build(); + + HttpResponse response = HttpClient + .newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()); + + if (!HttpStatus.valueOf(response.statusCode()).is2xxSuccessful()) { + throw new ApiException(ErrorType.WRONG_VALUE, "url", uri.toString()); + } + + return response.body(); + } catch (URISyntaxException | IOException e) { + throw new ApiException(ErrorType.WRONG_VALUE, "url", aiHost + endpoint); + } catch (InterruptedException e) { + log.warn("InterruptedException during requesting ai with {}", aiHost + endpoint, e); + Thread.currentThread().interrupt(); + throw new ApiException(ErrorType.INTERNAL_ERROR, "url", aiHost + endpoint); + } + } } diff --git a/src/main/resources/ai-configuration-description-schema.json b/src/main/resources/ai-configuration-description-schema.json new file mode 100644 index 00000000..1a9b970c --- /dev/null +++ b/src/main/resources/ai-configuration-description-schema.json @@ -0,0 +1,127 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "unevaluatedProperties": false, + "type": "object", + "required": ["name", "version", "description", "maintainer"], + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "version": { + "type": "string", + "maxLength": 255 + }, + "description": { + "type": "string" + }, + "maintainer": { + "type": "string", + "maxLength": 255 + }, + "icon": { + "type": "string" + }, + "templates": { + "type": "array", + "items": { + "anyOf": [{ + "type": "object", + "unevaluatedItems": false, + "minItems": 1, + "required": ["name", "type", "basePath", "plugins", "files"], + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "type": { + "enum": ["PROJECT"] + }, + "description": { + "type": "string" + }, + "documentationUrl": { + "type": "string" + }, + "basePath": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "plugins": { + "type": "array", + "items": { + "type": "string" + }, + "unevaluatedItems": false, + "minItems": 1 + }, + "schemas": { + "type": "array", + "items": { + "type": "string" + }, + "unevaluatedItems": false + }, + "files": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "unevaluatedItems": false, + "minItems": 1 + } + } + }, { + "type": "object", + "unevaluatedItems": false, + "minItems": 1, + "required": ["name", "type", "basePath", "plugin", "files"], + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "type": { + "enum": ["DIAGRAM", "COMPONENT"] + }, + "description": { + "type": "string" + }, + "documentationUrl": { + "type": "string" + }, + "basePath": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "plugin": { + "type": "string" + }, + "schemas": { + "type": "array", + "items": { + "type": "string" + }, + "unevaluatedItems": false + }, + "files": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "unevaluatedItems": false, + "minItems": 1 + } + } + }] + } + } + } +} diff --git a/src/test/java/com/ditrit/letomodelizerapi/controller/AIConfigurationControllerTest.java b/src/test/java/com/ditrit/letomodelizerapi/controller/AIConfigurationControllerTest.java index e5765635..c9c933f2 100644 --- a/src/test/java/com/ditrit/letomodelizerapi/controller/AIConfigurationControllerTest.java +++ b/src/test/java/com/ditrit/letomodelizerapi/controller/AIConfigurationControllerTest.java @@ -6,6 +6,8 @@ import com.ditrit.letomodelizerapi.persistence.model.AIConfiguration; import com.ditrit.letomodelizerapi.persistence.model.User; import com.ditrit.letomodelizerapi.service.AIConfigurationService; +import com.ditrit.letomodelizerapi.service.AISecretService; +import com.ditrit.letomodelizerapi.service.AIService; import com.ditrit.letomodelizerapi.service.UserPermissionService; import com.ditrit.letomodelizerapi.service.UserService; import jakarta.servlet.http.HttpServletRequest; @@ -41,6 +43,12 @@ class AIConfigurationControllerTest extends MockHelper { @Mock AIConfigurationService aiConfigurationService; + @Mock + AISecretService aiSecretService; + + @Mock + AIService aiService; + @InjectMocks AIConfigurationController controller; @@ -100,6 +108,8 @@ void testCreateConfiguration() { Mockito.doNothing().when(userPermissionService).checkPermission(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.when(this.aiConfigurationService.create(Mockito.any())).thenReturn(new AIConfiguration()); + Mockito.when(this.aiSecretService.generateConfiguration(Mockito.any())).thenReturn("test".getBytes()); + Mockito.doNothing().when(this.aiService).sendConfiguration(Mockito.any()); final Response response = this.controller.createConfiguration(request, new AIConfigurationRecord("handler","key", "value")); @@ -123,6 +133,8 @@ void testUpdateConfiguration() { Mockito.any()); Mockito.when(this.aiConfigurationService.update(Mockito.any(), Mockito.any())) .thenReturn(new AIConfiguration()); + Mockito.when(this.aiSecretService.generateConfiguration(Mockito.any())).thenReturn("test".getBytes()); + Mockito.doNothing().when(this.aiService).sendConfiguration(Mockito.any()); final Response response = this.controller.updateConfiguration(request, UUID.randomUUID(), new AIConfigurationRecord("handler", "key", "value")); @@ -133,7 +145,7 @@ void testUpdateConfiguration() { } @Test - @DisplayName("Test updateConfiguration: should return valid response on delete a configuration.") + @DisplayName("Test deleteConfiguration: should return valid response on delete a configuration.") void testDeleteConfiguration() { User user = new User(); user.setLogin("login"); @@ -145,6 +157,9 @@ void testDeleteConfiguration() { Mockito.doNothing().when(userPermissionService).checkPermission(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.doNothing().when(aiConfigurationService).delete(Mockito.any()); + Mockito.when(aiConfigurationService.findById(Mockito.any())).thenReturn(new AIConfiguration()); + Mockito.when(this.aiSecretService.generateConfiguration(Mockito.any())).thenReturn("test".getBytes()); + Mockito.doNothing().when(this.aiService).sendConfiguration(Mockito.any()); final Response response = this.controller.deleteConfiguration(request, UUID.randomUUID()); diff --git a/src/test/java/com/ditrit/letomodelizerapi/service/AISecretImplTest.java b/src/test/java/com/ditrit/letomodelizerapi/service/AISecretImplTest.java index 7086c30e..23f6afcc 100644 --- a/src/test/java/com/ditrit/letomodelizerapi/service/AISecretImplTest.java +++ b/src/test/java/com/ditrit/letomodelizerapi/service/AISecretImplTest.java @@ -4,6 +4,7 @@ import com.ditrit.letomodelizerapi.model.error.ApiException; import com.ditrit.letomodelizerapi.model.error.ErrorType; import com.ditrit.letomodelizerapi.persistence.model.AISecret; +import com.ditrit.letomodelizerapi.persistence.repository.AIConfigurationRepository; import com.ditrit.letomodelizerapi.persistence.repository.AISecretRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -37,13 +38,17 @@ class AISecretImplTest { @Mock AISecretRepository aiSecretRepository; + @Mock + AIConfigurationRepository aiConfigurationRepository; + @InjectMocks AISecretServiceImpl service; @BeforeEach public void setUp() { MockitoAnnotations.openMocks(this); - service = new AISecretServiceImpl(aiSecretRepository, "secret"); // Initialisation avec la clé + service = new AISecretServiceImpl(aiSecretRepository, aiConfigurationRepository, "secret1", + "secret2"); } @Test