137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
import pytest
|
|
import json
|
|
from unittest.mock import patch
|
|
import app as flask_app # Asumsi file utamamu bernama app.py
|
|
|
|
# ==========================================
|
|
# FIXTURE: Setup Lingkungan Testing
|
|
# ==========================================
|
|
@pytest.fixture
|
|
def setup_client():
|
|
# 1. Override nama database ke database khusus testing
|
|
flask_app.DB_CONFIG['database'] = 'db_absensi_test'
|
|
flask_app.init_database()
|
|
|
|
# 2. Buka koneksi dan Seed Data Dummy
|
|
conn = flask_app.get_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
# Seed Mahasiswa
|
|
hashed_pw = flask_app.bcrypt.hashpw(b'Password123', flask_app.bcrypt.gensalt()).decode()
|
|
cur.execute("""
|
|
INSERT INTO mahasiswa (npm, password, nama, jenkel, fakultas, jurusan, semester, device_id)
|
|
VALUES ('202310715297', %s, 'Ariq', 'L', 'Fasilkom', 'Informatika', 6, 'device_sah_01')
|
|
""", (hashed_pw,))
|
|
|
|
# Seed Mata Kuliah
|
|
cur.execute("INSERT INTO mata_kuliah (kode_matkul, nama_matkul, sks, dosen) VALUES ('IF123', 'Integration Testing', 3, 'Tim QA')")
|
|
id_matkul = cur.lastrowid
|
|
|
|
# Seed Jadwal (Diset 00:00 - 23:59 hari ini agar [FIX 6] selalu lolos saat testing kapan pun)
|
|
hari_ini = flask_app.get_hari_indo()
|
|
cur.execute("""
|
|
INSERT INTO jadwal_kelas (id_matkul, hari, jam_mulai, jam_selesai, ruangan, jurusan, semester)
|
|
VALUES (%s, %s, '00:00:00', '23:59:59', 'Lab RPL', 'Informatika', 6)
|
|
""", (id_matkul, hari_ini))
|
|
id_jadwal = cur.lastrowid
|
|
|
|
conn.commit()
|
|
cur.close()
|
|
conn.close()
|
|
|
|
# 3. Jalankan Client Testing
|
|
with flask_app.app.test_client() as client:
|
|
yield client, id_jadwal
|
|
|
|
# 4. Teardown: Bersihkan/Drop database testing setelah selesai
|
|
conn = flask_app.get_db_connection()
|
|
cur = conn.cursor()
|
|
cur.execute("DROP DATABASE db_absensi_test")
|
|
cur.close()
|
|
conn.close()
|
|
|
|
# ==========================================
|
|
# SKENARIO INTEGRATION TESTING
|
|
# ==========================================
|
|
|
|
def test_it001_login_berhasil_dan_jwt_terbit(setup_client):
|
|
client, _ = setup_client
|
|
response = client.post('/api/auth/login', json={
|
|
"npm": "202310715297",
|
|
"password": "Password123",
|
|
"device_id": "device_sah_01"
|
|
})
|
|
data = json.loads(response.data)
|
|
|
|
assert response.status_code == 200
|
|
assert "token" in data['data']
|
|
assert data["message"] == "Login berhasil"
|
|
|
|
def test_it002_login_ditolak_device_berbeda(setup_client):
|
|
client, _ = setup_client
|
|
response = client.post('/api/auth/login', json={
|
|
"npm": "202310715297",
|
|
"password": "Password123",
|
|
"device_id": "device_ilegal_999" # [FIX 3] Memicu device binding error
|
|
})
|
|
|
|
assert response.status_code == 403
|
|
assert "terdaftar di perangkat lain" in json.loads(response.data)["error"]
|
|
|
|
def test_it003_request_location_token_berhasil(setup_client):
|
|
client, _ = setup_client
|
|
|
|
# Login ambil token
|
|
res_login = client.post('/api/auth/login', json={"npm": "202310715297", "password": "Password123", "device_id": "device_sah_01"})
|
|
jwt_token = json.loads(res_login.data)['data']['token']
|
|
|
|
# Koordinat di-offset sedikit dari KAMPUS_LATITUDE agar lolos [FIX 8] (Anomali Koordinat Identik)
|
|
# tetapi tetap masuk dalam radius 500m
|
|
lat_valid = flask_app.KAMPUS_LATITUDE + 0.0001
|
|
lon_valid = flask_app.KAMPUS_LONGITUDE + 0.0001
|
|
|
|
res_loc = client.post('/api/absensi/request-location-token',
|
|
json={"latitude": lat_valid, "longitude": lon_valid},
|
|
headers={'Authorization': f'Bearer {jwt_token}'}
|
|
)
|
|
data_loc = json.loads(res_loc.data)
|
|
|
|
assert res_loc.status_code == 200
|
|
assert "location_token" in data_loc
|
|
assert data_loc["expires_in_seconds"] == 120
|
|
|
|
@patch('app.requests.post') # Mock eksekusi jaringan eksternal (webhook) agar tidak hit API luar
|
|
@patch('app.validasi_foto') # Mock validasi foto agar tidak perlu kirim base64 5KB+ di script test
|
|
def test_it005_e2e_absensi_berhasil(mock_validasi_foto, mock_requests_post, setup_client):
|
|
client, id_jadwal = setup_client
|
|
mock_validasi_foto.return_value = (True, "OK")
|
|
|
|
# 1. Login
|
|
res_login = client.post('/api/auth/login', json={"npm": "202310715297", "password": "Password123", "device_id": "device_sah_01"})
|
|
jwt_token = json.loads(res_login.data)['data']['token']
|
|
|
|
# 2. Minta Location Token [FIX 7]
|
|
lat_valid = flask_app.KAMPUS_LATITUDE + 0.0001
|
|
lon_valid = flask_app.KAMPUS_LONGITUDE + 0.0001
|
|
|
|
res_loc = client.post('/api/absensi/request-location-token',
|
|
json={"latitude": lat_valid, "longitude": lon_valid},
|
|
headers={'Authorization': f'Bearer {jwt_token}'}
|
|
)
|
|
loc_token = json.loads(res_loc.data)['location_token']
|
|
|
|
# 3. Submit Absensi E2E
|
|
absensi_payload = {
|
|
"location_token": loc_token,
|
|
"id_jadwal": id_jadwal,
|
|
"foto_base64": "data:image/jpeg;base64,mocked_base64_string",
|
|
"status": "HADIR"
|
|
}
|
|
|
|
res_submit = client.post('/api/absensi/submit',
|
|
json=absensi_payload,
|
|
headers={'Authorization': f'Bearer {jwt_token}'}
|
|
)
|
|
|
|
assert res_submit.status_code == 201
|
|
assert json.loads(res_submit.data)['message'] == "Absensi berhasil disimpan" |