Feature: Support both Free-Text and Multiple-Choice question types
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from app.database import engine
|
||||
from sqlalchemy import text
|
||||
|
||||
def migrate():
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE questions ADD COLUMN IF NOT EXISTS question_type VARCHAR(20) DEFAULT 'text'"))
|
||||
conn.execute(text("ALTER TABLE questions ADD COLUMN IF NOT EXISTS options TEXT NULL"))
|
||||
conn.commit()
|
||||
print("Migration successful: added question_type and options columns.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate()
|
||||
@@ -0,0 +1,71 @@
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import http.cookiejar
|
||||
import json
|
||||
|
||||
BASE_URL = "http://127.0.0.1:8001"
|
||||
|
||||
def test_mc():
|
||||
print("--- 1. Submit Multiple Choice Suggestion ---")
|
||||
sugg_payload = json.dumps({
|
||||
"frage": "Welches Tier ist das Wappentier von Ravenclaw?",
|
||||
"antwort": "Adler",
|
||||
"tipp": "Viele Muggel denken fälschlicherweise an einen Raben.",
|
||||
"name": "Luna Lovegood",
|
||||
"question_type": "multiple_choice",
|
||||
"falsche_antworten": ["Rabe", "Dachs", "Schlange"]
|
||||
}).encode("utf-8")
|
||||
|
||||
req = urllib.request.Request(f"{BASE_URL}/api/suggestions", data=sugg_payload, headers={"Content-Type": "application/json"})
|
||||
res = urllib.request.urlopen(req)
|
||||
res_data = json.loads(res.read().decode())
|
||||
print("Suggestion result:", res_data)
|
||||
assert res_data["success"] is True
|
||||
|
||||
print("\n--- 2. Admin Login & Approve Question ---")
|
||||
cj = http.cookiejar.CookieJar()
|
||||
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
|
||||
|
||||
login_data = urllib.parse.urlencode({"username": "admin", "password": "Alohomora2026!"}).encode()
|
||||
opener.open(f"{BASE_URL}/admin/login", data=login_data)
|
||||
|
||||
admin_page = opener.open(f"{BASE_URL}/admin").read().decode()
|
||||
assert "Wappentier von Ravenclaw" in admin_page
|
||||
assert "Multiple-Choice" in admin_page
|
||||
print("Admin dashboard shows new Multiple-Choice question.")
|
||||
|
||||
# Find the question ID from DB
|
||||
from app.database import SessionLocal
|
||||
from app.models import Question
|
||||
db = SessionLocal()
|
||||
q = db.query(Question).filter(Question.question.like("%Wappentier von Ravenclaw%")).first()
|
||||
qid = q.id
|
||||
print(f"Found Question ID #{qid}, status={q.status}, type={q.question_type}, options={q.options}")
|
||||
assert q.question_type == "multiple_choice"
|
||||
parsed_opts = json.loads(q.options)
|
||||
assert "Adler" in parsed_opts
|
||||
assert "Rabe" in parsed_opts
|
||||
db.close()
|
||||
|
||||
# Approve question
|
||||
approve_res = opener.open(f"{BASE_URL}/admin/questions/{qid}/approve", data=b"")
|
||||
app_data = json.loads(approve_res.read().decode())
|
||||
print("Approve result:", app_data)
|
||||
assert app_data["success"] is True
|
||||
|
||||
print("\n--- 3. Verify Question Served to Client with Shuffled Options ---")
|
||||
from app.main import format_question_for_client
|
||||
db = SessionLocal()
|
||||
q_updated = db.query(Question).filter(Question.id == qid).first()
|
||||
client_q = format_question_for_client(q_updated, 16, 16)
|
||||
print("Client Question formatted:", client_q)
|
||||
assert client_q["question_type"] == "multiple_choice"
|
||||
assert len(client_q["options"]) == 4
|
||||
assert "Adler" in client_q["options"]
|
||||
assert "canonical_answer" not in client_q
|
||||
db.close()
|
||||
|
||||
print("\n✅ Multiple Choice & Free Text integration tests successfully passed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_mc()
|
||||
@@ -0,0 +1,78 @@
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import http.cookiejar
|
||||
import json
|
||||
|
||||
BASE_URL = "http://127.0.0.1:8001"
|
||||
|
||||
def test_flow():
|
||||
print("--- 1. Test Game Start ---")
|
||||
req = urllib.request.Request(f"{BASE_URL}/api/game/start", data=b"{}", headers={"Content-Type": "application/json"})
|
||||
res = urllib.request.urlopen(req)
|
||||
start_data = json.loads(res.read().decode())
|
||||
print("Start Response:", start_data)
|
||||
session_id = start_data["session_id"]
|
||||
assert start_data["question_number"] == 1
|
||||
assert start_data["score"] == 0
|
||||
assert "canonical_answer" not in start_data
|
||||
|
||||
print("\n--- 2. Test Correct Answer (James) ---")
|
||||
payload = json.dumps({"session_id": session_id, "answer": "James"}).encode()
|
||||
req = urllib.request.Request(f"{BASE_URL}/api/game/check", data=payload, headers={"Content-Type": "application/json"})
|
||||
res = urllib.request.urlopen(req)
|
||||
check_data = json.loads(res.read().decode())
|
||||
print("Check Response:", check_data)
|
||||
assert check_data["correct"] is True
|
||||
assert check_data["score"] == 1
|
||||
assert check_data["next_question"]["question_number"] == 2
|
||||
|
||||
print("\n--- 3. Test Wrong Answer ---")
|
||||
payload = json.dumps({"session_id": session_id, "answer": "Vollkommen falsch"}).encode()
|
||||
req = urllib.request.Request(f"{BASE_URL}/api/game/check", data=payload, headers={"Content-Type": "application/json"})
|
||||
res = urllib.request.urlopen(req)
|
||||
wrong_data = json.loads(res.read().decode())
|
||||
print("Wrong Response:", wrong_data)
|
||||
assert wrong_data["correct"] is False
|
||||
assert wrong_data["attempts_used"] == 1
|
||||
assert wrong_data["attempts_left"] == 2
|
||||
|
||||
print("\n--- 4. Test Question Suggestion (Community) ---")
|
||||
sugg_payload = json.dumps({
|
||||
"frage": "Wie lautet der Tarnname von Sirius Black als Hund?",
|
||||
"antwort": "Tatze",
|
||||
"tipp": "Einer der Rumtreiber",
|
||||
"name": "Remus Lupin"
|
||||
}).encode()
|
||||
req = urllib.request.Request(f"{BASE_URL}/api/suggestions", data=sugg_payload, headers={"Content-Type": "application/json"})
|
||||
res = urllib.request.urlopen(req)
|
||||
sugg_data = json.loads(res.read().decode())
|
||||
print("Suggestion Response:", sugg_data)
|
||||
assert sugg_data["success"] is True
|
||||
|
||||
print("\n--- 5. Test Admin Login & Moderation Flow ---")
|
||||
cj = http.cookiejar.CookieJar()
|
||||
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
|
||||
|
||||
login_data = urllib.parse.urlencode({"username": "admin", "password": "Alohomora2026!"}).encode()
|
||||
login_req = urllib.request.Request(f"{BASE_URL}/admin/login", data=login_data)
|
||||
login_res = opener.open(login_req)
|
||||
assert login_res.getcode() == 200
|
||||
print("Admin logged in successfully.")
|
||||
|
||||
admin_dashboard_res = opener.open(f"{BASE_URL}/admin")
|
||||
content = admin_dashboard_res.read().decode()
|
||||
assert "Großinquisitor-Cockpit" in content
|
||||
assert "Tatze" in content
|
||||
print("Dashboard rendered with pending suggestions.")
|
||||
|
||||
# Test Matcher API
|
||||
test_match_data = urllib.parse.urlencode({"test_input": "Taze", "canonical": "Tatze"}).encode()
|
||||
tm_res = opener.open(f"{BASE_URL}/admin/questions/test-match", data=test_match_data)
|
||||
tm_json = json.loads(tm_res.read().decode())
|
||||
print("Matcher Test ('Taze' vs 'Tatze'):", tm_json)
|
||||
assert tm_json["matched"] is True
|
||||
|
||||
print("\n✅ All Game, Suggestion & Admin Tests Passed Successfully!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_flow()
|
||||
Reference in New Issue
Block a user