From 68d4fc9a36a57b701006444553e3cd86ea14e273 Mon Sep 17 00:00:00 2001 From: Florian Uhlig Date: Tue, 7 Oct 2025 08:32:52 +0200 Subject: [PATCH] Testing --- .flake8 | 4 ++ .github/workflows/linters.yml | 8 +--- .github/workflows/unit-test.yml | 10 +---- config/__pycache__/database.cpython-312.pyc | Bin 0 -> 3097 bytes database/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1838 bytes .../flask_integration.cpython-312.pyc | Bin 0 -> 2285 bytes .../__pycache__/interface.cpython-312.pyc | Bin 0 -> 2345 bytes .../__pycache__/sqlite_db.cpython-312.pyc | Bin 0 -> 10999 bytes database/flask_integration.py | 2 +- database/interface.py | 2 +- database/sqlite_db.py | 18 ++++----- frontend/__pycache__/app.cpython-312.pyc | Bin 0 -> 12192 bytes services/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 150 bytes .../__pycache__/auth_service.cpython-312.pyc | Bin 0 -> 2673 bytes .../__pycache__/user_service.cpython-312.pyc | Bin 0 -> 4843 bytes services/user_service.py | 37 +++++++++--------- utils/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 147 bytes .../auth_decorators.cpython-312.pyc | Bin 0 -> 2198 bytes .../password_utils.cpython-312.pyc | Bin 0 -> 2067 bytes utils/__pycache__/validation.cpython-312.pyc | Bin 0 -> 3408 bytes 20 files changed, 36 insertions(+), 45 deletions(-) create mode 100644 .flake8 create mode 100644 config/__pycache__/database.cpython-312.pyc create mode 100644 database/__pycache__/__init__.cpython-312.pyc create mode 100644 database/__pycache__/flask_integration.cpython-312.pyc create mode 100644 database/__pycache__/interface.cpython-312.pyc create mode 100644 database/__pycache__/sqlite_db.cpython-312.pyc create mode 100644 frontend/__pycache__/app.cpython-312.pyc create mode 100644 services/__pycache__/__init__.cpython-312.pyc create mode 100644 services/__pycache__/auth_service.cpython-312.pyc create mode 100644 services/__pycache__/user_service.cpython-312.pyc create mode 100644 utils/__pycache__/__init__.cpython-312.pyc create mode 100644 utils/__pycache__/auth_decorators.cpython-312.pyc create mode 100644 utils/__pycache__/password_utils.cpython-312.pyc create mode 100644 utils/__pycache__/validation.cpython-312.pyc diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..a60b3be --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +#ignore = E226,E302,E41 +max-line-length = 130 +max-complexity = 10 diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index c211b2f..31c33e5 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -5,7 +5,7 @@ on: branches: - Development jobs: - on-success: + check: if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: @@ -27,9 +27,3 @@ jobs: - name: Run pylint run: pylint frontend services utils - - on-failure: - if: ${{ github.event.workflow_run.conclusion == 'failure' }} - runs-on: ubuntu-latest - steps: - - run: echo 'The Review workflow failed' diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index c1e9191..9d7ccd1 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -6,7 +6,7 @@ on: - main jobs: - on-success: + Unit-Test: runs-on: ubuntu-latest steps: @@ -28,10 +28,4 @@ jobs: # 6. Run unit tests - name: Run pytest - run: pytest - - on-failure: - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'failure' }} - steps: - - run: echo 'The triggering workflow failed' + run: pytest \ No newline at end of file diff --git a/config/__pycache__/database.cpython-312.pyc b/config/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..760518b43a557a178e18bd628818bed8759521c8 GIT binary patch literal 3097 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 literal 0 HcmV?d00001 diff --git a/database/__pycache__/flask_integration.cpython-312.pyc b/database/__pycache__/flask_integration.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d821a4e1b366db4063e6cbe81b68966729d2b4c GIT binary patch literal 2285 zcmah~O>ERg6rSNQDEph^X-g#@gYvS)slohLqu6br1(%ub!rD2rI&m#ZQvL;Uh>J%+x3YRS~V-67rl_O zx@A*4@knIqj`|eF(vXVUn-fU{>h(o?d_S8`H1NGHQ{@8LLk9%uP@HTGR{eY(so| zykCH?zZ=97n#c3VgkI7*=mF+O-9yp?nsB(#XC1R1dA1?NazNcWX!UOJR3ATV)0rh3F2r59n$8+;>Br zID#rJ`zy+BQjgG37oZ&p^dpTUx?khq zC;lw)m~{O)%cc5En>zL^(AaC_L1HzdM)y!oTSsidrn(KB4k^*=KGV}+Mu~CU1pnAB z=*1=5_dHlL-jz%@qku9KE~iW5qU$%{;=~G}wWwiWHV7U8r-{xq?p$op&?L3)zHNz@ z7v|~@T-FGkFzPvnoW<@*u{GFY5ySKZ`>L}X*ai`z7ot#{PxC_u(LSatoW==&g zq>W%8K&t;vnwP#mJTEns%)0U4M8#sJZEiz5QrMtVN9st199@L@IbJh1+;RHor>(pJ zGSnQ80JOT*(mw@S=!6CtXM)^cKsMbyoMWAW$LpBw`oR{k6#;f!X#4GWfH*-qn~lJp z*d7o@K|mTSlrf))=mAe2FgyWR6;V0F+t-%NydRu`lOPt*-zpl}vo!U?)V;C$*B%ca zyQQuT4lfT*tZ5^w+C)#ASkd0k;XM09JGZVNO&3VM0VLmt0P+-cc$sbHP=X?DP=~gG zbrT5Kkh)V>X~PLZ=1`U=UOYaN>B}&AA@b(LDUCgUZJaRRfwT=1>32@nrZ5|iIr z#%rw*;uSgoqeb+GeCCymm5Ic*Lf`W8n>4SX8Z>~t$YxU#gRHvB%Iu3Gh48(c?WBe!D m@d6FMKu4dI$8Np5S|00_$L^hcTt2u^{HHjME3Xg;5$j*7BMN%} literal 0 HcmV?d00001 diff --git a/database/__pycache__/interface.cpython-312.pyc b/database/__pycache__/interface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3b58ab48abed48d70296b2f3fe603f7b537e87f GIT binary patch literal 2345 zcmbVNy>HuA5P$p>EsL@hyB`VCHmw391d6CY(4atz0F4aP3baSJ2$<(_?%A_JGaQgrrPw#$r_tV|I$6rjd zN??8c(;wZR6+(W;AJe7f%H$R(Pl!)^DIlR_OQCGb68@G0B~)!S)NC!(ZCxTVxkG&A zG4WL@J%@O>?8;EH^gmIxWUhR9dz;tYc9JsJOGBD=W8YF>I~sCzr{|^I__m++;>Zm= zbeXP|T$!v(NQM$y@`)`|#b1Yn>hs)et71nj?Py|0EA8m8qtl9CfyaPHqDIH?S4xN# zK%iBkRk}(mwAPU?LI!*~s$VT3%{aCH+9=?LcfYq$~vw`xz1=hV9~Az6`NVun28GF2^o?@Y4;EWR}&g^5`ZM8RyZg>|*G+H7Y0J;EwR+QZ*ZV_W@!}`~=>EkyHrI2L zj=6EN9+BUS+5{BxW`3*&5N4vNgUA@Nbx?WL?S zWACECablN1BKz(ams56i$ghMfdmrtnPzV6(KIVmd+3P& zxu{liptm7JQ7iA9EUcUb)?$XmW;wyM?)zKwg;<2cdj5AV?ETkCT*BZpJ$BA&^6_FS zi_sXlAjBIHeK;G}!!DKAlCD`w(KU7z91;YcP0i+@@1M>2l**#2Ey8^SkqVOM?cjXX zR(%@K{ID$=-I950CM665p7&kr0yi8751Qlfs^f&QKL}8-InMV3H^?ju4-9Ldcnifl zAh-fwC$6^RIAE7?vw{L|E`~Q3`vApY$x>Pk21rJ|*Dzqat48?AP(mOWb@dZiY_;@Zg7$I=VY?wG2S>bm!b}u8QJW0e03@C*+xcovq1cX zWfE7nsOGL^aK&wV42M?qkL&5M-;4IL_J~G_mWc+;HszJ^MK%bx*eBqQ-@614`l%vG Z(hJgjLAFlF*57(lGM_5{5O|5i{{pWd5H$b* literal 0 HcmV?d00001 diff --git a/database/__pycache__/sqlite_db.cpython-312.pyc b/database/__pycache__/sqlite_db.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8044406fd9ca17b71756b588520e4479df63efc2 GIT binary patch literal 10999 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 Uy?p=F!su_f6Tk#q8&!h-551-xt^fc4 literal 0 HcmV?d00001 diff --git a/services/__pycache__/__init__.cpython-312.pyc b/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7639efae90e3fee62f6c5b9536b4277794d8bc7 GIT binary patch literal 150 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 VVPM_f@!0Y&%SD=o9EOyb{Vx|~RHgs` literal 0 HcmV?d00001 diff --git a/services/user_service.py b/services/user_service.py index 83fa5b7..e95d1d1 100644 --- a/services/user_service.py +++ b/services/user_service.py @@ -12,39 +12,38 @@ class UserService: def __init__(self, database: DatabaseInterface): self.db = database - def create_user( - self, username: str, email: str, password: str - ) -> tuple[bool, list[str]]: - errors = [] + def create_user(self, username: str, email: str, password: str) -> tuple[bool, list[str]]: + errors = self._validate_user_inputs(username, email, password) + if errors: + return False, errors - # Validierung + errors = self._check_user_existence(username, email) + if errors: + return False, errors + + password_hash = PasswordUtils.hash_password_simple(password) + return self._attempt_user_creation(username, email, password_hash) + + def _validate_user_inputs(self, username: str, email: str, password: str) -> list[str]: + errors = [] if not ValidationUtils.validate_username(username): errors.append("Invalid username format") - if not ValidationUtils.validate_email(email): errors.append("Invalid email format") - password_valid, password_errors = ValidationUtils.validate_password(password) if not password_valid: errors.extend(password_errors) + return errors - if errors: - return False, errors - - # Prüfe auf existierende User + def _check_user_existence(self, username: str, email: str) -> list[str]: + errors = [] if self.get_user_by_email(email): errors.append("Email already registered") - if self.get_user_by_username(username): errors.append("Username already taken") + return errors - if errors: - return False, errors - - # Passwort hashen - password_hash = PasswordUtils.hash_password_simple(password) - - # User erstellen + def _attempt_user_creation(self, username: str, email: str, password_hash: str) -> tuple[bool, list[str]]: try: success = self.db.create_user(username, email.lower(), password_hash) if success: diff --git a/utils/__pycache__/__init__.cpython-312.pyc b/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f45d26bba00dfe65b71d8a55f9ae7afdbe535dd3 GIT binary patch literal 147 zcmX@j%ge<81o^ukW`O9&AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWu%{xpPQ2KczG$)vkyY UXapk=7lRldnHd=wi7%Q6rNr0+G}U?(}dDADb0okiu;q$A}Ugaq5>i)DWOt9w7!_FXKZh}-ZeAp zMzMkviAuB=-~b{LMe3nKM7eO_1_$n3Y$d2-2~rVAh?}W7pq_Yd?6uRB0|!R(n>Rc2 zX5P>DJ%8WZn<6m2_~w`5Nu7{iaS@GJTWKzW@+qN&DlW<3th!1`&8Q0MF*jDyGFnN` z=%sijUP@#VB_m@fL?w(Y4n~)CXO`MfTB}SC)vl3@Np+CDG!C+FSxG1U!0m-~CDfO= zT^4CQ)bp+_ilM<7bvVlfVS=-FDolVPm=KQdg^3Dxvw5HA+Ly~GFa-QHCqcXqK?I%M z8zge$I>{?kxn^ug6GGlqR+I}Qtya&bEEq4doX_pR=T_ibW$p*?zQz4Yz=RbPZP39L z@XE3)L6LcZld}VGNZRJwSIPVwiZH0UbIo4~q6$EkskWiE zPAVkOBCEwjq;+Si2S&77l+}$^t@$(Zxf+EIclAcQL4Tytc(fnQ7oeIq)D_~873H?_ z0j4Lh04W!YXB`^qtYkZGb^N@`Y{4woFIbL;>9TVW!!3@NHaG^uF?V4cJ{1@bHOGTOW9Xs5JX-c0FW_-JmISdL z3pJY;L>RvUkHM_u1t!w6R_{+w75x&M%2$f6Q<#EcECJ|M=RI_5SgiG2Vo`3CBR!RsJaI!yp8L=534bgDLEL z&|N+XA2DO%5D4(=uM~;*TtD&N#ipw3k2hl^-q%2_(MQaKHSICJAI|-c{Nj=$U4B7P zA0U<}`W2U2zUMAmmw{grOwMJOSxZ1PYvJ`b5NZX$kJF;tQ*5#@c?>@~-Z9{1!4U!D z5_uvmAIm`mozWpAg-;I=k^tc`opMl>#&!@8E#(M01O$S%drLI`3u*KPZh~EP|0Ta- z+1N8&&TP8ejf3xj{V++P-Cg!=PSaYK09}mpAvhJGS{7GUyLuEthtBk$5Q(iwoC48C zWTM_PCZTxz%d1=FsTSVmHxE3$Idr0Cp4g2y2?mKbK7u#x1X}+tH#ok9;OQXE>>y3+ zAPuyJxf5w%&>7hBuo}%d5W89@;iKKgNfmr($6DRBj^UbWH)9vbvYw7tUy#6fmAPer zN-A<+)$7+J8FIQdTrH@PucV7{`X1j)kg2`8cR@y^vO9 z8P`cGZIX@y@IbcxKo-sU1e(H6JOg5tGz?~$~fwX`w8zyBuzV8Z-32gn3UXk6j1tx@V3t-p? z!X&tKuGM6XY!<`GX88b~!CoQDD4pckF}k7LH)cvct+?zAKLQ)b191{WBc>?IPo(c> rGWt82`GL&bO^#gOcPBYgPmZiTx0#$+jo*v+DhKabAevT6X+`i4$&?3| literal 0 HcmV?d00001 diff --git a/utils/__pycache__/password_utils.cpython-312.pyc b/utils/__pycache__/password_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb59076055cd258ba665126c62e546c26f82e3bf GIT binary patch literal 2067 zcmZuxO=ufO6rTN)R<`ABBF9lBrwH4EsMHZ{TGxQo$j@iCbza8B%2Jn}E>YuyL2z6Hf0jp@B4{aRrogIG5z?J)h)xggM>g8VxuW_c&*l zlz4O(Y5X?Qggw5e^K5W2@fB=V(vHfZc05cxC5$u>M&K#QV?pz2JQg*9{Y5Nc5sMlA zZHCEsG-+GfbF5L)!;paU^s(NIta-P^K0@vx;095XhfaJfrV-1(nxgbOgJ@rv`2tjQK#{Pf_Nz7u_o>FZj?<>)chxFrDVRlIoh`(3MiIv|73Mk{FB1dcG`{+! zx|~aycMLOLfp&=Z?d|Q2@`mNN?|1c^;nm^xxpD5$$K28TjBKHoqe}huaUj$Q%(eov z&ClC`>-EG@Alz7dy4ViP)a9elbo1V`cwIT3yw)f^Ej86n^j0f+>&4x6G|`^?q&|8S zoaqGTTEV$?Fkbf`%i~YR9*-Sfx%r3u;gK@6qa7;Khw}6(bBC-^Y8r%P1;R-~r@9NK z-0s|On9x9`t4I1Us;6b0$h#B#HS6k{P!r95@4o33s0vliwOB*Xxs{kmz$FO+fp)dV z(?+qJB3N~5>lQNv0K1}o092r+5WU5zgR+sUaD(zKCi%=pzvP?O&e!0=@dUG>?=iB4 z{s~|2gy&n~`Td6n6YcP)Ejd{C|M=iond~UhmJ)3i_rGc@ONa6jfwv~zpJ}#%@3u4d zJrmf>BYpUk>0tF|3YqX3j7i&^()dvG*(CESVAp@l9i*9V(IeucmT%5n(vO<;_P z0^}_qFv~H{o)rqQMx69boa<4Q=8NS5-kd(4!#80dB{a?mYv-ZPW!Zc3=<%bS+)u_c zV>jD=XTGg`+)X2gk`k<03^iIa8 znKJX~A~th|W(n+_uq3m>qO{w{64wo*KqBluGR??yM`jp^Tm}Mg z@-7glfL+8h`L>h0MKZ^9P+hyly8_2?f1+{c_tF=tU+wrpEnjG7q3xU9^8DwS;1b*k I0&3s(KQYVD?*IS* literal 0 HcmV?d00001 diff --git a/utils/__pycache__/validation.cpython-312.pyc b/utils/__pycache__/validation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a3e984bc906ed97ed3fe3af8fd54cdbd15f7eea GIT binary patch literal 3408 zcmb_e%Wo4$7@u8x?RDbBPD~yoX<0}@V;~8Hh^7=$2+vlKQX!BU2=3yY*lQfGnOy_f zIuZve8TC*QhomZ1Bekli=)qDs1ocno#SlxROH_f{OHNG*QYB9PX1(i&6oL<3X}@`V zk9qy(_sws0bu|dSAAfxweaIv9CvDiD)2ghVfXWP#ki^8%SfOOb7#3j^vu$i2D6!){ zQ)rGkS#%6Z>{_Xm9BK3o`j)kQ z7?fsDqo8y2sJ`>UIk8ti*gLkf_r1PQ_x9fL?FY(wpn1N}YL9*%>}!`+YOQa^|D z9RrJj)@S~vncCa6cj}(_J0JNw7yR8BZn5!5#=R71xyydXf6dRe-0NOw-MJ9hmEoWJ zT9&+xUya@z&AqiddnEgAuKoSIci%H#OUAbf;gP|oR9L4tiPy&9Fd*;$!QeE~iYNpO zS%{X{OMqv!eodX@(qV+i7R|o5C|1NKoqOSvoNM`3Vr`0%IKX(F?W6hZTFh_PO658j zUwTx6h~y>V!^( z&|6C=y&4V(N9eK$T@j&Im(cYRdMyFg&9qK0CTRp=qnQMtVKfxMXAh)rel0$26B%fP%Rdo3HwwI9rTM;0lVNfTWN zpA=Q~szRg+Fw+EL66!EOZ3acqG?3UENwq1>tVO93V?&n#;!HCWKx1nGP;#sTY^MqA zhS!Y1l}0Gha?DJku#&P*0>Ig1tiCRqyXt6s712CZN2{qKT5Z+Qyj4W2D~nd5n&Np$ z^_O}OvXWIXwZ)25P}*3@mu4ynByF8^&_AkgIAwuMkX3=;%PE;)>F6>=+42AgeY9vW znNl_3A{ImqZo3HeK_MI!i5Q05q6%>(5fQ|MB$#FiMHBF~FvgOwYhb9%PUp@+avaxp zSB4EM2~Cs}rPxXW3#nuhldz~_7>_0}@P*u3ov)c9%YE7NlHOCrppqPsHNE-V<^!Ew z-50iQ>k01Y`(Vr7&!^M-hR5~6Dk7*!9G1m+Y0-o}a`cX(t3^=od=e($-Gn8`_I!x~ z4VMTDPe`N<+)UatTq+hxI9j4a;J^;h8ZJzTLezC>Fln9eIy7jTbVE$_ZxGkfvxY#% z1teACLP)(QZ=TGx?aX_30kzF;y|q8*6_(cK#KnU3qW`m(4z{lTr3?8RznZ!^HQPEj zl=tnh)Va2g^WIZSzJ@ocn&$YtFIe9)fqnY;(@HDaUUz33oJDYw)2EeLjRjMolO*sYt0ft$RbE z%PBE#X}s_x)8z0Z)}o43NLprs+CY!1ot_G(rIK-MIO8%r*gB!-CuELbCMZJhmMQ49 zR#gx;&?48c!q#%Fv*8s4#hmnl7OM{AYBc*-+RU04REJg&6!#9lpoL`twUrLjqKOlz z1r+y=WV_~_^QRwrvi*;CjJ%+IrujcC)C%*VQwh2ZH+?w5gRuZMPXucx%$FqTqEj{V zusP{%v`KT5xsX@