Skip to content

Commit

Permalink
[Hexlet#260] unified error display format and add instancio, faker de…
Browse files Browse the repository at this point in the history
…pendency for tests
  • Loading branch information
d1z3d committed Jun 23, 2024
1 parent c20d43e commit 9f25184
Show file tree
Hide file tree
Showing 18 changed files with 249 additions and 63 deletions.
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ dependencies {
implementation("org.mapstruct:mapstruct:1.5.3.Final")
// Annotation processors
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")

//Generating models for tests
implementation("org.instancio:instancio-junit:3.6.0")
implementation("net.datafaker:datafaker:2.0.2")
// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import java.lang.annotation.Target;

@NotBlank
@Pattern(regexp = "^[-_A-Za-z0-9]*$")
@Pattern(regexp = "^[-_A-Za-z0-9]*$", message = "{validation.alert.wrong-username-pattern}")
@Size(min = 2, max = 20)
@Constraint(validatedBy = {})
@Target({ElementType.FIELD, ElementType.PARAMETER})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class SignupAccountModel {
private String username;

@Email(regexp = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
message = "The email \"${validatedValue}\" is not valid")
//message = "The email \"${validatedValue}\" is not valid")
message = "{validation.alert.wrong-email}")
private String email;

@AccountPassword
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
public class WorkspaceUserModel {

@Email(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
message = "The email \"${validatedValue}\" is not valid")
message = "{validation.alert.wrong-email}")
private String email;
}
3 changes: 3 additions & 0 deletions src/main/resources/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,6 @@ btn.delete-from-wks=Delete from workspace

alert.password-wrong-format=Password must be between 8 and 20 characters \
and contain only latin letters, digits and symbols ~`!@#$%^&*()_-+={[}]|\:;"'<,>.?/

validation.alert.wrong-email=The email "${validatedValue}" is not valid
validation.alert.wrong-username-pattern=The pattern of username must be "{regexp}"
2 changes: 2 additions & 0 deletions src/main/resources/messages_ru.properties
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ text.wks-delete-confirm=Удалить пространство?
alert.password-wrong-format=Пароль должен быть от 8 до 20 символов\
\ и содержать только буквы латинского алфавита,\
\ цифры и символы ~`!@#$%^&*()_-+={[}]|\:;"'<,>.?/
validation.alert.wrong-email=Электронная почта "${validatedValue}" указана некорректно
validation.alert.wrong-username-pattern=Имя пользователя должен быть подходить под шаблон "{regexp}"
12 changes: 6 additions & 6 deletions src/main/resources/templates/account/signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
th:classappend="${!#fields.hasErrors('username') && formModified}? 'is-valid'"
th:errorclass="is-invalid">
<label for="inputUsername" th:text="#{username}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('username')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('username')}">
<p th:each="err : ${#fields.errors('username')}" th:text="${err}"></p>
</div>
</div>
Expand All @@ -24,7 +24,7 @@
th:classappend="${!#fields.hasErrors('email') && formModified}? 'is-valid'"
th:errorclass="is-invalid">
<label for="inputEmail" th:text="#{email}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('email')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('email')}">
<p th:each="err : ${#fields.errors('email')}" th:text="${err}"></p>
</div>
</div>
Expand All @@ -35,7 +35,7 @@
th:classappend="${!#fields.hasErrors('firstName') && formModified}? 'is-valid'"
th:errorclass="is-invalid">
<label for="inputFirstName" th:text="#{firstname}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('firstName')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('firstName')}">
<p th:each="err : ${#fields.errors('firstName')}" th:text="${err}"></p>
</div>
</div>
Expand All @@ -46,7 +46,7 @@
th:classappend="${!#fields.hasErrors('lastName') && formModified}? 'is-valid'"
th:errorclass="is-invalid">
<label for="inputLastName" th:text="#{lastname}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('lastName')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('lastName')}">
<p th:each="err : ${#fields.errors('lastName')}" th:text="${err}"></p>
</div>
</div>
Expand All @@ -55,7 +55,7 @@
<input id="inputPassword" placeholder="p" type="password" th:field="*{password}"
class="form-control" th:errorclass="is-invalid">
<label for="inputPassword" th:text="#{password}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('password')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('password')}">
<p th:each="err : ${#fields.errors('password')}" th:text="${err}"></p>
</div>
</div>
Expand All @@ -64,7 +64,7 @@
<input id="inputConfirmPassword" placeholder="p" type="password" th:field="*{confirmPassword}"
class="form-control" th:errorclass="is-invalid">
<label for="inputConfirmPassword" th:text="#{password.confirm}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('confirmPassword')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('confirmPassword')}">
<p th:each="err : ${#fields.errors('confirmPassword')}" th:text="${err}"></p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/error-general.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/header :: head}"></head>
<body>
<div class="alert alert-danger">
<div class="invalid-feedback">
<span th:text="#{alert.general-error}"></span>
</div>
</body>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<main class="container">
<div class="row">
<div class="col">
<div class="alert alert-danger" role="alert" th:if="${param.error}" th:text="#{alert.bad-credential}"></div>
<div class="invalid-feedback" role="alert" th:if="${param.error}" th:text="#{alert.bad-credential}"></div>
<div class="alert alert-warning" role="alert" th:if="${param.logout}" th:text="#{alert.logout}"></div>
<form method="post" th:action="@{/login}">
<div class="form-floating mb-3">
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/widget/report-typo-error.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/header :: head}"></head>
<body>
<div class="alert alert-danger">
<div class="invalid-feedback">
<span th:text="#{alert.report-error}"></span>
</div>
</body>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/widget/typo-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
type="text"
></textarea>
<label class="form-label" for="commentTextarea" th:text="#{text.leave-comment}"></label>
<div class="alert alert-danger" th:if="${#fields.hasErrors('reporterComment')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('reporterComment')}">
<p th:each="err : ${#fields.errors('reporterComment')}" th:text="${err}"></p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/workspace/wks-settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
</div>
<div class="row mt-2">
<div class="col-12">
<ul class="alert alert-danger ps-5" th:if="${#fields.hasErrors('url')}">
<ul class="invalid-feedback" th:if="${#fields.hasErrors('url')}">
<li th:each="err : ${#fields.errors('url')}" th:text="${err}"></li>
</ul>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/workspace/wks-users.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
placeholder="Enter user email. For example: hexlet@gmail.com" th:field="*{email}"
th:classappend="${!#fields.hasErrors('email') && formModified}? 'is-valid'"
th:errorclass="is-invalid" required>
<div class="alert alert-danger mt-1" th:if="${#fields.hasErrors('email')}">
<div class="invalid-feedback" th:if="${#fields.hasErrors('email')}">
<p class="mb-0" th:each="err : ${#fields.errors('email')}" th:text="${err}"></p>
</div>
<small id="emailHelp" class="form-text text-muted"></small>
Expand Down
18 changes: 18 additions & 0 deletions src/test/java/io/hexlet/typoreporter/config/TestConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.hexlet.typoreporter.config;

import net.datafaker.Faker;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class TestConfig {
@Bean
public Faker getFaker() {
return new Faker();
}
@Bean
public ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.hexlet.typoreporter.test.factory;

import io.hexlet.typoreporter.web.model.SignupAccountModel;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import net.datafaker.Faker;
import org.instancio.Instancio;
import org.instancio.Model;
import org.instancio.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.Serializable;

import static io.hexlet.typoreporter.test.factory.EntitiesFactory.ACCOUNT_INCORRECT_EMAIL;

@Component
@Getter
public class AccountModelGenerator {
private Model<SignupAccountModel> correctAccountModel;
private Model<SignupAccountModel> incorrectAccountModel;
@Autowired
private Faker faker;

@PostConstruct
public void init() {
final String password = faker.internet().password(8, 20);
final String incorrectPassword = faker.internet().password(1, 7);
correctAccountModel = Instancio.of(SignupAccountModel.class)
.supply(Select.field(SignupAccountModel::getUsername), () -> faker.name().firstName())
.supply(Select.field(SignupAccountModel::getEmail), () -> faker.internet().emailAddress())
.supply(Select.field(SignupAccountModel::getPassword), () -> password)
.supply(Select.field(SignupAccountModel::getConfirmPassword), () -> password)
.supply(Select.field(SignupAccountModel::getFirstName), () -> faker.name().firstName())
.supply(Select.field(SignupAccountModel::getLastName), () -> faker.name().lastName())
.toModel();
incorrectAccountModel = Instancio.of(SignupAccountModel.class)
.supply(Select.field(SignupAccountModel::getUsername), () -> faker.name().firstName() + ".")
.supply(Select.field(SignupAccountModel::getEmail), () -> ACCOUNT_INCORRECT_EMAIL)
.supply(Select.field(SignupAccountModel::getPassword), () -> incorrectPassword)
.supply(Select.field(SignupAccountModel::getConfirmPassword), () -> incorrectPassword)
.supply(Select.field(SignupAccountModel::getFirstName), () -> "")
.supply(Select.field(SignupAccountModel::getLastName), () -> "")
.toModel();
}
}
75 changes: 75 additions & 0 deletions src/test/java/io/hexlet/typoreporter/utils/BundleSourceUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.hexlet.typoreporter.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class BundleSourceUtils {
@Autowired
private MessageSource messageSource;

/**
* @param key - Ключ из messages_LANG.properties, по которому необходимо получить значение
* Для RU необходимо использовать new Locale("ru")
* @param locale - Параметр, по которому определяется язык (локализация)
* @param isReplaceToQuot - Признак необходимости замены " на &quot;
* @param args - Аргументы с типом String, которые необходимо подставить при интерполяции
* @return Строка из resource bundle изменениями
*/
public String getValueByKey(String key, Locale locale, boolean isReplaceToQuot, String[] args) {
String value = messageSource.getMessage(key, null, locale);
if (args != null) {
Pattern pattern = Pattern.compile("(\\$\\{(\\w+)\\})|(\\{(\\w+)\\})");
Matcher matcher = pattern.matcher(value);
while (matcher.find()) {
value = value.replace(matcher.group(), "%s");
}
value = isReplaceToQuot
? value.replace("\"", "&quot;")
: value;
return String.format(value, args);
}
return isReplaceToQuot ? value.replace("\"", "&quot;") : value;
}

/**
* @param key - Ключ из messages_LANG.properties, по которому необходимо получить значение
* @param isReplaceToQuot - Признак необходимости замены " на &quot;
* @param args - Аргументы с типом String, которые необходимо подставить при интерполяции
* @return - Строка из resource bundle изменениями, по умолчанию на английском языке
*/
public String getValueByKey(String key, boolean isReplaceToQuot, String[] args) {
return getValueByKey(key, Locale.ENGLISH, isReplaceToQuot, args);
}

/**
* @param key - Ключ из messages_LANGUAGE.properties, по которому необходимо получить значение.
* @param isReplaceToQuot - Признак необходимости замены " на &quot;
* @return - Строка из resource bundle изменениями, по умолчанию на английском языке
*/
public String getValueByKey(String key, boolean isReplaceToQuot) {
return getValueByKey(key, isReplaceToQuot, null);
}

/**
* @param key - Ключ из messages_LANG.properties, по которому необходимо получить значение
* @param args - Аргументы с типом String, которые необходимо подставить при интерполяции
* @return - Строка из resource bundle, по умолчанию на английском языке
*/
public String getValueByKey(String key, String[] args) {
return getValueByKey(key, false, args);
}

/**
* @param key - Ключ из messages_LANG.properties, по которому необходимо получить значение
* @return - Строка из resource bundle, по умолчанию на английском языке
*/
public String getValueByKey(String key) {
return getValueByKey(key, null);
}
}
32 changes: 32 additions & 0 deletions src/test/java/io/hexlet/typoreporter/utils/ModelUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.hexlet.typoreporter.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.Map;
import java.util.Set;

@Component
public class ModelUtils {
@Autowired
private ObjectMapper objectMapper;

public MultiValueMap<String, String> toFormParams(Object dto) throws Exception {
return toFormParams(dto, Set.of());
}

public MultiValueMap<String, String> toFormParams(Object dto, Set<String> excludeFields) throws Exception {
ObjectReader reader = objectMapper.readerFor(Map.class);
Map<String, String> map = reader.readValue(objectMapper.writeValueAsString(dto));

MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
map.entrySet().stream()
.filter(e -> !excludeFields.contains(e.getKey()))
.forEach(e -> multiValueMap.add(e.getKey(), (e.getValue() == null ? "" : e.getValue())));
return multiValueMap;
}
}
Loading

0 comments on commit 9f25184

Please sign in to comment.