- Добавил интеграционные тесты с помощью Testcontainers
- Убрал сохранение данных сессии в БД, с ним не работало тестирования логина пользователя. - Изменил стратегию генерации Id для сообщений с 'AUTO' на `IDENTITY`. Без этого ругалось на дублирования id при добавлении нового сообщения в тестах. https://www.youtube.com/watch?v=Lnc3o8cCwZY&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=26
This commit is contained in:
25
pom.xml
25
pom.xml
@@ -41,6 +41,26 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>mysql</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-testcontainers</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
@@ -81,12 +101,7 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.session</groupId>
|
|
||||||
<artifactId>spring-session-jdbc</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package net.sytes.kashey.sweater.config;
|
|||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.Customizer;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
@@ -20,18 +19,19 @@ public class WebSecurityConfig {
|
|||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder(8);
|
return new BCryptPasswordEncoder(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests((requests) -> requests
|
.authorizeHttpRequests((requests) -> requests
|
||||||
.requestMatchers("/", "/registration","/static/**", "/activate/*").permitAll()
|
.requestMatchers("/", "/registration", "/static/**", "/activate/*").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.formLogin((form) -> form
|
.formLogin((form) -> form
|
||||||
.loginPage("/login")
|
.loginPage("/login")
|
||||||
.defaultSuccessUrl("/main", true)
|
.defaultSuccessUrl("/main", true)
|
||||||
.permitAll()
|
.permitAll()
|
||||||
).rememberMe(Customizer.withDefaults())
|
)
|
||||||
.logout(LogoutConfigurer::permitAll);
|
.logout(LogoutConfigurer::permitAll);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.hibernate.validator.constraints.Length;
|
|||||||
@Entity
|
@Entity
|
||||||
public class Message {
|
public class Message {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@NotBlank(message = "Please fill in the field.")
|
@NotBlank(message = "Please fill in the field.")
|
||||||
|
|||||||
@@ -19,6 +19,3 @@ spring.mail.protocol=smtps
|
|||||||
mail.debug=true
|
mail.debug=true
|
||||||
|
|
||||||
recaptcha.secret=
|
recaptcha.secret=
|
||||||
spring.session.jdbc.serialize=java
|
|
||||||
spring.session.jdbc.initialize-schema=always
|
|
||||||
spring.session.jdbc.table-name=SPRING_SESSION
|
|
||||||
|
|||||||
@@ -1,48 +1,47 @@
|
|||||||
create table message
|
CREATE TABLE IF NOT EXISTS message
|
||||||
(
|
(
|
||||||
id bigint not null,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
user_id bigint,
|
user_id BIGINT,
|
||||||
filename varchar(255),
|
filename VARCHAR(255),
|
||||||
tag varchar(255),
|
tag VARCHAR(255),
|
||||||
text varchar(2048) not null,
|
text VARCHAR(2048) NOT NULL
|
||||||
primary key (id)
|
) ENGINE = InnoDB;
|
||||||
) engine = InnoDB;
|
|
||||||
|
|
||||||
create table message_seq
|
CREATE TABLE message_seq
|
||||||
(
|
(
|
||||||
next_val bigint
|
next_val BIGINT
|
||||||
) engine = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
insert into message_seq
|
INSERT INTO message_seq
|
||||||
values (1);
|
VALUES (1);
|
||||||
|
|
||||||
create table user_role
|
CREATE TABLE user_role
|
||||||
(
|
(
|
||||||
user_id bigint not null,
|
user_id BIGINT NOT NULL,
|
||||||
roles enum ('ADMIN','USER')
|
roles ENUM ('ADMIN','USER')
|
||||||
) engine = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
create table usr
|
CREATE TABLE usr
|
||||||
(
|
(
|
||||||
enabled bit not null,
|
enabled BIT NOT NULL,
|
||||||
id bigint not null,
|
id BIGINT NOT NULL,
|
||||||
activation_code varchar(255),
|
activation_code VARCHAR(255),
|
||||||
email varchar(255),
|
email VARCHAR(255),
|
||||||
password varchar(255) not null,
|
password VARCHAR(255) NOT NULL,
|
||||||
username varchar(255) not null,
|
username VARCHAR(255) NOT NULL,
|
||||||
primary key (id)
|
PRIMARY KEY (id)
|
||||||
) engine = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
create table usr_seq
|
CREATE TABLE usr_seq
|
||||||
(
|
(
|
||||||
next_val bigint
|
next_val BIGINT
|
||||||
) engine = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
insert into usr_seq
|
INSERT INTO usr_seq
|
||||||
values (1);
|
VALUES (1);
|
||||||
|
|
||||||
alter table message
|
ALTER TABLE message
|
||||||
add constraint message_user_fk foreign key (user_id) references usr (id);
|
ADD CONSTRAINT message_user_fk FOREIGN KEY (user_id) REFERENCES usr (id);
|
||||||
|
|
||||||
alter table user_role
|
ALTER TABLE user_role
|
||||||
add constraint user_role_user_fk foreign key (user_id) references usr (id);
|
ADD CONSTRAINT user_role_user_fk FOREIGN KEY (user_id) REFERENCES usr (id);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<#import "parts/common.ftl" as com>
|
<#import "parts/common.ftl" as com>
|
||||||
<@com.page>
|
<@com.page>
|
||||||
<link href="/static/custom.css" rel="stylesheet">
|
<link href="/static/custom.css" rel="stylesheet"/>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<form method="get" action="/main" class="form-inline">
|
<form method="get" action="/main" class="form-inline">
|
||||||
<input type="text" name="tag" value="${tag}" placeholder="Tag to search">
|
<input type="text" name="tag" value="${tag}" placeholder="Tag to search"/>
|
||||||
<button type="submit" class="btn btn-primary ml-2">Search</button>
|
<button type="submit" class="btn btn-primary ml-2">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Sweater</title>
|
<title>Sweater</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"/>
|
||||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
<script src="https://www.google.com/recaptcha/api.js" async="async" defer="defer"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control ${(tagError??)?string('is-invalid', '')}"
|
<input type="text" class="form-control ${(tagError??)?string('is-invalid', '')}"
|
||||||
value="<#if message??>${message.tag}</#if>" name="tag" placeholder="Tag">
|
value="<#if message??>${message.tag}</#if>" name="tag" placeholder="Tag"/>
|
||||||
<#if tagError??>
|
<#if tagError??>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
${tagError}
|
${tagError}
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" name="file" id="customFile">
|
<input type="file" name="file" id="customFile"/>
|
||||||
<label class="custom-file-label" for="customFile">Choose file</label>
|
<label class="custom-file-label" for="customFile">Choose file</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<#include "security.ftl">
|
<#include "security.ftl">
|
||||||
|
|
||||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
<div class="row row-cols-1 row-cols-md-3 g-4" id="message-list">
|
||||||
<#list messages as message>
|
<#list messages as message>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card h-100"> <!-- h-100 делает карточки одинаковой высоты -->
|
<div class="card h-100" data-id="${message.id}"> <!-- h-100 делает карточки одинаковой высоты -->
|
||||||
<#if message.filename??>
|
<#if message.filename??>
|
||||||
<img src="/img/${message.filename}" class="card-img-top">
|
<img src="/img/${message.filename}" class="card-img-top">
|
||||||
</#if>
|
</#if>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span>${message.text}</span><br>
|
<span>${message.text}</span><br/>
|
||||||
<i>#${message.tag}</i>
|
<i>#${message.tag}</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex justify-content-between">
|
<div class="card-footer d-flex justify-content-between">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<#import "login.ftl" as l>
|
<#import "login.ftl" as l>
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
<link href="/static/custom.css" rel="stylesheet">
|
<link href="/static/custom.css" rel="stylesheet"/>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">Sweater</a>
|
<a class="navbar-brand" href="/">Sweater</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<form action="/users" method="post" class="needs-validation" novalidate>
|
<form action="/users" method="post" class="needs-validation" novalidate>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
<input type="text" class="form-control" id="username" name="username" value="${user.username}" required>
|
<input type="text" class="form-control" id="username" name="username" value="${user.username}" required/>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please provide a username.
|
Please provide a username.
|
||||||
</div>
|
</div>
|
||||||
@@ -14,15 +14,15 @@
|
|||||||
<#list roles as role>
|
<#list roles as role>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" name="${role}"
|
<input class="form-check-input" type="checkbox" name="${role}"
|
||||||
id="${role}" ${user.roles?seq_contains(role)?string("checked", "")}>
|
id="${role}" ${user.roles?seq_contains(role)?string("checked", "")}/>
|
||||||
<label class="form-check-label" for="${role}">
|
<label class="form-check-label" for="${role}">
|
||||||
${role}
|
${role}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</#list>
|
</#list>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" value="${user.id}" name="userId">
|
<input type="hidden" value="${user.id}" name="userId"/>
|
||||||
<input type="hidden" value="${_csrf.token}" name="_csrf">
|
<input type="hidden" value="${_csrf.token}" name="_csrf"/>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Save</button>
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
80
src/test/java/net/sytes/kashey/sweater/LoginTest.java
Normal file
80
src/test/java/net/sytes/kashey/sweater/LoginTest.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package net.sytes.kashey.sweater;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.testcontainers.containers.MySQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@Testcontainers
|
||||||
|
@TestPropertySource("/application-test.properties")
|
||||||
|
public class LoginTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Container
|
||||||
|
private static final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
|
||||||
|
.withDatabaseName("sweater_db_test")
|
||||||
|
.withUsername("user")
|
||||||
|
.withPassword("123456");
|
||||||
|
|
||||||
|
@DynamicPropertySource
|
||||||
|
static void configureProperties(DynamicPropertyRegistry registry) {
|
||||||
|
registry.add("spring.datasource.url", mysql::getJdbcUrl);
|
||||||
|
registry.add("spring.datasource.username", mysql::getUsername);
|
||||||
|
registry.add("spring.datasource.password", mysql::getPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void guestPageShouldContainLoginMessage() throws Exception {
|
||||||
|
mockMvc.perform(get("/"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(containsString("Hello, guest")))
|
||||||
|
.andExpect(content().string(containsString("Log in")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unauthorizedUserShouldBeRedirectedToLogin() throws Exception {
|
||||||
|
mockMvc.perform(get("/main"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("http://localhost/login"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql(
|
||||||
|
scripts = "/import.sql",
|
||||||
|
config = @SqlConfig(encoding = "UTF-8")
|
||||||
|
)
|
||||||
|
void authorizedUserShouldBeRedirectedToMainPage() throws Exception {
|
||||||
|
mockMvc.perform(formLogin().user("user").password("user"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("/main"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnForbiddenWhenLoginWithInvalidCredentials() throws Exception {
|
||||||
|
mockMvc.perform(post("/login").param("username", "Dummy"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/test/java/net/sytes/kashey/sweater/MainControllerTest.java
Normal file
102
src/test/java/net/sytes/kashey/sweater/MainControllerTest.java
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package net.sytes.kashey.sweater;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.security.test.context.support.WithUserDetails;
|
||||||
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
|
import org.testcontainers.containers.MySQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||||
|
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@Testcontainers
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@WithUserDetails(value = "admin")
|
||||||
|
@TestPropertySource("/application-test.properties")
|
||||||
|
@Sql(
|
||||||
|
scripts = {"/import.sql"},
|
||||||
|
config = @SqlConfig(encoding = "UTF-8")
|
||||||
|
|
||||||
|
)
|
||||||
|
public class MainControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
@Container
|
||||||
|
private static final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
|
||||||
|
.withDatabaseName("sweater_db_test")
|
||||||
|
.withUsername("user")
|
||||||
|
.withPassword("123456");
|
||||||
|
|
||||||
|
@DynamicPropertySource
|
||||||
|
static void configureProperties(DynamicPropertyRegistry registry) {
|
||||||
|
registry.add("spring.datasource.url", mysql::getJdbcUrl);
|
||||||
|
registry.add("spring.datasource.username", mysql::getUsername);
|
||||||
|
registry.add("spring.datasource.password", mysql::getPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDisplayUsernameInNavbarWhenUserIsAuthenticated() throws Exception {
|
||||||
|
|
||||||
|
mockMvc.perform(get("/main"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(authenticated())
|
||||||
|
.andExpect(xpath("/html/body/nav/div/div[2]").string("admin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDisplayAllMessagesOnMainPage() throws Exception {
|
||||||
|
|
||||||
|
mockMvc.perform(get("/main"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(authenticated())
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div").nodeCount(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails(value = "admin")
|
||||||
|
void shouldFilterMessagesByTag() throws Exception {
|
||||||
|
|
||||||
|
mockMvc.perform(get("/main").param("tag", "my-tag"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(authenticated())
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div").nodeCount(2))
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div/div[@data-id='1']").exists())
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div/div[@data-id='3']").exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAddNewMessageAndDisplayItOnMainPage() throws Exception {
|
||||||
|
MockHttpServletRequestBuilder multipart = multipart("/main")
|
||||||
|
.file("file", "123".getBytes())
|
||||||
|
.param("text", "fifth")
|
||||||
|
.param("tag", "new one")
|
||||||
|
.with(csrf());
|
||||||
|
|
||||||
|
this.mockMvc.perform(multipart)
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(authenticated())
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div").nodeCount(5))
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div/div[@data-id='5']").exists())
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div/div[@data-id='5']/div/span").string("fifth"))
|
||||||
|
.andExpect(xpath("//*[@id='message-list']/div/div[@data-id='5']/div/i").string("#new one"));
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/test/resources/application-test.properties
Normal file
19
src/test/resources/application-test.properties
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
spring.jpa.hibernate.ddl-auto=validate
|
||||||
|
spring.jpa.show-sql=true
|
||||||
|
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/sweater_db_test
|
||||||
|
spring.datasource.username=user
|
||||||
|
spring.datasource.password=123456
|
||||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
|
spring.freemarker.expose-request-attributes=true
|
||||||
|
spring.freemarker.expose-session-attributes=true
|
||||||
|
spring.freemarker.suffix=.ftl
|
||||||
|
upload.path=/home/kashey/soft/sweater/uploads
|
||||||
|
hosting.path=http://localhost:8080
|
||||||
|
spring.mail.host=smtp.yandex.ru
|
||||||
|
spring.mail.username=username@yandex.ru
|
||||||
|
spring.mail.password=pa$$word
|
||||||
|
spring.mail.port=465
|
||||||
|
spring.mail.protocol=smtps
|
||||||
|
mail.debug=true
|
||||||
|
|
||||||
|
recaptcha.secret=
|
||||||
49
src/test/resources/import.sql
Normal file
49
src/test/resources/import.sql
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
DROP TABLE IF EXISTS message;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message
|
||||||
|
(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id BIGINT,
|
||||||
|
filename VARCHAR(255),
|
||||||
|
tag VARCHAR(255),
|
||||||
|
text VARCHAR(2048) NOT NULL
|
||||||
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_role
|
||||||
|
(
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
roles ENUM ('ADMIN', 'USER')
|
||||||
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS usr
|
||||||
|
(
|
||||||
|
enabled bit not null,
|
||||||
|
id bigint not null,
|
||||||
|
activation_code varchar(255),
|
||||||
|
email varchar(255),
|
||||||
|
password varchar(255) not null,
|
||||||
|
username varchar(255) not null,
|
||||||
|
primary key (id)
|
||||||
|
) engine = InnoDB;
|
||||||
|
|
||||||
|
DELETE
|
||||||
|
FROM user_role;
|
||||||
|
|
||||||
|
DELETE
|
||||||
|
FROM usr;
|
||||||
|
|
||||||
|
INSERT INTO usr (id, username, password, enabled)
|
||||||
|
VALUES (1, 'admin', '$2a$12$.XB1cxFOs6F8vgouixcTP.TaY2GXwFspYHID12JpLwotvoYI0JwnC', true);
|
||||||
|
|
||||||
|
INSERT INTO usr (id, username, password, enabled)
|
||||||
|
VALUES (2, 'user', '$2a$12$K3ShbpKTnDVozTH2HJ.mbu7jXoiy2oqAoVTEF0iRGFxUfJvse3oke', true);
|
||||||
|
|
||||||
|
INSERT INTO user_role (user_id, roles)
|
||||||
|
VALUES (1, 'USER'),
|
||||||
|
(1, 'ADMIN');
|
||||||
|
|
||||||
|
INSERT INTO message (text, tag, user_id)
|
||||||
|
VALUES ('first', 'my-tag', 1),
|
||||||
|
('second', 'more', 1),
|
||||||
|
('third', 'my-tag', 1),
|
||||||
|
('fourth', 'another', 2);
|
||||||
Reference in New Issue
Block a user