diff --git a/CHANGELOG_CLIENT.md b/CHANGELOG_CLIENT.md
index d9e0b2c064d01dacdc5e0628fade29921e6c72fe..01888c756cf1e2b8a66ef089643823e4c367ca1b 100644
--- a/CHANGELOG_CLIENT.md
+++ b/CHANGELOG_CLIENT.md
@@ -1,5 +1,25 @@
 # Ubiquity-student (Client)
 
+## 1.2.6 (2025-03-24)
+
+### fixed
+- Correction de la mise à jour du TP, suite au risque de déplacement du dossier entier.
+Seule les fichiers communs entre serveur et local sont sauvegardé dans le dossier .ubiquity_back
+
+## 1.2.2 (2025-03-10)
+
+### added
+- Revue de la fenêtre de nouveau TP. Ajout d'icones et bulles d'informations.
+- Ajout de la possibilité de crée un repertoire de travail pour les TP.
+
+### changed
+- Ajustement de largeur de colonne dans la fenêtre des TP récents.
+
+## 1.2.1 (2025-03-10)
+
+### fixed
+- Masquage des fichiers cachés dans la fenêtre de choix de dossier du TP.
+
 ## 1.2.0 (2025-02-25)
 
 ### fixed
@@ -10,7 +30,6 @@
 ### fixed
 - Correction du nom ubiquity vers ubiquitysoftware
 
-
 ## 1.1.1 (2023-06-29)
 
 ### fixed
diff --git a/CHANGELOG_SERVER.md b/CHANGELOG_SERVER.md
index 4b03c7fa104b85ec0c7a67631c199176e2e158f8..bf936ece6dfe1ec263f4be33333f9398462ab5e9 100644
--- a/CHANGELOG_SERVER.md
+++ b/CHANGELOG_SERVER.md
@@ -1,6 +1,12 @@
 # Ubiquity (Serveur)
 
 
+## 1.6.0 (2024-12-10)
+
+### added
+- Ajout du mode multi-langage. Un nouvel indicateur @u:language <nomdulangage> permet de définir un langage par bloc.
+- Ajout de la prise en compte des fichiers Jupyter Notebbok.
+
 ## 1.5.1 (2023-10-10)
 
 ### fixed
diff --git a/ubiquity-student/Pipfile b/ubiquity-student/Pipfile
index 3e26304c0e9ef07ae94a23529ff9d5262b080137..9d163f1bf9401edca8c4a9ffb4d763e30d1fd7ef 100644
--- a/ubiquity-student/Pipfile
+++ b/ubiquity-student/Pipfile
@@ -9,5 +9,6 @@ requests = "*"
 [dev-packages]
 pylint = "==2.13.8"
 pyinstaller = "==5.13.1"
+pytest = "*"
 
 [requires]
diff --git a/ubiquity-student/Pipfile.lock b/ubiquity-student/Pipfile.lock
index 28c5a24799a120ccbd34103e0909a8788b49d9f9..7a2c7070000d1d3f6aaec3017942b538e1203c7f 100644
--- a/ubiquity-student/Pipfile.lock
+++ b/ubiquity-student/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "770df0774808fc1f56b15ab3a4153be9090e9568c70fc7d0feeaba968da25891"
+            "sha256": "23ebc9648a386328d033293e29ee2882dc9af953e83ea5b29f6af039e9e212db"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -24,45 +24,126 @@
         },
         "charset-normalizer": {
             "hashes": [
-                "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
-                "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
+                "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537",
+                "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa",
+                "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a",
+                "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294",
+                "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b",
+                "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd",
+                "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601",
+                "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd",
+                "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4",
+                "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d",
+                "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2",
+                "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313",
+                "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd",
+                "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa",
+                "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8",
+                "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1",
+                "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2",
+                "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496",
+                "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d",
+                "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b",
+                "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e",
+                "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a",
+                "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4",
+                "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca",
+                "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78",
+                "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408",
+                "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5",
+                "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3",
+                "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f",
+                "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a",
+                "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765",
+                "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6",
+                "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146",
+                "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6",
+                "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9",
+                "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd",
+                "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c",
+                "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f",
+                "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545",
+                "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176",
+                "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770",
+                "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824",
+                "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f",
+                "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf",
+                "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487",
+                "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d",
+                "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd",
+                "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b",
+                "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534",
+                "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f",
+                "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b",
+                "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9",
+                "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd",
+                "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125",
+                "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9",
+                "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de",
+                "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11",
+                "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d",
+                "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35",
+                "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f",
+                "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda",
+                "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7",
+                "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a",
+                "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971",
+                "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8",
+                "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41",
+                "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d",
+                "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f",
+                "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757",
+                "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a",
+                "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886",
+                "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77",
+                "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76",
+                "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247",
+                "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85",
+                "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb",
+                "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7",
+                "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e",
+                "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6",
+                "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037",
+                "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1",
+                "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e",
+                "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807",
+                "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407",
+                "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c",
+                "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12",
+                "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3",
+                "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089",
+                "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd",
+                "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e",
+                "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00",
+                "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"
             ],
-            "markers": "python_version >= '3'",
-            "version": "==2.0.12"
+            "markers": "python_version >= '3.7'",
+            "version": "==3.4.1"
         },
         "idna": {
             "hashes": [
                 "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
                 "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
             ],
-            "markers": "python_version >= '3'",
+            "markers": "python_version >= '3.6'",
             "version": "==3.10"
         },
         "requests": {
             "hashes": [
-                "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
-                "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
+                "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
+                "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
             ],
             "index": "pypi",
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==2.27.1"
-        },
-        "tkinter-tooltip": {
-            "hashes": [
-                "sha256:5bad74f2d476d9e2418598b2438f06e4b658dd6a89ee39a0bbfebb4fb7fdeb7f",
-                "sha256:dac8a6720c851936dfea2d96f90920a4dac31b46b78379c5e0ef0b6ed8af2964"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.7'",
-            "version": "==3.1.2"
+            "markers": "python_version >= '3.8'",
+            "version": "==2.32.3"
         },
         "urllib3": {
             "hashes": [
-                "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e",
-                "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"
+                "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
+                "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==1.26.20"
+            "markers": "python_version >= '3.9'",
+            "version": "==2.3.0"
         }
     },
     "develop": {
@@ -89,6 +170,22 @@
             "markers": "python_version >= '3.8'",
             "version": "==0.3.9"
         },
+        "exceptiongroup": {
+            "hashes": [
+                "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",
+                "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"
+            ],
+            "markers": "python_version < '3.11'",
+            "version": "==1.2.2"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7",
+                "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"
+            ],
+            "markers": "python_version >= '3.8'",
+            "version": "==2.1.0"
+        },
         "isort": {
             "hashes": [
                 "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109",
@@ -158,11 +255,19 @@
         },
         "platformdirs": {
             "hashes": [
-                "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907",
-                "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"
+                "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94",
+                "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==4.3.7"
+        },
+        "pluggy": {
+            "hashes": [
+                "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1",
+                "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==4.3.6"
+            "version": "==1.5.0"
         },
         "pyinstaller": {
             "hashes": [
@@ -200,13 +305,22 @@
             "markers": "python_full_version >= '3.6.2'",
             "version": "==2.13.8"
         },
+        "pytest": {
+            "hashes": [
+                "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820",
+                "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.8'",
+            "version": "==8.3.5"
+        },
         "setuptools": {
             "hashes": [
-                "sha256:4880473a969e5f23f2a2be3646b2dfd84af9028716d398e46192f84bc36900d2",
-                "sha256:558e47c15f1811c1fa7adbd0096669bf76c1d3f433f58324df69f3f5ecac4e8f"
+                "sha256:81a234dff81a82bb52e522c8aef145d0dd4de1fd6de4d3b196d0f77dc2fded26",
+                "sha256:a1246a1b4178c66d7cf50c9fc6d530fac3f89bc284cf803c7fa878c41b1a03b2"
             ],
             "markers": "python_version >= '3.9'",
-            "version": "==75.8.2"
+            "version": "==77.0.1"
         },
         "tomli": {
             "hashes": [
diff --git a/ubiquity-student/requirements-dev.txt b/ubiquity-student/requirements-dev.txt
index 0756ef208f80e38083fb09f1826ed0dd121121af..aabe028d9ef2bcebf145ff13978f8840cfe63ccd 100644
--- a/ubiquity-student/requirements-dev.txt
+++ b/ubiquity-student/requirements-dev.txt
@@ -13,3 +13,4 @@ setuptools==62.2.0; python_version >= '3.7'
 tomli==2.0.1; python_version < '3.11'
 typing-extensions==4.2.0; python_version < '3.10'
 wrapt==1.14.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+pytest==8.3.5 ; python_version >= '3.8'
\ No newline at end of file
diff --git a/ubiquity-student/src/ubiquitysoftware/VERSION b/ubiquity-student/src/ubiquitysoftware/VERSION
index 3a1f10eaec313857553d899694a174ee22623d19..7e099ec5d75abf22ee60bb93ebb1803ec9b2687a 100644
--- a/ubiquity-student/src/ubiquitysoftware/VERSION
+++ b/ubiquity-student/src/ubiquitysoftware/VERSION
@@ -1 +1 @@
-1.2.5
\ No newline at end of file
+1.2.6
\ No newline at end of file
diff --git a/ubiquity-student/src/ubiquitysoftware/controllers/main_controller.py b/ubiquity-student/src/ubiquitysoftware/controllers/main_controller.py
index 22770ac42d83efe8744a94ef3564e12946944f68..47d9e165b3e4374a338e13519f4f2b3a63eb7187 100644
--- a/ubiquity-student/src/ubiquitysoftware/controllers/main_controller.py
+++ b/ubiquity-student/src/ubiquitysoftware/controllers/main_controller.py
@@ -15,7 +15,6 @@
 #      You should have received a copy of the GNU General Public License
 #      along with this program.  If not, see <https://www.gnu.org/licenses/>.
 import os.path
-import re
 import shutil
 from datetime import datetime
 from enum import unique, Enum, auto
@@ -151,8 +150,8 @@ class MainController:
             for filename in zip_file.namelist():
                 if os.path.exists(os.path.join(self.model.directory.get(), filename)):
                     print_message(LabelEnum.WARNING_FILE_EXISTS,
-                                  color=ConsoleColor.WARNING if has_color else None,
-                                  end=" ")
+                                color=ConsoleColor.WARNING if has_color else None,
+                                end=" ")
                     print_value(filename)
                 else:
                     zip_file.extract(filename, self.model.directory.get())
@@ -175,7 +174,7 @@ class MainController:
         print_message(LabelEnum.EXTRACT_ZIP_COMPLETED, 1)
 
     def extract_updated_zip(self) -> None:
-        """Extract a restored zip file to the working directory"""
+        """Extract a update zip file to the working directory"""
         response = requests.get(self.model.url_api_update_student_environment(), timeout=5)
         print_message(LabelEnum.UPDATING)
         with ZipFile(BytesIO(response.content)) as zip_file:
@@ -188,15 +187,18 @@ class MainController:
 
     def copy_to_back(self):
         """Method copying files to the ".ubiquity_backs" directory"""
-        file_names = os.listdir(self.model.directory.get())
+        local_file_names = os.listdir(self.model.directory.get())
         back_base = os.path.join(self.model.directory.get(), ".ubiquity_backs")
         back_dir = os.path.join(back_base, f'back_{datetime.now().strftime("%Y_%m_%d_%H_%M_%S")}')
         os.makedirs(back_dir, mode=0o755, exist_ok=True)
-        for file_name in file_names:
-            old_file_path = os.path.join(self.model.directory.get(), file_name)
-            new_file_path = os.path.join(back_dir, file_name)
-            if not re.match(fr'^{re.escape(back_base)}$', old_file_path):
-                shutil.move(old_file_path, new_file_path)
+
+        response = requests.get(self.model.url_api_get_student_environment(), timeout=5)
+        with ZipFile(BytesIO(response.content)) as zip_file:
+            for server_practical_work_file_name in zip_file.namelist():
+                if server_practical_work_file_name in local_file_names:
+                    old_file_path = os.path.join(self.model.directory.get(), server_practical_work_file_name)
+                    shutil.move(old_file_path, back_dir)
+                    zip_file.extract(server_practical_work_file_name, self.model.directory.get())
 
     def _check_is_new(self) -> bool:
         """Method checking if is a new project
diff --git a/ubiquity-student/src/ubiquitysoftware/controllers/worker.py b/ubiquity-student/src/ubiquitysoftware/controllers/worker.py
index f82557b13cbacf20c35493af9e418ec0d277829e..2609457bf03aaad409409ed4e30ce901a144f8ff 100644
--- a/ubiquity-student/src/ubiquitysoftware/controllers/worker.py
+++ b/ubiquity-student/src/ubiquitysoftware/controllers/worker.py
@@ -14,6 +14,8 @@
 #
 #      You should have received a copy of the GNU General Public License
 #      along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# Imports standards
 import signal
 import time
 import os
@@ -24,9 +26,12 @@ from os import stat
 from os.path import isfile, join
 from threading import Thread
 from typing import List, Optional
+
 import requests
-from ..views.gui.dialog_views import ask_for_download, information_message
-from ..views.utils import gettext, ConsoleColor
+
+from ubiquitysoftware.views.gui.dialog_views import ask_for_download, information_message
+from ubiquitysoftware.views.utils import gettext, ConsoleColor
+
 _ = gettext.gettext
 
 TIMEOUT = 5
@@ -86,7 +91,19 @@ class Worker:
     def _init_files(self):
         """Method initializing the files to follow"""
         student_files = self._get_student_files()
-        # Checking if files not created locally exist in the database
+        other_files = self._check_file_exist(student_files)
+        self._check_file_previous_date(student_files, other_files)
+
+    def _check_file_exist(self, student_files: List[str]) -> List[str]:
+        """
+        Checking if files not created locally exist in the database
+
+        Args:
+            student_files (List[str]): Student files list
+
+        Returns:
+            List[str]: Other files list
+        """
         other_files = []
         for file_path in student_files:
             if not os.path.exists(file_path):
@@ -97,8 +114,19 @@ class Worker:
             if not response:
                 for other_file in other_files:
                     self._delete(other_file)
+        return other_files
+
+    def _check_file_previous_date(self, student_files: List[str], other_files: List[str]) -> List[str]:
+        """
+        Checking previous modification dates
 
-        # Checking previous modification dates
+        Args:
+            student_files (List[str]): Student files list
+            other_files (List[str]): Other files list
+
+        Returns:
+            List[str]: Update files list
+        """
         update_files = []
         for file_path in student_files:
             if file_path in other_files:
@@ -112,6 +140,7 @@ class Worker:
         if len(update_files) > 0:
             message = "An older version of the files exists."
             self._ask_for_download(update_files, message)
+        return update_files
 
     def run(self):
         """Method running the worker"""
@@ -167,7 +196,7 @@ class Worker:
         try:
             with open(file_path, "r", encoding='utf8') as file:
                 data = {'code': file.read(),
-                        'last_update_date': datetime.fromtimestamp(os.path.getmtime(file_path), 
+                        'last_update_date': datetime.fromtimestamp(os.path.getmtime(file_path),
                                                                     tz=timezone.utc).isoformat()}
             file_name = file_path[len(self._model.directory.get()):]
             response = requests.post(self._model.url_api_action_file(file_name), data=data, timeout=TIMEOUT)
@@ -233,12 +262,12 @@ class Worker:
                 return data.get('update_date')
             if response.status_code == StatusCode.SUSPENDED:
                 self._handle_suspended()
-        except requests.exceptions.RequestException as e:
-            information_message(self._view, _("HTTP request error: {e}").format(e=e), ConsoleColor.ERROR)
+        except requests.exceptions.RequestException as execp:
+            information_message(self._view, _("HTTP request error: {e}").format(e=execp), ConsoleColor.ERROR)
         except UnicodeDecodeError:
             information_message(self._view, _("Decoding error for file handling."), ConsoleColor.ERROR)
-        except Exception as e:
-            information_message(self._view, _("Error : {e}").format(e=e), ConsoleColor.ERROR)
+        except Exception as execp:
+            information_message(self._view, _("Error : {e}").format(e=execp), ConsoleColor.ERROR)
         return None
 
     def _ask_for_download(self, download_files: List[str], message: str) -> bool:
@@ -267,15 +296,15 @@ class Worker:
                                     stream=True, timeout=TIMEOUT)
             if response.status_code == StatusCode.OK:
                 try:
-                    with open(download_file, 'wb') as f:
+                    with open(download_file, 'wb') as file:
                         for chunk in response.iter_content(chunk_size=1024):
                             if chunk:
-                                f.write(chunk)
+                                file.write(chunk)
                     information_message(self._view,
                         _("The file {download_file} has been downloaded successfully.").format(
                             download_file=download_file), ConsoleColor.SUCCESS)
-                except Exception as e:
-                    information_message(self._view, _("Error reading file! : {e}").format(e=e),
+                except Exception as execp:
+                    information_message(self._view, _("Error reading file! : {e}").format(e=execp),
                                                     ConsoleColor.ERROR)
             elif response.status_code == StatusCode.SUSPENDED:
                 self._handle_suspended()
diff --git a/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.mo b/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.mo
index 5ce2ed46000d22f7aecc41bcf79f6d82de2a5923..37d5e3116c0b434b6c252afa273b4cdf3ce4b9d7 100644
Binary files a/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.mo and b/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.mo differ
diff --git a/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.po b/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.po
index 33c9251f771a93155c81e6e3707f660cb0114216..fc3428fc1ee57d887f12a8406c2e69db42729b7f 100644
--- a/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.po
+++ b/ubiquity-student/src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.po
@@ -450,5 +450,8 @@ msgstr "Erreur lecture fichier ! : {e}"
 msgid "Error downloading the file {download_file}. Status code: {response_status_code}"
 msgstr "Erreur lors du téléchargement du fichier {download_file}. Code de statut : {response_status_code}"
 
+#: main_controller.py:
+msgid "Your TP must not be in the root directory or /home!"
+msgstr "Votre TP ne doit pas être dans le répertoire racine ou /home!"
 
 #: msgfmt src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.po --output-file=src/ubiquitysoftware/locale/fr/LC_MESSAGES/ubiquity-student.mo
\ No newline at end of file
diff --git a/ubiquity-student/src/ubiquitysoftware/tktooltip/__init__.py b/ubiquity-student/src/ubiquitysoftware/tktooltip/__init__.py
index 3b58e102c9615ecde32665a8c24c7a03f6ce507e..4e1d5b6b753076b9613e95fc8d92ed2e10f4154f 100644
--- a/ubiquity-student/src/ubiquitysoftware/tktooltip/__init__.py
+++ b/ubiquity-student/src/ubiquitysoftware/tktooltip/__init__.py
@@ -19,5 +19,5 @@ from .tooltip import ToolTip, ToolTipStatus
 __all__ = [
     "ToolTip",
     "ToolTipStatus",
-    "__version__",
+    # "__version__",
 ]
diff --git a/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/confirm.py b/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/confirm.py
index 360b1c33fb6fd6ef05793c6c488344441f0b2c29..ed9320e38c33034631e7e795adea25ddbb25210d 100644
--- a/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/confirm.py
+++ b/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/confirm.py
@@ -39,9 +39,11 @@ class ConfirmDialog(BaseDialog):
         ttk.Label(mainframe, text=self.message).grid(column=0, row=0, columnspan=3, sticky=EW)
 
         # Create buttons
-        ttk.Button(mainframe, text=LabelEnum.YES.value, command=self.confirm_yes).grid(column=0, row=1, sticky=EW)
-        ttk.Button(mainframe, text=LabelEnum.NO.value, command=self.confirm_no).grid(column=1, row=1, sticky=EW)
-        ttk.Button(mainframe, text=LabelEnum.DOCUMENTATION.value, command=self.documentation).grid(
+        ttk.Button(mainframe, text=LabelEnum.YES.value, command=self.confirm_yes).grid(
+            column=0, row=1, sticky=EW)
+        ttk.Button(mainframe, text=LabelEnum.NO.value, command=self.confirm_no).grid(
+            column=1, row=1, sticky=EW)
+        ttk.Button(mainframe, text=LabelEnum.DOCUMENTATION.value+" ...", command=self.documentation).grid(
             column=2, row=1, sticky=EW)
 
         for child in mainframe.winfo_children():
diff --git a/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/form.py b/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/form.py
index 757add520e62ae6362790d5e38eaae55daa93e76..3bfc0801edb6c84f63ef84d7a8c2b3f9d46002b6 100644
--- a/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/form.py
+++ b/ubiquity-student/src/ubiquitysoftware/views/gui/dialogs/form.py
@@ -23,7 +23,8 @@ from ubiquitysoftware.tktooltip import ToolTip
 
 from .base import BaseDialog
 from .create_directory import CreateDirectory
-from ...utils import LabelEnum, ErrorMessage, gettext
+from .message import MessageDialog
+from ...utils import LabelEnum, ErrorMessage, gettext, ConsoleColor
 
 _ = gettext.gettext
 
@@ -35,7 +36,6 @@ class FormDialog(BaseDialog):
         self.parent.model.error.set('')
         super().__init__(parent, LabelEnum.NEW.value)
 
-
     def _event_setup(self):
         self.bind("<Return>", self.access)
 
@@ -115,16 +115,21 @@ class FormDialog(BaseDialog):
 
     def access(self, *_):
         """Method accessing and run if the values are valid"""
-        self._model.error.set('')
-        if self._is_valid():
-            self.is_valid = self.parent.submit()
-            if self.is_valid:
-                self.dismiss()
+        if self._model.directory.get() in ["/", "/home"]:
+            MessageDialog(self.parent, title=LabelEnum.ERROR.value,
+                            message=ErrorMessage.FORBIDDEN_DIRECTORY.value,
+                            color=ConsoleColor.ERROR)
+        else:
+            self._model.error.set('')
+            if self._is_valid():
+                self.is_valid = self.parent.submit()
+                if self.is_valid:
+                    self.dismiss()
 
     def create_directory(self):
-        """Creates a new folder in the selected directory.  
-        Prompts the user to enter a folder name and creates it within the current directory.  
-        If successful, updates the directory path in the model.  
+        """Creates a new folder in the selected directory.
+        Prompts the user to enter a folder name and creates it within the current directory.
+        If successful, updates the directory path in the model.
         Displays an error message if the folder cannot be created.
         """
         new_folder_name = CreateDirectory(self.parent, f"{LabelEnum.NEW.value} {LabelEnum.DIRECTORY.value}").result
@@ -133,5 +138,5 @@ class FormDialog(BaseDialog):
             try:
                 os.makedirs(new_folder_path)
                 self._model.directory.set(new_folder_path)
-            except Exception as e:
-                messagebox.showerror(LabelEnum.ERROR, f"{LabelEnum.UNABLE_FOLDER.value} : {e}")
+            except Exception as execp:
+                messagebox.showerror(LabelEnum.ERROR, f"{LabelEnum.UNABLE_FOLDER.value} : {execp}")
diff --git a/ubiquity-student/src/ubiquitysoftware/views/gui/main_view.py b/ubiquity-student/src/ubiquitysoftware/views/gui/main_view.py
index 3091d760e9c27ee4731de63a503925b0df7dfd70..61795a58147612563d1e1f40071cd4c49adffed7 100644
--- a/ubiquity-student/src/ubiquitysoftware/views/gui/main_view.py
+++ b/ubiquity-student/src/ubiquitysoftware/views/gui/main_view.py
@@ -120,9 +120,9 @@ class MainView(ttk.Frame):
         try:
             try:
                 self.tk.call('tk_getOpenFile', '-foobarbaz')
-            except TclError as e:
+            except TclError as execp:
                 pass
             self.tk.call('set', '::tk::dialog::file::showHiddenBtn', '1')
             self.tk.call('set', '::tk::dialog::file::showHiddenVar', '0')
-        except TclError as e:
-            messagebox.showerror(LabelEnum.ERROR, f"{e}")
+        except TclError as execp:
+            messagebox.showerror(LabelEnum.ERROR, f"{execp}")
diff --git a/ubiquity-student/src/ubiquitysoftware/views/gui/themes/sv_ttk/__init__.py b/ubiquity-student/src/ubiquitysoftware/views/gui/themes/sv_ttk/__init__.py
index d384ea9e5e236e15a5fb0f6330259b60bef98001..08b6d8527eb0fb31bd80aa743a96cf1421f65607 100755
--- a/ubiquity-student/src/ubiquitysoftware/views/gui/themes/sv_ttk/__init__.py
+++ b/ubiquity-student/src/ubiquitysoftware/views/gui/themes/sv_ttk/__init__.py
@@ -4,13 +4,12 @@ from pathlib import Path
 inited = False
 root = None
 
-
 def init(func):
     """
     Decorator to initialize the Tkinter theme before executing the decorated function.
 
-    This ensures that the theme is loaded only once and Tkinter is initialized before 
-    applying the theme. If the theme has already been initialized, the decorated function 
+    This ensures that the theme is loaded only once and Tkinter is initialized before
+    applying the theme. If the theme has already been initialized, the decorated function
     is simply executed.
 
     Args:
@@ -49,7 +48,7 @@ def set_theme(theme):
     """
     Sets the theme for the Tkinter application.
 
-    This function applies either a "dark" or "light" theme to the Tkinter root widget. 
+    This function applies either a "dark" or "light" theme to the Tkinter root widget.
     If an invalid theme is provided, a RuntimeError is raised.
 
     Args:
@@ -69,8 +68,8 @@ def get_theme():
     """
     Retrieves the current theme being used in the Tkinter application.
 
-    This function checks which theme is currently applied (either "dark" or "light") 
-    and returns the corresponding value. If the theme is not recognized, it returns the 
+    This function checks which theme is currently applied (either "dark" or "light")
+    and returns the corresponding value. If the theme is not recognized, it returns the
     raw theme name.
 
     Returns:
@@ -89,7 +88,7 @@ def toggle_theme():
     """
     Toggles between the "dark" and "light" themes in the Tkinter application.
 
-    This function checks the current theme and switches it to the opposite one: 
+    This function checks the current theme and switches it to the opposite one:
     from "dark" to "light" or from "light" to "dark".
 
     If the current theme is "dark", it will apply the light theme, and vice versa.
diff --git a/ubiquity-student/src/ubiquitysoftware/views/utils.py b/ubiquity-student/src/ubiquitysoftware/views/utils.py
index 7ac0bccb845642669c50528e23b04706c0baf734..3890bea7ef0036a908da32f67b1f6f4cbe589aea 100644
--- a/ubiquity-student/src/ubiquitysoftware/views/utils.py
+++ b/ubiquity-student/src/ubiquitysoftware/views/utils.py
@@ -123,6 +123,7 @@ class ErrorMessage(Enum):
     CONFIG_DELETED = _('ERROR: The group no longer exists')
     VERSION = _('ERROR: The client version is not valid')
     PRACTICAL_WORK_SUSPENDED = _('ERROR: The practical work is suspended')
+    FORBIDDEN_DIRECTORY = _("Your TP must not be in the root directory or /home!")
 
 
 class ConsoleColor(Enum):
diff --git a/ubiquity-student/tests/__init__.py b/ubiquity-student/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ubiquity-student/tests/controllers/__init__.py b/ubiquity-student/tests/controllers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ubiquity-student/tests/controllers/test_copy_to_back.py b/ubiquity-student/tests/controllers/test_copy_to_back.py
new file mode 100644
index 0000000000000000000000000000000000000000..c535e94b92446b148d0a6a5b573dccecb003877d
--- /dev/null
+++ b/ubiquity-student/tests/controllers/test_copy_to_back.py
@@ -0,0 +1,58 @@
+"""Module managing the tests of main controller copy_to_back"""
+#!/usr/bin/python3
+#      ubiquity
+#      Copyright (C) 2022  INSA Rouen Normandie - CIP
+#
+#      This program is free software: you can redistribute it and/or modify
+#      it under the terms of the GNU General Public License as published by
+#      the Free Software Foundation, either version 3 of the License, or
+#      (at your option) any later version.
+#
+#      This program is distributed in the hope that it will be useful,
+#      but WITHOUT ANY WARRANTY; without even the implied warranty of
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#      GNU General Public License for more details.
+#
+#      You should have received a copy of the GNU General Public License
+#      along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+from datetime import datetime
+from unittest.mock import MagicMock, patch
+from ubiquitysoftware.controllers.main_controller import MainController
+from tests.fixtures.fixtures import create_temp_directory, mock_config, mock_model, mock_student_key, mock_group_key
+
+
+def test_copy_to_back(create_temp_directory,
+                      mock_config: MagicMock,
+                      mock_model: MagicMock,
+                      ) -> None:
+    """pytest tests/controllers/test_copy_to_back.py"""
+    # Répertoires de sauvegarde
+    test_dir, zip_buffer = create_temp_directory
+    back_base = os.path.join(test_dir, ".ubiquity_backs")
+
+    # Création d'un fichier qui ne doit pas être déplacé
+    file_not_in_zip = os.path.join(test_dir, "file3.txt")
+    with open(file_not_in_zip, "w") as f:
+        f.write("Test file 3")
+
+    # Lancement de la fonction
+    with patch.object(mock_model, 'student_key', mock_student_key), \
+         patch.object(mock_model, 'group_key', mock_group_key):
+        mock_response = MagicMock()
+        mock_response.content = zip_buffer.getvalue()
+        with patch('requests.get', return_value=mock_response):
+            main_controller = MainController(mock_config, mock_model, has_gui=True)
+            main_controller.copy_to_back()
+
+    # Vérification de la création du dossier .ubiquity_backs
+    assert os.path.exists(back_base)
+    back_dir = os.path.join(back_base, f"back_{datetime.now().strftime('%Y_%m_%d_%H_%M_%S')}")
+
+    # Vérification des fichiers déplacés
+    assert os.path.exists(os.path.join(back_dir, "file1.txt")), "Le fichier file1.txt n'a pas été déplacé."
+    assert os.path.exists(os.path.join(back_dir, "file2.txt")), "Le fichier file2.txt n'a pas été déplacé."
+
+    # Vérification que les fichiers qui ne sont pas dans le ZIP sont toujours là
+    assert os.path.exists(file_not_in_zip), "Le fichier file3.txt a été supprimé alors qu'il ne devait pas l'être."
diff --git a/ubiquity-student/tests/controllers/test_extract_restored_zip.py b/ubiquity-student/tests/controllers/test_extract_restored_zip.py
new file mode 100644
index 0000000000000000000000000000000000000000..1dcb61b0eee4ffe237547a707f0b429841d54de6
--- /dev/null
+++ b/ubiquity-student/tests/controllers/test_extract_restored_zip.py
@@ -0,0 +1,66 @@
+"""Module managing the tests of main controller extract_restored_zip"""
+#!/usr/bin/python3
+#      ubiquity
+#      Copyright (C) 2022  INSA Rouen Normandie - CIP
+#
+#      This program is free software: you can redistribute it and/or modify
+#      it under the terms of the GNU General Public License as published by
+#      the Free Software Foundation, either version 3 of the License, or
+#      (at your option) any later version.
+#
+#      This program is distributed in the hope that it will be useful,
+#      but WITHOUT ANY WARRANTY; without even the implied warranty of
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#      GNU General Public License for more details.
+#
+#      You should have received a copy of the GNU General Public License
+#      along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+from unittest.mock import MagicMock, patch
+from ubiquitysoftware.controllers.main_controller import MainController
+from tests.fixtures.fixtures import create_temp_directory, mock_config, mock_model, mock_student_key, mock_group_key
+
+
+def test_extract_restored_zip(mock_config: MagicMock, 
+                            mock_model: MagicMock,
+                            mock_student_key: MagicMock,
+                            mock_group_key: MagicMock,
+                            create_temp_directory) -> None:
+    """pytest tests/controllers/test_extract_restored_zip.py"""
+    test_dir, zip_buffer = create_temp_directory
+
+    # Patch des méthodes pour que l'URL soit construite dynamiquement avec les mocks
+    with patch.object(mock_model, 'student_key', mock_student_key), \
+         patch.object(mock_model, 'group_key', mock_group_key):
+        
+        # Simulation de la réponse de requests.get
+        mock_response = MagicMock()
+        mock_response.content = zip_buffer.getvalue()
+        with patch('requests.get', return_value=mock_response):
+            main_controller = MainController(mock_config, mock_model, has_gui=True)
+            main_controller.extract_restored_zip()
+
+    # Vérifications après l'exécution
+    # Vérification de la création du répertoire cible
+    assert os.path.exists(test_dir), f"Le répertoire {test_dir} n'a pas été créé."
+    # Vérification que les fichiers ont été extrait   
+    extracted_file_path_1 = os.path.join(test_dir, "file1.txt")
+    assert os.path.exists(extracted_file_path_1), f"Le fichier {extracted_file_path_1} n'a pas été extrait correctement."
+    extracted_file_path_2 = os.path.join(test_dir, "file2.txt")
+    assert os.path.exists(extracted_file_path_2), f"Le fichier {extracted_file_path_2} n'a pas été extrait correctement."
+
+    # Vérification du contenu des fichier
+    with open(extracted_file_path_1, 'r') as f:
+        content = f.read()
+        assert content == "Test file 1", "Le contenu du fichier extrait ne correspond pas."
+    with open(extracted_file_path_2, 'r') as f:
+        content = f.read()
+        assert content == "Test file 2", "Le contenu du fichier extrait ne correspond pas."
+
+    # Vérifier que les fichiers ne sont pas extraits à nouveau 
+    with patch('requests.get', return_value=mock_response):
+        main_controller.extract_restored_zip()
+
+    assert os.path.exists(extracted_file_path_1), f"Le fichier {extracted_file_path_1} a été extrait à nouveau."
+    assert os.path.exists(extracted_file_path_2), f"Le fichier {extracted_file_path_2} a été extrait à nouveau."
diff --git a/ubiquity-student/tests/controllers/test_extract_updated_zip.py b/ubiquity-student/tests/controllers/test_extract_updated_zip.py
new file mode 100644
index 0000000000000000000000000000000000000000..09e0d77a35b47d87700f1ec88ac1fef2c0d4b703
--- /dev/null
+++ b/ubiquity-student/tests/controllers/test_extract_updated_zip.py
@@ -0,0 +1,60 @@
+"""Module managing the tests of main controller extract_updated_zip"""
+#!/usr/bin/python3
+#      ubiquity
+#      Copyright (C) 2022  INSA Rouen Normandie - CIP
+#
+#      This program is free software: you can redistribute it and/or modify
+#      it under the terms of the GNU General Public License as published by
+#      the Free Software Foundation, either version 3 of the License, or
+#      (at your option) any later version.
+#
+#      This program is distributed in the hope that it will be useful,
+#      but WITHOUT ANY WARRANTY; without even the implied warranty of
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#      GNU General Public License for more details.
+#
+#      You should have received a copy of the GNU General Public License
+#      along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+from unittest.mock import MagicMock, patch
+from ubiquitysoftware.controllers.main_controller import MainController
+from tests.fixtures.fixtures import create_temp_directory, mock_config, mock_model, mock_student_key, mock_group_key
+
+
+def test_extract_updated_zip(mock_config: MagicMock, 
+                            mock_model: MagicMock,
+                            mock_student_key: MagicMock,
+                            mock_group_key: MagicMock,
+                            create_temp_directory) -> None:
+    """pytest tests/controllers/test_extract_updated_zip.py"""
+    test_dir, zip_buffer = create_temp_directory
+
+    # Patch des méthodes pour que l'URL soit construite dynamiquement avec les mocks
+    with patch.object(mock_model, 'student_key', mock_student_key), \
+         patch.object(mock_model, 'group_key', mock_group_key):
+        
+        # Simuler la réponse de requests.get
+        mock_response = MagicMock()
+        mock_response.content = zip_buffer.getvalue()  # Contenu du ZIP simulé
+        with patch('requests.get', return_value=mock_response):
+            main_controller = MainController(mock_config, mock_model, has_gui=True)
+            main_controller.extract_updated_zip()
+
+    # Vérifications après l'exécution
+    # Vérification de la création du répertoire cible
+    assert os.path.exists(test_dir), f"Le répertoire {test_dir} n'a pas été créé."
+    # Vérification que les fichiers ont été extrait
+    extracted_file_path_1 = os.path.join(test_dir, "file1.txt")
+    assert os.path.exists(extracted_file_path_1), f"Le fichier {extracted_file_path_1} n'a pas été extrait correctement."
+    extracted_file_path_2 = os.path.join(test_dir, "file2.txt")
+    assert os.path.exists(extracted_file_path_2), f"Le fichier {extracted_file_path_2} n'a pas été extrait correctement."
+
+    # Vérification du contenu des fichier
+    with open(extracted_file_path_1, 'r') as f:
+        content = f.read()
+        assert content == "Test file 1 content", "Le contenu du fichier extrait ne correspond pas."
+    with open(extracted_file_path_2, 'r') as f:
+        content = f.read()
+        assert content == "Test file 2 content", "Le contenu du fichier extrait ne correspond pas."
+
diff --git a/ubiquity-student/tests/controllers/test_extract_zip.py b/ubiquity-student/tests/controllers/test_extract_zip.py
new file mode 100644
index 0000000000000000000000000000000000000000..abacf5d4a631195db4c0fb77c25631177e5a241e
--- /dev/null
+++ b/ubiquity-student/tests/controllers/test_extract_zip.py
@@ -0,0 +1,80 @@
+"""Module managing the tests of main controller extract_zip"""
+#!/usr/bin/python3
+#      ubiquity
+#      Copyright (C) 2022  INSA Rouen Normandie - CIP
+#
+#      This program is free software: you can redistribute it and/or modify
+#      it under the terms of the GNU General Public License as published by
+#      the Free Software Foundation, either version 3 of the License, or
+#      (at your option) any later version.
+#
+#      This program is distributed in the hope that it will be useful,
+#      but WITHOUT ANY WARRANTY; without even the implied warranty of
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#      GNU General Public License for more details.
+#
+#      You should have received a copy of the GNU General Public License
+#      along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+import stat
+from unittest.mock import MagicMock, patch
+from ubiquitysoftware.controllers.main_controller import MainController
+from ubiquitysoftware.views.utils import LabelEnum
+from tests.fixtures.fixtures import create_temp_directory, mock_config, mock_model, mock_student_key, mock_group_key
+
+
+def test_extract_zip(mock_config: MagicMock, 
+                     mock_model: MagicMock,
+                     mock_student_key: MagicMock,
+                     mock_group_key: MagicMock,
+                     create_temp_directory,
+                     capsys) -> None:
+    """pytest tests/controllers/test_extract_zip.py"""
+    test_dir, zip_buffer = create_temp_directory
+
+    # Patch des méthodes pour que l'URL soit construite dynamiquement avec les mocks
+    with patch.object(mock_model, 'student_key', mock_student_key), \
+         patch.object(mock_model, 'group_key', mock_group_key):
+        
+        # Simuler la réponse de requests.get
+        mock_response = MagicMock()
+        mock_response.content = zip_buffer.getvalue()
+        with patch('requests.get', return_value=mock_response):
+            main_controller = MainController(mock_config, mock_model, has_gui=True)
+            main_controller.extract_zip(has_color=True)
+
+    # Vérification de la création du répertoire cible
+    assert os.path.exists(test_dir), f"Le répertoire {test_dir} n'a pas été créé."
+    
+    # Vérification des fichiers
+    extracted_file_path_1 = os.path.join(test_dir, "file1.txt")
+    assert os.path.exists(extracted_file_path_1), f"Le fichier {extracted_file_path_1} n'a pas été extrait correctement."
+    extracted_file_path_2 = os.path.join(test_dir, "file2.txt")
+    assert os.path.exists(extracted_file_path_2), f"Le fichier {extracted_file_path_2} n'a pas été extrait correctement."
+
+    # Simulation d'un fichier existant
+    with patch('requests.get', return_value=mock_response):
+        main_controller.extract_zip(has_color=True)
+
+    # Vérification des messages d'avertissement
+    captured = capsys.readouterr()
+    assert LabelEnum.WARNING_FILE_EXISTS.value in captured.out, "Le message d'avertissement n'a pas été affiché \
+        pour le fichier existant."
+
+    # Vérification des permissions
+    file1_permissions = os.stat(extracted_file_path_1).st_mode
+    is_readable = bool(file1_permissions & stat.S_IRUSR)
+    is_writable = bool(file1_permissions & stat.S_IWUSR)
+    is_executable = bool(file1_permissions & stat.S_IXUSR)
+
+    assert is_readable and is_writable and not is_executable, \
+        f"Les permissions du fichier {extracted_file_path_1} ne sont pas correctes."
+
+    # Vérification du contenu des fichiers extraits
+    with open(extracted_file_path_1, 'r') as f:
+        content = f.read()
+        assert content == "Test file 1", "Le contenu du fichier extrait ne correspond pas."
+    with open(extracted_file_path_2, 'r') as f:
+        content = f.read()
+        assert content == "Test file 2", "Le contenu du fichier extrait ne correspond pas."
diff --git a/ubiquity-student/tests/fixtures/__init__.py b/ubiquity-student/tests/fixtures/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ubiquity-student/tests/fixtures/fixtures.py b/ubiquity-student/tests/fixtures/fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..b920a637d73070ae3fd3fcbf831ba395f843c049
--- /dev/null
+++ b/ubiquity-student/tests/fixtures/fixtures.py
@@ -0,0 +1,73 @@
+"""Module managing the fixtures of tests"""
+#!/usr/bin/python3
+#      ubiquity
+#      Copyright (C) 2022  INSA Rouen Normandie - CIP
+#
+#      This program is free software: you can redistribute it and/or modify
+#      it under the terms of the GNU General Public License as published by
+#      the Free Software Foundation, either version 3 of the License, or
+#      (at your option) any later version.
+#
+#      This program is distributed in the hope that it will be useful,
+#      but WITHOUT ANY WARRANTY; without even the implied warranty of
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#      GNU General Public License for more details.
+#
+#      You should have received a copy of the GNU General Public License
+#      along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import pytest
+import tempfile
+import shutil
+import os
+from io import BytesIO
+from zipfile import ZipFile
+from unittest.mock import MagicMock
+
+
+@pytest.fixture
+def create_temp_directory():
+    """Fixture de création d'un répertoire temporaire et de fichiers"""
+    test_dir = tempfile.mkdtemp()
+    
+    # Création d'un fichier ZIP
+    file1 = os.path.join(test_dir, "file1.txt")
+    with open(file1, "w") as f:
+        f.write("Test file 1")
+    file2 = os.path.join(test_dir, "file2.txt")
+    with open(file2, "w") as f:
+        f.write("Test file 2")
+    zip_buffer = BytesIO()
+    with ZipFile(zip_buffer, 'w') as zip_file:
+        zip_file.writestr("file1.txt", "Test file 1 content")
+        zip_file.writestr("file2.txt", "Test file 2 content")
+    zip_buffer.seek(0)  # Remise du pointeur à 0
+
+    yield test_dir, zip_buffer
+    shutil.rmtree(test_dir) # Effacement du répertoire aprés test
+
+@pytest.fixture
+def mock_config():
+    """Mock de la config"""
+    return MagicMock()
+
+@pytest.fixture
+def mock_model(create_temp_directory):
+    test_dir, _ = create_temp_directory
+    mock = MagicMock()
+    mock.directory.get.return_value = test_dir
+    mock.url_api_get_student_environment.return_value = "http://ubiquity.fr/environment.zip"
+    mock.url_api_update_student_environment.return_value = "http://ubiquity.fr/update.zip"
+    return mock
+
+@pytest.fixture
+def mock_student_key():
+    mock = MagicMock()
+    mock.get.return_value = "student_key"
+    return mock
+
+@pytest.fixture
+def mock_group_key():
+    mock = MagicMock()
+    mock.get.return_value = "group_key"
+    return mock
diff --git a/ubiquity/tests/tutoring/views/client/test_get_last_update.py b/ubiquity/tests/tutoring/views/client/test_get_last_update.py
index 06043d92ec01bf7a8b93b49320de6df00f2487ac..805b16b15fcce61e77e7afd449e9900978fe90b7 100644
--- a/ubiquity/tests/tutoring/views/client/test_get_last_update.py
+++ b/ubiquity/tests/tutoring/views/client/test_get_last_update.py
@@ -15,6 +15,7 @@
 #      You should have received a copy of the GNU General Public License
 #      along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+import json
 from typing import List
 from django.urls import reverse
 from django.utils.timezone import now
@@ -86,7 +87,11 @@ class GetLastUpdateTestCase(APITestCase):
             'student_key': self.student_1.key,
             'group_key': self.groups[0].student_key
         })
-        response = self.client.get(self.url, {'file_path': self.code_file.file_path}, format='json')
+        response = self.client.generic("GET", self.url, 
+                               data=json.dumps({'file_path': self.code_file.file_path}), 
+                               content_type="application/json")
+
+
         update_date = response.data
 
         self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -101,7 +106,9 @@ class GetLastUpdateTestCase(APITestCase):
             'student_key': self.student_1.key,
             'group_key': self.groups[0].student_key
         })
-        response = self.client.get(self.url, {'file_path': self.code_file.file_path}, format='json')
+        response = self.client.generic("GET", self.url, 
+                               data=json.dumps({'file_path': self.code_file.file_path}), 
+                               content_type="application/json")
         update_date = response.data
 
         self.assertEqual(response.status_code, status.HTTP_200_OK)
diff --git a/ubiquity/tutoring/views/client/views.py b/ubiquity/tutoring/views/client/views.py
index 7d0f3c86d4de1b9e31e8497f1279b4e91cf84056..c4aa9b0bf91e8f5bc9ea9bab5b99553d9fa36e5e 100644
--- a/ubiquity/tutoring/views/client/views.py
+++ b/ubiquity/tutoring/views/client/views.py
@@ -459,7 +459,10 @@ def get_last_update(request: Request, student_key: str, group_key: str) -> Respo
     if not student_group.group.practical_work.state:
         return practical_work_has_suspended()
 
-    _, file_name = os.path.split(request.query_params.get('file_path', ''))
+    file_path = request.data['file_path']
+    if not file_path:
+        return Response({'error': 'file_path is required'}, status=status.HTTP_400_BAD_REQUEST)
+    _, file_name = os.path.split(file_path)
 
     codes_file = CodeFile.objects.filter(source_code=student_group.source_code, file_path=file_name)