diff --git a/pom.xml b/pom.xml
index 1df9f9e..2fbedfa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,11 @@
test
+
+ org.springframework.security
+ spring-security-test
+
+
org.springframework.boot
--
2.51.2
From 8d1a3e6ed28ffa090a3101b8957b1d8903d97845 Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 11:26:41 +0200
Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=94=A7=20:=20do=20not=20use=20flywa?=
=?UTF-8?q?y=20when=20testing?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/test/resources/application.properties | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 93e7d05..bef1c43 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -13,3 +13,5 @@ spring.security.oauth2.client.registration.gitlab.client-secret=${gitlab.client-
spring.security.oauth2.client.registration.gitlab.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.gitlab.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.gitlab.scope[0]=api
+
+spring.flyway.enabled=false
--
2.51.2
From dd8d47cbffd071a11406f8e6f9c68ad5b33d3a1e Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 11:44:43 +0200
Subject: [PATCH 03/14] =?UTF-8?q?=E2=9C=85=20:=20test=20role=20enforcement?=
=?UTF-8?q?=20for=20quiz=20edition?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../classrooms/ui/QuizControllerMVCTest.java | 62 +++++++++++++++++++
.../classrooms/ui/WithMockClassroomUser.java | 16 +++++
...ckClassroomUserSecurityContextFactory.java | 36 +++++++++++
3 files changed, 114 insertions(+)
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUser.java
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUserSecurityContextFactory.java
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java
new file mode 100644
index 0000000..68329cd
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java
@@ -0,0 +1,62 @@
+package fr.univ_lille.gitlab.classrooms.ui;
+
+import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
+import org.junit.jupiter.api.Nested;
+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.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class QuizControllerMVCTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Nested
+ @WithMockClassroomUser(username = "obiwan.kenobi", roles = {ClassroomRole.TEACHER})
+ class TeacherRole {
+
+ @Test
+ void shouldAccessQuizListPage() throws Exception {
+ mockMvc.perform(get("/quiz"))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ void shouldAccessNewQuizPage() throws Exception {
+ mockMvc.perform(get("/quiz/new/edit"))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+
+ }
+
+ @Nested
+ @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
+ class StudentRole {
+
+ @Test
+ void shouldNotAccessQuizListPage() throws Exception {
+ mockMvc.perform(get("/quiz"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ void shouldNotAccessNewQuizPage() throws Exception {
+ mockMvc.perform(get("/quiz/new/edit"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+
+ }
+
+}
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUser.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUser.java
new file mode 100644
index 0000000..0503f57
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUser.java
@@ -0,0 +1,16 @@
+package fr.univ_lille.gitlab.classrooms.ui;
+
+import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
+import org.springframework.security.test.context.support.WithSecurityContext;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@WithSecurityContext(factory = WithMockClassroomUserSecurityContextFactory.class)
+public @interface WithMockClassroomUser {
+ String username() default "luke.skywalker";
+
+ ClassroomRole[] roles() default {ClassroomRole.STUDENT};
+
+}
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUserSecurityContextFactory.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUserSecurityContextFactory.java
new file mode 100644
index 0000000..15c529c
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/WithMockClassroomUserSecurityContextFactory.java
@@ -0,0 +1,36 @@
+package fr.univ_lille.gitlab.classrooms.ui;
+
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.test.context.support.WithSecurityContextFactory;
+
+import java.util.Arrays;
+import java.util.Map;
+
+public class WithMockClassroomUserSecurityContextFactory implements WithSecurityContextFactory {
+
+ @Override
+ public SecurityContext createSecurityContext(WithMockClassroomUser customUser) {
+ SecurityContext context = SecurityContextHolder.createEmptyContext();
+
+ var authorities = Arrays.stream(customUser.roles())
+ .map(it -> new SimpleGrantedAuthority("ROLE_" + it.name()))
+ .toList();
+
+ Map attributes = Map.of(
+ "name", customUser.username(),
+ "avatar_url", ""
+ );
+
+ // create a fake gitlab user
+ OAuth2User principal = new DefaultOAuth2User(authorities, attributes, "name");
+ var auth = new OAuth2AuthenticationToken(principal, principal.getAuthorities(), "gitlab");
+
+ context.setAuthentication(auth);
+ return context;
+ }
+}
--
2.51.2
From e4a09d2c74cd583cf498b5b9762ce8dd163a2351 Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 11:53:34 +0200
Subject: [PATCH 04/14] =?UTF-8?q?=E2=9C=85=20:=20test=20role=20enforcement?=
=?UTF-8?q?=20for=20home=20page?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../classrooms/ui/HomeControllerMVCTest.java | 50 +++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/ui/HomeControllerMVCTest.java
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/HomeControllerMVCTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/HomeControllerMVCTest.java
new file mode 100644
index 0000000..fd9bc57
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/HomeControllerMVCTest.java
@@ -0,0 +1,50 @@
+package fr.univ_lille.gitlab.classrooms.ui;
+
+import fr.univ_lille.gitlab.classrooms.domain.ClassroomRepository;
+import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
+import org.junit.jupiter.api.Nested;
+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.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class HomeControllerMVCTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockBean
+ private ClassroomRepository classroomRepository;
+
+ @Nested
+ @WithMockClassroomUser(username = "obiwan.kenobi", roles = {ClassroomRole.TEACHER})
+ class TeacherRole {
+
+ @Test
+ void shouldAccessHomePage() throws Exception {
+ mockMvc.perform(get("/"))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+ }
+
+ @Nested
+ @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
+ class StudentRole {
+
+ @Test
+ void shouldNotAccessHomePage() throws Exception {
+ mockMvc.perform(get("/"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+ }
+}
--
2.51.2
From 3965f6b1f23a944de7c5da00bf500e2f342827ed Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 11:56:05 +0200
Subject: [PATCH 05/14] =?UTF-8?q?=E2=9C=85=20:=20test=20role=20enforcement?=
=?UTF-8?q?=20for=20new=20classroom=20page?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../classrooms/ui/ClassroomController.java | 2 +
.../ui/ClassroomControllerMVCTest.java | 46 +++++++++++++++++++
2 files changed, 48 insertions(+)
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomControllerMVCTest.java
diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomController.java b/src/main/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomController.java
index 8327655..fc56b8e 100644
--- a/src/main/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomController.java
+++ b/src/main/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomController.java
@@ -2,6 +2,7 @@ package fr.univ_lille.gitlab.classrooms.ui;
import fr.univ_lille.gitlab.classrooms.domain.Classroom;
import fr.univ_lille.gitlab.classrooms.domain.ClassroomRepository;
+import jakarta.annotation.security.RolesAllowed;
import org.gitlab4j.api.GitLabApiException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
@@ -15,6 +16,7 @@ import java.util.UUID;
@Controller
@RequestMapping("/classrooms")
+@RolesAllowed("TEACHER")
public class ClassroomController {
private ClassroomRepository classroomRepository;
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomControllerMVCTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomControllerMVCTest.java
new file mode 100644
index 0000000..64b7368
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/ClassroomControllerMVCTest.java
@@ -0,0 +1,46 @@
+package fr.univ_lille.gitlab.classrooms.ui;
+
+import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
+import org.junit.jupiter.api.Nested;
+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.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class ClassroomControllerMVCTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Nested
+ @WithMockClassroomUser(username = "obiwan.kenobi", roles = {ClassroomRole.TEACHER})
+ class TeacherRole {
+
+ @Test
+ void shouldAccessNewClassroomPage() throws Exception {
+ mockMvc.perform(get("/classrooms/new"))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+ }
+
+ @Nested
+ @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
+ class StudentRole {
+
+ @Test
+ void shouldNotAccessHomePage() throws Exception {
+ mockMvc.perform(get("/classrooms/new"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+ }
+
+}
--
2.51.2
From 65ca38aa48f7b0bbfbd2dbb69b81408a6daaba85 Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 15:09:09 +0200
Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=91=B7=20:=20add=20deployment=20to?=
=?UTF-8?q?=20clever-cloud?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitlab-ci.yml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6d23cae..a193ada 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -29,3 +29,16 @@ verify:
stage: test
script:
- 'mvn $MAVEN_CLI_OPTS verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar $SONAR_CLI_OPTS'
+
+deploy:
+ image: clevercloud/clever-tools
+ stage: deploy
+ rules:
+ - if: $CI_COMMIT_REF_NAME == 'main'
+ script:
+ - clever deploy
+ environment:
+ name: production
+ deployment_tier: production
+ url: https://gitlab-classrooms.cleverapps.io/
+ action: start
--
2.51.2
From 32b758fe354e8da293e02cfe7a2d091c473cc9dc Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 11:26:25 +0200
Subject: [PATCH 07/14] =?UTF-8?q?=E2=9E=95=20:=20add=20spring-security-tes?=
=?UTF-8?q?t?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pom.xml b/pom.xml
index 2fbedfa..dc1f753 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,7 @@
org.springframework.security
spring-security-test
+ test
--
2.51.2
From 19fd88538d762b446c4f71de1188320811b6d552 Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 11:44:43 +0200
Subject: [PATCH 08/14] =?UTF-8?q?=E2=9C=85=20:=20test=20role=20enforcement?=
=?UTF-8?q?=20for=20quiz=20edition?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../classrooms/ui/QuizControllerTest.java | 64 +++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java
new file mode 100644
index 0000000..b36920a
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java
@@ -0,0 +1,64 @@
+package fr.univ_lille.gitlab.classrooms.ui;
+
+import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
+import org.junit.jupiter.api.Nested;
+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.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class QuizControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Nested
+ @WithMockClassroomUser(username = "obiwan.kenobi", roles = {ClassroomRole.TEACHER})
+ class TeacherRole {
+
+ @Test
+ void shouldAccessQuizListPage() throws Exception {
+ mockMvc.perform(get("/quiz"))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ void shouldAccessNewQuizPage() throws Exception {
+ mockMvc.perform(get("/quiz/new/edit"))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+
+ }
+
+ @Nested
+ @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
+ class StudentRole {
+
+ @Test
+ void shouldNotAccessQuizListPage() throws Exception {
+ mockMvc.perform(get("/quiz"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ void shouldNotAccessNewQuizPage() throws Exception {
+ mockMvc.perform(get("/quiz/new/edit"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+
+ }
+
+}
--
2.51.2
From 0ac9e880b55c264cc5d8d3e8c9bb6d01ecc3c90d Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 15:17:17 +0200
Subject: [PATCH 09/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20:=20upgrade=20spring?=
=?UTF-8?q?-boot-starter-parent=20to=203.1.2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index dc1f753..f873177 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.0.5
+ 3.1.2
fr.univ-lille
--
2.51.2
From bfd4e87832957f13fea3f77c06de6ff7f59a5ef0 Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 15:21:14 +0200
Subject: [PATCH 10/14] =?UTF-8?q?=F0=9F=94=A5=20:=20delete=20unused=20mock?=
=?UTF-8?q?=20class?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../mock/ClassroomRepositoryGitlabImpl.java | 1 -
.../mock/ClassroomRepositoryMock.java | 24 -------------------
2 files changed, 25 deletions(-)
delete mode 100644 src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryMock.java
diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java b/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java
index fff593b..1e29a3c 100644
--- a/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java
+++ b/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java
@@ -12,7 +12,6 @@ import org.springframework.stereotype.Repository;
* Implementation of the Classroom repository that uses the Gitlab API
*/
@Repository
-@Primary
class ClassroomRepositoryGitlabImpl implements ClassroomRepository {
GitLabApi gitLabApi;
diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryMock.java b/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryMock.java
deleted file mode 100644
index 606dfe4..0000000
--- a/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryMock.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package fr.univ_lille.gitlab.classrooms.mock;
-
-import fr.univ_lille.gitlab.classrooms.domain.Classroom;
-import fr.univ_lille.gitlab.classrooms.domain.ClassroomRepository;
-import org.springframework.stereotype.Repository;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@Repository
-class ClassroomRepositoryMock implements ClassroomRepository {
-
- Map classrooms = new HashMap<>();
-
- @Override
- public Iterable findAllClassrooms() {
- return classrooms.values();
- }
-
- @Override
- public void saveClassroom(Classroom classroom) {
- this.classrooms.put(classroom.id(), classroom);
- }
-}
--
2.51.2
From 1d7a38ff85d4f3d862f1af63ddd3d2e046274f1b Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 15:24:58 +0200
Subject: [PATCH 11/14] =?UTF-8?q?=E2=9C=85=20:=20add=20main=20test=20class?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../GitlabClassroomsApplicationTest.java | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/GitlabClassroomsApplicationTest.java
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/GitlabClassroomsApplicationTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/GitlabClassroomsApplicationTest.java
new file mode 100644
index 0000000..5941d59
--- /dev/null
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/GitlabClassroomsApplicationTest.java
@@ -0,0 +1,14 @@
+package fr.univ_lille.gitlab.classrooms;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class GitlabClassroomsApplicationTest {
+
+ @Test
+ void contextLoads() {
+ }
+}
\ No newline at end of file
--
2.51.2
From 7b50438d39e216479369c26c4cefd246bf789b0d Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 15:26:01 +0200
Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=9A=9A=20:=20move=20file=20to=20git?=
=?UTF-8?q?lab=20package?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../{mock => gitlab}/ClassroomRepositoryGitlabImpl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename src/main/java/fr/univ_lille/gitlab/classrooms/{mock => gitlab}/ClassroomRepositoryGitlabImpl.java (96%)
diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java b/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/ClassroomRepositoryGitlabImpl.java
similarity index 96%
rename from src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java
rename to src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/ClassroomRepositoryGitlabImpl.java
index 1e29a3c..c1e2089 100644
--- a/src/main/java/fr/univ_lille/gitlab/classrooms/mock/ClassroomRepositoryGitlabImpl.java
+++ b/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/ClassroomRepositoryGitlabImpl.java
@@ -1,4 +1,4 @@
-package fr.univ_lille.gitlab.classrooms.mock;
+package fr.univ_lille.gitlab.classrooms.gitlab;
import fr.univ_lille.gitlab.classrooms.domain.Classroom;
import fr.univ_lille.gitlab.classrooms.domain.ClassroomRepository;
--
2.51.2
From 55497c808a74bf69182ee56ca30dbf103846848e Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 15:27:19 +0200
Subject: [PATCH 13/14] =?UTF-8?q?=F0=9F=94=A5=20:=20remove=20dead=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/fr/univ_lille/gitlab/classrooms/quiz/Quiz.java | 6 ------
1 file changed, 6 deletions(-)
diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/Quiz.java b/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/Quiz.java
index 319ce06..7723960 100644
--- a/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/Quiz.java
+++ b/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/Quiz.java
@@ -34,12 +34,6 @@ public class Quiz {
return questions;
}
- public void reset() {
- for (Question question : questions) {
- question.reset();
- }
- }
-
/**
* Ugly method to answer questions from a form submission
*
--
2.51.2
From 8ca6775882a8cfddd4263bc926a8883b00aae244 Mon Sep 17 00:00:00 2001
From: Julien WITTOUCK
Date: Thu, 3 Aug 2023 16:02:54 +0200
Subject: [PATCH 14/14] =?UTF-8?q?=E2=9C=85=20:=20fully=20test=20MVC=20for?=
=?UTF-8?q?=20QuizController?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../classrooms/ui/QuizControllerMVCTest.java | 93 ++++++++++++++-
.../classrooms/ui/QuizControllerTest.java | 110 ++++++++++--------
2 files changed, 155 insertions(+), 48 deletions(-)
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java
index 68329cd..fc82ba3 100644
--- a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerMVCTest.java
@@ -1,16 +1,28 @@
package fr.univ_lille.gitlab.classrooms.ui;
import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
+import fr.univ_lille.gitlab.classrooms.quiz.QuizEntity;
+import fr.univ_lille.gitlab.classrooms.quiz.QuizRepository;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.Nested;
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.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
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.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
@@ -19,6 +31,9 @@ class QuizControllerMVCTest {
@Autowired
private MockMvc mockMvc;
+ @MockBean
+ private QuizRepository quizRepository;
+
@Nested
@WithMockClassroomUser(username = "obiwan.kenobi", roles = {ClassroomRole.TEACHER})
class TeacherRole {
@@ -37,6 +52,41 @@ class QuizControllerMVCTest {
.andExpect(status().isOk());
}
+ @Test
+ void shouldAccessQuizEditPage() throws Exception {
+ var quiz = new QuizEntity();
+ quiz.setName("death-star-quiz");
+ quiz.setMarkdownContent("""
+ # who build the Death Star ?
+ (x) the Galactic Empire
+ ( ) Sauron
+ """);
+ when(quizRepository.findById("death-star-quiz")).thenReturn(Optional.of(quiz));
+
+ mockMvc.perform(get("/quiz/death-star-quiz/edit"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(view().name("quiz/edit"))
+ .andExpect(model().attributeExists("quiz"))
+ .andExpect(model().attribute("quiz", hasProperty("name", equalTo("death-star-quiz"))));
+ }
+
+ @Test
+ void shouldSaveQuiz() throws Exception {
+ var quiz = new QuizEntity();
+ quiz.setName("death-star-quiz");
+ quiz.setMarkdownContent("""
+ # who build the Death Star ?
+ (x) the Galactic Empire
+ ( ) Sauron
+ """);
+ when(quizRepository.findById("death-star-quiz")).thenReturn(Optional.of(quiz));
+
+ mockMvc.perform(post("/quiz/death-star-quiz/edit").with(csrf()))
+ .andDo(print())
+ .andExpect(status().isOk());
+ }
+
}
@Nested
@@ -57,6 +107,47 @@ class QuizControllerMVCTest {
.andExpect(status().isForbidden());
}
+ @Test
+ void shouldNotAccessQuizEditPage() throws Exception {
+ mockMvc.perform(get("/quiz/death-star-quiz/edit"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ void shouldNotSaveQuiz() throws Exception {
+ mockMvc.perform(post("/quiz/death-star-quiz/edit"))
+ .andDo(print())
+ .andExpect(status().isForbidden());
+ }
+
+ }
+
+ @Test
+ @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
+ void shouldReturn404_whenQuizDoesNotExists() throws Exception {
+ mockMvc.perform(get("/quiz/unknown"))
+ .andDo(print())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
+ void shouldReturnQuizPage_whenQuizExists() throws Exception {
+ var quiz = new QuizEntity();
+ quiz.setName("death-star-quiz");
+ quiz.setMarkdownContent("""
+ # who build the Death Star ?
+ (x) the Galactic Empire
+ ( ) Sauron
+ """);
+ when(quizRepository.findById("death-star-quiz")).thenReturn(Optional.of(quiz));
+
+ mockMvc.perform(get("/quiz/death-star-quiz"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(view().name("quiz"))
+ .andExpect(model().attribute("quiz", notNullValue()));
}
}
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java
index b36920a..8bee74e 100644
--- a/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java
+++ b/src/test/java/fr/univ_lille/gitlab/classrooms/ui/QuizControllerTest.java
@@ -1,64 +1,80 @@
package fr.univ_lille.gitlab.classrooms.ui;
-import fr.univ_lille.gitlab.classrooms.domain.ClassroomRole;
-import org.junit.jupiter.api.Nested;
+import fr.univ_lille.gitlab.classrooms.quiz.Quiz;
+import fr.univ_lille.gitlab.classrooms.quiz.QuizEntity;
+import fr.univ_lille.gitlab.classrooms.quiz.QuizRepository;
+import fr.univ_lille.gitlab.classrooms.quiz.QuizScoreRepository;
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.WithMockUser;
-import org.springframework.test.web.servlet.MockMvc;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@SpringBootTest
-@AutoConfigureMockMvc
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.ui.ConcurrentModel;
+import org.springframework.ui.Model;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
class QuizControllerTest {
- @Autowired
- private MockMvc mockMvc;
+ @InjectMocks
+ private QuizController quizController;
+
+ @Mock
+ private QuizRepository quizRepository;
- @Nested
- @WithMockClassroomUser(username = "obiwan.kenobi", roles = {ClassroomRole.TEACHER})
- class TeacherRole {
+ @Mock
+ private QuizScoreRepository quizScoreRepository;
- @Test
- void shouldAccessQuizListPage() throws Exception {
- mockMvc.perform(get("/quiz"))
- .andDo(print())
- .andExpect(status().isOk());
- }
+ @Mock
+ private Authentication authentication;
- @Test
- void shouldAccessNewQuizPage() throws Exception {
- mockMvc.perform(get("/quiz/new/edit"))
- .andDo(print())
- .andExpect(status().isOk());
- }
+ @Mock
+ private Model model;
+ @Test
+ void submitQuizAnswers_shouldOutputAnError_whenQuizIsNotFullyAnswered(){
+ var quiz = new QuizEntity();
+ quiz.setMarkdownContent("""
+ # a question
+ [ ] wrong answer
+ [x] good answer
+ """);
+ when(quizRepository.findById("testQuiz")).thenReturn(Optional.of(quiz));
+
+ quizController.submitQuizAnswers(model, "testQuiz", Map.of(), authentication);
+
+ verify(model).addAttribute("message", "Il manque des réponses à certaines questions.");
}
- @Nested
- @WithMockClassroomUser(username = "luke.skywalker", roles = {ClassroomRole.STUDENT})
- class StudentRole {
+ @Test
+ void submitQuizAnswers_shouldSaveTheScore_whenQuizIsAnswered(){
+ var quizId = "testQuiz";
+ var quiz = new QuizEntity();
+ quiz.setName(quizId);
+ quiz.setMarkdownContent("""
+ # a question
+ [ ] wrong answer
+ [x] good answer
+ """);
+ when(quizRepository.findById(quizId)).thenReturn(Optional.of(quiz));
+
+ // get the answer id from the first answer of the quiz
+ var answerKey = Quiz.fromMarkdown(quiz.getMarkdownContent(), quizId).getQuestions().get(0).getAnswers().get(0).getId();
- @Test
- void shouldNotAccessQuizListPage() throws Exception {
- mockMvc.perform(get("/quiz"))
- .andDo(print())
- .andExpect(status().isForbidden());
- }
+ var result = quizController.submitQuizAnswers(model, quizId, Map.of(answerKey, "wrong answer"), authentication);
- @Test
- void shouldNotAccessNewQuizPage() throws Exception {
- mockMvc.perform(get("/quiz/new/edit"))
- .andDo(print())
- .andExpect(status().isForbidden());
- }
+ assertThat(result).isEqualTo("quiz-submitted-with-answers-correction");
+ verify(quizScoreRepository).save(any());
}
}