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"