49 Commits

Author SHA1 Message Date
ffcb257ab5 fixed command 2025-07-11 09:21:25 +02:00
57a5dfb8d8 refactor dir, added integration test for feature 2025-07-11 09:17:07 +02:00
297468e4ad added html report 2025-07-10 17:04:06 +02:00
a734f66f22 added testing paragraph 2025-07-10 15:13:02 +02:00
97019fb223 added testing paragraph 2025-07-10 11:27:44 +02:00
5ba0e4aecf Merge pull request #10 from ylxdre/feature/issue7
Feature/issue7
2025-07-09 12:32:43 +02:00
9e3e049535 template fixed, 2 tests on display 2025-07-09 12:30:10 +02:00
811c635a47 Merge branch 'QA' of github.com:ylxdre/OCR-P11-Testing into feature/issue7
branch created earlier : retrieve base code
2025-07-09 11:54:08 +02:00
db692a068e Merge pull request #8 from ylxdre/bug/issue6
Bug/issue6
2025-07-08 16:58:04 +02:00
772048a6ed removed print 2025-07-08 16:56:15 +02:00
81ca8765a2 fixed indentation error 2025-07-08 16:54:58 +02:00
99b1c00200 added docstring and comments 2025-07-08 16:18:43 +02:00
c26fee3fbd made test more simple 2025-07-07 15:49:13 +02:00
0f18efad16 added base code 2025-07-07 15:01:38 +02:00
fb83a81699 Merge pull request #7 from ylxdre/bug/issue5
Bug/issue5
2025-07-07 14:32:39 +02:00
df5cbfcf6c Merge branch 'QA' into bug/issue5 2025-07-07 14:32:30 +02:00
394f584f08 date test with ward 2025-07-07 14:25:00 +02:00
ff0baa26b1 fixed first date check 2025-07-07 14:20:09 +02:00
7ba1a440ce fixed test on mail 2025-07-07 14:09:59 +02:00
c43795515b Merge pull request #5 from ylxdre/bug/issue4
Bug/issue4
2025-07-07 14:00:11 +02:00
5411980f7e Merge branch 'QA' into bug/issue4 2025-07-07 13:59:52 +02:00
65a05e73b7 fixed booking confirmation message 2025-07-07 11:12:45 +02:00
185744ff7b added test for multiple book 2025-07-07 11:06:55 +02:00
5f861f968d removed unused print 2025-07-07 11:01:54 +02:00
798a9a0d91 add coverage config 2025-07-07 10:59:30 +02:00
f79c92e8c3 Merge pull request #4 from ylxdre/bug/issue2
Bug/issue2
2025-07-07 10:44:53 +02:00
4565f2a16a Merge branch 'QA' into bug/issue2 2025-07-07 10:43:56 +02:00
fe4e2143a7 Merge pull request #3 from ylxdre/bug/issue1
Bug/issue1
2025-07-07 10:40:04 +02:00
79a6b00401 removed print 2025-07-07 10:27:54 +02:00
8ae4065270 test on forged URL for past competition 2025-07-07 10:08:03 +02:00
5ed18c0c3c first test on link displayed based on date 2025-07-07 10:02:55 +02:00
ada3a5cc76 test old competition hasn`t link to book 2025-07-04 15:38:55 +02:00
5a799ccd7a added/setup coverage 2025-07-03 15:35:51 +02:00
df7a0836ba coverage installed 2025-07-03 15:26:11 +02:00
0370de60e0 added ok and nok on points 2025-07-03 11:41:17 +02:00
813b7849fe test Ok on soup 2025-07-02 17:27:06 +02:00
9705789809 works better with init 2025-07-02 15:07:28 +02:00
b34382c46c first try to get session cookie 2025-07-02 15:06:46 +02:00
90cae5ffc9 first try with fixture for login 2025-07-02 11:36:51 +02:00
40226a45a6 removed print, unused decode 2025-07-01 09:52:24 +02:00
1009ebfc1e added 3 login tests 2025-06-30 14:02:22 +02:00
9d49f5f79f update club`s points 2025-06-30 10:38:25 +02:00
e089e78fc5 let this fix for issue6 2025-06-30 10:36:15 +02:00
d3e9eda937 let this fix for issue6 2025-06-30 10:32:46 +02:00
2dc85f19e7 added public list of club`s points 2025-06-26 15:11:13 +02:00
1009e35d17 cleaned an unused print 2025-06-26 12:07:42 +02:00
7521a05db9 added now to every welcome template call 2025-06-26 11:37:02 +02:00
364bdb3d9f past comp is displayed; condition on link and route 2025-06-26 11:26:57 +02:00
0345e8e8df added filter on date in view 2025-06-26 11:13:38 +02:00
31 changed files with 2243 additions and 54 deletions

2
.coveragerc Normal file
View File

@@ -0,0 +1,2 @@
[run]
omit = tests/*

View File

@@ -10,6 +10,12 @@ itsdangerous = "==1.1.0"
jinja2 = "==2.11.2"
markupsafe = "==1.1.1"
werkzeug = "==1.0.1"
ward = "*"
coverage = "*"
pytest-cov = "*"
pytest = "*"
pytest-flask = "*"
bs4 = "*"
[dev-packages]

281
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "55e68c2c6e7fb3f1685b9dfe66180ae537a367985700c08a86ccd664d56ccad7"
"sha256": "42b0dc67898b0101df0af5ed2bd8ab0d6d7eb8f54964fb0a4a0a22ac504db2e5"
},
"pipfile-spec": 6,
"requires": {
@@ -17,6 +17,22 @@
]
},
"default": {
"beautifulsoup4": {
"hashes": [
"sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b",
"sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==4.13.4"
},
"bs4": {
"hashes": [
"sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925",
"sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"
],
"index": "pypi",
"version": "==0.0.2"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
@@ -26,6 +42,103 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"click-completion": {
"hashes": [
"sha256:5bf816b81367e638a190b6e91b50779007d14301b3f9f3145d68e3cade7bce86"
],
"version": "==0.5.2"
},
"click-default-group": {
"hashes": [
"sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f",
"sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"
],
"markers": "python_version >= '2.7'",
"version": "==1.2.4"
},
"cucumber-tag-expressions": {
"hashes": [
"sha256:e314d5fed6eebb2f90380271f562248fb15e18636764faf40f4dde4b28b1f960"
],
"markers": "python_version >= '2.7'",
"version": "==4.1.0"
"coverage": {
"extras":
"toml"
],
"hashes": [
"sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a",
"sha256:0a17eaf46f56ae0f870f14a3cbc2e4632fe3771eab7f687eda1ee59b73d09fe4",
"sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c",
"sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c",
"sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b",
"sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa",
"sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6",
"sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2",
"sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926",
"sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2",
"sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6",
"sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e",
"sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd",
"sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e",
"sha256:2f3da12e0ccbcb348969221d29441ac714bbddc4d74e13923d3d5a7a0bebef7a",
"sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9",
"sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc",
"sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a",
"sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74",
"sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694",
"sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d",
"sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf",
"sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f",
"sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b",
"sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7",
"sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5",
"sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868",
"sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584",
"sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912",
"sha256:669135a9d25df55d1ed56a11bf555f37c922cf08d80799d4f65d77d7d6123fcf",
"sha256:71ae8b53855644a0b1579d4041304ddc9995c7b21c8a1f16753c4d8903b4dfed",
"sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d",
"sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d",
"sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355",
"sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050",
"sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8",
"sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038",
"sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b",
"sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3",
"sha256:9d3a700304d01a627df9db4322dc082a0ce1e8fc74ac238e2af39ced4c083193",
"sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0",
"sha256:a535c0c7364acd55229749c2b3e5eebf141865de3a8f697076a3291985f02d30",
"sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba",
"sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3",
"sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0",
"sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46",
"sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3",
"sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14",
"sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e",
"sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547",
"sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62",
"sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac",
"sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb",
"sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b",
"sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f",
"sha256:dd7a57b33b5cf27acb491e890720af45db05589a80c1ffc798462a765be6d4d7",
"sha256:ddc39510ac922a5c4c27849b739f875d3e1d9e590d1e7b64c98dadf037a16cce",
"sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45",
"sha256:df0f9ef28e0f20c767ccdccfc5ae5f83a6f4a2fbdfbcbcc8487a8a78771168c8",
"sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4",
"sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f",
"sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b",
"sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd",
"sha256:f65bb452e579d5540c8b37ec105dd54d8b9307b07bcaa186818c104ffda22441",
"sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1",
"sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b",
"sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==7.9.2"
},
"flask": {
"hashes": [
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
@@ -35,6 +148,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.1.2"
},
"iniconfig": {
"hashes": [
"sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7",
"sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"
],
"markers": "python_version >= '3.8'",
"version": "==2.1.0"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
@@ -53,6 +174,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.2"
},
"markdown-it-py": {
"hashes": [
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
"sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"
],
"markers": "python_version >= '3.8'",
"version": "==3.0.0"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
@@ -112,6 +241,156 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1"
},
"mdurl": {
"hashes": [
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
],
"markers": "python_version >= '3.7'",
"version": "==0.1.2"
"packaging": {
"hashes": [
"sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484",
"sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"
],
"markers": "python_version >= '3.8'",
"version": "==25.0"
},
"pluggy": {
"hashes": [
"sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3",
"sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"
],
"markers": "python_version >= '3.9'",
"version": "==1.6.0"
},
"pprintpp": {
"hashes": [
"sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d",
"sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"
],
"version": "==0.4.0"
},
"pygments": {
"hashes": [
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887",
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"
],
"markers": "python_version >= '3.8'",
"version": "==2.19.2"
},
"rich": {
"hashes": [
"sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0",
"sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==14.0.0"
},
"shellingham": {
"hashes": [
"sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686",
"sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"
],
"markers": "python_version >= '3.7'",
"version": "==1.5.4"
},
"six": {
"hashes": [
"sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.17.0"
},
"tomli": {
"hashes": [
"sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6",
"sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd",
"sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c",
"sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b",
"sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8",
"sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6",
"sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77",
"sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff",
"sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea",
"sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192",
"sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249",
"sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee",
"sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4",
"sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98",
"sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8",
"sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4",
"sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281",
"sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744",
"sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69",
"sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13",
"sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140",
"sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e",
"sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e",
"sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc",
"sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff",
"sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec",
"sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2",
"sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222",
"sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106",
"sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272",
"sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a",
"sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"
],
"markers": "python_version >= '3.8'",
"version": "==2.2.1"
},
"ward": {
"hashes": [
"sha256:0847e6b95db9d2b83c7d1b9cea9bcb7ac3b8e8f6d341b8dc8920d6afb05458b1",
"sha256:d8aafa4ddb81f4d5787d95bdb2f7ba69a2e89f183feec78d8afcc64b2cd19ee9"
],
"index": "pypi",
"markers": "python_full_version >= '3.7.8' and python_full_version < '4.0.0'",
"version": "==0.68.0b0"
"pytest": {
"hashes": [
"sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7",
"sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"
],
"markers": "python_version >= '3.9'",
"version": "==8.4.1"
},
"pytest-cov": {
"hashes": [
"sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2",
"sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==6.2.1"
},
"pytest-flask": {
"hashes": [
"sha256:58be1c97b21ba3c4d47e0a7691eb41007748506c36bf51004f78df10691fa95e",
"sha256:c0e36e6b0fddc3b91c4362661db83fa694d1feb91fa505475be6732b5bc8c253"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"soupsieve": {
"hashes": [
"sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4",
"sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"
],
"markers": "python_version >= '3.8'",
"version": "==2.7"
},
"typing-extensions": {
"hashes": [
"sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4",
"sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"
],
"markers": "python_version >= '3.9'",
"version": "==4.14.0"
},
"werkzeug": {
"hashes": [
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",

View File

@@ -43,9 +43,15 @@
* clubs.json - list of clubs with relevant information. You can look here to see what email addresses the app will accept for login.
5. Testing
- unit and integration tests :
You are free to use whatever testing framework you like-the main thing is that you can show what tests you are using.
All tests are made with Pytest, except <code>test_ward_book_old.py</code> which uses Ward
Run the tests by using : `pytest -s -v --cov=. --ignore=tests/test_ward_book_old.py`
You'll see the result, the list and the coverage rapport
We also like to show how well we're testing, so there's a module called
[coverage](https://coverage.readthedocs.io/en/coverage-5.1/) you should add to your project.
you can also run `ward` from the root folder to see the two ward tests
- performance tests :
run the app : `FLASK_APP=server.py flask run`
then run `locust -f tests/performance_test/locustfile.py` from the root directory and connect with browser to `
http://0.0.0.0:8089` and launch tests from the web interface

View File

@@ -7,7 +7,7 @@
},
{
"name": "Fall Classic",
"date": "2020-10-22 13:30:00",
"date": "2025-10-22 13:30:00",
"numberOfPlaces": "13"
}
]

115
htmlcov/class_index.html generated Normal file
View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Coverage report</title>
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
<link rel="stylesheet" href="style_cb_81f8c14c.css" type="text/css">
<script src="coverage_html_cb_6fb7b396.js" defer></script>
</head>
<body class="indexfile">
<header>
<div class="content">
<h1>Coverage report:
<span class="pc_cov">94%</span>
</h1>
<aside id="help_panel_wrapper">
<input id="help_panel_state" type="checkbox">
<label for="help_panel_state">
<img id="keyboard_icon" src="keybd_closed_cb_ce680311.png" alt="Show/hide keyboard shortcuts">
</label>
<div id="help_panel">
<p class="legend">Shortcuts on this page</p>
<div class="keyhelp">
<p>
<kbd>f</kbd>
<kbd>n</kbd>
<kbd>s</kbd>
<kbd>m</kbd>
<kbd>x</kbd>
<kbd>c</kbd>
&nbsp; change column sorting
</p>
<p>
<kbd>[</kbd>
<kbd>]</kbd>
&nbsp; prev/next file
</p>
<p>
<kbd>?</kbd> &nbsp; show/hide this help
</p>
</div>
</div>
</aside>
<form id="filter_container">
<input id="filter" type="text" value="" placeholder="filter...">
<div>
<input id="hide100" type="checkbox" >
<label for="hide100">hide covered</label>
</div>
</form>
<h2>
<a class="button" href="index.html">Files</a>
<a class="button" href="function_index.html">Functions</a>
<a class="button current">Classes</a>
</h2>
<p class="text">
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
</header>
<main id="index">
<table class="index" data-sortable>
<thead>
<tr class="tablehead" title="Click to sort">
<th id="file" class="name left" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
<th id="region" class="name left" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">class<span class="arrows"></span></th>
<th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
<th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
<th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
<th id="coverage" class="right" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
</tr>
</thead>
<tbody>
<tr class="region">
<td class="name left"><a href="server_py.html">server.py</a></td>
<td class="name left"><a href="server_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
<td>65</td>
<td>4</td>
<td>0</td>
<td class="right" data-ratio="61 65">94%</td>
</tr>
</tbody>
<tfoot>
<tr class="total">
<td class="name left">Total</td>
<td class="name left">&nbsp;</td>
<td>65</td>
<td>4</td>
<td>0</td>
<td class="right" data-ratio="61 65">94%</td>
</tr>
</tfoot>
</table>
<p id="no_rows">
No items found using the specified filter.
</p>
</main>
<footer>
<div class="content">
<p>
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
<aside class="hidden">
<a id="prevFileLink" class="nav" href=""></a>
<a id="nextFileLink" class="nav" href=""></a>
<button type="button" class="button_prev_file" data-shortcut="["></button>
<button type="button" class="button_next_file" data-shortcut="]"></button>
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
</aside>
</footer>
</body>
</html>

733
htmlcov/coverage_html_cb_6fb7b396.js generated Normal file
View File

@@ -0,0 +1,733 @@
// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
// Coverage.py HTML report browser code.
/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
/*global coverage: true, document, window, $ */
coverage = {};
// General helpers
function debounce(callback, wait) {
let timeoutId = null;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
callback.apply(this, args);
}, wait);
};
};
function checkVisible(element) {
const rect = element.getBoundingClientRect();
const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight);
const viewTop = 30;
return !(rect.bottom < viewTop || rect.top >= viewBottom);
}
function on_click(sel, fn) {
const elt = document.querySelector(sel);
if (elt) {
elt.addEventListener("click", fn);
}
}
// Helpers for table sorting
function getCellValue(row, column = 0) {
const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection
if (cell.childElementCount == 1) {
var child = cell.firstElementChild;
if (child.tagName === "A") {
child = child.firstElementChild;
}
if (child instanceof HTMLDataElement && child.value) {
return child.value;
}
}
return cell.innerText || cell.textContent;
}
function rowComparator(rowA, rowB, column = 0) {
let valueA = getCellValue(rowA, column);
let valueB = getCellValue(rowB, column);
if (!isNaN(valueA) && !isNaN(valueB)) {
return valueA - valueB;
}
return valueA.localeCompare(valueB, undefined, {numeric: true});
}
function sortColumn(th) {
// Get the current sorting direction of the selected header,
// clear state on other headers and then set the new sorting direction.
const currentSortOrder = th.getAttribute("aria-sort");
[...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none"));
var direction;
if (currentSortOrder === "none") {
direction = th.dataset.defaultSortOrder || "ascending";
}
else if (currentSortOrder === "ascending") {
direction = "descending";
}
else {
direction = "ascending";
}
th.setAttribute("aria-sort", direction);
const column = [...th.parentElement.cells].indexOf(th)
// Sort all rows and afterwards append them in order to move them in the DOM.
Array.from(th.closest("table").querySelectorAll("tbody tr"))
.sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1))
.forEach(tr => tr.parentElement.appendChild(tr));
// Save the sort order for next time.
if (th.id !== "region") {
let th_id = "file"; // Sort by file if we don't have a column id
let current_direction = direction;
const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE);
if (stored_list) {
({th_id, direction} = JSON.parse(stored_list))
}
localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({
"th_id": th.id,
"direction": current_direction
}));
if (th.id !== th_id || document.getElementById("region")) {
// Sort column has changed, unset sorting by function or class.
localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({
"by_region": false,
"region_direction": current_direction
}));
}
}
else {
// Sort column has changed to by function or class, remember that.
localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({
"by_region": true,
"region_direction": direction
}));
}
}
// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
coverage.assign_shortkeys = function () {
document.querySelectorAll("[data-shortcut]").forEach(element => {
document.addEventListener("keypress", event => {
if (event.target.tagName.toLowerCase() === "input") {
return; // ignore keypress from search filter
}
if (event.key === element.dataset.shortcut) {
element.click();
}
});
});
};
// Create the events for the filter box.
coverage.wire_up_filter = function () {
// Populate the filter and hide100 inputs if there are saved values for them.
const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE);
if (saved_filter_value) {
document.getElementById("filter").value = saved_filter_value;
}
const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE);
if (saved_hide100_value) {
document.getElementById("hide100").checked = JSON.parse(saved_hide100_value);
}
// Cache elements.
const table = document.querySelector("table.index");
const table_body_rows = table.querySelectorAll("tbody tr");
const no_rows = document.getElementById("no_rows");
// Observe filter keyevents.
const filter_handler = (event => {
// Keep running total of each metric, first index contains number of shown rows
const totals = new Array(table.rows[0].cells.length).fill(0);
// Accumulate the percentage as fraction
totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection
var text = document.getElementById("filter").value;
// Store filter value
localStorage.setItem(coverage.FILTER_STORAGE, text);
const casefold = (text === text.toLowerCase());
const hide100 = document.getElementById("hide100").checked;
// Store hide value.
localStorage.setItem(coverage.HIDE100_STORAGE, JSON.stringify(hide100));
// Hide / show elements.
table_body_rows.forEach(row => {
var show = false;
// Check the text filter.
for (let column = 0; column < totals.length; column++) {
cell = row.cells[column];
if (cell.classList.contains("name")) {
var celltext = cell.textContent;
if (casefold) {
celltext = celltext.toLowerCase();
}
if (celltext.includes(text)) {
show = true;
}
}
}
// Check the "hide covered" filter.
if (show && hide100) {
const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" ");
show = (numer !== denom);
}
if (!show) {
// hide
row.classList.add("hidden");
return;
}
// show
row.classList.remove("hidden");
totals[0]++;
for (let column = 0; column < totals.length; column++) {
// Accumulate dynamic totals
cell = row.cells[column] // nosemgrep: eslint.detect-object-injection
if (cell.classList.contains("name")) {
continue;
}
if (column === totals.length - 1) {
// Last column contains percentage
const [numer, denom] = cell.dataset.ratio.split(" ");
totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection
totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection
}
else {
totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection
}
}
});
// Show placeholder if no rows will be displayed.
if (!totals[0]) {
// Show placeholder, hide table.
no_rows.style.display = "block";
table.style.display = "none";
return;
}
// Hide placeholder, show table.
no_rows.style.display = null;
table.style.display = null;
const footer = table.tFoot.rows[0];
// Calculate new dynamic sum values based on visible rows.
for (let column = 0; column < totals.length; column++) {
// Get footer cell element.
const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection
if (cell.classList.contains("name")) {
continue;
}
// Set value into dynamic footer cell element.
if (column === totals.length - 1) {
// Percentage column uses the numerator and denominator,
// and adapts to the number of decimal places.
const match = /\.([0-9]+)/.exec(cell.textContent);
const places = match ? match[1].length : 0;
const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection
cell.dataset.ratio = `${numer} ${denom}`;
// Check denom to prevent NaN if filtered files contain no statements
cell.textContent = denom
? `${(numer * 100 / denom).toFixed(places)}%`
: `${(100).toFixed(places)}%`;
}
else {
cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection
}
}
});
document.getElementById("filter").addEventListener("input", debounce(filter_handler));
document.getElementById("hide100").addEventListener("input", debounce(filter_handler));
// Trigger change event on setup, to force filter on page refresh
// (filter value may still be present).
document.getElementById("filter").dispatchEvent(new Event("input"));
document.getElementById("hide100").dispatchEvent(new Event("input"));
};
coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE";
coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE";
// Set up the click-to-sort columns.
coverage.wire_up_sorting = function () {
document.querySelectorAll("[data-sortable] th[aria-sort]").forEach(
th => th.addEventListener("click", e => sortColumn(e.target))
);
// Look for a localStorage item containing previous sort settings:
let th_id = "file", direction = "ascending";
const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE);
if (stored_list) {
({th_id, direction} = JSON.parse(stored_list));
}
let by_region = false, region_direction = "ascending";
const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION);
if (sorted_by_region) {
({
by_region,
region_direction
} = JSON.parse(sorted_by_region));
}
const region_id = "region";
if (by_region && document.getElementById(region_id)) {
direction = region_direction;
}
// If we are in a page that has a column with id of "region", sort on
// it if the last sort was by function or class.
let th;
if (document.getElementById(region_id)) {
th = document.getElementById(by_region ? region_id : th_id);
}
else {
th = document.getElementById(th_id);
}
th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending");
th.click()
};
coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2";
coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION";
// Loaded on index.html
coverage.index_ready = function () {
coverage.assign_shortkeys();
coverage.wire_up_filter();
coverage.wire_up_sorting();
on_click(".button_prev_file", coverage.to_prev_file);
on_click(".button_next_file", coverage.to_next_file);
on_click(".button_show_hide_help", coverage.show_hide_help);
};
// -- pyfile stuff --
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
coverage.pyfile_ready = function () {
// If we're directed to a particular line number, highlight the line.
var frag = location.hash;
if (frag.length > 2 && frag[1] === "t") {
document.querySelector(frag).closest(".n").classList.add("highlight");
coverage.set_sel(parseInt(frag.substr(2), 10));
}
else {
coverage.set_sel(0);
}
on_click(".button_toggle_run", coverage.toggle_lines);
on_click(".button_toggle_mis", coverage.toggle_lines);
on_click(".button_toggle_exc", coverage.toggle_lines);
on_click(".button_toggle_par", coverage.toggle_lines);
on_click(".button_next_chunk", coverage.to_next_chunk_nicely);
on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely);
on_click(".button_top_of_page", coverage.to_top);
on_click(".button_first_chunk", coverage.to_first_chunk);
on_click(".button_prev_file", coverage.to_prev_file);
on_click(".button_next_file", coverage.to_next_file);
on_click(".button_to_index", coverage.to_index);
on_click(".button_show_hide_help", coverage.show_hide_help);
coverage.filters = undefined;
try {
coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
} catch(err) {}
if (coverage.filters) {
coverage.filters = JSON.parse(coverage.filters);
}
else {
coverage.filters = {run: false, exc: true, mis: true, par: true};
}
for (cls in coverage.filters) {
coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection
}
coverage.assign_shortkeys();
coverage.init_scroll_markers();
coverage.wire_up_sticky_header();
document.querySelectorAll("[id^=ctxs]").forEach(
cbox => cbox.addEventListener("click", coverage.expand_contexts)
);
// Rebuild scroll markers when the window height changes.
window.addEventListener("resize", coverage.build_scroll_markers);
};
coverage.toggle_lines = function (event) {
const btn = event.target.closest("button");
const category = btn.value
const show = !btn.classList.contains("show_" + category);
coverage.set_line_visibilty(category, show);
coverage.build_scroll_markers();
coverage.filters[category] = show;
try {
localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
} catch(err) {}
};
coverage.set_line_visibilty = function (category, should_show) {
const cls = "show_" + category;
const btn = document.querySelector(".button_toggle_" + category);
if (btn) {
if (should_show) {
document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls));
btn.classList.add(cls);
}
else {
document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls));
btn.classList.remove(cls);
}
}
};
// Return the nth line div.
coverage.line_elt = function (n) {
return document.getElementById("t" + n)?.closest("p");
};
// Set the selection. b and e are line numbers.
coverage.set_sel = function (b, e) {
// The first line selected.
coverage.sel_begin = b;
// The next line not selected.
coverage.sel_end = (e === undefined) ? b+1 : e;
};
coverage.to_top = function () {
coverage.set_sel(0, 1);
coverage.scroll_window(0);
};
coverage.to_first_chunk = function () {
coverage.set_sel(0, 1);
coverage.to_next_chunk();
};
coverage.to_prev_file = function () {
window.location = document.getElementById("prevFileLink").href;
}
coverage.to_next_file = function () {
window.location = document.getElementById("nextFileLink").href;
}
coverage.to_index = function () {
location.href = document.getElementById("indexLink").href;
}
coverage.show_hide_help = function () {
const helpCheck = document.getElementById("help_panel_state")
helpCheck.checked = !helpCheck.checked;
}
// Return a string indicating what kind of chunk this line belongs to,
// or null if not a chunk.
coverage.chunk_indicator = function (line_elt) {
const classes = line_elt?.className;
if (!classes) {
return null;
}
const match = classes.match(/\bshow_\w+\b/);
if (!match) {
return null;
}
return match[0];
};
coverage.to_next_chunk = function () {
const c = coverage;
// Find the start of the next colored chunk.
var probe = c.sel_end;
var chunk_indicator, probe_line;
while (true) {
probe_line = c.line_elt(probe);
if (!probe_line) {
return;
}
chunk_indicator = c.chunk_indicator(probe_line);
if (chunk_indicator) {
break;
}
probe++;
}
// There's a next chunk, `probe` points to it.
var begin = probe;
// Find the end of this chunk.
var next_indicator = chunk_indicator;
while (next_indicator === chunk_indicator) {
probe++;
probe_line = c.line_elt(probe);
next_indicator = c.chunk_indicator(probe_line);
}
c.set_sel(begin, probe);
c.show_selection();
};
coverage.to_prev_chunk = function () {
const c = coverage;
// Find the end of the prev colored chunk.
var probe = c.sel_begin-1;
var probe_line = c.line_elt(probe);
if (!probe_line) {
return;
}
var chunk_indicator = c.chunk_indicator(probe_line);
while (probe > 1 && !chunk_indicator) {
probe--;
probe_line = c.line_elt(probe);
if (!probe_line) {
return;
}
chunk_indicator = c.chunk_indicator(probe_line);
}
// There's a prev chunk, `probe` points to its last line.
var end = probe+1;
// Find the beginning of this chunk.
var prev_indicator = chunk_indicator;
while (prev_indicator === chunk_indicator) {
probe--;
if (probe <= 0) {
return;
}
probe_line = c.line_elt(probe);
prev_indicator = c.chunk_indicator(probe_line);
}
c.set_sel(probe+1, end);
c.show_selection();
};
// Returns 0, 1, or 2: how many of the two ends of the selection are on
// the screen right now?
coverage.selection_ends_on_screen = function () {
if (coverage.sel_begin === 0) {
return 0;
}
const begin = coverage.line_elt(coverage.sel_begin);
const end = coverage.line_elt(coverage.sel_end-1);
return (
(checkVisible(begin) ? 1 : 0)
+ (checkVisible(end) ? 1 : 0)
);
};
coverage.to_next_chunk_nicely = function () {
if (coverage.selection_ends_on_screen() === 0) {
// The selection is entirely off the screen:
// Set the top line on the screen as selection.
// This will select the top-left of the viewport
// As this is most likely the span with the line number we take the parent
const line = document.elementFromPoint(0, 0).parentElement;
if (line.parentElement !== document.getElementById("source")) {
// The element is not a source line but the header or similar
coverage.select_line_or_chunk(1);
}
else {
// We extract the line number from the id
coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
}
}
coverage.to_next_chunk();
};
coverage.to_prev_chunk_nicely = function () {
if (coverage.selection_ends_on_screen() === 0) {
// The selection is entirely off the screen:
// Set the lowest line on the screen as selection.
// This will select the bottom-left of the viewport
// As this is most likely the span with the line number we take the parent
const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement;
if (line.parentElement !== document.getElementById("source")) {
// The element is not a source line but the header or similar
coverage.select_line_or_chunk(coverage.lines_len);
}
else {
// We extract the line number from the id
coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
}
}
coverage.to_prev_chunk();
};
// Select line number lineno, or if it is in a colored chunk, select the
// entire chunk
coverage.select_line_or_chunk = function (lineno) {
var c = coverage;
var probe_line = c.line_elt(lineno);
if (!probe_line) {
return;
}
var the_indicator = c.chunk_indicator(probe_line);
if (the_indicator) {
// The line is in a highlighted chunk.
// Search backward for the first line.
var probe = lineno;
var indicator = the_indicator;
while (probe > 0 && indicator === the_indicator) {
probe--;
probe_line = c.line_elt(probe);
if (!probe_line) {
break;
}
indicator = c.chunk_indicator(probe_line);
}
var begin = probe + 1;
// Search forward for the last line.
probe = lineno;
indicator = the_indicator;
while (indicator === the_indicator) {
probe++;
probe_line = c.line_elt(probe);
indicator = c.chunk_indicator(probe_line);
}
coverage.set_sel(begin, probe);
}
else {
coverage.set_sel(lineno);
}
};
coverage.show_selection = function () {
// Highlight the lines in the chunk
document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight"));
for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) {
coverage.line_elt(probe).querySelector(".n").classList.add("highlight");
}
coverage.scroll_to_selection();
};
coverage.scroll_to_selection = function () {
// Scroll the page if the chunk isn't fully visible.
if (coverage.selection_ends_on_screen() < 2) {
const element = coverage.line_elt(coverage.sel_begin);
coverage.scroll_window(element.offsetTop - 60);
}
};
coverage.scroll_window = function (to_pos) {
window.scroll({top: to_pos, behavior: "smooth"});
};
coverage.init_scroll_markers = function () {
// Init some variables
coverage.lines_len = document.querySelectorAll("#source > p").length;
// Build html
coverage.build_scroll_markers();
};
coverage.build_scroll_markers = function () {
const temp_scroll_marker = document.getElementById("scroll_marker")
if (temp_scroll_marker) temp_scroll_marker.remove();
// Don't build markers if the window has no scroll bar.
if (document.body.scrollHeight <= window.innerHeight) {
return;
}
const marker_scale = window.innerHeight / document.body.scrollHeight;
const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10);
let previous_line = -99, last_mark, last_top;
const scroll_marker = document.createElement("div");
scroll_marker.id = "scroll_marker";
document.getElementById("source").querySelectorAll(
"p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par"
).forEach(element => {
const line_top = Math.floor(element.offsetTop * marker_scale);
const line_number = parseInt(element.querySelector(".n a").id.substr(1));
if (line_number === previous_line + 1) {
// If this solid missed block just make previous mark higher.
last_mark.style.height = `${line_top + line_height - last_top}px`;
}
else {
// Add colored line in scroll_marker block.
last_mark = document.createElement("div");
last_mark.id = `m${line_number}`;
last_mark.classList.add("marker");
last_mark.style.height = `${line_height}px`;
last_mark.style.top = `${line_top}px`;
scroll_marker.append(last_mark);
last_top = line_top;
}
previous_line = line_number;
});
// Append last to prevent layout calculation
document.body.append(scroll_marker);
};
coverage.wire_up_sticky_header = function () {
const header = document.querySelector("header");
const header_bottom = (
header.querySelector(".content h2").getBoundingClientRect().top -
header.getBoundingClientRect().top
);
function updateHeader() {
if (window.scrollY > header_bottom) {
header.classList.add("sticky");
}
else {
header.classList.remove("sticky");
}
}
window.addEventListener("scroll", updateHeader);
updateHeader();
};
coverage.expand_contexts = function (e) {
var ctxs = e.target.parentNode.querySelector(".ctxs");
if (!ctxs.classList.contains("expanded")) {
var ctxs_text = ctxs.textContent;
var width = Number(ctxs_text[0]);
ctxs.textContent = "";
for (var i = 1; i < ctxs_text.length; i += width) {
key = ctxs_text.substring(i, i + width).trim();
ctxs.appendChild(document.createTextNode(contexts[key]));
ctxs.appendChild(document.createElement("br"));
}
ctxs.classList.add("expanded");
}
};
document.addEventListener("DOMContentLoaded", () => {
if (document.body.classList.contains("indexfile")) {
coverage.index_ready();
}
else {
coverage.pyfile_ready();
}
});

BIN
htmlcov/favicon_32_cb_58284776.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

179
htmlcov/function_index.html generated Normal file
View File

@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Coverage report</title>
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
<link rel="stylesheet" href="style_cb_81f8c14c.css" type="text/css">
<script src="coverage_html_cb_6fb7b396.js" defer></script>
</head>
<body class="indexfile">
<header>
<div class="content">
<h1>Coverage report:
<span class="pc_cov">94%</span>
</h1>
<aside id="help_panel_wrapper">
<input id="help_panel_state" type="checkbox">
<label for="help_panel_state">
<img id="keyboard_icon" src="keybd_closed_cb_ce680311.png" alt="Show/hide keyboard shortcuts">
</label>
<div id="help_panel">
<p class="legend">Shortcuts on this page</p>
<div class="keyhelp">
<p>
<kbd>f</kbd>
<kbd>n</kbd>
<kbd>s</kbd>
<kbd>m</kbd>
<kbd>x</kbd>
<kbd>c</kbd>
&nbsp; change column sorting
</p>
<p>
<kbd>[</kbd>
<kbd>]</kbd>
&nbsp; prev/next file
</p>
<p>
<kbd>?</kbd> &nbsp; show/hide this help
</p>
</div>
</div>
</aside>
<form id="filter_container">
<input id="filter" type="text" value="" placeholder="filter...">
<div>
<input id="hide100" type="checkbox" >
<label for="hide100">hide covered</label>
</div>
</form>
<h2>
<a class="button" href="index.html">Files</a>
<a class="button current">Functions</a>
<a class="button" href="class_index.html">Classes</a>
</h2>
<p class="text">
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
</header>
<main id="index">
<table class="index" data-sortable>
<thead>
<tr class="tablehead" title="Click to sort">
<th id="file" class="name left" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
<th id="region" class="name left" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">function<span class="arrows"></span></th>
<th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
<th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
<th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
<th id="coverage" class="right" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
</tr>
</thead>
<tbody>
<tr class="region">
<td class="name left"><a href="server_py.html#t6">server.py</a></td>
<td class="name left"><a href="server_py.html#t6"><data value='loadClubs'>loadClubs</data></a></td>
<td>3</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="3 3">100%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t12">server.py</a></td>
<td class="name left"><a href="server_py.html#t12"><data value='loadCompetitions'>loadCompetitions</data></a></td>
<td>3</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="3 3">100%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t27">server.py</a></td>
<td class="name left"><a href="server_py.html#t27"><data value='index'>index</data></a></td>
<td>1</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="1 1">100%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t31">server.py</a></td>
<td class="name left"><a href="server_py.html#t31"><data value='showSummary'>showSummary</data></a></td>
<td>6</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="6 6">100%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t40">server.py</a></td>
<td class="name left"><a href="server_py.html#t40"><data value='book'>book</data></a></td>
<td>9</td>
<td>3</td>
<td>0</td>
<td class="right" data-ratio="6 9">67%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t56">server.py</a></td>
<td class="name left"><a href="server_py.html#t56"><data value='purchasePlaces'>purchasePlaces</data></a></td>
<td>19</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="19 19">100%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t88">server.py</a></td>
<td class="name left"><a href="server_py.html#t88"><data value='displayPoints'>displayPoints</data></a></td>
<td>1</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="1 1">100%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html#t92">server.py</a></td>
<td class="name left"><a href="server_py.html#t92"><data value='logout'>logout</data></a></td>
<td>1</td>
<td>1</td>
<td>0</td>
<td class="right" data-ratio="0 1">0%</td>
</tr>
<tr class="region">
<td class="name left"><a href="server_py.html">server.py</a></td>
<td class="name left"><a href="server_py.html"><data value=''><span class='no-noun'>(no function)</span></data></a></td>
<td>22</td>
<td>0</td>
<td>0</td>
<td class="right" data-ratio="22 22">100%</td>
</tr>
</tbody>
<tfoot>
<tr class="total">
<td class="name left">Total</td>
<td class="name left">&nbsp;</td>
<td>65</td>
<td>4</td>
<td>0</td>
<td class="right" data-ratio="61 65">94%</td>
</tr>
</tfoot>
</table>
<p id="no_rows">
No items found using the specified filter.
</p>
</main>
<footer>
<div class="content">
<p>
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
<aside class="hidden">
<a id="prevFileLink" class="nav" href=""></a>
<a id="nextFileLink" class="nav" href=""></a>
<button type="button" class="button_prev_file" data-shortcut="["></button>
<button type="button" class="button_next_file" data-shortcut="]"></button>
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
</aside>
</footer>
</body>
</html>

111
htmlcov/index.html generated Normal file
View File

@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Coverage report</title>
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
<link rel="stylesheet" href="style_cb_81f8c14c.css" type="text/css">
<script src="coverage_html_cb_6fb7b396.js" defer></script>
</head>
<body class="indexfile">
<header>
<div class="content">
<h1>Coverage report:
<span class="pc_cov">94%</span>
</h1>
<aside id="help_panel_wrapper">
<input id="help_panel_state" type="checkbox">
<label for="help_panel_state">
<img id="keyboard_icon" src="keybd_closed_cb_ce680311.png" alt="Show/hide keyboard shortcuts">
</label>
<div id="help_panel">
<p class="legend">Shortcuts on this page</p>
<div class="keyhelp">
<p>
<kbd>f</kbd>
<kbd>s</kbd>
<kbd>m</kbd>
<kbd>x</kbd>
<kbd>c</kbd>
&nbsp; change column sorting
</p>
<p>
<kbd>[</kbd>
<kbd>]</kbd>
&nbsp; prev/next file
</p>
<p>
<kbd>?</kbd> &nbsp; show/hide this help
</p>
</div>
</div>
</aside>
<form id="filter_container">
<input id="filter" type="text" value="" placeholder="filter...">
<div>
<input id="hide100" type="checkbox" >
<label for="hide100">hide covered</label>
</div>
</form>
<h2>
<a class="button current">Files</a>
<a class="button" href="function_index.html">Functions</a>
<a class="button" href="class_index.html">Classes</a>
</h2>
<p class="text">
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
</header>
<main id="index">
<table class="index" data-sortable>
<thead>
<tr class="tablehead" title="Click to sort">
<th id="file" class="name left" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
<th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
<th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
<th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
<th id="coverage" class="right" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
</tr>
</thead>
<tbody>
<tr class="region">
<td class="name left"><a href="server_py.html">server.py</a></td>
<td>65</td>
<td>4</td>
<td>0</td>
<td class="right" data-ratio="61 65">94%</td>
</tr>
</tbody>
<tfoot>
<tr class="total">
<td class="name left">Total</td>
<td>65</td>
<td>4</td>
<td>0</td>
<td class="right" data-ratio="61 65">94%</td>
</tr>
</tfoot>
</table>
<p id="no_rows">
No items found using the specified filter.
</p>
</main>
<footer>
<div class="content">
<p>
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
<aside class="hidden">
<a id="prevFileLink" class="nav" href="server_py.html"></a>
<a id="nextFileLink" class="nav" href="server_py.html"></a>
<button type="button" class="button_prev_file" data-shortcut="["></button>
<button type="button" class="button_next_file" data-shortcut="]"></button>
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
</aside>
</footer>
</body>
</html>

BIN
htmlcov/keybd_closed_cb_ce680311.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

191
htmlcov/server_py.html generated Normal file
View File

@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Coverage for server.py: 94%</title>
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
<link rel="stylesheet" href="style_cb_81f8c14c.css" type="text/css">
<script src="coverage_html_cb_6fb7b396.js" defer></script>
</head>
<body class="pyfile">
<header>
<div class="content">
<h1>
<span class="text">Coverage for </span><b>server.py</b>:
<span class="pc_cov">94%</span>
</h1>
<aside id="help_panel_wrapper">
<input id="help_panel_state" type="checkbox">
<label for="help_panel_state">
<img id="keyboard_icon" src="keybd_closed_cb_ce680311.png" alt="Show/hide keyboard shortcuts">
</label>
<div id="help_panel">
<p class="legend">Shortcuts on this page</p>
<div class="keyhelp">
<p>
<kbd>r</kbd>
<kbd>m</kbd>
<kbd>x</kbd>
&nbsp; toggle line displays
</p>
<p>
<kbd>j</kbd>
<kbd>k</kbd>
&nbsp; next/prev highlighted chunk
</p>
<p>
<kbd>0</kbd> &nbsp; (zero) top of page
</p>
<p>
<kbd>1</kbd> &nbsp; (one) first highlighted chunk
</p>
<p>
<kbd>[</kbd>
<kbd>]</kbd>
&nbsp; prev/next file
</p>
<p>
<kbd>u</kbd> &nbsp; up to the index
</p>
<p>
<kbd>?</kbd> &nbsp; show/hide this help
</p>
</div>
</div>
</aside>
<h2>
<span class="text">65 statements &nbsp;</span>
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">61<span class="text"> run</span></button>
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">4<span class="text"> missing</span></button>
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">0<span class="text"> excluded</span></button>
</h2>
<p class="text">
<a id="prevFileLink" class="nav" href="index.html">&#xab; prev</a> &nbsp; &nbsp;
<a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
<a id="nextFileLink" class="nav" href="index.html">&#xbb; next</a>
&nbsp; &nbsp; &nbsp;
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
<aside class="hidden">
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
<button type="button" class="button_prev_file" data-shortcut="["></button>
<button type="button" class="button_next_file" data-shortcut="]"></button>
<button type="button" class="button_to_index" data-shortcut="u"></button>
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
</aside>
</div>
</header>
<main id="source">
<p class="run"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="key">from</span> <span class="nam">flask</span> <span class="key">import</span> <span class="nam">Flask</span><span class="op">,</span><span class="nam">render_template</span><span class="op">,</span><span class="nam">request</span><span class="op">,</span><span class="nam">redirect</span><span class="op">,</span><span class="nam">flash</span><span class="op">,</span><span class="nam">url_for</span><span class="op">,</span><span class="nam">session</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="key">def</span> <span class="nam">loadClubs</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="str">'clubs.json'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">c</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"> <span class="nam">listOfClubs</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">load</span><span class="op">(</span><span class="nam">c</span><span class="op">)</span><span class="op">[</span><span class="str">'clubs'</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"> <span class="key">return</span> <span class="nam">listOfClubs</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="key">def</span> <span class="nam">loadCompetitions</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="str">'competitions.json'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">comps</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"> <span class="nam">listOfCompetitions</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">load</span><span class="op">(</span><span class="nam">comps</span><span class="op">)</span><span class="op">[</span><span class="str">'competitions'</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"> <span class="key">return</span> <span class="nam">listOfCompetitions</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"><span class="nam">app</span> <span class="op">=</span> <span class="nam">Flask</span><span class="op">(</span><span class="nam">__name__</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"><span class="nam">app</span><span class="op">.</span><span class="nam">secret_key</span> <span class="op">=</span> <span class="str">'something_special'</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="nam">competitions</span> <span class="op">=</span> <span class="nam">loadCompetitions</span><span class="op">(</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="nam">clubs</span> <span class="op">=</span> <span class="nam">loadClubs</span><span class="op">(</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"><span class="nam">now</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="str">"%Y-%m-%d %H:%M:%S"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"><span class="op">@</span><span class="nam">app</span><span class="op">.</span><span class="nam">route</span><span class="op">(</span><span class="str">'/'</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"><span class="key">def</span> <span class="nam">index</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'index.html'</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"><span class="op">@</span><span class="nam">app</span><span class="op">.</span><span class="nam">route</span><span class="op">(</span><span class="str">'/showSummary'</span><span class="op">,</span><span class="nam">methods</span><span class="op">=</span><span class="op">[</span><span class="str">'POST'</span><span class="op">]</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"><span class="key">def</span> <span class="nam">showSummary</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> <span class="key">try</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> <span class="nam">club</span> <span class="op">=</span> <span class="op">[</span><span class="nam">club</span> <span class="key">for</span> <span class="nam">club</span> <span class="key">in</span> <span class="nam">clubs</span> <span class="key">if</span> <span class="nam">club</span><span class="op">[</span><span class="str">'email'</span><span class="op">]</span> <span class="op">==</span> <span class="nam">request</span><span class="op">.</span><span class="nam">form</span><span class="op">[</span><span class="str">'email'</span><span class="op">]</span><span class="op">]</span><span class="op">[</span><span class="num">0</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'welcome.html'</span><span class="op">,</span> <span class="nam">club</span><span class="op">=</span><span class="nam">club</span><span class="op">,</span> <span class="nam">competitions</span><span class="op">=</span><span class="nam">competitions</span><span class="op">,</span> <span class="nam">now</span><span class="op">=</span><span class="nam">now</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="key">except</span> <span class="nam">IndexError</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">"Sorry, that email wasn't found"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="key">return</span> <span class="nam">redirect</span><span class="op">(</span><span class="nam">url_for</span><span class="op">(</span><span class="str">'index'</span><span class="op">)</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"><span class="op">@</span><span class="nam">app</span><span class="op">.</span><span class="nam">route</span><span class="op">(</span><span class="str">'/book/&lt;competition>/&lt;club>'</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"><span class="key">def</span> <span class="nam">book</span><span class="op">(</span><span class="nam">competition</span><span class="op">,</span><span class="nam">club</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="nam">foundClub</span> <span class="op">=</span> <span class="op">[</span><span class="nam">c</span> <span class="key">for</span> <span class="nam">c</span> <span class="key">in</span> <span class="nam">clubs</span> <span class="key">if</span> <span class="nam">c</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span> <span class="op">==</span> <span class="nam">club</span><span class="op">]</span><span class="op">[</span><span class="num">0</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> <span class="nam">foundCompetition</span> <span class="op">=</span> <span class="op">[</span><span class="nam">c</span> <span class="key">for</span> <span class="nam">c</span> <span class="key">in</span> <span class="nam">competitions</span> <span class="key">if</span> <span class="nam">c</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span> <span class="op">==</span> <span class="nam">competition</span><span class="op">]</span><span class="op">[</span><span class="num">0</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="key">if</span> <span class="nam">foundClub</span> <span class="key">and</span> <span class="nam">foundCompetition</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="key">if</span> <span class="nam">foundCompetition</span><span class="op">[</span><span class="str">'date'</span><span class="op">]</span> <span class="op">></span> <span class="nam">now</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="mis show_mis"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'booking.html'</span><span class="op">,</span><span class="nam">club</span><span class="op">=</span><span class="nam">foundClub</span><span class="op">,</span><span class="nam">competition</span><span class="op">=</span><span class="nam">foundCompetition</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="key">else</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">"You cannot book for a past competition"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'welcome.html'</span><span class="op">,</span> <span class="nam">club</span><span class="op">=</span><span class="nam">foundClub</span><span class="op">,</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> <span class="nam">competitions</span><span class="op">=</span><span class="nam">competitions</span><span class="op">,</span> <span class="nam">now</span><span class="op">=</span><span class="nam">now</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="key">else</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="mis show_mis"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">"Something went wrong-please try again"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="mis show_mis"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'welcome.html'</span><span class="op">,</span> <span class="nam">club</span><span class="op">=</span><span class="nam">club</span><span class="op">,</span> <span class="nam">competitions</span><span class="op">=</span><span class="nam">competitions</span><span class="op">,</span> <span class="nam">now</span><span class="op">=</span><span class="nam">now</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"><span class="op">@</span><span class="nam">app</span><span class="op">.</span><span class="nam">route</span><span class="op">(</span><span class="str">'/purchasePlaces'</span><span class="op">,</span><span class="nam">methods</span><span class="op">=</span><span class="op">[</span><span class="str">'POST'</span><span class="op">]</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"><span class="key">def</span> <span class="nam">purchasePlaces</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="nam">competition</span> <span class="op">=</span> <span class="op">[</span><span class="nam">c</span> <span class="key">for</span> <span class="nam">c</span> <span class="key">in</span> <span class="nam">competitions</span> <span class="key">if</span> <span class="nam">c</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span> <span class="op">==</span> <span class="nam">request</span><span class="op">.</span><span class="nam">form</span><span class="op">[</span><span class="str">'competition'</span><span class="op">]</span><span class="op">]</span><span class="op">[</span><span class="num">0</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="nam">club</span> <span class="op">=</span> <span class="op">[</span><span class="nam">c</span> <span class="key">for</span> <span class="nam">c</span> <span class="key">in</span> <span class="nam">clubs</span> <span class="key">if</span> <span class="nam">c</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span> <span class="op">==</span> <span class="nam">request</span><span class="op">.</span><span class="nam">form</span><span class="op">[</span><span class="str">'club'</span><span class="op">]</span><span class="op">]</span><span class="op">[</span><span class="num">0</span><span class="op">]</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="nam">placesRequired</span> <span class="op">=</span> <span class="nam">int</span><span class="op">(</span><span class="nam">request</span><span class="op">.</span><span class="nam">form</span><span class="op">[</span><span class="str">'places'</span><span class="op">]</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> <span class="com"># check if that's the first book, and update session</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="key">if</span> <span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span> <span class="key">in</span> <span class="nam">session</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="nam">places</span> <span class="op">=</span> <span class="op">{</span><span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span><span class="op">:</span> <span class="nam">session</span><span class="op">[</span><span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span><span class="op">]</span> <span class="op">+</span> <span class="nam">placesRequired</span><span class="op">}</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="key">else</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> <span class="nam">places</span> <span class="op">=</span> <span class="op">{</span><span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span><span class="op">:</span> <span class="nam">placesRequired</span><span class="op">}</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="nam">points</span> <span class="op">=</span> <span class="nam">int</span><span class="op">(</span><span class="nam">club</span><span class="op">[</span><span class="str">'points'</span><span class="op">]</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="com"># prevent to book more than 12 places</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="key">if</span> <span class="nam">placesRequired</span> <span class="op">&lt;=</span> <span class="num">12</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="key">if</span> <span class="nam">places</span><span class="op">[</span><span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span><span class="op">]</span> <span class="op">&lt;=</span> <span class="num">12</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> <span class="com"># prevent to book more than available points</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="key">if</span> <span class="nam">placesRequired</span> <span class="op">&lt;=</span> <span class="nam">points</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="nam">competition</span><span class="op">[</span><span class="str">'numberOfPlaces'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">int</span><span class="op">(</span><span class="nam">competition</span><span class="op">[</span><span class="str">'numberOfPlaces'</span><span class="op">]</span><span class="op">)</span> <span class="op">-</span> <span class="nam">placesRequired</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="nam">club</span><span class="op">[</span><span class="str">'points'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">int</span><span class="op">(</span><span class="nam">club</span><span class="op">[</span><span class="str">'points'</span><span class="op">]</span><span class="op">)</span> <span class="op">-</span> <span class="nam">placesRequired</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="com"># set the session if that's the first book for this competition</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span> <span class="key">in</span> <span class="nam">session</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="nam">session</span><span class="op">[</span><span class="nam">competition</span><span class="op">[</span><span class="str">'name'</span><span class="op">]</span><span class="op">]</span> <span class="op">=</span> <span class="nam">placesRequired</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">f"Great ! {placesRequired} places booked for {competition['name']}"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="key">else</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">"You don't have enough points"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="key">else</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">f"You already booked 12 places for {competition['name']}"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> <span class="key">else</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> <span class="nam">flash</span><span class="op">(</span><span class="str">"You can't book more than 12 places"</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'welcome.html'</span><span class="op">,</span> <span class="nam">club</span><span class="op">=</span><span class="nam">club</span><span class="op">,</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="nam">competitions</span><span class="op">=</span><span class="nam">competitions</span><span class="op">,</span> <span class="nam">now</span><span class="op">=</span><span class="nam">now</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"><span class="com"># TODO: Add route for points display</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"><span class="op">@</span><span class="nam">app</span><span class="op">.</span><span class="nam">route</span><span class="op">(</span><span class="str">'/points'</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"><span class="key">def</span> <span class="nam">displayPoints</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="key">return</span> <span class="nam">render_template</span><span class="op">(</span><span class="str">'points.html'</span><span class="op">,</span> <span class="nam">clubs</span><span class="op">=</span><span class="nam">clubs</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"><span class="op">@</span><span class="nam">app</span><span class="op">.</span><span class="nam">route</span><span class="op">(</span><span class="str">'/logout'</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"><span class="key">def</span> <span class="nam">logout</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"></span></p>
<p class="mis show_mis"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="key">return</span> <span class="nam">redirect</span><span class="op">(</span><span class="nam">url_for</span><span class="op">(</span><span class="str">'index'</span><span class="op">)</span><span class="op">)</span>&nbsp;</span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
</main>
<footer>
<div class="content">
<p>
<a class="nav" href="index.html">&#xab; prev</a> &nbsp; &nbsp;
<a class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
<a class="nav" href="index.html">&#xbb; next</a>
&nbsp; &nbsp; &nbsp;
<a class="nav" href="https://coverage.readthedocs.io/en/7.9.2">coverage.py v7.9.2</a>,
created at 2025-07-09 17:52 +0200
</p>
</div>
</footer>
</body>
</html>

1
htmlcov/status.json generated Normal file
View File

@@ -0,0 +1 @@
{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.9.2","globals":"f6383954cb5f2f418478438f0d714de2","files":{"server_py":{"hash":"2b55d0a34f1fca890aa508080df3a7a2","index":{"url":"server_py.html","file":"server.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":65,"n_excluded":0,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}}}}

337
htmlcov/style_cb_81f8c14c.css generated Normal file
View File

@@ -0,0 +1,337 @@
@charset "UTF-8";
/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
/* Don't edit this .css file. Edit the .scss file instead! */
html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; }
@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } }
@media (prefers-color-scheme: dark) { body { color: #eee; } }
html > body { font-size: 16px; }
a:active, a:focus { outline: 2px dashed #007acc; }
p { font-size: .875em; line-height: 1.4em; }
table { border-collapse: collapse; }
td { vertical-align: top; }
table tr.hidden { display: none !important; }
p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
a.nav { text-decoration: none; color: inherit; }
a.nav:hover { text-decoration: underline; color: inherit; }
.hidden { display: none; }
header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; }
@media (prefers-color-scheme: dark) { header { background: black; } }
@media (prefers-color-scheme: dark) { header { border-color: #333; } }
header .content { padding: 1rem 3.5rem; }
header h2 { margin-top: .5em; font-size: 1em; }
header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; }
@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } }
@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } }
header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; }
@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } }
@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } }
header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; }
@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } }
header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; }
header.sticky .text { display: none; }
header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; }
header.sticky .content { padding: 0.5rem 3.5rem; }
header.sticky .content p { font-size: 1em; }
header.sticky ~ #source { padding-top: 6.5em; }
main { position: relative; z-index: 1; }
footer { margin: 1rem 3.5rem; }
footer .content { padding: 0; color: #666; font-style: italic; }
@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } }
#index { margin: 1rem 0 0 3.5rem; }
h1 { font-size: 1.25em; display: inline-block; }
#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; }
#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; }
@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } }
@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } }
@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } }
#filter_container #filter:focus { border-color: #007acc; }
#filter_container :disabled ~ label { color: #ccc; }
@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } }
#filter_container label { font-size: .875em; color: #666; }
@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } }
header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; }
@media (prefers-color-scheme: dark) { header button { background: #333; } }
@media (prefers-color-scheme: dark) { header button { border-color: #444; } }
header button:active, header button:focus { outline: 2px dashed #007acc; }
header button.run { background: #eeffee; }
@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } }
header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; }
@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } }
header button.mis { background: #ffeeee; }
@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } }
header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; }
@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } }
header button.exc { background: #f7f7f7; }
@media (prefers-color-scheme: dark) { header button.exc { background: #333; } }
header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; }
@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } }
header button.par { background: #ffffd5; }
@media (prefers-color-scheme: dark) { header button.par { background: #650; } }
header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; }
@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } }
#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }
#help_panel_wrapper { float: right; position: relative; }
#keyboard_icon { margin: 5px; }
#help_panel_state { display: none; }
#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; }
#help_panel .keyhelp p { margin-top: .75em; }
#help_panel .legend { font-style: italic; margin-bottom: 1em; }
.indexfile #help_panel { width: 25em; }
.pyfile #help_panel { width: 18em; }
#help_panel_state:checked ~ #help_panel { display: block; }
kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; }
#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
#source p { position: relative; white-space: pre; }
#source p * { box-sizing: border-box; }
#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; }
@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
#source p .n.highlight { background: #ffdd00; }
#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; }
@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } }
#source p .n a:hover { text-decoration: underline; color: #999; }
@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } }
#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; }
@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } }
#source p .t:hover { background: #f2f2f2; }
@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } }
#source p .t:hover ~ .r .annotate.long { display: block; }
#source p .t .com { color: #008000; font-style: italic; line-height: 1px; }
@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } }
#source p .t .key { font-weight: bold; line-height: 1px; }
#source p .t .str, #source p .t .fst { color: #0451a5; }
@media (prefers-color-scheme: dark) { #source p .t .str, #source p .t .fst { color: #9cdcfe; } }
#source p.mis .t { border-left: 0.2em solid #ff0000; }
#source p.mis.show_mis .t { background: #fdd; }
@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } }
#source p.mis.show_mis .t:hover { background: #f2d2d2; }
@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } }
#source p.run .t { border-left: 0.2em solid #00dd00; }
#source p.run.show_run .t { background: #dfd; }
@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } }
#source p.run.show_run .t:hover { background: #d2f2d2; }
@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } }
#source p.exc .t { border-left: 0.2em solid #808080; }
#source p.exc.show_exc .t { background: #eee; }
@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } }
#source p.exc.show_exc .t:hover { background: #e2e2e2; }
@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } }
#source p.par .t { border-left: 0.2em solid #bbbb00; }
#source p.par.show_par .t { background: #ffa; }
@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } }
#source p.par.show_par .t:hover { background: #f2f2a2; }
@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } }
#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; }
@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } }
#source p .annotate.short:hover ~ .long { display: block; }
#source p .annotate.long { width: 30em; right: 2.5em; }
#source p input { display: none; }
#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; }
#source p input ~ .r label.ctx::before { content: "▶ "; }
#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; }
@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } }
@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } }
#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; }
@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } }
@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } }
#source p input:checked ~ .r label.ctx::before { content: "▼ "; }
#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; }
#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; }
@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } }
#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; }
@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } }
#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; }
#index table.index { margin-left: -.5em; }
#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; }
@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } }
#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; }
#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; }
@media (prefers-color-scheme: dark) { #index th { color: #ddd; } }
#index th:hover { background: #eee; }
@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } }
#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; }
#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; }
@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } }
#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; }
#index th[aria-sort="descending"] .arrows::after { content: " ▼"; }
#index td.name { font-size: 1.15em; }
#index td.name a { text-decoration: none; color: inherit; }
#index td.name .no-noun { font-style: italic; }
#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; }
#index tr.region:hover { background: #eee; }
@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } }
#index tr.region:hover td.name { text-decoration: underline; color: inherit; }
#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; }
@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } }
@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } }
#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; }
@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } }

View File

@@ -1,5 +1,6 @@
import json
from flask import Flask,render_template,request,redirect,flash,url_for,session
from datetime import datetime
def loadClubs():
@@ -19,6 +20,8 @@ app.secret_key = 'something_special'
competitions = loadCompetitions()
clubs = loadClubs()
now = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
@app.route('/')
def index():
@@ -26,9 +29,10 @@ def index():
@app.route('/showSummary',methods=['POST'])
def showSummary():
club = [club for club in clubs if club['email'] == request.form['email']]
if club:
return render_template('welcome.html', club=club[0], competitions=competitions)
try:
club = [club for club in clubs if club['email'] == request.form['email']][0]
return render_template('welcome.html', club=club, competitions=competitions, now=now)
except IndexError:
flash("Sorry, that email wasn't found")
return redirect(url_for('index'))
@@ -37,10 +41,15 @@ def book(competition,club):
foundClub = [c for c in clubs if c['name'] == club][0]
foundCompetition = [c for c in competitions if c['name'] == competition][0]
if foundClub and foundCompetition:
if foundCompetition['date'] > now:
return render_template('booking.html',club=foundClub,competition=foundCompetition)
else:
flash("You cannot book for a past competition")
return render_template('welcome.html', club=foundClub,
competitions=competitions, now=now)
else:
flash("Something went wrong-please try again")
return render_template('welcome.html', club=club, competitions=competitions)
return render_template('welcome.html', club=club, competitions=competitions, now=now)
@app.route('/purchasePlaces',methods=['POST'])
@@ -48,18 +57,23 @@ def purchasePlaces():
competition = [c for c in competitions if c['name'] == request.form['competition']][0]
club = [c for c in clubs if c['name'] == request.form['club']][0]
placesRequired = int(request.form['places'])
# check if that's the first book, and update session
if competition['name'] in session:
places = {competition['name']: session[competition['name']] + placesRequired}
else:
places = {competition['name']: placesRequired}
points = int(club['points'])
# prevent to book more than 12 places
if placesRequired <= 12:
if places[competition['name']] <= 12:
# prevent to book more than available points
if placesRequired <= points:
competition['numberOfPlaces'] = int(competition['numberOfPlaces']) - placesRequired
club['points'] = int(club['points']) - placesRequired
# set the session if that's the first book for this competition
if not competition['name'] in session:
session[competition['name']] = placesRequired
flash('Great-booking complete!')
flash(f"Great ! {placesRequired} places booked for {competition['name']}")
else:
flash("You don't have enough points")
else:
@@ -67,14 +81,14 @@ def purchasePlaces():
else:
flash("You can't book more than 12 places")
return render_template('welcome.html', club=club,
competitions=competitions)
competitions=competitions, now=now)
# TODO: Add route for points display
@app.route('/points')
def displayPoints():
return render_template('points.html', clubs=clubs)
@app.route('/logout')
def logout():
return redirect(url_for('index'))
if (__name__ == "__main__"):
app.run(debug=True)

View File

@@ -22,5 +22,6 @@
<input type="email" name="email" id=""/>
<button type="submit">Enter</button>
</form>
<a href="{{ url_for('displayPoints') }}">Board of points</a>
</body>
</html>

38
templates/points.html Normal file
View File

@@ -0,0 +1,38 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Board of clubs and points || GUDLFT</title>
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
table.center {
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<h2>Board of clubs</h2>
<table class="center">
<caption>
Points per club
</caption>
<thead>
<th scope="col">Club</th>
<th scope="col">Points</th>
</thead>
<tbody>
{% for club in clubs %}
<tr>
<td id="{{club['name']}}">{{club['name']}}</td>
<td id="points">{{club['points']}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ url_for('index') }}"> Back </a>
</body>
</html>

View File

@@ -23,7 +23,7 @@
{{comp['name']}}<br />
Date: {{comp['date']}}</br>
Number of Places: {{comp['numberOfPlaces']}}
{%if comp['numberOfPlaces']|int >0%}
{%if comp['numberOfPlaces']|int >0 and comp['date'] > now%}
<a href="{{ url_for('book',competition=comp['name'],club=club['name']) }}">Book Places</a>
{%endif%}
</li>

View File

@@ -27,4 +27,3 @@ def connect(client):
response = client.post('/showSummary', data={"email": EMAIL1})
soup = BeautifulSoup(response.data, 'html.parser')
return soup

View File

View File

@@ -0,0 +1,36 @@
from tests import conftest
from server import loadClubs
from bs4 import BeautifulSoup
class TestBoardPointsUpdate:
def test_should_board_be_updated_after_booking(self, client):
'''
test if the board is well displayed, and retrive points displayed for first club
then connect with this club email and book places
then check on board if the balance reflect the points decrease
'''
nb_places = 10
data = {
"competition": "Fall Classic",
"club": "Simply Lift",
"places": nb_places,
}
list_club = loadClubs()
# connect on board and check if points for first club are equal to points in DB (json)
response = client.get('/points')
soup = BeautifulSoup(response.data, "html.parser")
points1 = soup.find(id='points').text
assert response.status_code == 200
assert points1 == list_club[0]['points']
# then connect with mail of first club
connect = client.post('/showSummary', data={"email":"john@simplylift.co"})
assert connect.status_code == 200
# then book places
response = client.post('purchasePlaces', data=data)
assert f"Great ! "+str(nb_places)+" places booked for "+data['competition'] in response.data.decode()
# then check points on board
check_board = client.get('/points')
soup = BeautifulSoup(check_board.data, "html.parser")
points = soup.find(id='points').text
assert int(points) == int(points1)-10

View File

View File

@@ -0,0 +1,28 @@
from locust import HttpUser, task
class PerfConnectionTest(HttpUser):
@task
def index(self):
response = self.client.get("/")
@task
def login(self):
response = self.client.post("/showSummary", {"email": "admin@irontemple.com"})
class PerfBookTest(HttpUser):
@task
def bookPlaces(self):
response = self.client.post("/showSummary", {"email": "admin@irontemple.com"})
response = self.client.post("/purchasePlaces", {"club": "Iron Temple", "competition": "Fall Classic", "places": 2})
class PerfBoard(HttpUser):
@task
def board(self):
response = self.client.get("/points")

View File

@@ -1,28 +0,0 @@
from bs4 import BeautifulSoup
from flask import session
class TestPoints:
def test_should_nok_when_too_much_points(self, client, connect, club1):
points = int(connect.span.text)
club1.update({"places": points+1})
response = client.post('/purchasePlaces', data=club1)
soup = BeautifulSoup(response.data, "html.parser")
assert "You don't have enough points" == soup.li.text
def test_should_ok_when_enough_points(self, client, connect, club1):
points = int(connect.span.text)
club1.update({"places": points-1})
response = client.post('/purchasePlaces', data=club1)
soup = BeautifulSoup(response.data, "html.parser")
assert "Great-booking complete!" == soup.li.text
class TestPlaces:
def test_should_refuse_more_than_12(self, client, club1):
club1.update({"places": 13})
response = client.post('/purchasePlaces', data=club1)
soup = BeautifulSoup(response.data, "html.parser")
assert "You can't book more than 12 places" == soup.li.text

View File

@@ -0,0 +1,28 @@
from ward import fixture, test, using
from server import app
from bs4 import BeautifulSoup
@fixture(scope="global")
def test_client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
@fixture(scope="global")
def connect(co=test_client):
response = co.post('/showSummary', data={"email": "admin@irontemple.com"})
soup = BeautifulSoup(response.data, "html.parser")
return soup
@test("authentication is ok, welcome well displayed")
def _(client=connect):
assert client.h2.text == "Welcome, admin@irontemple.com"
@test("should be no book link for old competition")
def _(client=connect):
li = client.find_all("li")
assert not li[0].a
assert li[1].a

View File

View File

@@ -0,0 +1,24 @@
from bs4 import BeautifulSoup
from server import loadClubs
class TestBoardDisplayPoints:
def test_should_get_200(self, client):
'''
test if the page is retrieved
'''
response = client.get('/points')
assert response.status_code == 200
def test_should_display_right_size_list(self, client):
'''
test if the list of club displayed and in DB have the same size
'''
list_club = loadClubs()
response = client.get('/points')
soup = BeautifulSoup(response.data, "html.parser")
tr = soup.find_all("tr")
assert len(tr) == len(list_club)

View File

@@ -0,0 +1,22 @@
class TestDate:
'''
test the booking for the past competitions
'''
def test_should_not_display_book_link_for_past_competitions(self, connect):
'''
test that the booking link isn't displayed when competition date is older than today
'''
li = connect.find_all("li")
assert not li[0].a
assert li[1].a
def test_forged_url_on_past_competition_should_raise_flash(self, client):
'''
test that a flash warning occur when trying to connect to an URL on an old competition
'''
url = '/book/Spring Festival/Iron Temple'
response = client.get(url)
assert "You cannot book for a past competition" in response.data.decode()

View File

@@ -10,7 +10,6 @@ def test_should_status_code_ok(client):
def test_should_display_sorry_with_unknown_email(client):
email = "test@test.com"
response = client.post('/showSummary', data={"email": email})
print(session)
assert "_flashes" in session
assert session["_flashes"] == [("message", "Sorry, that email wasn't found")]
@@ -18,9 +17,6 @@ def test_should_display_sorry_with_unknown_email(client):
def test_shoul_display_page_on_known_email(client):
email = "admin@irontemple.com"
response = client.post('/showSummary', data={"email": email})
print(session)
soup = BeautifulSoup(response.data, 'html.parser')
assert soup.h2.text == "Welcome, "+email
welcome = "Welcome, "+email
assert welcome in response.data.decode()

View File

@@ -0,0 +1,59 @@
from bs4 import BeautifulSoup
from flask import session
class TestPoints:
'''
testing both case, book with and without enough points
'''
def test_should_nok_when_too_much_points(self, client, connect, club2):
'''
test booking an amount of places greater than the available points
'''
points = int(connect.span.text)
club2.update({"places": points+1})
response = client.post('/purchasePlaces', data=club2)
soup = BeautifulSoup(response.data, "html.parser")
assert "You don't have enough points" == soup.li.text
def test_should_ok_when_enough_points(self, client, connect, club2):
'''
test booking with enough points
'''
points = int(connect.span.text)
# One remaining point after that :
club2.update({"places": points-1})
response = client.post('/purchasePlaces', data=club2)
soup = BeautifulSoup(response.data, "html.parser")
assert f"Great ! "+str(points-1)+" places booked for "+club2['competition'] == soup.li.text
class TestPlaces:
'''
test the booking limit of 12 places
'''
def test_should_refuse_more_12_once(self, client, club2):
'''
test to book more than 12 places in one shot
'''
club2.update({"places": 13})
response = client.post('/purchasePlaces', data=club2)
soup = BeautifulSoup(response.data, "html.parser")
assert "You can't book more than 12 places" == soup.li.text
def test_should_refuse_more_12_total(self, client, club2):
'''
test to book more than 12 places in a two-part reservation
'''
club2.update({"places": 1})
response = client.post('/purchasePlaces', data=club2)
soup = BeautifulSoup(response.data, "html.parser")
assert "Great ! 1 places booked for "+club2['competition'] == soup.li.text
club2.update({"places": 12})
response = client.post('/purchasePlaces', data=club2)
soup = BeautifulSoup(response.data, "html.parser")
assert "You already booked 12 places for "+club2['competition'] == soup.li.text

View File

@@ -0,0 +1,12 @@
class TestPointsUpdate:
def test_should_not_be_ok_the_second_time(self, connect, client, club2):
'''
this books an amount of points-1 places
then test if 1 points remains displayed on page
'''
points = int(connect.span.text)
club2.update({"places": points-1})
response = client.post('/purchasePlaces', data=club2)
assert f"Great ! {points-1} places booked for {club2['competition']}" in response.data.decode()