Nantes Université

Skip to content
Extraits de code Groupes Projets
Valider fff7bea6 rédigé par Zomzog's avatar Zomzog
Parcourir les fichiers

TD1

parent ee06b2c0
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
== IUT Spring Kotlin - 2024
link:td1.adoc[TD1]
\ No newline at end of file
== TD1
Checkout de https://github.com/Zomzog/2024-iut-td.git
Importer le projet dans son IDE
Les modifications sont à faire dans le module Exo1
== Exo 0
Créer une implémentation de Database nommé `ListDatabase``,
cette implémentation utilisera une liste en mémoire pour la persistance.
== Exo 1
En utilisant l'approche des @Bean,
créer un fichier AppConfig qui gère la création des beans movieservice et Database
Dans la classe de test Exercice#exo1_1 charger le context Spring à partir de AppConfig
pour obtenir une instance de movieservice
== Exo 2
Avec la même approche,
ajouter la création d'un bean Supermovieservice.
Il doit partager la même instance de `Database`.
Dans la classe de test Exercice#exo1_2 charger le context Spring à partir de AppConfig
pour obtenir une instance des services
Le test doit passer.
== Exo 3
Faire en sorte que partagent plus la même instance de `Database`
Dans la classe de test Exercice#exo1_3 charger le context Spring à partir de AppConfig
pour obtenir une instance des services.
Le test doit passer, le test exo1_2 ne passe plus.
== Exo 4
Supprimer le constructeur de Supermovieservice et utiliser l'injection avec Autowired pour ce service.
Le test exo1_3 doit toujours fonctionner
== Exo 5
Remplacer la création du bean `Database` en utilisant le stereotype `@Repository`
Le test exo1_3 doit toujours fonctionner
== Exo 6
Créer une classe de test unitaires ListDatabaseTest qui couvre à 100% ListDatabase
== Exo 6
Créer une nouvelle version de Database nommée `HashDatabase` en utilisant une `Map<UUID, User>` comme persistance.
Elle doit répondre aux mêmes tests que ListDatabaseTest
== Exo 7
Dans AppConfig créer le bean de HashDatabase en scope Singleton
Utiliser ce bean pour supermovieservice.
Le test suivant doit passer
```kotlin
@Test
fun exo1_7() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val movieservice = context.getBean(movieservice::class.java)
val supermovieservice = context.getBean(Supermovieservice::class.java)
assertThat(movieservice.database).isInstanceOf(ListDatabase::class)
assertThat(supermovieservice.database).isInstanceOf(HashDatabase::class)
}
```
== Exo 8
Ajouter les dépendances `org.springframework.boot:spring-boot-starter` et `org.springframework.boot:spring-boot-starter-test`
Remplacer tout ce qui est possible par l'utilisation de l'annotation SpringBootApplication dans le main
Le test suivant doit passer.
```kotlin
@SpringBootTest
class Exo8 {
@Autowired
private lateinit var movieservice: movieservice
@Test
fun exo_8() {
// Test le chargement du contexte
}
}
```
== Exo 9
Ajouter les dépendances de test `io.mockk:mockk-jvm:1.13.12` et `com.ninja-squad:springmockk:4.0.2`
Dans la class Exo8,
remplacer la Database de movieservice par un mock.
Ajouter ce test et le compléter pour qu'il soit valide.
```
@Test
fun exo_9() {
// GIVEN TODO
// THEN
assertThrows<NoSuchElementException> { movieservice.delete(user()) }
movieservice.delete(user(UUID.randomUUID()))
}
```
== Exo 10
A partir de cet exercice, les modifications seront à faire dans le module Exo10
Créer HelloController.kt dans un sous-package controller.
[source,kotlin]
----
@RestController
class HelloController {
@GetMapping("/hello")
fun hello() = "world"
}
----
=== Lancer && tester
Lancer **Application.kt qui est à la racine du projet (clic droit -> run).
Appeler GET 192.168.1.44:8080/hello et vérifier que la réponse est bien world.
Par exemple en CURL
```bash
curl -XGET -v 192.168.1.44:8080/hello
...
< HTTP/1.1 200
...
world
```
== CRUD
Le but de la suite des exercices est de créer un premier CRUD (Create, Read, Update, Delete).
Le CRUD doit manipuler des films dont on a les informations: Nom, Date de sortie, Note, List des langues
Pour cette implémentation, une Map en mémoire permettra de faire office de base de données.
La clé unique est le nom du film.
La map peut-être initialisé avec une liste de film (cf MOVIES dans la classe Movie)
L'implémentation se fera dans une classe MovieController.
== Exo 11: Create
Le premier endpoint POST `/api/movies` qui prend le JSON d'un film, l'enregistre dans la Map et répond un HTTP 201 avec le contenu du film en body.
Exemple d'appel:
----
curl --location '192.168.1.44:8080/api/movies' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Jurassic Park",
"rating": 91,
"releaseDate": 1993,
"languages": [ "VO", "VFF", "VFQ"]
}'
----
== Exo 12: Create - Conflit
Un endpoint de création doit normalement signaler si la ressource existe déjà.
Modifier le endpoint pour que si on envoie deux fois la même nom de film, la réponse soit un HTTP 409 (conflit).
Exemple d'appel:
----
curl --location '192.168.1.44:8080/api/movies' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Jurassic Park",
"rating": 91,
"releaseDate": 1993,
"languages": [ "VO", "VFF", "VFQ"]
}' &&
curl --location '192.168.1.44:8080/api/movies' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Jurassic Park",
"rating": 90,
"releaseDate": 1992,
"languages": [ "VO" ]
}'
----
== Exo 13: Read - List
Le premier endpoint de lecture est un endpoint de liste.
Un appel à GET `/api/movies` doit répondre 200 avec la liste des utilisateurs qui sont dans la Map.
Exemple d'appel:
----
curl --location '192.168.1.44:8080/api/movies'
----
Reponse:
[source,json]
----
[
{
"name": "The Dark Knight",
"releaseDate": 2008,
"rating": 9,
"languages": [
"VO"
]
}
]
----
== Exo 14: Read - Unique
Ajouter un endpoint GET `/api/movies/{name}` qui retourne :
- un status 200 avec le contenu du film s'il existe dans la Map,
- un status 404 sinon.
Exemple d'appel:
----
curl -v --location '192.168.1.44:8080/api/movies/Dune'
HTTP/1.1 404
----
----
curl --location '192.168.1.44:8080/api/movies/Inception'
----
Reponse:
[source,json]
----
{
"name": "Inception",
"releaseDate": 2010,
"rating": 8,
"languages": [
"VF"
]
}
----
== Exo 15: Update
Ajouter un endpoint PUT `/api/movies/{name}` qui retourne :
- un status 400 si la requête est invalide
- un status 404 si le film n'existe pas
- un status 200 sinon met à jour le film dans la Map et le retourne,
Exemple d'appel:
----
curl --location --request PUT '192.168.1.44:8080/api/movies/Inception' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Inception",
"releaseDate": 2010,
"rating": 87,
"languages": [
"VF", "VO"
]
}'
----
Reponse:
[source,json]
----
{
"name": "Inception",
"releaseDate": 2010,
"rating": 87,
"languages": [
"VF", "VO"
]
}
----
----
curl -v --location --request PUT '192.168.1.44:8080/api/movies/Inception' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "My Little Pony",
"releaseDate": 2010,
"rating": 87,
"languages": [
"VF", "VO"
]
}'
HTTP/1.1 400
----
----
curl -v --location --request PUT '192.168.1.44:8080/api/movies/Dune' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Dune",
"releaseDate": 2010,
"rating": 87,
"languages": [
"VF", "VO"
]
}'
HTTP/1.1 404
----
== Exp 16: Delete
Ajouter un endpoint DELETE `/api/movies/{name}` qui retourne :
- un status 204 si le film existe et le supprime de la Map,
- un status 400 sinon.
Exemple d'appel:
----
curl -v --location --request DELETE '192.168.1.44:8080/api/movies/Inception'
HTTP/1.1 204
----
----
curl -v --location --request DELETE '192.168.1.44:8080/api/movies/Dune'
HTTP/1.1 404
----
== Exo 17: Liste filtrée
Ajouter sur la liste des films la possibilité de filtrer par note.
Exemple d'appel:
----
curl --location '192.168.1.44:8080/api/movies?rating=99'
----
Reponse:
[source,json]
----
[
{
"name": "My Little Pony",
"releaseDate": 2017,
"rating": 99,
"languages": [
"VO",
"VFF"
]
}
]
----
== Exo 18: List filtré par header
Sur la liste des films, si le header `Accept-Language` est fourni,
filtrer la liste des résultats
Exemple d'appel:
----
curl --header "Accept-Language: VFF" --location '192.168.1.44:8080/api/movies'
----
Reponse:
[source,json]
----
[
{
"name": "My Little Pony",
"releaseDate": 2017,
"rating": 99,
"languages": [
"VO",
"VFF"
]
}
]
----
== Exo 19
Spring fourni un framework appelé MockMvc pour faire des requêtes et des assertions lors des tests.
Cette API peut-être injecté en ajoutant `@AutoConfigureMockMvc` à un test.
Un example est fourni dans MovieControllerTest,
La documentation se trouve https://docs.spring.io/spring-framework/reference/testing/spring-mvc-test-framework.html[ici]
Utiliser ce framework, faire de test de la class MovieController
......@@ -21,27 +21,6 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk-jvm</artifactId>
<version>1.13.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ninja-squad</groupId>
<artifactId>springmockk</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package iut.nantes
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Bean
fun database() = ListDatabase()
@Bean
fun userService(@Qualifier("database") db: Database) = UserService(db)
@Bean
fun superUserService(@Qualifier("database") db: Database) = SuperUserService(database())
}
\ No newline at end of file
package iut.nantes
import java.util.*
import org.springframework.stereotype.Repository
@Repository
class HashDatabase : Database {
override fun save(user: User) {
TODO("Not yet implemented")
}
override fun delete(user: User) {
TODO("Not yet implemented")
}
override fun update(user: User) {
TODO("Not yet implemented")
}
override fun findOne(id: UUID): User? {
TODO("Not yet implemented")
}
override fun findAll(name: String?): List<User> {
TODO("Not yet implemented")
}
}
\ No newline at end of file
package iut.nantes
import java.util.UUID
class ListDatabase : Database {
private val users = mutableListOf<User>()
override fun save(user: User) {
users.add(user)
}
override fun delete(user: User) {
users.remove(user)
}
override fun update(user: User) {
val index = users.indexOfFirst { it.id == user.id }
if (index != -1) {
users[index] = user
}
}
override fun findOne(id: UUID): User? {
return users.find { it.id == id }
}
override fun findAll(name: String?): List<User> {
return if (name != null) {
users.filter { it.name == name }
} else {
users
}
}
}
\ No newline at end of file
package iut.nantes
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
open class MyApp
fun main(args: Array<String>) {
runApplication<MyApp>(*args)
}
......@@ -17,8 +17,7 @@ class Exercies {
@Test
fun exo1_1() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val userService = context.getBean(UserService::class.java)
val userService: UserService = TODO()
userService.save(user())
val user = userService.findOne(user().id)
......@@ -27,9 +26,8 @@ class Exercies {
@Test
fun exo1_2() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val userService = context.getBean(UserService::class.java)
val superUserService = context.getBean(SuperUserService::class.java)
val userService: UserService = TODO()
val superUserService: SuperUserService = TODO()
userService.save(user())
assertThat(superUserService.findAll()).isEqualTo(listOf(user()))
......@@ -37,9 +35,8 @@ class Exercies {
@Test
fun exo1_3() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val userService = context.getBean(UserService::class.java)
val superUserService = context.getBean(SuperUserService::class.java)
val userService: UserService = TODO()
val superUserService: SuperUserService = TODO()
userService.save(user())
assertThat(superUserService.findAll()).isEmpty()
......@@ -48,25 +45,4 @@ class Exercies {
}
@SpringBootTest
class Exo8 {
@MockkBean
private lateinit var database: ListDatabase
@Autowired
private lateinit var userService: UserService
@Test
fun exo_9() {
// GIVEN TODO
every { database.delete(any()) } returns Unit
every { database.delete(user()) } throws NoSuchElementException()
// THEN
assertThrows<NoSuchElementException> { userService.delete(user()) }
userService.delete(user(UUID.randomUUID()))
}
}
private fun user(uuid: UUID = UUID(0, 1)) = User(uuid, "John Doe", "email@noop.pony", 42)
......@@ -9,7 +9,7 @@
<artifactId>td1</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>exo1</artifactId>
<artifactId>exo10</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
......@@ -17,13 +17,9 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......
package iut.nantes
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class Exo10Application
fun main(args: Array<String>) {
runApplication<Exo10Application>(*args)
}
package iut.nantes
data class Movie(val name: String, val releaseDate: Int, val rating: Int, val languages: List<String>)
val MOVIES = listOf(
Movie("The Dark Knight", 2008, 9, listOf("VO")),
Movie("Inception", 2010, 8, listOf("VF")),
Movie("My Little Pony", 2017, 99, listOf("VO", "VFF"))
)
\ No newline at end of file
package iut.nantes
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.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post
/**
*
*
*
* FOR EXO 19
*
*
*
*
*/
@AutoConfigureMockMvc
@SpringBootTest
class MovieControllerTest {
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun demoGet() {
mockMvc.get("/api/movies")
.andExpect {
status { isOk() }
content { contentType("application/json") }
jsonPath("$[0].name") { value("The Dark Knight") }
}
}
@Test
fun demoPost() {
mockMvc.post("/api/movies") {
contentType = MediaType.APPLICATION_JSON
content = """{"name": "The Dark Knight", "year": 2008}"""
}.andExpect {
status { isBadRequest() }
}
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@
<modules>
<module>exo1</module>
<module>exo10</module>
</modules>
<properties>
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter