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)