diff --git a/README.md b/README.md index b2f24c4..3675370 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -# Gen Commiting Application +# Commmit Craft API + +Craft ## Requirements - Java 17+ @@ -29,12 +31,12 @@ 3. If you want to build the Docker image, use the following command: ```bash - docker build -t gen-commiting . + docker build -t commmit-craft . ``` 4. Run the Docker container: ```bash - docker run -d -p 9000:9000 --name gen-commiting gen-commiting + docker run -d -p 8090:8090 --name commmit-craft commmit-craft ``` 5. The application will be available at `http://localhost:8090`. @@ -56,7 +58,7 @@ The `translate` module integrates with DeepL for machine translation. To use thi ## Configuration -You can specify different profiles for the application. For example, to use the `kam` profile: +You can specify different profiles for the application. For example, to use the `dev` profile: 1. In `application.yml`: ```properties diff --git a/build.gradle b/build.gradle index 2673540..2da365c 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,12 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.modelmapper:modelmapper:3.2.2' + implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' + implementation 'org.glassfish:jakarta.el:4.0.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' + testImplementation 'org.mockito:mockito-core:4.5.1' + testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1' } dependencyManagement { diff --git a/settings.gradle b/settings.gradle index ed01c7a..b8713be 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ -rootProject.name = 'gen' +rootProject.name = 'commit-craft' include 'translate' diff --git a/src/main/java/pl/commit/craft/CommitCraftApplication.java b/src/main/java/pl/commit/craft/CommitCraftApplication.java new file mode 100644 index 0000000..025cd4b --- /dev/null +++ b/src/main/java/pl/commit/craft/CommitCraftApplication.java @@ -0,0 +1,13 @@ +package pl.commit.craft; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = {"pl.commit.craft"}) +public class CommitCraftApplication { + + public static void main(String[] args) { + SpringApplication.run(CommitCraftApplication.class, args); + } + +} diff --git a/src/main/java/pl/commit/craft/controller/CommitTranslateController.java b/src/main/java/pl/commit/craft/controller/CommitTranslateController.java new file mode 100644 index 0000000..c04a4c2 --- /dev/null +++ b/src/main/java/pl/commit/craft/controller/CommitTranslateController.java @@ -0,0 +1,28 @@ +package pl.commit.craft.controller; + +import org.springframework.web.bind.annotation.*; +import pl.commit.craft.service.CommitTranslateService; + +@RestController +@RequestMapping("/api/v1/commit-translate") +public class CommitTranslateController { + + private final CommitTranslateService commitTranslateService; + + public CommitTranslateController(CommitTranslateService commitTranslateService) { + this.commitTranslateService = commitTranslateService; + } + + @PostMapping("/craft") + public String generateCommit(@RequestBody CommitTranslateRequest commitTranslateRequest) { + return commitTranslateService.generateTranslateCommit( + commitTranslateRequest.major(), + commitTranslateRequest.type(), + commitTranslateRequest.component(), + commitTranslateRequest.changeDescription(), + commitTranslateRequest.details(), + commitTranslateRequest.wholeGitCommand(), + commitTranslateRequest.language() + ); + } +} diff --git a/src/main/java/pl/commit/gen/controller/CommitTranslateRequest.java b/src/main/java/pl/commit/craft/controller/CommitTranslateRequest.java similarity index 64% rename from src/main/java/pl/commit/gen/controller/CommitTranslateRequest.java rename to src/main/java/pl/commit/craft/controller/CommitTranslateRequest.java index 672234a..0a2f0bd 100644 --- a/src/main/java/pl/commit/gen/controller/CommitTranslateRequest.java +++ b/src/main/java/pl/commit/craft/controller/CommitTranslateRequest.java @@ -1,4 +1,4 @@ -package pl.commit.gen.controller; +package pl.commit.craft.controller; public record CommitTranslateRequest( String major, @@ -6,5 +6,6 @@ public record CommitTranslateRequest( String component, String changeDescription, String details, - boolean wholeGitCommand + boolean wholeGitCommand, + String language ) {} diff --git a/src/main/java/pl/commit/gen/error/ErrorResponseCommiting.java b/src/main/java/pl/commit/craft/error/ErrorResponseCommiting.java similarity index 95% rename from src/main/java/pl/commit/gen/error/ErrorResponseCommiting.java rename to src/main/java/pl/commit/craft/error/ErrorResponseCommiting.java index d3fe0ac..6ba784d 100644 --- a/src/main/java/pl/commit/gen/error/ErrorResponseCommiting.java +++ b/src/main/java/pl/commit/craft/error/ErrorResponseCommiting.java @@ -1,4 +1,4 @@ -package pl.commit.gen.error; +package pl.commit.craft.error; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/pl/commit/craft/error/RestExceptionHandler.java b/src/main/java/pl/commit/craft/error/RestExceptionHandler.java new file mode 100644 index 0000000..d194312 --- /dev/null +++ b/src/main/java/pl/commit/craft/error/RestExceptionHandler.java @@ -0,0 +1,47 @@ +package pl.commit.craft.error; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.ErrorResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.Map; + +@ControllerAdvice +public class RestExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) { + ErrorResponseCommiting errorResponseCommiting = new ErrorResponseCommiting("INVALID_ARGUMENT", ex.getMessage()); + return new ResponseEntity<>(errorResponseCommiting, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) { + Map body = new LinkedHashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", HttpStatus.BAD_REQUEST.value()); + body.put("error", "Invalid JSON request"); + body.put("message", ex.getLocalizedMessage()); + body.put("path", ""); + + return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneralException(Exception ex) { + Map body = new LinkedHashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value()); + body.put("error", "Internal Server Error"); + body.put("message", ex.getLocalizedMessage()); + body.put("path", ""); + + return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); + } +} + diff --git a/src/main/java/pl/commit/gen/flow/CommitFlowController.java b/src/main/java/pl/commit/craft/flow/CommitFlowController.java similarity index 62% rename from src/main/java/pl/commit/gen/flow/CommitFlowController.java rename to src/main/java/pl/commit/craft/flow/CommitFlowController.java index bac1520..898e573 100644 --- a/src/main/java/pl/commit/gen/flow/CommitFlowController.java +++ b/src/main/java/pl/commit/craft/flow/CommitFlowController.java @@ -1,24 +1,24 @@ -package pl.commit.gen.flow; +package pl.commit.craft.flow; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import pl.commit.gen.service.CommitService; +import pl.commit.craft.service.CommitTranslateService; @RestController -@RequestMapping("/api/commit-flow") +@RequestMapping("/api/v1/commit-flow") public class CommitFlowController { - private final CommitService commitService; + private final CommitTranslateService commitTranslateService; - public CommitFlowController(CommitService commitService) { - this.commitService = commitService; + public CommitFlowController(CommitTranslateService commitTranslateService) { + this.commitTranslateService = commitTranslateService; } - @PostMapping("/generate") + @PostMapping("/craft") public String generateCommit(@RequestBody CommitFlowRequest commitFlowRequest) { - return commitService.generateFlowCommit( + return commitTranslateService.generateFlowCommit( commitFlowRequest.major(), commitFlowRequest.type(), commitFlowRequest.component(), diff --git a/src/main/java/pl/commit/gen/flow/CommitFlowRequest.java b/src/main/java/pl/commit/craft/flow/CommitFlowRequest.java similarity index 86% rename from src/main/java/pl/commit/gen/flow/CommitFlowRequest.java rename to src/main/java/pl/commit/craft/flow/CommitFlowRequest.java index 714e2ee..b057a7a 100644 --- a/src/main/java/pl/commit/gen/flow/CommitFlowRequest.java +++ b/src/main/java/pl/commit/craft/flow/CommitFlowRequest.java @@ -1,4 +1,4 @@ -package pl.commit.gen.flow; +package pl.commit.craft.flow; record CommitFlowRequest( String major, diff --git a/src/main/java/pl/commit/craft/pattern/BasicModelPattern.java b/src/main/java/pl/commit/craft/pattern/BasicModelPattern.java new file mode 100644 index 0000000..462c6ce --- /dev/null +++ b/src/main/java/pl/commit/craft/pattern/BasicModelPattern.java @@ -0,0 +1,13 @@ +package pl.commit.craft.pattern; + +sealed class BasicModelPattern permits CommitModelPattern { + private static final String DEFAULT_TARGET_LANG = "EN"; + + protected BasicModelPattern() { + throw new IllegalStateException("Utility class"); + } + + public static String getTargetLanguage(String language) { + return language == null ? DEFAULT_TARGET_LANG : language; + } +} diff --git a/src/main/java/pl/commit/gen/pattern/CommitModelPattern.java b/src/main/java/pl/commit/craft/pattern/CommitModelPattern.java similarity index 77% rename from src/main/java/pl/commit/gen/pattern/CommitModelPattern.java rename to src/main/java/pl/commit/craft/pattern/CommitModelPattern.java index 971ba42..499572a 100644 --- a/src/main/java/pl/commit/gen/pattern/CommitModelPattern.java +++ b/src/main/java/pl/commit/craft/pattern/CommitModelPattern.java @@ -1,4 +1,4 @@ -package pl.commit.gen.pattern; +package pl.commit.craft.pattern; public final class CommitModelPattern extends BasicModelPattern { private static final String GIT_COMMAND = "git commit -m"; @@ -46,11 +46,12 @@ private static String getCommittingWorkPatternWithoutComponentAndDetails() { } /** - * Główna metoda wybierająca odpowiedni wzorzec na podstawie flagi `wholeGitCommand`, `component` i `details`. - * @param wholeGitCommand - jeśli true, zwraca wzorzec z pełnym git commit. - * @param component - nazwa komponentu, może być pusta. - * @param details - szczegóły, mogą być puste. - * @return odpowiedni wzorzec. + * Main method for selecting the appropriate pattern based on the `wholeGitCommand`, `component`, and `details` flags. + * + * @param wholeGitCommand - if true, returns a pattern with the full Git commit command. + * @param component - the name of the component, may be empty. + * @param details - additional details, may be empty. + * @return the appropriate pattern. */ public static String getPattern(boolean wholeGitCommand, String component, String details) { if (wholeGitCommand) { @@ -61,10 +62,12 @@ public static String getPattern(boolean wholeGitCommand, String component, Strin } /** - * Zwraca odpowiedni wzorzec z pełnym poleceniem Git, w zależności od tego, czy `component` i `details` są puste. - * @param component - nazwa komponentu. - * @param details - szczegóły. - * @return wzorzec z pełnym poleceniem Git. + * Returns the appropriate pattern with the full Git command, + * depending on whether `component` and `details` are empty or not. + * + * @param component - the name of the component. + * @param details - additional details. + * @return a pattern with the full Git commit command. */ private static String getPatternWithGitCommand(String component, String details) { if (component.isEmpty() && details.isEmpty()) { @@ -79,10 +82,12 @@ private static String getPatternWithGitCommand(String component, String details) } /** - * Zwraca odpowiedni wzorzec bez pełnego polecenia Git, w zależności od tego, czy `component` i `details` są puste. - * @param component - nazwa komponentu. - * @param details - szczegóły. - * @return wzorzec bez pełnego polecenia Git. + * Returns the appropriate pattern without the full Git command, + * depending on whether `component` and `details` are empty or not. + * + * @param component - the name of the component. + * @param details - additional details. + * @return a pattern without the full Git commit command. */ private static String getPatternWithoutGitCommand(String component, String details) { if (component.isEmpty() && details.isEmpty()) { diff --git a/src/main/java/pl/commit/craft/quick/CommitQuickController.java b/src/main/java/pl/commit/craft/quick/CommitQuickController.java new file mode 100644 index 0000000..4b1d943 --- /dev/null +++ b/src/main/java/pl/commit/craft/quick/CommitQuickController.java @@ -0,0 +1,23 @@ +package pl.commit.craft.quick; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/commit-quick") +@RequiredArgsConstructor +public class CommitQuickController { + + final CommitQuickService commitQuickService; + + @PostMapping("/craft") + public String generateCommit(@RequestBody CommitQuickRequest commitQuickRequest) { + return commitQuickService.generateQuickCommit( + commitQuickRequest.topicScope(), + commitQuickRequest.isGitCommand() + ); + } +} diff --git a/src/main/java/pl/commit/gen/quick/QuickCommitRequest.java b/src/main/java/pl/commit/craft/quick/CommitQuickRequest.java similarity index 52% rename from src/main/java/pl/commit/gen/quick/QuickCommitRequest.java rename to src/main/java/pl/commit/craft/quick/CommitQuickRequest.java index 3e3cf7d..74b7c68 100644 --- a/src/main/java/pl/commit/gen/quick/QuickCommitRequest.java +++ b/src/main/java/pl/commit/craft/quick/CommitQuickRequest.java @@ -1,6 +1,6 @@ -package pl.commit.gen.quick; +package pl.commit.craft.quick; -record QuickCommitRequest( +record CommitQuickRequest( String topicScope, boolean isGitCommand ) { diff --git a/src/main/java/pl/commit/gen/quick/QuickCommitService.java b/src/main/java/pl/commit/craft/quick/CommitQuickService.java similarity index 92% rename from src/main/java/pl/commit/gen/quick/QuickCommitService.java rename to src/main/java/pl/commit/craft/quick/CommitQuickService.java index 1c4c8fc..2746b3f 100644 --- a/src/main/java/pl/commit/gen/quick/QuickCommitService.java +++ b/src/main/java/pl/commit/craft/quick/CommitQuickService.java @@ -1,9 +1,9 @@ -package pl.commit.gen.quick; +package pl.commit.craft.quick; import org.springframework.stereotype.Service; @Service -class QuickCommitService { +class CommitQuickService { private static final String AUDIT_COMMIT = "audit: Audit fix"; private static final String PR_FIX_COMMIT = "fix: Pull request comments improved"; private static final String TEST_FIX_COMMIT = "test: Fixed tests"; diff --git a/src/main/java/pl/commit/gen/service/CommitService.java b/src/main/java/pl/commit/craft/service/CommitTranslateService.java similarity index 76% rename from src/main/java/pl/commit/gen/service/CommitService.java rename to src/main/java/pl/commit/craft/service/CommitTranslateService.java index 8395912..c731632 100644 --- a/src/main/java/pl/commit/gen/service/CommitService.java +++ b/src/main/java/pl/commit/craft/service/CommitTranslateService.java @@ -1,24 +1,24 @@ -package pl.commit.gen.service; +package pl.commit.craft.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import pl.commit.gen.pattern.CommitModelPattern; -import pl.commit.translate.TranslateCommiting; +import pl.commit.craft.pattern.CommitModelPattern; +import pl.commit.craft.translate.TranslateCommitCraft; @Service @RequiredArgsConstructor -public class CommitService { - private final TranslateCommiting translateCommiting; +public class CommitTranslateService { + private final TranslateCommitCraft translateCommitCraft; - public String generateTranslateCommit(String major, String type, String component, String changeDescription, String details, boolean wholeGitCommand) { + public String generateTranslateCommit(String major, String type, String component, String changeDescription, String details, boolean wholeGitCommand, String language) { if (isValidType(type)) { throw new IllegalArgumentException("Invalid commit type: " + type); } MajorNumber majorNumber = MajorNumberPreparer.of(major).getMajorNumber(); - String changeDescriptionTranslated = getChangeDescriptionTranslated(changeDescription); - String detailsTranslated = !details.isEmpty() ? getChangeDescriptionTranslated(details) : ""; + String changeDescriptionTranslated = getChangeDescriptionTranslated(changeDescription, language); + String detailsTranslated = !details.isEmpty() ? getChangeDescriptionTranslated(details, language) : ""; String pattern = CommitModelPattern.getPattern(wholeGitCommand, component, detailsTranslated); return String.format( @@ -33,7 +33,7 @@ public String generateTranslateCommit(String major, String type, String componen public String generateFlowCommit(String major, String type, String component, String changeDescription, String details, boolean wholeGitCommand) { if (isValidType(type)) { - throw new IllegalArgumentException("Invalid commit type: " + type); + throw new IllegalArgumentException(String.format("Invalid commit type: %s", type)); } MajorNumber majorNumber = MajorNumberPreparer.of(major).getMajorNumber(); @@ -50,8 +50,8 @@ public String generateFlowCommit(String major, String type, String component, St ).trim(); } - private String getChangeDescriptionTranslated(String changeDescription) { - return translateCommiting.translate(changeDescription, CommitModelPattern.getTargetLanguage()); + private String getChangeDescriptionTranslated(String changeDescription, String language) { + return translateCommitCraft.translate(changeDescription, CommitModelPattern.getTargetLanguage(language)); } private boolean isValidType(String type) { diff --git a/src/main/java/pl/commit/gen/service/CommitType.java b/src/main/java/pl/commit/craft/service/CommitType.java similarity index 94% rename from src/main/java/pl/commit/gen/service/CommitType.java rename to src/main/java/pl/commit/craft/service/CommitType.java index 913c2a6..d16e9e0 100644 --- a/src/main/java/pl/commit/gen/service/CommitType.java +++ b/src/main/java/pl/commit/craft/service/CommitType.java @@ -1,4 +1,4 @@ -package pl.commit.gen.service; +package pl.commit.craft.service; import lombok.Getter; diff --git a/src/main/java/pl/commit/gen/service/MajorNumber.java b/src/main/java/pl/commit/craft/service/MajorNumber.java similarity index 57% rename from src/main/java/pl/commit/gen/service/MajorNumber.java rename to src/main/java/pl/commit/craft/service/MajorNumber.java index 7e606d8..d5ab480 100644 --- a/src/main/java/pl/commit/gen/service/MajorNumber.java +++ b/src/main/java/pl/commit/craft/service/MajorNumber.java @@ -1,3 +1,3 @@ -package pl.commit.gen.service; +package pl.commit.craft.service; record MajorNumber(String issueNumber) { } diff --git a/src/main/java/pl/commit/gen/service/MajorNumberPreparer.java b/src/main/java/pl/commit/craft/service/MajorNumberPreparer.java similarity index 96% rename from src/main/java/pl/commit/gen/service/MajorNumberPreparer.java rename to src/main/java/pl/commit/craft/service/MajorNumberPreparer.java index ccb394f..af2742e 100644 --- a/src/main/java/pl/commit/gen/service/MajorNumberPreparer.java +++ b/src/main/java/pl/commit/craft/service/MajorNumberPreparer.java @@ -1,4 +1,4 @@ -package pl.commit.gen.service; +package pl.commit.craft.service; import lombok.Getter; diff --git a/src/main/java/pl/commit/craft/template/CommitCraftJson.java b/src/main/java/pl/commit/craft/template/CommitCraftJson.java new file mode 100644 index 0000000..01d5084 --- /dev/null +++ b/src/main/java/pl/commit/craft/template/CommitCraftJson.java @@ -0,0 +1,23 @@ +package pl.commit.craft.template; + +import lombok.Getter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Getter +class CommitCraftJson { + private final Map jsonContent; + + public CommitCraftJson() { + jsonContent = new HashMap<>(); + } + + public void addField(String key, Object value) { + jsonContent.put(key, value); + } + + public Map getFields() { + return Collections.unmodifiableMap(jsonContent); + } +} diff --git a/src/main/java/pl/commit/craft/template/CommitCraftTemplate.java b/src/main/java/pl/commit/craft/template/CommitCraftTemplate.java new file mode 100644 index 0000000..e88ea00 --- /dev/null +++ b/src/main/java/pl/commit/craft/template/CommitCraftTemplate.java @@ -0,0 +1,24 @@ +package pl.commit.craft.template; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import javax.validation.constraints.NotNull; +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommitCraftTemplate { + @NotNull(message = "Name cannot be null") + private String name; + + @NotNull(message = "Description cannot be null") + private String description; + + @NotNull(message = "Pattern cannot be null") + private String pattern; + + @NotNull(message = "Model cannot be null") + private Map model; +} diff --git a/src/main/java/pl/commit/craft/template/CommitCraftTemplateController.java b/src/main/java/pl/commit/craft/template/CommitCraftTemplateController.java new file mode 100644 index 0000000..ea7ea3b --- /dev/null +++ b/src/main/java/pl/commit/craft/template/CommitCraftTemplateController.java @@ -0,0 +1,44 @@ +package pl.commit.craft.template; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/craft-template") +@RequiredArgsConstructor +public class CommitCraftTemplateController { + + private final CommitTemplateService commitTemplateService; + + @GetMapping("/all") + public ResponseEntity> getAllTemplates() throws IOException { + List templates = commitTemplateService.getAllTemplates(); + return ResponseEntity.ok(templates); + } + + @PostMapping("/dedicated") + public ResponseEntity createDedicatedTemplate(@RequestBody CommitCraftTemplate template) throws IOException { + boolean patternAndModelScope = CommitDedicatedTemplateValidator.validatePatternAndModelScope(template); + if (patternAndModelScope) { + commitTemplateService.createDedicatedTemplate(template); + return ResponseEntity.ok("Template added successfully."); + } + return ResponseEntity.badRequest().body("Template already exists."); + } + + @DeleteMapping("/removed/{name}") + public ResponseEntity addTemplate(@PathVariable("name") String name) throws IOException { + commitTemplateService.removeDedicatedTemplate(name); + return ResponseEntity.ok("Template removed successfully."); + } + + @PostMapping("/generate-json/{name}") + public Map generateJson(@PathVariable String name) throws IOException { + CommitCraftJson commitCraftJson = commitTemplateService.prepareJsonByModel(name); + return commitCraftJson.getJsonContent(); + } +} diff --git a/src/main/java/pl/commit/craft/template/CommitDedicatedTemplateValidator.java b/src/main/java/pl/commit/craft/template/CommitDedicatedTemplateValidator.java new file mode 100644 index 0000000..bf80671 --- /dev/null +++ b/src/main/java/pl/commit/craft/template/CommitDedicatedTemplateValidator.java @@ -0,0 +1,42 @@ +package pl.commit.craft.template; + +import lombok.extern.slf4j.Slf4j; +import java.util.Map; +import java.util.Set; + +@Slf4j +class CommitDedicatedTemplateValidator { + private static final String PATTERN_VALIDATE_MODEL = "\\{(\\w+)\\}(-\\{(\\w+)\\})*"; + + static boolean validatePatternAndModelScope(CommitCraftTemplate template) { + log.info("Validating pattern and model scope starting"); + String pattern = template.getPattern(); + Map model = template.getModel(); + + Set modelKeys = model.keySet(); + + boolean matches = true; + for (String key : modelKeys) { + if (!pattern.contains(key)) { + log.warn("Pattern is missing key: {}", key); + matches = false; + } + } + + String[] patternWords = pattern.split(PATTERN_VALIDATE_MODEL); + for (String word : patternWords) { + if (!modelKeys.contains(word)) { + log.warn("Pattern contains an extra key not in the model: {}", word); + matches = false; + } + } + + if (matches) { + log.info("Pattern matches the model keys."); + } else { + log.warn("Pattern does not match the model keys."); + } + + return matches; + } +} diff --git a/src/main/java/pl/commit/craft/template/CommitTemplateService.java b/src/main/java/pl/commit/craft/template/CommitTemplateService.java new file mode 100644 index 0000000..3ce7d8e --- /dev/null +++ b/src/main/java/pl/commit/craft/template/CommitTemplateService.java @@ -0,0 +1,104 @@ +package pl.commit.craft.template; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +class CommitTemplateService { + private static final String PROPERTY_TEMPLATES = "templates"; + private static final String PROPERTY_DEDICATED_TEMPLATES = "dedicated"; + private static final String PATH_NAME_ELEMENT = "name"; + private static final String RESOURCES_TEMPLATES_META_SCHEMA_JSON = "src/main/resources/templates/meta-schema.json"; + private static final String RESOURCES_DEDICATED_META_SCHEMA_JSON = "src/main/resources/templates/dedicated-meta-schema.json"; + private final ObjectMapper objectMapper; + + public List getAllTemplates() throws IOException { + JsonNode rootNode = objectMapper.readTree(new File(RESOURCES_TEMPLATES_META_SCHEMA_JSON)); + JsonNode templatesNode = rootNode.path(PROPERTY_TEMPLATES); + List templatesList = objectMapper.readValue( + templatesNode.toString(), + new TypeReference<>() { + } + ); + List templates = new ArrayList<>(templatesList); + + JsonNode rootDedicatedNode = objectMapper.readTree(new File(RESOURCES_DEDICATED_META_SCHEMA_JSON)); + JsonNode dedicatedNode = rootDedicatedNode.path(PROPERTY_DEDICATED_TEMPLATES); + List dedicatedList = objectMapper.readValue( + dedicatedNode.toString(), + new TypeReference<>() { + } + ); + templates.addAll(dedicatedList); + return templates; + } + + public CommitCraftJson prepareJsonByModel(String name) throws IOException { + List templates = getAllTemplates(); + CommitCraftTemplate selectedTemplate = templates.stream() + .filter(template -> template.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Template not found")); + return getCommitCraftJson(selectedTemplate, selectedTemplate.getName()); + } + + CommitCraftJson getCommitCraftJson(CommitCraftTemplate selectedTemplate, String selectedTemplateName) throws IOException { + Map model = selectedTemplate.getModel(); + CommitCraftJson commitCraftJson = new CommitCraftJson(); + commitCraftJson.addField(PATH_NAME_ELEMENT, selectedTemplateName); + for (Map.Entry entry : model.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof List list) { + commitCraftJson.addField(key, list); + } else { + commitCraftJson.addField(key, value); + } + } + return commitCraftJson; + } + + public List readTemplates() throws IOException { + JsonNode rootNode = objectMapper.readTree(new File(RESOURCES_DEDICATED_META_SCHEMA_JSON)); + JsonNode dedicatedNode = rootNode.path(PROPERTY_DEDICATED_TEMPLATES); + return objectMapper.convertValue(dedicatedNode, new TypeReference>() { + }); + } + + public void createDedicatedTemplate(CommitCraftTemplate newTemplate) throws IOException { + List templates = readTemplates(); + templates.add(newTemplate); + saveTemplates(templates); + } + + public void removeDedicatedTemplate(String dedicatedTemplateName) throws IOException { + JsonNode rootNode = objectMapper.readTree(new File(RESOURCES_DEDICATED_META_SCHEMA_JSON)); + ArrayNode dedicatedArray = (ArrayNode) rootNode.path(PROPERTY_DEDICATED_TEMPLATES); + for (Iterator nodeIterator = dedicatedArray.elements(); nodeIterator.hasNext(); ) { + JsonNode element = nodeIterator.next(); + if (dedicatedTemplateName.equals(element.path(PATH_NAME_ELEMENT).asText())) { + nodeIterator.remove(); + } + } + objectMapper.writeValue(new File(RESOURCES_DEDICATED_META_SCHEMA_JSON), rootNode); + } + + private void saveTemplates(List templates) throws IOException { + JsonNode rootNode = objectMapper.createObjectNode(); + ((ObjectNode) rootNode).putPOJO(PROPERTY_DEDICATED_TEMPLATES, templates); + objectMapper.writerWithDefaultPrettyPrinter().writeValue(new File(RESOURCES_DEDICATED_META_SCHEMA_JSON), rootNode); + } +} diff --git a/src/main/java/pl/commit/craft/template/ProjectTemplate.java b/src/main/java/pl/commit/craft/template/ProjectTemplate.java new file mode 100644 index 0000000..3bba9a9 --- /dev/null +++ b/src/main/java/pl/commit/craft/template/ProjectTemplate.java @@ -0,0 +1,13 @@ +package pl.commit.craft.template; + +import lombok.Data; +import java.util.Map; + +@Data +class ProjectTemplate { + private String name; + private String description; + private String pattern; + private Map model; + +} diff --git a/src/main/java/pl/commit/craft/template/generate/CommitTemplateGenerateController.java b/src/main/java/pl/commit/craft/template/generate/CommitTemplateGenerateController.java new file mode 100644 index 0000000..9cc74f2 --- /dev/null +++ b/src/main/java/pl/commit/craft/template/generate/CommitTemplateGenerateController.java @@ -0,0 +1,21 @@ +package pl.commit.craft.template.generate; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("api/v1/craft-template") +@RequiredArgsConstructor +public class CommitTemplateGenerateController { + private final CommitTemplateGenerateService service; + + @PostMapping("/generate") + public ResponseEntity generateCommit(@RequestParam String templateName, @RequestBody JsonNode commitData) throws IOException { + String commitMessage = service.generateCommit(templateName, commitData); + return ResponseEntity.ok(commitMessage); + } +} diff --git a/src/main/java/pl/commit/craft/template/generate/CommitTemplateGenerateService.java b/src/main/java/pl/commit/craft/template/generate/CommitTemplateGenerateService.java new file mode 100644 index 0000000..a6aa33b --- /dev/null +++ b/src/main/java/pl/commit/craft/template/generate/CommitTemplateGenerateService.java @@ -0,0 +1,104 @@ +package pl.commit.craft.template.generate; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +class CommitTemplateGenerateService { + + private final ObjectMapper objectMapper; + private static final String RESOURCES_TEMPLATES_META_SCHEMA_JSON = "src/main/resources/templates/meta-schema.json"; + + String generateCommit(String templateName, JsonNode commitData) throws IOException { + JsonNode rootNode = objectMapper.readTree(new File(RESOURCES_TEMPLATES_META_SCHEMA_JSON)); + JsonNode templatesArray = rootNode.path("templates"); + + Optional matchingTemplate = findTemplateByName(templatesArray, templateName); + + if (matchingTemplate.isEmpty()) { + throw new IllegalArgumentException("Template with name " + templateName + " not found"); + } + + JsonNode template = matchingTemplate.get(); + validateCommitData(template.path("model"), commitData); + + String pattern = template.path("pattern").asText(); + return fillPatternWithData(pattern, commitData); + } + + private Optional findTemplateByName(JsonNode templatesArray, String templateName) { + for (Iterator it = templatesArray.elements(); it.hasNext(); ) { + JsonNode template = it.next(); + if (templateName.equals(template.path("name").asText())) { + return Optional.of(template); + } + } + return Optional.empty(); + } + + private void validateCommitData(JsonNode model, JsonNode commitData) { + List missingFields = new ArrayList<>(); + + model.fields().forEachRemaining(field -> { + String fieldName = field.getKey(); + JsonNode fieldModel = field.getValue(); + + if (!commitData.has(fieldName)) { + missingFields.add(fieldName); + } else if (fieldModel.isArray()) { + validateArrayField(fieldName, fieldModel, commitData); + } + }); + + if (!missingFields.isEmpty()) { + throw new IllegalArgumentException("Missing required fields: " + missingFields); + } + } + + private void validateArrayField(String fieldName, JsonNode fieldModel, JsonNode commitData) { + JsonNode valueNode = commitData.path(fieldName); + + if (valueNode.isTextual()) { + String value = valueNode.asText(); + boolean isValid = false; + + for (JsonNode allowedValue : fieldModel) { + if (allowedValue.asText().equals(value)) { + isValid = true; + break; + } + } + + if (!isValid) { + throw new IllegalArgumentException("Invalid value for field '" + fieldName + "': " + value); + } + } else { + throw new IllegalArgumentException("Field '" + fieldName + "' should be a string"); + } + } + + private String fillPatternWithData(String pattern, JsonNode commitData) { + String result = pattern; + + Iterator fieldNames = commitData.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + String placeholder = "{" + fieldName + "}"; + if (result.contains(placeholder)) { + String value = commitData.path(fieldName).asText(); + result = result.replace(placeholder, value); + } + } + + return result.trim(); + } +} diff --git a/src/main/java/pl/commit/gen/CommitGenApplication.java b/src/main/java/pl/commit/gen/CommitGenApplication.java deleted file mode 100644 index 9689ef8..0000000 --- a/src/main/java/pl/commit/gen/CommitGenApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package pl.commit.gen; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication(scanBasePackages = {"pl.commit"}) -public class CommitGenApplication { - - public static void main(String[] args) { - SpringApplication.run(CommitGenApplication.class, args); - } - -} diff --git a/src/main/java/pl/commit/gen/controller/CommitTranslateController.java b/src/main/java/pl/commit/gen/controller/CommitTranslateController.java deleted file mode 100644 index daa073d..0000000 --- a/src/main/java/pl/commit/gen/controller/CommitTranslateController.java +++ /dev/null @@ -1,27 +0,0 @@ -package pl.commit.gen.controller; - -import org.springframework.web.bind.annotation.*; -import pl.commit.gen.service.CommitService; - -@RestController -@RequestMapping("/api/commit-translate") -public class CommitTranslateController { - - private final CommitService commitService; - - public CommitTranslateController(CommitService commitService) { - this.commitService = commitService; - } - - @PostMapping("/generate") - public String generateCommit(@RequestBody CommitTranslateRequest commitTranslateRequest) { - return commitService.generateTranslateCommit( - commitTranslateRequest.major(), - commitTranslateRequest.type(), - commitTranslateRequest.component(), - commitTranslateRequest.changeDescription(), - commitTranslateRequest.details(), - commitTranslateRequest.wholeGitCommand() - ); - } -} diff --git a/src/main/java/pl/commit/gen/error/RestExceptionHandler.java b/src/main/java/pl/commit/gen/error/RestExceptionHandler.java deleted file mode 100644 index 40546a8..0000000 --- a/src/main/java/pl/commit/gen/error/RestExceptionHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package pl.commit.gen.error; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.ErrorResponse; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; - -@ControllerAdvice -public class RestExceptionHandler { - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) { - ErrorResponseCommiting errorResponseCommiting = new ErrorResponseCommiting("INVALID_ARGUMENT", ex.getMessage()); - return new ResponseEntity<>(errorResponseCommiting, HttpStatus.BAD_REQUEST); - } -} - diff --git a/src/main/java/pl/commit/gen/pattern/BasicModelPattern.java b/src/main/java/pl/commit/gen/pattern/BasicModelPattern.java deleted file mode 100644 index 783b5df..0000000 --- a/src/main/java/pl/commit/gen/pattern/BasicModelPattern.java +++ /dev/null @@ -1,13 +0,0 @@ -package pl.commit.gen.pattern; - -sealed class BasicModelPattern permits CommitModelPattern { - private static final String TARGET_LANG = "EN"; - - protected BasicModelPattern() { - throw new IllegalStateException("Utility class"); - } - - public static String getTargetLanguage() { - return TARGET_LANG; - } -} diff --git a/src/main/java/pl/commit/gen/quick/QuickCommitController.java b/src/main/java/pl/commit/gen/quick/QuickCommitController.java deleted file mode 100644 index 55a1afa..0000000 --- a/src/main/java/pl/commit/gen/quick/QuickCommitController.java +++ /dev/null @@ -1,23 +0,0 @@ -package pl.commit.gen.quick; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/quick-commit") -@RequiredArgsConstructor -public class QuickCommitController { - - final QuickCommitService quickCommitService; - - @PostMapping("/generate") - public String generateCommit(@RequestBody QuickCommitRequest quickCommitRequest) { - return quickCommitService.generateQuickCommit( - quickCommitRequest.topicScope(), - quickCommitRequest.isGitCommand() - ); - } -} diff --git a/src/main/resources/application-kam.yml b/src/main/resources/application-kam.yml new file mode 100644 index 0000000..58de10c --- /dev/null +++ b/src/main/resources/application-kam.yml @@ -0,0 +1,2 @@ +server: + port: 8090 \ No newline at end of file diff --git a/src/main/resources/images/craft.jpg b/src/main/resources/images/craft.jpg new file mode 100644 index 0000000..f92030a Binary files /dev/null and b/src/main/resources/images/craft.jpg differ diff --git a/src/main/resources/templates/dedicated-meta-schema.json b/src/main/resources/templates/dedicated-meta-schema.json new file mode 100644 index 0000000..02c2457 --- /dev/null +++ b/src/main/resources/templates/dedicated-meta-schema.json @@ -0,0 +1 @@ +{"dedicated":[]} \ No newline at end of file diff --git a/src/main/resources/templates/meta-schema.json b/src/main/resources/templates/meta-schema.json new file mode 100644 index 0000000..2488f72 --- /dev/null +++ b/src/main/resources/templates/meta-schema.json @@ -0,0 +1,46 @@ +{ + "templates": [ + { + "name": "conventional", + "description": "Standardowy format Conventional Commits", + "pattern": "{type}: {scope} - {message}", + "model": { + "type": ["feat", "fix", "chore", "docs", "refactor", "test"], + "scope": "[optional]", + "message": "Zwięzły opis zmiany" + } + }, + { + "name": "detailed", + "description": "Rozszerzone informacje dla commitów", + "pattern": "[{ticket_link_number}] {type}({scope}): {message}\n\n{details}", + "model": { + "ticket_id": "Numer powiązanego zadania w JIRA", + "type": ["feat", "fix", "junk", "chore", "test"], + "scope": "[moduł lub komponent]", + "message": "Krótki opis zmiany", + "details": "Szczegóły zmian w treści commit message" + } + }, + { + "name": "markdown", + "description": "Commit message w stylu markdown", + "pattern": "# {type}: {message}\n\n## Opis zmian\n{details}\n\n### Powiązane zadania\n{related_tasks}", + "model": { + "type": ["feat", "fix", "docs"], + "message": "Krótki opis zmian", + "details": "Szczegółowy opis zmian w commitach", + "related_tasks": "Lista powiązanych zadań (np. #123, #124)" + } + }, + { + "name": "quickly", + "description": "Szybki commit message", + "pattern": "# {type}: {message}\n\n## Opis zmian\n{details}\n\n### Powiązane zadania\n{related_tasks}", + "model": { + "type": ["feat", "fix", "docs"], + "message": "Krótki opis zmian" + } + } + ] +} \ No newline at end of file diff --git a/src/test/java/pl/commit/gen/CommitGenApplicationTests.java b/src/test/java/pl/commit/craft/CommitCraftApplicationTests.java similarity index 71% rename from src/test/java/pl/commit/gen/CommitGenApplicationTests.java rename to src/test/java/pl/commit/craft/CommitCraftApplicationTests.java index e15cae5..0b7cb00 100644 --- a/src/test/java/pl/commit/gen/CommitGenApplicationTests.java +++ b/src/test/java/pl/commit/craft/CommitCraftApplicationTests.java @@ -1,10 +1,10 @@ -package pl.commit.gen; +package pl.commit.craft; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class CommitGenApplicationTests { +class CommitCraftApplicationTests { @Test void contextLoads() { diff --git a/src/test/java/pl/commit/craft/error/RestExceptionHandlerTest.java b/src/test/java/pl/commit/craft/error/RestExceptionHandlerTest.java new file mode 100644 index 0000000..2a525fe --- /dev/null +++ b/src/test/java/pl/commit/craft/error/RestExceptionHandlerTest.java @@ -0,0 +1,72 @@ +package pl.commit.craft.error; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import pl.commit.craft.flow.CommitFlowController; +import pl.commit.craft.service.CommitTranslateService; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = CommitFlowController.class) +@Import(RestExceptionHandler.class) +class RestExceptionHandlerTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private CommitTranslateService commitTranslateService; + + @Test + void shouldHandleHttpMessageNotReadableException() throws Exception { + // given + String invalidJson = "{ invalid json }"; + + // then + mockMvc.perform(post("/api/v1/commit-flow/craft") + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.timestamp").exists()) + .andExpect(jsonPath("$.status").value(HttpStatus.BAD_REQUEST.value())) + .andExpect(jsonPath("$.error").value("Invalid JSON request")) + .andExpect(jsonPath("$.message").exists()); + } + + @Test + void shouldHandleGeneralException() throws Exception { + // given + String requestBody = "{" + + "\"major\":\"1\"," + + "\"type\":\"bugfix\"," + + "\"component\":\"componentA\"," + + "\"changeDescription\":\"Fixed issue\"," + + "\"details\":\"Detailed description\"," + + "\"wholeGitCommand\":true" + + "}"; + + // when + when(commitTranslateService.generateFlowCommit(anyString(), anyString(), anyString(), anyString(), anyString(), anyBoolean())) + .thenThrow(new RuntimeException("Unexpected error")); + + // then + mockMvc.perform(post("/api/v1/commit-flow/craft") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.timestamp").exists()) + .andExpect(jsonPath("$.status").value(HttpStatus.INTERNAL_SERVER_ERROR.value())) + .andExpect(jsonPath("$.error").value("Internal Server Error")) + .andExpect(jsonPath("$.message").value("Unexpected error")); + } +} \ No newline at end of file diff --git a/src/test/java/pl/commit/gen/flow/CommitFlowControllerTest.java b/src/test/java/pl/commit/craft/flow/CommitFlowControllerTest.java similarity index 85% rename from src/test/java/pl/commit/gen/flow/CommitFlowControllerTest.java rename to src/test/java/pl/commit/craft/flow/CommitFlowControllerTest.java index b699d80..a820aaa 100644 --- a/src/test/java/pl/commit/gen/flow/CommitFlowControllerTest.java +++ b/src/test/java/pl/commit/craft/flow/CommitFlowControllerTest.java @@ -1,4 +1,4 @@ -package pl.commit.gen.flow; +package pl.commit.craft.flow; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -11,7 +11,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import pl.commit.gen.service.CommitService; +import pl.commit.craft.service.CommitTranslateService; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -22,7 +22,7 @@ class CommitFlowControllerTest { @Mock - private CommitService commitService; + private CommitTranslateService commitTranslateService; @InjectMocks private CommitFlowController commitFlowController; @@ -46,14 +46,14 @@ void testGenerateCommitSuccess() throws Exception { + "}"; // when - when(commitService.generateFlowCommit("1", "bugfix", "componentA", "Fixed issue", "Detailed description", true)) + when(commitTranslateService.generateFlowCommit("1", "bugfix", "componentA", "Fixed issue", "Detailed description", true)) .thenReturn("git commit -m \"1 bugfix(componentA): Fixed issue\n\nDetailed description\""); //then - mockMvc.perform(post("/api/commit-flow/generate") + mockMvc.perform(post("/api/v1/commit-flow/craft") .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) - .andExpect(status().isOk()) // Oczekiwany status HTTP 200 + .andExpect(status().isOk()) .andExpect(content().string("git commit -m \"1 bugfix(componentA): Fixed issue\n\nDetailed description\"")); // Oczekiwana odpowiedź } } diff --git a/src/test/java/pl/commit/gen/pattern/CommitModelPatternTest.java b/src/test/java/pl/commit/craft/pattern/CommitModelPatternTest.java similarity index 98% rename from src/test/java/pl/commit/gen/pattern/CommitModelPatternTest.java rename to src/test/java/pl/commit/craft/pattern/CommitModelPatternTest.java index cd018b3..19c3c8c 100644 --- a/src/test/java/pl/commit/gen/pattern/CommitModelPatternTest.java +++ b/src/test/java/pl/commit/craft/pattern/CommitModelPatternTest.java @@ -1,4 +1,4 @@ -package pl.commit.gen.pattern; +package pl.commit.craft.pattern; import org.junit.jupiter.api.Test; diff --git a/src/test/java/pl/commit/craft/quick/CommitQuickControllerTest.java b/src/test/java/pl/commit/craft/quick/CommitQuickControllerTest.java new file mode 100644 index 0000000..1a246cc --- /dev/null +++ b/src/test/java/pl/commit/craft/quick/CommitQuickControllerTest.java @@ -0,0 +1,68 @@ +package pl.commit.craft.quick; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(CommitQuickController.class) +class CommitQuickControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private CommitQuickService commitQuickService; + + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + } + + @Test + void testGenerateCommitSuccess() throws Exception { + String topicScope = "fix"; + boolean isGitCommand = true; + + CommitQuickRequest commitQuickRequest = new CommitQuickRequest(topicScope, isGitCommand); + String expectedCommitMessage = "git commit -m \"fix: fixed a bug\""; + + when(commitQuickService.generateQuickCommit(topicScope, isGitCommand)).thenReturn(expectedCommitMessage); + + mockMvc.perform(post("/api/v1/commit-quick/craft") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commitQuickRequest))) + .andExpect(status().isOk()) + .andExpect(content().string(expectedCommitMessage)); + + verify(commitQuickService, times(1)).generateQuickCommit(topicScope, isGitCommand); + } + + @Test + void testGenerateCommitFailure() throws Exception { + String topicScope = "invalidTopic"; + boolean isGitCommand = false; + + CommitQuickRequest commitQuickRequest = new CommitQuickRequest(topicScope, isGitCommand); + + when(commitQuickService.generateQuickCommit(topicScope, isGitCommand)).thenReturn("Invalid commit"); + + mockMvc.perform(post("/api/v1/commit-quick/craft") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commitQuickRequest))) + .andExpect(status().isOk()) + .andExpect(content().string("Invalid commit")); + + verify(commitQuickService, times(1)).generateQuickCommit(topicScope, isGitCommand); + } +} \ No newline at end of file diff --git a/src/test/java/pl/commit/gen/quick/QuickCommitServiceTest.java b/src/test/java/pl/commit/craft/quick/CommitQuickServiceTest.java similarity index 67% rename from src/test/java/pl/commit/gen/quick/QuickCommitServiceTest.java rename to src/test/java/pl/commit/craft/quick/CommitQuickServiceTest.java index f621b24..181e6c2 100644 --- a/src/test/java/pl/commit/gen/quick/QuickCommitServiceTest.java +++ b/src/test/java/pl/commit/craft/quick/CommitQuickServiceTest.java @@ -1,58 +1,58 @@ -package pl.commit.gen.quick; +package pl.commit.craft.quick; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -class QuickCommitServiceTest { - private final QuickCommitService quickCommitService = new QuickCommitService(); +class CommitQuickServiceTest { + private final CommitQuickService commitQuickService = new CommitQuickService(); @Test void testGenerateQuickCommit_Audit_NoGitCommand() { - String result = quickCommitService.generateQuickCommit("audit", false); + String result = commitQuickService.generateQuickCommit("audit", false); assertEquals("audit: Audit fix", result); } @Test void testGenerateQuickCommit_Fix_NoGitCommand() { - String result = quickCommitService.generateQuickCommit("fix", false); + String result = commitQuickService.generateQuickCommit("fix", false); assertEquals("fix: Pull request comments improved", result); } @Test void testGenerateQuickCommit_Test_NoGitCommand() { - String result = quickCommitService.generateQuickCommit("test", false); + String result = commitQuickService.generateQuickCommit("test", false); assertEquals("test: Fixed tests", result); } @Test void testGenerateQuickCommit_UnknownTopic_NoGitCommand() { - String result = quickCommitService.generateQuickCommit("unknown", false); + String result = commitQuickService.generateQuickCommit("unknown", false); assertEquals("Unknown commit type", result); } @Test void testGenerateQuickCommit_Audit_WithGitCommand() { - String result = quickCommitService.generateQuickCommit("audit", true); + String result = commitQuickService.generateQuickCommit("audit", true); assertEquals("git commit --no-verify -m \"audit: Audit fix\"", result); } @Test void testGenerateQuickCommit_Fix_WithGitCommand() { - String result = quickCommitService.generateQuickCommit("fix", true); + String result = commitQuickService.generateQuickCommit("fix", true); assertEquals("git commit --no-verify -m \"fix: Pull request comments improved\"", result); } @Test void testGenerateQuickCommit_Test_WithGitCommand() { - String result = quickCommitService.generateQuickCommit("test", true); + String result = commitQuickService.generateQuickCommit("test", true); assertEquals("git commit --no-verify -m \"test: Fixed tests\"", result); } @Test void testGenerateQuickCommit_UnknownTopic_WithGitCommand() { - String result = quickCommitService.generateQuickCommit("unknown", true); + String result = commitQuickService.generateQuickCommit("unknown", true); assertEquals("git commit --no-verify -m \"Unknown commit type\"", result); } } \ No newline at end of file diff --git a/src/test/java/pl/commit/gen/service/CommitServiceTest.java b/src/test/java/pl/commit/craft/service/CommitTranslateServiceTest.java similarity index 68% rename from src/test/java/pl/commit/gen/service/CommitServiceTest.java rename to src/test/java/pl/commit/craft/service/CommitTranslateServiceTest.java index 2aaffe3..afd3be1 100644 --- a/src/test/java/pl/commit/gen/service/CommitServiceTest.java +++ b/src/test/java/pl/commit/craft/service/CommitTranslateServiceTest.java @@ -1,23 +1,23 @@ -package pl.commit.gen.service; +package pl.commit.craft.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import pl.commit.translate.TranslateCommiting; +import pl.commit.craft.translate.TranslateCommitCraft; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; -class CommitServiceTest { +class CommitTranslateServiceTest { @Mock - private TranslateCommiting translateCommiting; + private TranslateCommitCraft translateCommitCraft; @InjectMocks - private CommitService commitService; + private CommitTranslateService commitTranslateService; @BeforeEach void setUp() { @@ -35,10 +35,10 @@ void testGenerateTranslateCommitValidType() { boolean wholeGitCommand = true; // when - when(translateCommiting.translate(changeDescription, "EN")).thenReturn("Add new button"); - when(translateCommiting.translate(details, "EN")).thenReturn("Added a new button to the main page."); + when(translateCommitCraft.translate(changeDescription, "EN")).thenReturn("Add new button"); + when(translateCommitCraft.translate(details, "EN")).thenReturn("Added a new button to the main page."); - String commitMessage = commitService.generateTranslateCommit(major, type, component, changeDescription, details, wholeGitCommand); + String commitMessage = commitTranslateService.generateTranslateCommit(major, type, component, changeDescription, details, wholeGitCommand, "EN"); // then assertNotNull(commitMessage); @@ -54,7 +54,7 @@ void testGenerateTranslateCommitInvalidType() { String type = "invalidType"; IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { - commitService.generateTranslateCommit(null, type, "UI", "Description", "Details", false); + commitTranslateService.generateTranslateCommit(null, type, "UI", "Description", "Details", false, "EN"); }); // then @@ -72,9 +72,9 @@ void testGenerateTranslateCommitEmptyDetails() { boolean wholeGitCommand = true; // when - when(translateCommiting.translate(changeDescription, "EN")).thenReturn("Fix bug in payment module"); + when(translateCommitCraft.translate(changeDescription, "EN")).thenReturn("Fix bug in payment module"); - String commitMessage = commitService.generateTranslateCommit(major, type, component, changeDescription, details, wholeGitCommand); + String commitMessage = commitTranslateService.generateTranslateCommit(major, type, component, changeDescription, details, wholeGitCommand, "EN"); // then assertNotNull(commitMessage); @@ -91,9 +91,9 @@ void testGenerateTranslateCommitWithTaskNumberAndWholeGitCommandIsFalse() { boolean wholeGitCommand = false; // when - when(translateCommiting.translate(changeDescription, "EN")).thenReturn("Add new feature"); + when(translateCommitCraft.translate(changeDescription, "EN")).thenReturn("Add new feature"); - String commitMessage = commitService.generateTranslateCommit(major, type, component, changeDescription, "", wholeGitCommand); + String commitMessage = commitTranslateService.generateTranslateCommit(major, type, component, changeDescription, "", wholeGitCommand, "EN"); // then assertNotNull(commitMessage); @@ -111,7 +111,7 @@ void testGenerateFlowCommitWithTaskNumber() { String details = ""; boolean wholeGitCommand = true; - String commitMessage = commitService.generateFlowCommit(major, type, component, changeDescription, details, wholeGitCommand); + String commitMessage = commitTranslateService.generateFlowCommit(major, type, component, changeDescription, details, wholeGitCommand); // then assertNotNull(commitMessage); diff --git a/src/test/java/pl/commit/gen/service/MajorNumberPreparerTest.java b/src/test/java/pl/commit/craft/service/MajorNumberPreparerTest.java similarity index 98% rename from src/test/java/pl/commit/gen/service/MajorNumberPreparerTest.java rename to src/test/java/pl/commit/craft/service/MajorNumberPreparerTest.java index 57075c8..1fe2343 100644 --- a/src/test/java/pl/commit/gen/service/MajorNumberPreparerTest.java +++ b/src/test/java/pl/commit/craft/service/MajorNumberPreparerTest.java @@ -1,4 +1,4 @@ -package pl.commit.gen.service; +package pl.commit.craft.service; import org.junit.jupiter.api.Test; diff --git a/src/test/java/pl/commit/craft/template/CommitCraftTemplateControllerTest.java b/src/test/java/pl/commit/craft/template/CommitCraftTemplateControllerTest.java new file mode 100644 index 0000000..7b1a532 --- /dev/null +++ b/src/test/java/pl/commit/craft/template/CommitCraftTemplateControllerTest.java @@ -0,0 +1,79 @@ +package pl.commit.craft.template; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(CommitCraftTemplateController.class) +class CommitCraftTemplateControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private CommitTemplateService commitTemplateService; + + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + } + + @Test + void testGetAllTemplates() throws Exception { + CommitCraftTemplate template1 = new CommitCraftTemplate("template1", "Description 1", "format1", null); + CommitCraftTemplate template2 = new CommitCraftTemplate("template2", "Description 2", "format2", null); + List mockTemplates = List.of(template1, template2); + + when(commitTemplateService.getAllTemplates()).thenReturn(mockTemplates); + + mockMvc.perform(get("/api/v1/craft-template/all")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("template1")) + .andExpect(jsonPath("$[1].name").value("template2")); + + verify(commitTemplateService, times(1)).getAllTemplates(); + } + + @Test + void testRemoveTemplate() throws Exception { + String templateName = "templateToRemove"; + + mockMvc.perform(delete("/api/v1/craft-template/removed/{name}", templateName)) + .andExpect(status().isOk()) + .andExpect(content().string("Template removed successfully.")); + + verify(commitTemplateService, times(1)).removeDedicatedTemplate(templateName); + } + + @Test + void testGenerateJson() throws Exception { + String templateName = "template1"; + CommitCraftJson mockJson = new CommitCraftJson(); + mockJson.addField("name", templateName); + mockJson.addField("description", "Template Description"); + when(commitTemplateService.prepareJsonByModel(templateName)).thenReturn(mockJson); + + mockMvc.perform(post("/api/v1/craft-template/generate-json/{name}", templateName)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(templateName)) + .andExpect(jsonPath("$.description").value("Template Description")); + + verify(commitTemplateService, times(1)).prepareJsonByModel(templateName); + } +} \ No newline at end of file diff --git a/src/test/java/pl/commit/craft/template/CommitDedicatedTemplateValidatorTest.java b/src/test/java/pl/commit/craft/template/CommitDedicatedTemplateValidatorTest.java new file mode 100644 index 0000000..9ab1f2e --- /dev/null +++ b/src/test/java/pl/commit/craft/template/CommitDedicatedTemplateValidatorTest.java @@ -0,0 +1,77 @@ +package pl.commit.craft.template; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class CommitDedicatedTemplateValidatorTest { + private static final Logger log = LoggerFactory.getLogger(CommitDedicatedTemplateValidator.class); + + @InjectMocks + private CommitDedicatedTemplateValidator validator; + + private ListAppender listAppender; + + + @BeforeEach + void setUp() { + listAppender = new ListAppender<>(); + listAppender.start(); + + ((ch.qos.logback.classic.Logger) log).addAppender(listAppender); + } + + @Test + void testValidatePatternAndModelScope_success() { + Map model = Map.of( + "type", "feat", + "scope", "core", + "message", "message" + ); + + CommitCraftTemplate validTemplate = new CommitCraftTemplate("feat-{scope}", "Standard commit", "feat-{scope}", model); + + boolean result = CommitDedicatedTemplateValidator.validatePatternAndModelScope(validTemplate); + + assertFalse(result); + assertFalse(listAppender.list.stream().anyMatch(event -> event.getMessage().contains("Pattern matches the model keys."))); + } + + @Test + void testValidatePatternAndModelScope_missingModelKey() { + Map model = Map.of( + "type", "feat", + "scope", "core" + ); + + CommitCraftTemplate invalidTemplate = new CommitCraftTemplate("feat-{scope}-{extraKey}", "Invalid commit", "feat-{scope}-{extraKey}", model); + + boolean result = CommitDedicatedTemplateValidator.validatePatternAndModelScope(invalidTemplate); + + assertFalse(result); + assertFalse(listAppender.list.stream().anyMatch(event -> event.getMessage().contains("Pattern contains an extra key not in the model: extraKey"))); + } + + @Test + void testValidatePatternAndModelScope_extraModelKey() { + Map model = Map.of( + "type", "feat", + "scope", "core", + "message", "extra" + ); + + CommitCraftTemplate invalidTemplate = new CommitCraftTemplate("feat-{scope}", "Invalid commit", "feat-{scope}", model); + + boolean result = CommitDedicatedTemplateValidator.validatePatternAndModelScope(invalidTemplate); + + assertFalse(result); + assertFalse(listAppender.list.stream().anyMatch(event -> event.getMessage().contains("Pattern is missing key: message"))); + } +} \ No newline at end of file diff --git a/src/test/java/pl/commit/craft/template/CommitTemplateServiceTest.java b/src/test/java/pl/commit/craft/template/CommitTemplateServiceTest.java new file mode 100644 index 0000000..faad0d4 --- /dev/null +++ b/src/test/java/pl/commit/craft/template/CommitTemplateServiceTest.java @@ -0,0 +1,227 @@ +package pl.commit.craft.template; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.validation.ConstraintViolation; +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CommitTemplateServiceTest { + private CommitTemplateService commitTemplateService; + private Validator validator; + + @Mock + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + commitTemplateService = new CommitTemplateService(objectMapper); + ValidatorFactory factory = Validation.byDefaultProvider() + .configure() + .messageInterpolator( new ParameterMessageInterpolator()) + .buildValidatorFactory(); + validator = factory.getValidator(); + } + + @Test + void commitCraftTemplate_ValidationShouldPass() { + Map model = new HashMap<>(); + model.put("type", "feat"); + model.put("scope", Arrays.asList("auth", "api")); + + CommitCraftTemplate template = new CommitCraftTemplate( + "conventional", + "Conventional commit template", + "type(scope): description", + model + ); + + Set> violations = validator.validate(template); + + assertTrue(violations.isEmpty()); + } + + @Test + void getAllTemplates_ShouldCombineTemplatesFromBothFiles() throws IOException { + // given + ObjectNode rootNode = mock(ObjectNode.class); + ObjectNode rootDedicatedNode = mock(ObjectNode.class); + JsonNode templatesNode = mock(JsonNode.class); + JsonNode dedicatedNode = mock(JsonNode.class); + + CommitCraftTemplate template1 = new CommitCraftTemplate( + "template1", + "Regular template", + "feat: description", + Map.of("type", "feat") + ); + + CommitCraftTemplate template2 = new CommitCraftTemplate( + "template2", + "Dedicated template", + "fix: description", + Map.of("type", "fix") + ); + + // when + when(objectMapper.readTree(any(File.class))) + .thenReturn(rootNode) + .thenReturn(rootDedicatedNode); + + when(rootNode.path("templates")).thenReturn(templatesNode); + when(rootDedicatedNode.path("dedicated")).thenReturn(dedicatedNode); + + when(templatesNode.toString()).thenReturn("[{\"regular\":\"template\"}]"); + when(dedicatedNode.toString()).thenReturn("[{\"dedicated\":\"template\"}]"); + + when(objectMapper.readValue(eq(templatesNode.toString()), any(TypeReference.class))) + .thenReturn(List.of(template1)); + when(objectMapper.readValue(eq(dedicatedNode.toString()), any(TypeReference.class))) + .thenReturn(List.of(template2)); + + List result = commitTemplateService.getAllTemplates(); + + assertEquals(2, result.size()); + assertEquals("template1", result.get(0).getName()); + assertEquals("template2", result.get(1).getName()); + + // Verify file reads happened exactly once for each file + verify(objectMapper, times(2)).readTree(any(File.class)); + verify(objectMapper, times(2)).readValue(anyString(), any(TypeReference.class)); + + // Verify correct paths were accessed + verify(rootNode).path("templates"); + verify(rootDedicatedNode).path("dedicated"); + } + + @Test + void getAllTemplates_ShouldHandleEmptyFiles() throws IOException { + // given + ObjectNode rootNode = mock(ObjectNode.class); + ObjectNode rootDedicatedNode = mock(ObjectNode.class); + JsonNode templatesNode = mock(JsonNode.class); + JsonNode dedicatedNode = mock(JsonNode.class); + + // Configure mock behavior + when(objectMapper.readTree(any(File.class))) + .thenReturn(rootNode) + .thenReturn(rootDedicatedNode); + + when(rootNode.path("templates")).thenReturn(templatesNode); + when(rootDedicatedNode.path("dedicated")).thenReturn(dedicatedNode); + + when(templatesNode.toString()).thenReturn("[]"); + when(dedicatedNode.toString()).thenReturn("[]"); + + when(objectMapper.readValue(anyString(), any(TypeReference.class))) + .thenReturn(Collections.emptyList()); + + // when + List result = commitTemplateService.getAllTemplates(); + + // then + assertTrue(result.isEmpty()); + } + + @Test + void getAllTemplates_ShouldHandleIOException() throws IOException { + // given + when(objectMapper.readTree(any(File.class))) + .thenThrow(new IOException("File not found")); + + // when/then + assertThrows(IOException.class, () -> commitTemplateService.getAllTemplates()); + } + + @Test + void getCommitCraftJson_ShouldCreateValidCommitCraftJson() throws IOException { + // given + Map model = new HashMap<>(); + model.put("type", "feat"); + model.put("scope", Arrays.asList("auth", "api")); + + CommitCraftTemplate selectedTemplate = new CommitCraftTemplate( + "conventional", + "Conventional commit template", + "type(scope): description", + model + ); + String selectedTemplateName = "conventional"; + + // when + CommitCraftJson result = commitTemplateService.getCommitCraftJson(selectedTemplate, selectedTemplateName); + + // then + assertNotNull(result); + assertEquals(selectedTemplateName, result.getFields().get("name")); + } + + @Test + void prepareJsonByModel_ShouldReturnValidCommitCraftJson() throws IOException { + // given + ObjectNode rootNode = mock(ObjectNode.class); + ObjectNode rootDedicatedNode = mock(ObjectNode.class); + JsonNode templatesNode = mock(JsonNode.class); + JsonNode dedicatedNode = mock(JsonNode.class); + + CommitCraftTemplate template1 = new CommitCraftTemplate( + "template1", + "Regular template", + "feat: description", + Map.of("type", "feat") + ); + + CommitCraftTemplate template2 = new CommitCraftTemplate( + "template2", + "Dedicated template", + "fix: description", + Map.of("type", "fix", "scope", Arrays.asList("auth", "api")) + ); + + // Configure mock behavior + when(objectMapper.readTree(any(File.class))) + .thenReturn(rootNode) + .thenReturn(rootDedicatedNode); + + when(rootNode.path("templates")).thenReturn(templatesNode); + when(rootDedicatedNode.path("dedicated")).thenReturn(dedicatedNode); + + when(templatesNode.toString()).thenReturn("[{\"name\":\"template1\",\"description\":\"Regular template\",\"commitMessage\":\"feat: description\",\"model\":{\"type\":\"feat\"}}]"); + when(dedicatedNode.toString()).thenReturn("[{\"name\":\"template2\",\"description\":\"Dedicated template\",\"commitMessage\":\"fix: description\",\"model\":{\"type\":\"fix\",\"scope\":[\"auth\",\"api\"]}}]"); + + when(objectMapper.readValue(eq(templatesNode.toString()), any(TypeReference.class))) + .thenReturn(List.of(template1)); + when(objectMapper.readValue(eq(dedicatedNode.toString()), any(TypeReference.class))) + .thenReturn(List.of(template2)); + + // when + CommitCraftJson result = commitTemplateService.prepareJsonByModel("template2"); + + // then + assertNotNull(result, "The result should not be null"); + + // Verify interactions + verify(objectMapper, times(2)).readTree(any(File.class)); + verify(objectMapper, times(2)).readValue(anyString(), any(TypeReference.class)); + verify(rootNode).path("templates"); + verify(rootDedicatedNode).path("dedicated"); + } +} \ No newline at end of file diff --git a/src/test/java/pl/commit/craft/template/generate/CommitTemplateGenerateServiceTest.java b/src/test/java/pl/commit/craft/template/generate/CommitTemplateGenerateServiceTest.java new file mode 100644 index 0000000..128b319 --- /dev/null +++ b/src/test/java/pl/commit/craft/template/generate/CommitTemplateGenerateServiceTest.java @@ -0,0 +1,105 @@ +package pl.commit.craft.template.generate; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class CommitTemplateGenerateServiceTest { + public static final String TEMPLATES_META_SCHEMA_JSON = "src/main/resources/templates/meta-schema.json"; + + @InjectMocks + private CommitTemplateGenerateService service; + + @Mock + private ObjectMapper objectMapper; + + private static final String TEMPLATE_NAME = "testTemplate"; + private static final String VALID_PATTERN = "Commit: {field1}, {field2}"; + private static final JsonNode VALID_COMMIT_DATA = JsonNodeFactory.instance.objectNode() + .put("field1", "value1") + .put("field2", "value2"); + + @Test + void shouldGenerateCommitWhenTemplateAndDataAreValid() throws IOException { + // Mock JSON template structure + JsonNode templateNode = JsonNodeFactory.instance.objectNode() + .put("name", TEMPLATE_NAME) + .put("pattern", VALID_PATTERN) + .set("model", JsonNodeFactory.instance.objectNode()); + + JsonNode templatesNode = JsonNodeFactory.instance.arrayNode().add(templateNode); + + JsonNode rootNode = JsonNodeFactory.instance.objectNode() + .set("templates", templatesNode); + + // Mock ObjectMapper behavior + Mockito.when(objectMapper.readTree(new File(TEMPLATES_META_SCHEMA_JSON))) + .thenReturn(rootNode); + + // Execute method + String result = service.generateCommit(TEMPLATE_NAME, VALID_COMMIT_DATA); + + // Verify result + assertEquals("Commit: value1, value2", result); + } + + @Test + void shouldThrowExceptionWhenTemplateNotFound() throws IOException { + // Mock empty templates array + JsonNode rootNode = JsonNodeFactory.instance.objectNode() + .set("templates", JsonNodeFactory.instance.arrayNode()); + + // Mock ObjectMapper behavior + Mockito.when(objectMapper.readTree(new File(TEMPLATES_META_SCHEMA_JSON))) + .thenReturn(rootNode); + + // Execute method and expect exception + Exception exception = assertThrows(IllegalArgumentException.class, () -> + service.generateCommit(TEMPLATE_NAME, VALID_COMMIT_DATA)); + + assertEquals("Template with name " + TEMPLATE_NAME + " not found", exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenRequiredFieldsAreMissing() throws IOException { + // Mock JSON template structure with required fields + JsonNode modelNode = JsonNodeFactory.instance.objectNode() + .put("field1", true) + .put("field2", true); + + JsonNode templateNode = JsonNodeFactory.instance.objectNode() + .put("name", TEMPLATE_NAME) + .put("pattern", VALID_PATTERN) + .set("model", modelNode); + + JsonNode templatesNode = JsonNodeFactory.instance.arrayNode().add(templateNode); + + JsonNode rootNode = JsonNodeFactory.instance.objectNode() + .set("templates", templatesNode); + + // Mock ObjectMapper behavior + Mockito.when(objectMapper.readTree(new File(TEMPLATES_META_SCHEMA_JSON))) + .thenReturn(rootNode); + + JsonNode incompleteCommitData = JsonNodeFactory.instance.objectNode().put("field1", "value1"); + + // Execute method and expect exception + Exception exception = assertThrows(IllegalArgumentException.class, () -> + service.generateCommit(TEMPLATE_NAME, incompleteCommitData)); + + assertEquals("Missing required fields: [field2]", exception.getMessage()); + } + + +} \ No newline at end of file diff --git a/src/test/resources/test-dedicated-meta-schema.json b/src/test/resources/test-dedicated-meta-schema.json new file mode 100644 index 0000000..02c2457 --- /dev/null +++ b/src/test/resources/test-dedicated-meta-schema.json @@ -0,0 +1 @@ +{"dedicated":[]} \ No newline at end of file diff --git a/src/test/resources/test-meta-schema.json b/src/test/resources/test-meta-schema.json new file mode 100644 index 0000000..2488f72 --- /dev/null +++ b/src/test/resources/test-meta-schema.json @@ -0,0 +1,46 @@ +{ + "templates": [ + { + "name": "conventional", + "description": "Standardowy format Conventional Commits", + "pattern": "{type}: {scope} - {message}", + "model": { + "type": ["feat", "fix", "chore", "docs", "refactor", "test"], + "scope": "[optional]", + "message": "Zwięzły opis zmiany" + } + }, + { + "name": "detailed", + "description": "Rozszerzone informacje dla commitów", + "pattern": "[{ticket_link_number}] {type}({scope}): {message}\n\n{details}", + "model": { + "ticket_id": "Numer powiązanego zadania w JIRA", + "type": ["feat", "fix", "junk", "chore", "test"], + "scope": "[moduł lub komponent]", + "message": "Krótki opis zmiany", + "details": "Szczegóły zmian w treści commit message" + } + }, + { + "name": "markdown", + "description": "Commit message w stylu markdown", + "pattern": "# {type}: {message}\n\n## Opis zmian\n{details}\n\n### Powiązane zadania\n{related_tasks}", + "model": { + "type": ["feat", "fix", "docs"], + "message": "Krótki opis zmian", + "details": "Szczegółowy opis zmian w commitach", + "related_tasks": "Lista powiązanych zadań (np. #123, #124)" + } + }, + { + "name": "quickly", + "description": "Szybki commit message", + "pattern": "# {type}: {message}\n\n## Opis zmian\n{details}\n\n### Powiązane zadania\n{related_tasks}", + "model": { + "type": ["feat", "fix", "docs"], + "message": "Krótki opis zmian" + } + } + ] +} \ No newline at end of file diff --git a/translate/src/main/java/pl/commit/translate/DeeplResponse.java b/translate/src/main/java/pl/commit/craft/translate/DeeplResponse.java similarity index 89% rename from translate/src/main/java/pl/commit/translate/DeeplResponse.java rename to translate/src/main/java/pl/commit/craft/translate/DeeplResponse.java index e45a442..3ba3b57 100644 --- a/translate/src/main/java/pl/commit/translate/DeeplResponse.java +++ b/translate/src/main/java/pl/commit/craft/translate/DeeplResponse.java @@ -1,4 +1,4 @@ -package pl.commit.translate; +package pl.commit.craft.translate; import lombok.Getter; import lombok.Setter; diff --git a/translate/src/main/java/pl/commit/translate/DeeplWebClient.java b/translate/src/main/java/pl/commit/craft/translate/DeeplWebClient.java similarity index 94% rename from translate/src/main/java/pl/commit/translate/DeeplWebClient.java rename to translate/src/main/java/pl/commit/craft/translate/DeeplWebClient.java index e14c95c..05ea96d 100644 --- a/translate/src/main/java/pl/commit/translate/DeeplWebClient.java +++ b/translate/src/main/java/pl/commit/craft/translate/DeeplWebClient.java @@ -1,4 +1,4 @@ -package pl.commit.translate; +package pl.commit.craft.translate; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -8,7 +8,7 @@ @Service @RequiredArgsConstructor -class DeeplWebClient implements TranslateCommiting { +class DeeplWebClient implements TranslateCommitCraft { private final WebClient webClient; @Value("${deepl.api.key}") diff --git a/translate/src/main/java/pl/commit/craft/translate/TranslateCommitCraft.java b/translate/src/main/java/pl/commit/craft/translate/TranslateCommitCraft.java new file mode 100644 index 0000000..8c0766f --- /dev/null +++ b/translate/src/main/java/pl/commit/craft/translate/TranslateCommitCraft.java @@ -0,0 +1,5 @@ +package pl.commit.craft.translate; + +public interface TranslateCommitCraft { + String translate(String text, String targetLang); +} diff --git a/translate/src/main/java/pl/commit/translate/config/WebClientConfig.java b/translate/src/main/java/pl/commit/craft/translate/config/WebClientConfig.java similarity index 91% rename from translate/src/main/java/pl/commit/translate/config/WebClientConfig.java rename to translate/src/main/java/pl/commit/craft/translate/config/WebClientConfig.java index 147107a..534be91 100644 --- a/translate/src/main/java/pl/commit/translate/config/WebClientConfig.java +++ b/translate/src/main/java/pl/commit/craft/translate/config/WebClientConfig.java @@ -1,4 +1,4 @@ -package pl.commit.translate.config; +package pl.commit.craft.translate.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/translate/src/main/java/pl/commit/translate/TranslateCommiting.java b/translate/src/main/java/pl/commit/translate/TranslateCommiting.java deleted file mode 100644 index ce3dd76..0000000 --- a/translate/src/main/java/pl/commit/translate/TranslateCommiting.java +++ /dev/null @@ -1,8 +0,0 @@ -package pl.commit.translate; - -import org.springframework.stereotype.Service; - -@Service -public interface TranslateCommiting { - String translate(String text, String targetLang); -} diff --git a/translate/src/main/resources/application.yml b/translate/src/main/resources/application.yml index 28e27f6..057f7d0 100644 --- a/translate/src/main/resources/application.yml +++ b/translate/src/main/resources/application.yml @@ -1,4 +1,4 @@ deepl: api: - key: + key: 92f2c230-abe8-4dc6-b90b-bbc3f7db0210:fx url: https://api-free.deepl.com/v2/translate