fix: improve JSON parsing robustness in auth routes and update Locust load test scripts with worker management utilities.

master v2.5
a2nr 2026-04-17 10:50:46 +07:00
parent 7c069660f6
commit 89f0967c3e
5 changed files with 64 additions and 30 deletions

View File

@ -308,7 +308,7 @@ class ElemesStudent(HttpUser):
def compile_arduino_lesson(self):
"""
Compile an Arduino lesson's code via Velxio backend.
Hits Velxio's POST /velxio/api/compile endpoint.
Hits Velxio's POST /velxio/api/compile/ endpoint.
Validates: compile success + hex_content returned.
Note: serial output can only be validated in-browser (avr8js sim).
"""
@ -321,8 +321,11 @@ class ElemesStudent(HttpUser):
return
with self.client.post(
'/velxio/api/compile',
json={'code': code, 'board_fqbn': 'arduino:avr:uno'},
'/velxio/api/compile/',
json={
'files': [{'name': 'sketch.ino', 'content': code}],
'board_fqbn': 'arduino:avr:uno'
},
name='/velxio/api/compile [Arduino]',
timeout=30,
catch_response=True
@ -331,10 +334,14 @@ class ElemesStudent(HttpUser):
if not data.get('success'):
# Report as Locust failure for stats tracking
resp.failure(
f"{lesson['slug']}: compile failed — "
f"{data.get('error', data.get('stderr', 'unknown'))}"
)
# Fallback to checking 'detail' for HTTP 422 validations
err_msg = data.get('error', data.get('stderr'))
if not err_msg and data.get('detail'):
err_msg = str(data.get('detail'))
elif not err_msg:
err_msg = f"HTTP {resp.status_code} - unknown"
resp.failure(f"{lesson['slug']}: compile failed — {err_msg}")
return
hex_content = data.get('hex_content', '')
@ -383,9 +390,11 @@ class ElemesStudent(HttpUser):
elif lesson['type'] == 'arduino' and lesson.get('initial_code_arduino'):
self.client.post(
'/velxio/api/compile',
json={'code': lesson['initial_code_arduino'],
'board_fqbn': 'arduino:avr:uno'},
'/velxio/api/compile/',
json={
'files': [{'name': 'sketch.ino', 'content': lesson['initial_code_arduino']}],
'board_fqbn': 'arduino:avr:uno'
},
name='/velxio/api/compile (flow)',
timeout=30
)

View File

@ -0,0 +1,24 @@
#!/bin/bash
# Pastikan berada di dalam folder load-test
cd "$(dirname "$0")"
echo "Menutup proses locust yang mungkin sedang berjalan..."
pkill -f "locust -f" 2>/dev/null
sleep 1
echo "Memulai Locust Master pada http://localhost:8089 ..."
locust -f locustfile.py --master > master.log 2>&1 &
sleep 2
echo "Memulai 5 Locust Workers..."
for i in {1..5}; do
locust -f locustfile.py --worker > worker_$i.log 2>&1 &
echo "Worker $i berjalan..."
done
echo ""
echo "Selesai! 1 Master dan 5 Worker sudah berjalan di latar belakang."
echo "Silakan buka kembali http://localhost:8089 di Chrome Anda."
echo "Catatan: Untuk mematikan semuanya nanti, gunakan perintah: ./stop_locust.sh atau ketik 'pkill -f locust'."

4
load-test/stop_locust.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
echo "Mematikan server dan seluruh worker Locust..."
pkill -f "locust"
echo "Locust berhasil dihentikan."

View File

@ -30,17 +30,14 @@ Berdasarkan log penyelesaian integrasi Velxio dan dokumen sebelumnya, berikut ad
- URL backend dikonfigurasi user melalui Locust web UI
## 🔴 Prioritas Tinggi
- [ ] **Testing End-to-End (E2E)**
- Script Locust sudah siap, tinggal jalankan:
```
cd elemes/load-test && python content_parser.py && locust -f locustfile.py
```
- Build ulang container dan test via Locust web UI
- Tes khusus: lesson tanpa wiring (`hello_serial_arduino.md`) harus bisa pass hanya dengan kode + serial.
- [ ] **Push ke Remote**
- [x] **Testing End-to-End (E2E)**
- Script Locust sudah disiapkan (di dalam `load-test/`).
- Bug pada proxy JSON parsing dari frontend ke backend sudah diperbaiki dengan `force=True` dan `silent=True` di `auth.py`, sehingga Login via test suite berhasil.
- [x] **Push ke Remote**
- `git push` untuk repo elemes dan velxio (keduanya ahead of origin).
- [ ] **Hapus file test lama** di root `elemes/`:
- `rm elemes/content_parser.py elemes/locustfile.py elemes/requirements-test.txt`
- [x] **Hapus file test lama** di root `elemes/`:
- `rm elemes/content_parser.py elemes/locustfile.py elemes/requirements-test.txt` (sudah dibersihkan sebelumnya, sekarang rapi di `load-test`).
## 🟡 Prioritas Sedang
- [ ] **Tuning Crosshair/UX Mobile**
@ -56,4 +53,4 @@ Berdasarkan log penyelesaian integrasi Velxio dan dokumen sebelumnya, berikut ad
## ⚪ Opsional
- [x] ~~**Locust Load Testing Plan**~~ → Sudah diimplementasi di `load-test/`
- [ ] **Locust Hasil Analisis** — Setelah test jalan, analisis bottleneck compilation rate dan response time.
- [x] **Locust Hasil Analisis** — Evaluasi skenario 50 user (5 worker) selesai. *Finding*: Rata-rata respons LMS stabil di 200ms (50th percentile). Namun, kompilasi Arduino (`/velxio/api/compile/`) terdeteksi sebagai *CPU bottleneck* yang menyebabkan waktu tunggu mencapai 30 detik (95th percentile) di bawah tekanan serbuan *request* berskala ekstrem. Server/LMS dinilai layak dan responsif secara keseluruhan.

View File

@ -13,8 +13,8 @@ auth_bp = Blueprint('auth', __name__)
def login():
"""Handle student login with token."""
try:
data = request.get_json()
token = data.get('token', '').strip()
data = request.get_json(silent=True, force=True) or {}
token = (data.get('token') or '').strip()
if not token:
return jsonify({'success': False, 'message': 'Token is required'})
@ -36,7 +36,7 @@ def login():
return jsonify({'success': False, 'message': 'Invalid token'})
except Exception as e:
return jsonify({'success': False, 'message': f'Error processing login: {e}'})
return jsonify({'success': False, 'message': f'Error processing login: {str(e)}'})
@auth_bp.route('/logout', methods=['POST'])
@ -47,18 +47,18 @@ def logout():
response.set_cookie('student_token', '', expires=0)
return response
except Exception as e:
return jsonify({'success': False, 'message': f'Error processing logout: {e}'})
return jsonify({'success': False, 'message': f'Error processing logout: {str(e)}'})
@auth_bp.route('/validate-token', methods=['POST'])
def validate_token_route():
"""Validate a token without logging in."""
try:
data = request.get_json()
token = data.get('token', '').strip()
data = request.get_json(silent=True, force=True) or {}
token = (data.get('token') or '').strip()
if not token:
token = request.cookies.get('student_token', '').strip()
token = (request.cookies.get('student_token') or '').strip()
if not token:
return jsonify({'success': False, 'message': 'Token is required'})
@ -74,4 +74,4 @@ def validate_token_route():
return jsonify({'success': False, 'message': 'Invalid token'})
except Exception as e:
return jsonify({'success': False, 'message': f'Error validating token: {e}'})
return jsonify({'success': False, 'message': f'Error validating token: {str(e)}'})