From 2153bb07b9fc8f4cd8dd7c4a28a84215cebe89f8 Mon Sep 17 00:00:00 2001 From: Paul Martin Date: Mon, 20 May 2024 20:36:46 +0200 Subject: [PATCH] Tests --- init.py | 6 +- requirements.txt | 2 +- routing.py | 596 +++++++++++++++++------------------ tests_examples/login_data.py | 2 + tests_examples/test_app.py | 69 ++++ tests_examples/vergleich.py | 4 +- 6 files changed, 373 insertions(+), 306 deletions(-) create mode 100644 tests_examples/login_data.py create mode 100644 tests_examples/test_app.py diff --git a/init.py b/init.py index a2b1d4d..df01769 100644 --- a/init.py +++ b/init.py @@ -7,7 +7,8 @@ from get_mysql import get_mysql import atexit from flask_apscheduler import APScheduler -def create(): + +def create(testing: bool = False): """ Erstellt die Flask-App inkl. Datenbank und Login-Manager. :return app: @@ -25,7 +26,8 @@ def create(): login_manager.login_view = "login" # Shut down the scheduler when exiting the app - atexit.register(lambda: scheduler.shutdown()) + if not testing: + atexit.register(lambda: scheduler.shutdown()) @login_manager.user_loader def load_user(uid: int): diff --git a/requirements.txt b/requirements.txt index 264a7f5..64bbc88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ bs4~=0.0.1 pytz~=2023.3.post1 flask_talisman asyncio~=3.4.3 -httpx~=1.0.0b0 +httpx celery~=5.4.0rc2 flask[async] pymysql diff --git a/routing.py b/routing.py index 6cc4da4..220d849 100644 --- a/routing.py +++ b/routing.py @@ -18,338 +18,332 @@ from calendar_generation import getWeek from init import * -@app.route("/") -def index(): +def init_routes(flask_app: Flask): """ - Leitet den normalen Website-Aufruf zum Login weiter. - :return HTML: + Initialisiert die App-Routen. Nötig für Tests. """ - return redirect(url_for("login")) + @flask_app.route("/") + def index(): + """ + Leitet den normalen Website-Aufruf zum Login weiter. + :return HTML: + """ + return redirect(url_for("login")) -@app.route("/dashboard") -@login_required -def welcome(): - """ - Dashboard - :return HTML: - """ - if not current_user.kurs: - return redirect(url_for("getKurs", next=url_for(request.endpoint))) - sel = request.args.get("sel") - if not sel: - sel = "theorie" - kurs = current_user.kurs - name = current_user.name - if sel == "theorie": - t = "" - p = "hidden" - else: - t = "hidden" - p = "" - return render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p) + @flask_app.route("/dashboard") + @login_required + def welcome(): + """ + Dashboard + :return HTML: + """ + if not current_user.kurs: + return redirect(url_for("getKurs", next=url_for(request.endpoint))) + sel = request.args.get("sel") + if not sel: + sel = "theorie" + kurs = current_user.kurs + name = current_user.name + if sel == "theorie": + t = "" + p = "hidden" + else: + t = "hidden" + p = "" + return render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p) + @flask_app.route("/theorie/noten", methods=["GET", "POST"]) + @login_required + async def displayNoten(): + """ + Zeigt die Noten aus Dualis an. Hierfür ist ein aktives Token nötig. + :return HTML: + """ + d = Dualis.query.filter_by(uid=current_user.id).first() + if request.method == "POST": + d.semester = request.form.get("sem") + db.session.commit() + if not d.semester: + return redirect(url_for("getSemester", next=url_for(request.endpoint))) + t = d.token + chosensemester = d.semester + c = request.cookies.get("cnsc") + timeout = fetchDUALIS.timeOut(d, c, "displayNoten") + if timeout: + return timeout + semester = getSemesterList(current_user.id) + noten = await fetchDUALIS.getResults(t, c, chosensemester) + return render_template("noten.html", noten=noten, semester=semester, sel=chosensemester, s="n", praxis="hidden") -@app.route("/theorie/noten", methods=["GET", "POST"]) -@login_required -async def displayNoten(): - """ - Zeigt die Noten aus Dualis an. Hierfür ist ein aktives Token nötig. - :return HTML: - """ - d = Dualis.query.filter_by(uid=current_user.id).first() - if request.method == "POST": - d.semester = request.form.get("sem") - db.session.commit() - if not d.semester: - return redirect(url_for("getSemester", next=url_for(request.endpoint))) - t = d.token - chosensemester = d.semester - c = request.cookies.get("cnsc") - timeout = fetchDUALIS.timeOut(d, c, "displayNoten") - if timeout: - return timeout - semester = getSemesterList(current_user.id) - noten = await fetchDUALIS.getResults(t, c, chosensemester) - return render_template("noten.html", noten=noten, semester=semester, sel=chosensemester, s="n", praxis="hidden") - - -@app.route("/plan", methods=["GET"]) -@login_required -async def displayRapla(): - """ - Zeigt den Stundenplan für eingeloggte User an. \n - TODO: Persönliche Filter, Notizen, Essensvorlieben etc. berücksichtigen. - :return HTML: - """ - if not current_user.kurs: - return redirect(url_for("getKurs", next=url_for(request.endpoint))) - week = request.args.get("week") - if week: - week = datetime.datetime.strptime(week, "%Y-%m-%d") - else: - week = "today" - samstag = request.args.get("samstag") - if not samstag: - samstag = False - events = await getWeek(week, fetchRAPLA.getIcal(current_user.kurs), samstag) - return render_template("plan-user.html", events=events[0], eventdays=events[1], - name=current_user.name, prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], - s="p", praxis="hidden") - - -@app.route("/plan/") -async def displayPlan(kurs): - """ - Zeigt den Stundenplan ohne Login an. \n - Präferenzen werden nicht berücksichtigt. - :param kurs: - :return HTML: - """ - week = request.args.get("week") - if week: - week = datetime.datetime.strptime(week, "%Y-%m-%d") - else: - week = "today" - try: - if current_user.kurs == kurs.upper(): - return redirect(url_for("displayRapla")) - except AttributeError: - pass - kurs = kurs.upper() - plan = fetchRAPLA.getIcal(kurs) - if plan: + @flask_app.route("/plan", methods=["GET"]) + @login_required + async def displayRapla(): + """ + Zeigt den Stundenplan für eingeloggte User an. \n + TODO: Persönliche Filter, Notizen, Essensvorlieben etc. berücksichtigen. + :return HTML: + """ + if not current_user.kurs: + return redirect(url_for("getKurs", next=url_for(request.endpoint))) + week = request.args.get("week") + if week: + week = datetime.datetime.strptime(week, "%Y-%m-%d") + else: + week = "today" samstag = request.args.get("samstag") if not samstag: samstag = False - events = await getWeek(week, plan, samstag) - return render_template("plan-anon.html", events=events[0], eventdays=events[1], kurs=kurs, - prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], praxis="hidden") - else: - return redirect(url_for("login")) + events = await getWeek(week, fetchRAPLA.getIcal(current_user.kurs), samstag) + return render_template("plan-user.html", events=events[0], eventdays=events[1], + name=current_user.name, prev=str(events[2])[:10], next=str(events[3])[:10], + mon=events[4], + s="p", praxis="hidden") + @flask_app.route("/plan/") + async def displayPlan(kurs): + """ + Zeigt den Stundenplan ohne Login an. \n + Präferenzen werden nicht berücksichtigt. + :param kurs: + :return HTML: + """ + week = request.args.get("week") + if week: + week = datetime.datetime.strptime(week, "%Y-%m-%d") + else: + week = "today" + try: + if current_user.kurs == kurs.upper(): + return redirect(url_for("displayRapla")) + except AttributeError: + pass + kurs = kurs.upper() + plan = fetchRAPLA.getIcal(kurs) + if plan: + samstag = request.args.get("samstag") + if not samstag: + samstag = False + events = await getWeek(week, plan, samstag) + return render_template("plan-anon.html", events=events[0], eventdays=events[1], kurs=kurs, + prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], praxis="hidden") + else: + return redirect(url_for("login")) -@app.route("/set-up") -def redKurs(): - """ - Setup beginnt mit Kurs. - :return HTML: - """ - return redirect(url_for("getKurs")) + @flask_app.route("/set-up") + def redKurs(): + """ + Setup beginnt mit Kurs. + :return HTML: + """ + return redirect(url_for("getKurs")) - -@app.route("/set-up/kurs") -@login_required -async def getKurs(): - """ - Automatische Kurs-Auswahl. \n - Aktives Dualis-Token benötigt. - :return HTML: - """ - d = Dualis.query.filter_by(uid=current_user.id).first() - if d: - cookie = request.cookies.get("cnsc") - timeout = fetchDUALIS.timeOut(d, cookie, "getKurs") - if timeout: - return timeout - e = False - if not current_user.kurs: - kurs = await fetchDUALIS.getKurs(d.token, cookie) - if kurs != 0: - if not fetchRAPLA.getIcal(kurs): - return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", - file=False) + @flask_app.route("/set-up/kurs") + @login_required + async def getKurs(): + """ + Automatische Kurs-Auswahl. \n + Aktives Dualis-Token benötigt. + :return HTML: + """ + d = Dualis.query.filter_by(uid=current_user.id).first() + if d: + cookie = request.cookies.get("cnsc") + timeout = fetchDUALIS.timeOut(d, cookie, "getKurs") + if timeout: + return timeout + e = False + if not current_user.kurs: + kurs = await fetchDUALIS.getKurs(d.token, cookie) + if kurs != 0: + if not fetchRAPLA.getIcal(kurs): + return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", + praxis="hidden", + file=False) + current_user.kurs = kurs + db.session.commit() + else: + e = True + else: + kurs = current_user.kurs current_user.kurs = kurs db.session.commit() - else: - e = True else: - kurs = current_user.kurs - current_user.kurs = kurs + e = True + kurs = "" + return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", file=True) + + @flask_app.route("/set-up/semester") + @login_required + async def getSemester(): + """ + Manuelle Semester-Auswahl. + :return HTML: + """ + t = Dualis.query.filter_by(uid=current_user.id).first().token + c = request.cookies.get("cnsc") + semesterlist = Semesterlist.query.filter_by(uid=current_user.id).all() + if not semesterlist: + semester = await fetchDUALIS.getSem(t, c) + for i in semester: + semitem = Semesterlist(semestername=i[0], semesterid=i[1], uid=current_user.id, + itemid=current_user.id * int(i[1][-7:]) // 1000000) + db.session.add(semitem) db.session.commit() - else: - e = True - kurs = "" - return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", file=True) + else: + semester = getSemesterList(current_user.id) + return render_template("semester.html", semester=semester, s="s", theorie="hidden", praxis="hidden") - -@app.route("/set-up/semester") -@login_required -async def getSemester(): - """ - Manuelle Semester-Auswahl. - :return HTML: - """ - t = Dualis.query.filter_by(uid=current_user.id).first().token - c = request.cookies.get("cnsc") - semesterlist = Semesterlist.query.filter_by(uid=current_user.id).all() - if not semesterlist: - semester = await fetchDUALIS.getSem(t, c) - for i in semester: - semitem = Semesterlist(semestername=i[0], semesterid=i[1], uid=current_user.id, itemid=current_user.id*int(i[1][-7:])//1000000) - db.session.add(semitem) + @flask_app.route("/set-up/semester", methods=["POST"]) + @login_required + def setSemester(): + """ + Speichern der Semester-Auswahl. + :return HTML: + """ + n = request.args.get("next") + if not n: + n = url_for("welcome") + d = Dualis.query.filter_by(uid=current_user.id).first() + d.semester = request.form.get("sem") db.session.commit() - else: - semester = getSemesterList(current_user.id) - return render_template("semester.html", semester=semester, s="s", theorie="hidden", praxis="hidden") + return redirect(n) + @flask_app.route("/set-up/rapla") + @login_required + def chooseRaplas(): + """ + Manuelle Rapla-Auswahl. + :return HTML: + """ + r = getRaplas() + return render_template("rapla.html", raplas=r, s="s", theorie="hidden", praxis="hidden") -@app.route("/set-up/semester", methods=["POST"]) -@login_required -def setSemester(): - """ - Speichern der Semester-Auswahl. - :return HTML: - """ - n = request.args.get("next") - if not n: - n = url_for("welcome") - d = Dualis.query.filter_by(uid=current_user.id).first() - d.semester = request.form.get("sem") - db.session.commit() - return redirect(n) - - -@app.route("/set-up/rapla") -@login_required -def chooseRaplas(): - """ - Manuelle Rapla-Auswahl. - :return HTML: - """ - r = getRaplas() - return render_template("rapla.html", raplas=r, s="s", theorie="hidden", praxis="hidden") - - -@app.route("/set-up/rapla", methods=["POST"]) -@login_required -async def getRapla(): - """ - Verarbeitet die Eingabe von chooseRaplas(). - :return HTML: - """ - file = str(request.form.get("file")) - url = str(request.form.get("url")) - if file == url == "None": - return redirect(url_for("chooseRaplas")) - if file != "None": - loadUser(current_user.id).kurs = file[5:-5] - db.session.commit() - elif url != "None": - file = await getNewRapla(url) - if type(file) is not int: + @flask_app.route("/set-up/rapla", methods=["POST"]) + @login_required + async def getRapla(): + """ + Verarbeitet die Eingabe von chooseRaplas(). + :return HTML: + """ + file = str(request.form.get("file")) + url = str(request.form.get("url")) + if file == url == "None": + return redirect(url_for("chooseRaplas")) + if file != "None": loadUser(current_user.id).kurs = file[5:-5] db.session.commit() + elif url != "None": + file = await getNewRapla(url) + if type(file) is not int: + loadUser(current_user.id).kurs = file[5:-5] + db.session.commit() + else: + return redirect(url_for("error", ecode=900)) + return redirect(url_for("welcome")) + + @flask_app.route("/log-in") + def login(): + """ + Login-Maske. + :return HTML: + """ + return render_template("login.html", theorie="hidden", praxis="hidden", s="s") + + @flask_app.route("/log-in", methods=["POST"]) + async def login_post(): + """ + Verarbeitet die Eingabe von login(). \n + Falls der User schon angelegt ist, wird das Passwort verglichen. \n + Falls nicht, wird ein neuer angelegt. + :return HTML: + """ + email = request.form.get("email") + password = request.form.get("password") + n = request.args.get("next") + if n: + success = make_response(redirect(n)) else: - return redirect(url_for("error", ecode=900)) - return redirect(url_for("welcome")) + success = make_response(redirect(url_for("getKurs"))) + user = User.query.filter_by(email=email).first() + t = await fetchDUALIS.checkUser(email, password) + if t[0] == -2: + return redirect(url_for("login", code=-2)) + if user: + dualis = Dualis.query.filter_by(uid=user.id).first() + dualis.token = t[0] + newcookie = t[1] + dualis.token_created = time.time() + db.session.commit() + login_user(user) + if user.kurs: + if not dualis.semester: + success = make_response(redirect(url_for("getSemester"))) + elif not n: + success = make_response(redirect(url_for("welcome"))) + success.set_cookie("cnsc", value=newcookie, httponly=True, secure=True) -@app.route("/log-in") -def login(): - """ - Login-Maske. - :return HTML: - """ - return render_template("login.html", theorie="hidden", praxis="hidden", s="s") + else: + hashid = int(hashlib.sha1(email.encode("utf-8")).hexdigest(), 16) % (10 ** 8) + pname = email.find(".") + 1 + ename = min(email[pname:].find("."), email[pname:].find("@")) + name = email[pname:pname + ename].capitalize() + new_user = User(email=email, name=name, id=hashid) + db.session.add(new_user) + cookie = t[1] + new_dualis = Dualis(uid=hashid, token=t[0], token_created=int(time.time())) + db.session.add(new_dualis) + db.session.commit() + login_user(new_user) + success.set_cookie("cnsc", value=cookie, httponly=True, secure=True) + return success -@app.route("/log-in", methods=["POST"]) -async def login_post(): - """ - Verarbeitet die Eingabe von login(). \n - Falls der User schon angelegt ist, wird das Passwort verglichen. \n - Falls nicht, wird ein neuer angelegt. - :return HTML: - """ - email = request.form.get("email") - password = request.form.get("password") - n = request.args.get("next") - if n: - success = make_response(redirect(n)) - else: - success = make_response(redirect(url_for("getKurs"))) - - user = User.query.filter_by(email=email).first() - t = await fetchDUALIS.checkUser(email, password) - if t[0] == -2: - return redirect(url_for("login", code=-2)) - if user: - dualis = Dualis.query.filter_by(uid=user.id).first() - dualis.token = t[0] - newcookie = t[1] - dualis.token_created = time.time() + @flask_app.route("/log-out") + @login_required + async def logout(): + """ + Loggt den User aus. + :return Empty Token: + """ + cookie = request.cookies.get("cnsc") + dualis = Dualis.query.filter_by(uid=current_user.id).first() + await fetchDUALIS.logOut(dualis.token, cookie) + dualis.token = None db.session.commit() - login_user(user) - if user.kurs: - if not dualis.semester: - success = make_response(redirect(url_for("getSemester"))) - elif not n: - success = make_response(redirect(url_for("welcome"))) - success.set_cookie("cnsc", value=newcookie, httponly=True, secure=True) + logout_user() + red = make_response(redirect(url_for("login", code=1, next=url_for("welcome")))) + red.set_cookie("cnsc", value="Logged out! Your temporary token " + "on our server and the cookie on your device have been deleted.", httponly=True, + secure=True) + return red - else: - hashid = int(hashlib.sha1(email.encode("utf-8")).hexdigest(), 16) % (10 ** 8) - pname = email.find(".") + 1 - ename = min(email[pname:].find("."), email[pname:].find("@")) - name = email[pname:pname + ename].capitalize() - new_user = User(email=email, name=name, id=hashid) - db.session.add(new_user) - cookie = t[1] + @flask_app.route("/error") + def error(): + """ + Error Page für custom-Errors. \n + TODO: Funktion depreciaten. Ersetzen durch Errors auf den entsprechenden Seiten. + :return: + """ + error = request.args.get("ecode") + if error == "900": + msg = "Ungültige RAPLA-URL! Sicher, dass der Link zum DHBW-Rapla führt?" + elif error == "899": + msg = "Der Kalender wurde nicht gefunden! Sicher, dass der Link korrekt ist?" + else: + msg = str(error) + return render_template('display-message.html', message=msg) - new_dualis = Dualis(uid=hashid, token=t[0], token_created=int(time.time())) - db.session.add(new_dualis) - db.session.commit() - login_user(new_user) - success.set_cookie("cnsc", value=cookie, httponly=True, secure=True) - return success - - -@app.route("/log-out") -@login_required -async def logout(): - """ - Loggt den User aus. - :return Empty Token: - """ - cookie = request.cookies.get("cnsc") - dualis = Dualis.query.filter_by(uid=current_user.id).first() - await fetchDUALIS.logOut(dualis.token, cookie) - dualis.token = None - db.session.commit() - logout_user() - red = make_response(redirect(url_for("login", code=1, next=url_for("welcome")))) - red.set_cookie("cnsc", value="Logged out! Your temporary token " - "on our server and the cookie on your device have been deleted.", httponly=True, - secure=True) - return red - - -@app.route("/error") -def error(): - """ - Error Page für custom-Errors. \n - TODO: Funktion depreciaten. Ersetzen durch Errors auf den entsprechenden Seiten. - :return: - """ - error = request.args.get("ecode") - if error == "900": - msg = "Ungültige RAPLA-URL! Sicher, dass der Link zum DHBW-Rapla führt?" - elif error == "899": - msg = "Der Kalender wurde nicht gefunden! Sicher, dass der Link korrekt ist?" - else: - msg = str(error) - return render_template('display-message.html', message=msg) - - -@app.route("/error") -@app.errorhandler(HTTPException) -def handle(e): - """" - HTTP-Exception-Handler - """ - return render_template('display-message.html', message=e) + @flask_app.route("/error") + @flask_app.errorhandler(HTTPException) + def handle(e): + """" + HTTP-Exception-Handler + """ + return render_template('display-message.html', message=e) if __name__ == "__main__": + init_routes(app) app.run(host='0.0.0.0', port=2024, debug=True) diff --git a/tests_examples/login_data.py b/tests_examples/login_data.py new file mode 100644 index 0000000..73b7d40 --- /dev/null +++ b/tests_examples/login_data.py @@ -0,0 +1,2 @@ +email = "EMAIL-GOES-HERE" +password = "PASSWORD-GOES-HERE" diff --git a/tests_examples/test_app.py b/tests_examples/test_app.py new file mode 100644 index 0000000..9acfb33 --- /dev/null +++ b/tests_examples/test_app.py @@ -0,0 +1,69 @@ +import pytest +from bs4 import BeautifulSoup + +import routing +import init +from tests_examples import login_data + + +@pytest.fixture() +def app(): + app = init.create(testing=True) + app.config.update({ + "TESTING": True, + }) + + routing.init_routes(app) + + # other setup can go here + + yield app + + # clean up / reset resources here + + +@pytest.fixture() +def client(app): + return app.test_client() + + +@pytest.fixture() +def runner(app): + return app.test_cli_runner() + + +def login(client): + client.post('/log-in', data=dict(email=login_data.email, password=login_data.password), + follow_redirects=True) + cookie = client.get_cookie("cnsc") + return len(cookie.value) == 32 + + +def test_login(client): + loginpage = client.get("/log-in", follow_redirects=True) + assert b"Einloggen" in loginpage.data + assert loginpage.status_code == 200 + loginpage_html = BeautifulSoup(loginpage.text, "lxml") + login_form = loginpage_html.find("form") + login_action = login_form.get("action") + login_request = client.post(login_action, data=dict(email=login_data.email, password=login_data.password), + follow_redirects=True) + assert login_request.status_code == 200 + assert b"Willkommen, " in login_request.data + + +def test_noten(client): + if login(client): + notenpage = client.get("/theorie/noten", follow_redirects=True) + assert notenpage.status_code == 200 + + assert b"Deine Noten im" in notenpage.data + + +def test_logout(client): + if login(client): + loginpage = client.get("/log-out", follow_redirects=True) + assert loginpage.status_code == 200 + assert b"Einloggen" in loginpage.data + cookie = client.get_cookie("cnsc") + assert len(cookie.value) != 32 # CNSC-Länge: 32 → CNSC darf ausgeloggt nicht gesetzt sein diff --git a/tests_examples/vergleich.py b/tests_examples/vergleich.py index 3277049..0e2328f 100644 --- a/tests_examples/vergleich.py +++ b/tests_examples/vergleich.py @@ -8,7 +8,7 @@ from bs4 import BeautifulSoup from celery import Celery import fetchDUALIS -from login_data import passwort, email #CREATE LOCAL login_data.py!!! +from login_data import password, email #CREATE LOCAL login_data.py!!! app = Flask(__name__) app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0' @@ -26,7 +26,7 @@ headers = { } url = "https://dualis.dhbw.de/scripts/mgrqispi.dll" -fpw = urllib.parse.quote(passwort, safe='', encoding=None, errors=None) +fpw = urllib.parse.quote(password, safe='', encoding=None, errors=None) fmail = urllib.parse.quote(email, safe='', encoding=None, errors=None)