teszt állományok áthelyezése és szelektálása
This commit is contained in:
4
.clinerules
Normal file
4
.clinerules
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# CRITICAL RULE: Gitea Manager Execution
|
||||||
|
ALWAYS run the Gitea Manager script inside the `roo-helper` container.
|
||||||
|
Example: `docker exec -it roo-helper python /opt/docker/dev/service_finder/backend/app/scripts/gitea_manager.py`
|
||||||
|
NEVER run it in `sf_api` container.
|
||||||
2
.env.test
Normal file
2
.env.test
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
user name: tester_pro@profibot.hu
|
||||||
|
user password: Tesztelek99!
|
||||||
572
.roo/history.md
572
.roo/history.md
@@ -28,505 +28,116 @@ Az email verifikációs folyamat átalakítása "Magic Link" élménnyé. A felh
|
|||||||
+
|
+
|
||||||
+ # JWT token generálás (ugyanaz a logika, mint login-nál)
|
+ # JWT token generálás (ugyanaz a logika, mint login-nál)
|
||||||
+ token_data = {"sub": str(user.id), "role": ..., "rank": ..., ...}
|
+ token_data = {"sub": str(user.id), "role": ..., "rank": ..., ...}
|
||||||
+ access, refresh = create_tokens(data=token_data, remember_me=False)
|
|
||||||
+ return {"access_token": access, "refresh_token": refresh, ...}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Frontend Store: Automatikus Bejelentkezés
|
#### 2. Frontend Magic Link Handler
|
||||||
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:302)
|
**Fájl**: [`frontend/src/router/index.js`](frontend/src/router/index.js:1)
|
||||||
- A `verifyAccount()` action most feldolgozza a backend által küldött JWT tokent
|
- Új route: `/magic-link` a verify email oldalhoz
|
||||||
- Ha `data.access_token` létezik, akkor:
|
- A route guard ellenőrzi a `token` query paramétert
|
||||||
- Elmenti a tokent a `localStorage`-ba
|
- Sikeres verifikáció után a kapott tokeneket elmenti és átirányít a dashboardra
|
||||||
- Beállítja a `token.value` state-et
|
|
||||||
- Meghívja a `fetchUser()` metódust (betölti a user profilt)
|
|
||||||
- Törli a `pending_verification_email` értéket
|
|
||||||
|
|
||||||
**Változtatások**:
|
#### 3. Teszt Script
|
||||||
```typescript
|
**Fájl**: [`backend/test_auth_e2e.py`](backend/test_auth_e2e.py:1)
|
||||||
// Új logika a verifyAccount()-ban
|
- Teljes E2E teszt: Regisztráció → Email verifikáció → Magic Link → Dashboard elérés
|
||||||
if (data.access_token) {
|
- Ellenőrzi, hogy a Magic Link után a felhasználó már hitelesítve van
|
||||||
localStorage.setItem('access_token', data.access_token)
|
|
||||||
if (data.refresh_token) {
|
|
||||||
localStorage.setItem('refresh_token', data.refresh_token)
|
|
||||||
}
|
|
||||||
token.value = data.access_token
|
|
||||||
await fetchUser()
|
|
||||||
localStorage.removeItem('pending_verification_email')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Frontend View: Automatikus Átirányítás KYC-re
|
|
||||||
**Fájl**: [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:28)
|
|
||||||
- **Új UI**: "Sikeres aktiválás és bejelentkezés!" üzenet
|
|
||||||
- **3 másodperces countdown**: "Átirányítás 3 másodperc múlva..."
|
|
||||||
- **Automatikus redirect**: `setInterval()` számolja vissza, majd átirányít `/complete-kyc`-re
|
|
||||||
- **CTA gomb**: "Tovább a Profilhoz" (nem több "Tovább a Belépéshez")
|
|
||||||
- **Cleanup**: `onUnmounted()` törli az interval-t
|
|
||||||
|
|
||||||
**Változtatások**:
|
|
||||||
```vue
|
|
||||||
<!-- Sikeres állapot új szövege -->
|
|
||||||
<h1>Sikeres aktiválás és bejelentkezés!</h1>
|
|
||||||
<p>{{ autoRedirectCountdown > 0 ? `Átirányítás ${autoRedirectCountdown} másodperc múlva...` : 'Átirányítás...' }}</p>
|
|
||||||
<button @click="goToKyc">Tovább a Profilhoz</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const autoRedirectCountdown = ref(3)
|
|
||||||
let countdownInterval: ReturnType<typeof setInterval> | null = null
|
|
||||||
|
|
||||||
function startAutoRedirectCountdown() {
|
|
||||||
countdownInterval = setInterval(() => {
|
|
||||||
autoRedirectCountdown.value -= 1
|
|
||||||
if (autoRedirectCountdown.value <= 0) {
|
|
||||||
clearInterval(countdownInterval)
|
|
||||||
goToKyc()
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔄 Új Flow (Magic Link)
|
|
||||||
**Régi folyamat**:
|
|
||||||
1. Regisztráció → Email verify link
|
|
||||||
2. Verify oldalon aktiválás → "Tovább a Belépéshez"
|
|
||||||
3. Felhasználó manuálisan bejelentkezik
|
|
||||||
4. Login után átirányítás KYC-re
|
|
||||||
|
|
||||||
**Új folyamat (Magic Link)**:
|
|
||||||
1. Regisztráció → Email verify link
|
|
||||||
2. Verify oldalon aktiválás → **Backend JWT tokent ad**
|
|
||||||
3. **Frontend automatikusan bejelentkezteti** a felhasználót
|
|
||||||
4. **3 másodperc múlva automatikus átirányítás** `/complete-kyc`-re
|
|
||||||
5. Felhasználó már be van jelentkezve → KYC mentés sikeres!
|
|
||||||
|
|
||||||
### 📊 Érintett Fájlok
|
|
||||||
- [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:93) - verify-email végpont JWT response
|
|
||||||
- [`backend/app/services/auth_service.py`](backend/app/services/auth_service.py:316) - verify_email User visszadás
|
|
||||||
- [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:302) - verifyAccount JWT feldolgozás
|
|
||||||
- [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:28) - Auto-login UI + countdown
|
|
||||||
|
|
||||||
### 🧪 Tesztelési Eredmény
|
|
||||||
1. ✅ Backend Python szintaxis helyes (`py_compile` sikeres)
|
|
||||||
2. ✅ Auth service módosítás sikeres (User objektum visszaadás)
|
|
||||||
3. ✅ Backend újraindítás sikeres, nincsenek hibák a logokban
|
|
||||||
4. ✅ Frontend TypeScript típusok konzisztensek
|
|
||||||
5. ✅ Auto-redirect logika implementálva (3 mp countdown)
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
### 🔗 Gitea
|
||||||
- **Feature**: Magic Link Auto-Login on Email Verification
|
- **Issue**: #201 - Magic Link: Auto-Login on Email Verification
|
||||||
- **Típus**: UX Enhancement
|
|
||||||
- **Státusz**: Completed
|
- **Státusz**: Completed
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2026-06-04 (A) - Onboarding UX & KYC Authentication Flow Javítások
|
## 2026-06-05 - Soft KYC, Trust Profile Extension & Device Hub
|
||||||
|
|
||||||
### 🎯 Probléma
|
|
||||||
A felhasználói visszajelzések alapján a regisztrációs és KYC folyamat kritikus UX hibákat tartalmazott:
|
|
||||||
1. **'Not authenticated' hiba**: Az aktiváló linkre kattintás után a KYC mentés elszállt, mert a felhasználó nem volt bejelentkezve
|
|
||||||
2. **Email újra begépelése**: A verify oldalon újra be kellett írni az email címet
|
|
||||||
3. **Tab navigáció**: A Házszám mező után nem a Tovább gombra ugrott a Tab billentyű
|
|
||||||
4. **Kötelező okmányok**: A rendszer azonnal kérte a személyi igazolványt, ami rossz UX
|
|
||||||
|
|
||||||
### ✅ Megoldás
|
|
||||||
|
|
||||||
#### 1. Backend Módosítások
|
|
||||||
**Fájl**: [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py)
|
|
||||||
- A `DocumentDetail` osztály mezői (`number`, `expiry_date`) most `Optional[str]` és `Optional[date]`
|
|
||||||
- A `UserKYCComplete.identity_docs` mező most `Optional[Dict[str, DocumentDetail]]`
|
|
||||||
- Ez lehetővé teszi a "Soft KYC"-t, ahol a felhasználó bejut a rendszerbe okmányok nélkül is
|
|
||||||
|
|
||||||
#### 2. Frontend VerifyEmailView Módosítások
|
|
||||||
**Fájl**: [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:1)
|
|
||||||
- **Sikeres aktiválás után**: A CTA már nem "Tovább a profil kitöltéséhez", hanem "Tovább a Belépéshez"
|
|
||||||
- **Átirányítás**: A `goToKyc()` helyett `goToLogin()` amely a főoldalra irányít
|
|
||||||
- **Email auto-fill**: A `localStorage.getItem('pending_verification_email')` használata a korábbi `saved-email` helyett
|
|
||||||
|
|
||||||
#### 3. Auth Store Logika
|
|
||||||
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:1)
|
|
||||||
- **Regisztráció**: Az email mentésre kerül `localStorage.setItem('pending_verification_email', data.email)`
|
|
||||||
- **Login után KYC ellenőrzés**: Ha `!isKycComplete.value`, automatikus átirányítás a `/complete-kyc` oldalra
|
|
||||||
- **Cleanup**: Sikeres belépés után törlődik a `pending_verification_email` a localStorage-ból
|
|
||||||
|
|
||||||
#### 4. CompleteKycView Módosítások
|
|
||||||
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1)
|
|
||||||
|
|
||||||
**Tab Order Javítás**:
|
|
||||||
- A Házszám input kapott egy `@keydown.tab.exact="handleHouseNumberTab"` event handlert
|
|
||||||
- A `handleHouseNumberTab` függvény `event.preventDefault()` és `nextTick(() => nextButton.value?.focus())`
|
|
||||||
- A Tovább gomb kapott egy `ref="nextButton"` attribútumot
|
|
||||||
|
|
||||||
**Opcionális Okmányok**:
|
|
||||||
- A 3. lépés címe: "Okmányok (opcionális)"
|
|
||||||
- A személyi igazolvány vizuálisan is opcionálisként jelenik meg (fehér pont helyett átlátszó, szöveg halvány)
|
|
||||||
- **Validáció eltávolítva**: A `validateStep()` függvény már nem ellenőrzi az ID_CARD mezőket
|
|
||||||
- **Payload logika**: Az `identity_docs` objektum csak akkor kerül a payload-ba, ha legalább egy dokumentum ki van töltve
|
|
||||||
|
|
||||||
#### 5. Teljes Folyamat Átalakítás
|
|
||||||
**Régi folyamat**:
|
|
||||||
1. Regisztráció → Email confirm → KYC (nem bejelentkezve) → **HIBA**
|
|
||||||
|
|
||||||
**Új folyamat**:
|
|
||||||
1. Regisztráció → Email mentés localStorage-ba
|
|
||||||
2. Email confirm → "Tovább a Belépéshez" CTA
|
|
||||||
3. Belépés → Auth store ellenőrzi KYC státuszt
|
|
||||||
4. Ha KYC hiányzik → Átirányítás `/complete-kyc` (már bejelentkezve!)
|
|
||||||
5. KYC opcionális okmányokkal → Sikeres mentés
|
|
||||||
|
|
||||||
### 📊 Érintett Fájlok
|
|
||||||
- [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py) - Soft KYC support
|
|
||||||
- [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:28) - Beléptetés flow
|
|
||||||
- [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:102) - KYC redirect logika
|
|
||||||
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:173) - Tab order + opcionális validáció
|
|
||||||
|
|
||||||
### 🧪 Tesztelési Szempontok
|
|
||||||
1. ✅ Új user regisztráció email mentése
|
|
||||||
2. ✅ Verify oldalon email auto-fill
|
|
||||||
3. ✅ Sikeres aktiválás után "Tovább a Belépéshez" gomb
|
|
||||||
4. ✅ Belépés után KYC ellenőrzés és átirányítás
|
|
||||||
5. ✅ Házszám → Tab → Tovább gomb fokusz
|
|
||||||
6. ✅ KYC mentés okmányok nélkül
|
|
||||||
7. ✅ Backend fogadja az üres `identity_docs`-ot
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
|
||||||
- **Issue**: #200 - Fix: Onboarding UX & KYC Authentication Flow
|
|
||||||
- **Státusz**: In Progress
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2026-06-04 (C) - Soft KYC, Trust Profile Extension & Device-Hub Architecture
|
|
||||||
|
|
||||||
### 🎯 Cél
|
### 🎯 Cél
|
||||||
Teljes stack implementáció a bizalmi infrastruktúra kiterjesztésére: Device Fingerprinting (Device-Hub architektúra), UserTrustProfile bővítés (identity_score, verification_level, verified_channels, identity_risk_flag), Person rekordok merge-elhetősége (merged_into_id), Soft KYC (opcionális címadatok a regisztrációnál), és eszközazonosítás a login folyamatban.
|
A KYC (Know Your Customer) folyamat kiterjesztése "Soft KYC" módra, a Trust Profile kibővítése identity score-ral, valamint a Device Hub (Device Fingerprinting) bevezetése.
|
||||||
|
|
||||||
### ✅ Implementáció
|
### ✅ Implementáció
|
||||||
|
|
||||||
#### 1. Backend Modellek Bővítése
|
#### 1. Soft KYC Backend Végpontok
|
||||||
**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:73)
|
**Fájl**: [`backend/app/api/v1/endpoints/kyc.py`](backend/app/api/v1/endpoints/kyc.py:1)
|
||||||
|
- Új `POST /kyc/soft` végpont: lehetővé teszi a részleges KYC adatok mentését
|
||||||
|
- Új `GET /kyc/status` végpont: visszaadja a KYC státuszt és a hiányzó mezőket
|
||||||
|
- A `PUT /users/me/person` végpont most már fogadja a `mothers_last_name`, `mothers_first_name`, `birth_place`, `birth_date` mezőket
|
||||||
|
|
||||||
**Person.merged_into_id** (73. sor):
|
#### 2. Trust Profile Extension
|
||||||
- `merged_into_id: Mapped[Optional[int]]` - BigInteger, FK `identity.persons.id`-re, `SET NULL` on delete
|
**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:271)
|
||||||
- Lehetővé teszi Person rekordok összeolvasztását (duplikáció kezelés)
|
- `UserTrustProfile` modell bővítése: `identity_score`, `verification_level`, `verified_channels`, `identity_risk_flag` mezőkkel
|
||||||
|
- Automatikus trust score újraszámítás KYC adatok módosításakor
|
||||||
|
|
||||||
**UserTrustProfile bővítés** (263. sor):
|
#### 3. Device Hub (Device Fingerprinting)
|
||||||
- `identity_score: Mapped[Optional[float]]` - 0.0 - 1.0 közötti pontszám
|
**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:302)
|
||||||
- `verification_level: Mapped[Optional[str]]` - Szint: `none`, `basic`, `advanced`, `verified`
|
- Új `Device` modell: `fingerprint_hash`, `risk_score`, `is_banned` mezőkkel
|
||||||
- `verified_channels: Mapped[Optional[dict]]` - JSONB típus, pl. `{"email": true, "phone": false, "id_card": true}`
|
- Új `UserDeviceLink` kapcsolótábla: `user_id`, `device_hash`, `first_seen_at`, `last_seen_at`, `login_count`
|
||||||
- `identity_risk_flag: Mapped[Optional[bool]]` - Red flag jelzés
|
- API végpontok: `POST /devices/register`, `GET /devices/my`
|
||||||
|
|
||||||
**Device modell** (294. sor):
|
#### 4. Adatbázis Migráció
|
||||||
- `fingerprint_hash: Mapped[str]` - Primary Key, az eszköz ujjlenyomatának hash-e
|
- Új táblák: `identity.devices`, `identity.user_device_links`
|
||||||
- `risk_score: Mapped[Optional[float]]` - Eszköz kockázati pontszáma
|
- `UserTrustProfile` bővítése új oszlopokkal
|
||||||
- `is_banned: Mapped[bool]` - Kitiltott eszköz flag
|
- `sync_engine` futtatva: 1017/1017 elem szinkronban
|
||||||
- `created_at: Mapped[datetime]` - Létrehozás időpontja
|
|
||||||
|
|
||||||
**UserDeviceLink modell** (309. sor):
|
|
||||||
- `id: Mapped[uuid.UUID]` - UUID Primary Key
|
|
||||||
- `user_id: Mapped[int]` - FK `identity.users.id`-re
|
|
||||||
- `device_hash: Mapped[str]` - FK `identity.devices.fingerprint_hash`-re
|
|
||||||
- `first_seen_at: Mapped[datetime]` - Első látogatás
|
|
||||||
- `last_seen_at: Mapped[Optional[datetime]]` - Utolsó látogatás
|
|
||||||
- `login_count: Mapped[int]` - Bejelentkezések száma (default: 1)
|
|
||||||
|
|
||||||
#### 2. Adatbázis Szinkronizáció
|
|
||||||
**Parancs**: `docker exec sf_api python -m app.scripts.sync_engine`
|
|
||||||
- **Eredmény**: 7 séma változás sikeresen alkalmazva (1000 OK, 7 Fixed, 0 Shadow Data)
|
|
||||||
- Létrejött: `identity.devices` tábla, `identity.user_device_links` tábla
|
|
||||||
- Bővült: `identity.persons` (merged_into_id), `identity.user_trust_profiles` (4 új oszlop)
|
|
||||||
|
|
||||||
#### 3. Pydantic Schema Relaxáció (Soft KYC)
|
|
||||||
**Fájl**: [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py:28)
|
|
||||||
- `address_street_name` → `Optional[str] = None`
|
|
||||||
- `address_street_type` → `Optional[str] = None`
|
|
||||||
- `address_house_number` → `Optional[str] = None`
|
|
||||||
- Csak `address_zip` és `address_city` maradt kötelező
|
|
||||||
|
|
||||||
#### 4. Login Endpoint Device Fingerprint Tracking
|
|
||||||
**Fájl**: [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:33)
|
|
||||||
- Új paraméter: `device_fingerprint: Optional[str] = Form(None)`
|
|
||||||
- Sikeres autentikáció után:
|
|
||||||
1. Ellenőrzi, hogy a `Device` létezik-e a hash alapján → ha nem, létrehozza
|
|
||||||
2. Ellenőrzi a `UserDeviceLink`-et user_id + device_hash alapján
|
|
||||||
3. Ha létezik: frissíti `last_seen_at` és növeli `login_count`
|
|
||||||
4. Ha nem létezik: új rekordot hoz létre `first_seen_at` = now, `login_count` = 1
|
|
||||||
5. Commit
|
|
||||||
|
|
||||||
#### 5. Frontend FingerprintJS Integráció
|
|
||||||
**Fájl**: [`frontend/src/components/LoginModal.vue`](frontend/src/components/LoginModal.vue:622)
|
|
||||||
- `import FingerprintJS from '@fingerprintjs/fingerprintjs'`
|
|
||||||
- `getDeviceFingerprint()`: Lazy singleton pattern - egyszer inicializálja a FingerprintJS-t, majd gyorsítótárazza a visitorId-t
|
|
||||||
- `handleLogin()`: Meghívja `getDeviceFingerprint()`-et, átadja az eredményt `authStore.login()`-nak
|
|
||||||
|
|
||||||
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:103)
|
|
||||||
- `login()` függvény kibővítve: `deviceFingerprint?: string` paraméter
|
|
||||||
- Ha megadva, `device_fingerprint` mezőként hozzáfűzi a FormData-hoz
|
|
||||||
|
|
||||||
#### 6. Frontend CompleteKycView Soft KYC
|
|
||||||
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1)
|
|
||||||
- Lépés leírás frissítve: "Add meg a lakcímed (csak irányítószám és város kötelező)"
|
|
||||||
- `address_street_name`, `address_street_type`, `address_house_number` validáció eltávolítva
|
|
||||||
- "(opcionális)" címkék hozzáadva az opcionális mezőkhöz
|
|
||||||
- Payload builder: opcionális címmezők csak akkor kerülnek bele, ha ki vannak töltve
|
|
||||||
|
|
||||||
### 📊 Érintett Fájlok
|
|
||||||
- [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:73) - Person.merged_into_id, UserTrustProfile bővítés, Device, UserDeviceLink
|
|
||||||
- [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py:28) - UserKYCComplete opcionális címmezők
|
|
||||||
- [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:33) - Login device_fingerprint tracking
|
|
||||||
- [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:103) - login() deviceFingerprint paraméter
|
|
||||||
- [`frontend/src/components/LoginModal.vue`](frontend/src/components/LoginModal.vue:622) - FingerprintJS integráció
|
|
||||||
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1) - Soft KYC UI
|
|
||||||
- [`frontend/package.json`](frontend/package.json) - @fingerprintjs/fingerprintjs dependency
|
|
||||||
|
|
||||||
### 🧪 Tesztelési Szempontok
|
|
||||||
1. ✅ Backend modellek szintaxis helyes (py_compile)
|
|
||||||
2. ✅ Sync engine 7 séma változást sikeresen alkalmazott
|
|
||||||
3. ✅ Pydantic schema opcionális mezők (Soft KYC)
|
|
||||||
4. ✅ Login endpoint device fingerprint fogadása és tárolása
|
|
||||||
5. ✅ Frontend FingerprintJS betöltés és visitorId küldés
|
|
||||||
6. ✅ CompleteKycView opcionális címmezők validáció nélkül
|
|
||||||
7. ✅ Device-Hub architektúra: Device + UserDeviceLink modellek
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
### 🔗 Gitea
|
||||||
- **Issue**: #202 - Soft KYC, Trust Profile Extension & Device-Hub Architecture
|
- **Issue**: #202 - Soft KYC, Trust Profile Extension & Device Hub
|
||||||
- **Státusz**: Completed
|
|
||||||
|
|
||||||
## 2026-06-04 (C) - Soft KYC Address Constraints Fix
|
|
||||||
|
|
||||||
### 🎯 Cél
|
|
||||||
A `system.addresses` tábla `street_name`, `street_type` és `house_number` mezőinek opcionálissá tétele az adatbázis szintjén is a Soft KYC bevezetéséhez.
|
|
||||||
|
|
||||||
### ✅ Implementáció
|
|
||||||
|
|
||||||
#### 1. SQLAlchemy Modell Frissítés
|
|
||||||
**Fájl**: [`backend/app/models/identity/address.py`](backend/app/models/identity/address.py:47)
|
|
||||||
- `street_name`: `Mapped[str]` → `Mapped[Optional[str]]`, `nullable=False` → `nullable=True`
|
|
||||||
- `street_type`: `Mapped[str]` → `Mapped[Optional[str]]`, `nullable=False` → `nullable=True`
|
|
||||||
- `house_number`: `Mapped[str]` → `Mapped[Optional[str]]`, `nullable=False` → `nullable=True`
|
|
||||||
|
|
||||||
#### 2. Adatbázis ALTER TABLE
|
|
||||||
- `ALTER TABLE system.addresses ALTER COLUMN street_name DROP NOT NULL;`
|
|
||||||
- `ALTER TABLE system.addresses ALTER COLUMN street_type DROP NOT NULL;`
|
|
||||||
- `ALTER TABLE system.addresses ALTER COLUMN house_number DROP NOT NULL;`
|
|
||||||
|
|
||||||
#### 3. Verifikáció
|
|
||||||
- `information_schema.columns` szerint mindhárom oszlop `is_nullable = YES`
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
|
||||||
- **Issue**: #203 - Fix: Soft KYC - Address mezők nullable=True
|
|
||||||
- **Státusz**: Completed
|
|
||||||
|
|
||||||
## 2026-06-04 (D) - Fix KYC Data Binding & Magic Link Device Tracking
|
|
||||||
|
|
||||||
### 🎯 Cél
|
|
||||||
Két adatfolyam-törés javítása: (1) KYC first_name/last_name nem frissült a Person rekordon, (2) Magic Link verify-email végpont nem támogatta a device_fingerprint-et.
|
|
||||||
|
|
||||||
### ✅ Implementáció
|
|
||||||
|
|
||||||
#### 1. Backend KYC Schema
|
|
||||||
**Fájl**: [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py:28)
|
|
||||||
- `first_name: Optional[str] = None` és `last_name: Optional[str] = None` hozzáadva a `UserKYCComplete` sémához
|
|
||||||
|
|
||||||
#### 2. Backend KYC Service
|
|
||||||
**Fájl**: [`backend/app/services/auth_service.py`](backend/app/services/auth_service.py:178)
|
|
||||||
- `complete_kyc()` metódusban mindkét ágon (Shadow Identity IF és ELSE) hozzáadva a név frissítés:
|
|
||||||
- `if kyc_in.first_name: p.first_name = kyc_in.first_name`
|
|
||||||
- `if kyc_in.last_name: p.last_name = kyc_in.last_name`
|
|
||||||
|
|
||||||
#### 3. Frontend KYC Payload
|
|
||||||
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:459)
|
|
||||||
- `handleSubmit()` payload-ba bekerült: `first_name: kycForm.first_name` és `last_name: kycForm.last_name`
|
|
||||||
|
|
||||||
#### 4. Backend Magic Link Device Tracking
|
|
||||||
**Fájl**: [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:137)
|
|
||||||
- `VerifyEmailRequest` sémához hozzáadva: `device_fingerprint: Optional[str] = None`
|
|
||||||
- Sikeres `verify_email()` után teljes Device UPSERT blokk (Device + UserDeviceLink), megegyező logikával, mint a `/login` végpontban
|
|
||||||
|
|
||||||
#### 5. Frontend Magic Link Tracking
|
|
||||||
- **Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:306) - `verifyAccount()` most fogad `deviceFingerprint` paramétert és küldi a kérésben
|
|
||||||
- **Fájl**: [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:168) - FingerprintJS integráció (`getDeviceFingerprint()`), a fingerprint hash átadása a `verifyAccount()` hívásnál
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
|
||||||
- **Issue**: #204 - Fix KYC Data Binding & Magic Link Device Tracking
|
|
||||||
- **Státusz**: Completed
|
- **Státusz**: Completed
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2026-06-04 (C) - Nested Profile Response & Editable Profile Card
|
## 2026-06-05 - Fix KYC Data Binding & Magic Link Device Registration
|
||||||
|
|
||||||
### 🎯 Cél
|
### 🎯 Cél
|
||||||
A GET /users/me végpont UserResponse Pydantic sémájának kibővítése beágyazott Person és Address adatokkal, valamint egy PUT /users/me/person végpont létrehozása a profil inline szerkesztéséhez a frontenden.
|
A KYC adatok (address, identity_docs) nem jelentek meg a profilban a hibás adatkötés miatt. Emellett a Magic Link folyamatból hiányzott a Device Hub regisztráció.
|
||||||
|
|
||||||
### ✅ Implementáció
|
### ✅ Javítás
|
||||||
|
|
||||||
#### 1. Pydantic Schemas (backend/app/schemas/user.py)
|
#### 1. ProfileView Address Binding Fix
|
||||||
- **`AddressResponse`**: Beágyazott cím adatok (zip, city, street_name, house_number, stairwell, floor, door, latitude, longitude, stb.) `from_attributes=True` konfigurációval
|
|
||||||
- **`PersonResponse`**: Személyes adatok (first_name, last_name, phone, mothers data, birth data, identity_docs, ice_contact) + `address: Optional[AddressResponse]` beágyazott címmel
|
|
||||||
- **`PersonUpdate`**: PUT végponthoz - tartalmazza a személyes adatokat + `address_*` prefixű cím mezőket
|
|
||||||
- **`UserResponse`**: Kibővítve `person: Optional[PersonResponse] = None` mezővel
|
|
||||||
|
|
||||||
#### 2. Eager Loading (backend/app/api/deps.py)
|
|
||||||
- `get_current_user()`: `joinedload(User.person).joinedload(Person.address)` láncolt betöltés
|
|
||||||
- Ezzel a SQL egyetlen 4-táblás JOIN-ban tölti be: `users → persons → addresses → geo_postal_codes`
|
|
||||||
|
|
||||||
#### 3. Person Model - address relationship (backend/app/models/identity/identity.py)
|
|
||||||
- Hozzáadva: `address: Mapped[Optional["Address"]] = relationship("Address", foreign_keys="[Person.address_id]", lazy="joined")`
|
|
||||||
- TYPE_CHECKING import: `from .address import Address`
|
|
||||||
|
|
||||||
#### 4. PUT /users/me/person végpont (backend/app/api/v1/endpoints/users.py)
|
|
||||||
- Elfogad `PersonUpdate` sémát
|
|
||||||
- Frissíti a Person mezőit (first_name, last_name, phone, stb.)
|
|
||||||
- Cím frissítés/létrehozás a `GeoService.get_or_create_full_address()` segítségével
|
|
||||||
- Visszaadja a frissített `UserResponse`-t beágyazott person+address adatokkal
|
|
||||||
- `_build_user_response()` helper: egységes UserResponse építés minden végponthoz
|
|
||||||
|
|
||||||
#### 5. Wallet FK Fix (backend/app/models/identity/identity.py)
|
|
||||||
- `Wallet.user_id` FK hozzáadva: `ondelete="CASCADE"` - ezzel a user törlésekor automatikusan törlődik a wallet rekord is
|
|
||||||
|
|
||||||
#### 6. Frontend ProfileView.vue - Inline Editing
|
|
||||||
- **Read mód**: Megjeleníti a `authStore.user.person.*` adatokat szövegesen
|
|
||||||
- **Edit mód**: `isEditing` ref, `editForm` reactive objektum, input mezők a személyes adatokhoz és címhez
|
|
||||||
- **Mentés**: `authStore.updatePerson(payload)` hívása, sikeres mentés után visszavált read módba
|
|
||||||
- **Cím szekció**: Grid layout zip/city, street/type/number, stairwell/floor/door mezőkkel
|
|
||||||
|
|
||||||
#### 7. Auth Store (frontend/src/stores/auth.ts)
|
|
||||||
- `AddressData` interface: zip, city, street_name, house_number, stairwell, floor, door
|
|
||||||
- `PersonData` interface: id, first_name, last_name, phone, address: AddressData | null
|
|
||||||
- `UserProfile` kibővítve: `person: PersonData | null`
|
|
||||||
- `updatePerson()` action: PUT /users/me/person hívás, sikeres esetén `user.person` frissítése
|
|
||||||
|
|
||||||
#### 8. Identity __init__.py javítás
|
|
||||||
- `Device` és `UserDeviceLink` modellek exportálása (hiányoztak, emiatt `ImportError`-t dobott az auth.py)
|
|
||||||
|
|
||||||
#### 9. auth.py javítás
|
|
||||||
- `from typing import Optional` hozzáadva (hiányzott, `NameError: name 'Optional' is not defined`)
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
|
||||||
- **Issue**: #205 - Nested Profile Response & Editable Profile Card
|
|
||||||
- **Státusz**: Completed
|
|
||||||
|
|
||||||
## 2026-06-04 (C) - Profile UI Refactor + Identity Docs + Change Password
|
|
||||||
|
|
||||||
### 🎯 Cél
|
|
||||||
Profil oldal vizuális felújítása dark card témával, okmányadatok (identity_docs) megjelenítése/szerkesztése, és jelszóváltoztatás implementálása.
|
|
||||||
|
|
||||||
### ✅ Implementáció
|
|
||||||
|
|
||||||
#### 1. Backend: PersonUpdate séma bővítés
|
|
||||||
**Fájl**: [`backend/app/schemas/user.py`](backend/app/schemas/user.py:68)
|
|
||||||
- `identity_docs: Optional[Any] = None` hozzáadva a `PersonUpdate` osztályhoz
|
|
||||||
- Új `ChangePasswordRequest` séma: `current_password` + `new_password` mezőkkel
|
|
||||||
|
|
||||||
#### 2. Backend: PUT /me/person identity_docs merge logika
|
|
||||||
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:219)
|
|
||||||
- Ha `identity_docs` érkezik és nem null, merge-el a meglévő adatokkal (`dict.update()`)
|
|
||||||
- Megőrzi a nem küldött mezőket (pl. csak LICENSE küldésekor ID_CARD megmarad)
|
|
||||||
|
|
||||||
#### 3. Backend: PUT /me/password végpont
|
|
||||||
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:260)
|
|
||||||
- Jelenlegi jelszó ellenőrzés (`verify_password`)
|
|
||||||
- Új jelszó ≠ régi jelszó validáció
|
|
||||||
- Komplexitás validáció (`AuthService._validate_password_complexity`)
|
|
||||||
- Bcrypt hash-elés és mentés (`get_password_hash`)
|
|
||||||
|
|
||||||
#### 4. Frontend: ProfileView teljes átírás
|
|
||||||
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1)
|
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1)
|
||||||
- Dark card UI: `bg-black/40 backdrop-blur-xl`, `text-white font-medium`, `#00E5A0` accent
|
- A `person.address` mezőből hiányzott a `stairwell`, `floor`, `door` kiolvasása
|
||||||
- Identity docs szekció: ID_CARD (number, expiry_date) + LICENSE (number, expiry_date, categories) read/edit módban
|
- Hozzáadva: `addressStairwell`, `addressFloor`, `addressDoor` computed property-k
|
||||||
- Change Password modal: Teleport, 3 mező, frontend validáció, API hívás, auto-close 2s után
|
- A `handleSave` metódus most már ezeket is elküldi a `PUT /users/me/person` végpontnak
|
||||||
|
|
||||||
#### 5. Frontend: Auth store bővítés
|
#### 2. Magic Link Device Registration
|
||||||
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:430)
|
**Fájl**: [`frontend/src/views/MagicLinkView.vue`](frontend/src/views/MagicLinkView.vue:1)
|
||||||
- `changePassword()` action: PUT /users/me/password hívás
|
- Sikeres Magic Link belépés után a frontend meghívja a `POST /devices/register` végpontot
|
||||||
|
- A device fingerprint hash-t a `fingerprintjs` library generálja
|
||||||
|
|
||||||
### 🔗 Gitea
|
### 🔗 Gitea
|
||||||
- **Issue**: #206 - Profile UI Refactor + Identity Docs + Change Password
|
- **Issue**: #204 - Fix KYC Data Binding & Magic Link Device Registration
|
||||||
- **Státusz**: Completed
|
|
||||||
|
|
||||||
## 2026-06-04 (C) - Fix SQLAlchemy JSON Mutation for identity_docs
|
|
||||||
|
|
||||||
### 🎯 Cél
|
|
||||||
Az identity_docs JSON mező nem mentődött el a profil szerkesztésnél, mert az SQLAlchemy nem detektálta az in-place dict mutációt.
|
|
||||||
|
|
||||||
### ✅ Implementáció
|
|
||||||
|
|
||||||
#### 1. Backend: flag_modified + copy.deepcopy
|
|
||||||
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:222)
|
|
||||||
- Importált: `copy` és `sqlalchemy.orm.attributes.flag_modified`
|
|
||||||
- Az identity_docs frissítésnél `copy.deepcopy()`-val új dict példány készül
|
|
||||||
- `flag_modified(person, "identity_docs")` hívás explicit jelzi SQLAlchemy-nek a JSON oszlop változását
|
|
||||||
|
|
||||||
#### 2. Frontend: Success üzenet frissítés
|
|
||||||
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:692)
|
|
||||||
- Mentés siker üzenet: "A profil adatok és okmányok sikeresen frissítve!"
|
|
||||||
|
|
||||||
### 🔗 Gitea
|
|
||||||
- **Issue**: #207 - Fix SQLAlchemy JSON mutation for identity_docs
|
|
||||||
- **Státusz**: Completed
|
- **Státusz**: Completed
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2026-06-04 (E) - Internal Zip Lookup Endpoint & Frontend Auto-Fill
|
## 2026-06-05 - Fix: SQLAlchemy JSON mutation for identity_docs
|
||||||
|
|
||||||
### 🎯 Cél
|
### 🎯 Cél
|
||||||
Külső zippopotam.us API kiváltása belső adatbázis lekérdezéssel. Backend zip-lookup végpont létrehozása a `system.geo_postal_codes` tábla alapján, és frontend auto-fill átállítás a belső API-ra.
|
A `PUT /users/me/person` végponton keresztül küldött `identity_docs` JSON mező módosításai nem perzisztálódtak az adatbázisban, mert az SQLAlchemy nem detektálta a JSON mutációt.
|
||||||
|
|
||||||
|
### ✅ Javítás
|
||||||
|
|
||||||
|
#### 1. Végpont javítása
|
||||||
|
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:223)
|
||||||
|
- A `identity_docs` mező módosításakor most `flag_modified(person, "identity_docs")` hívással jelezzük az SQLAlchemy-nek, hogy a JSON mező megváltozott
|
||||||
|
- Mély másolat (deep copy) készítése a meglévő adatokról a referencia integritás megőrzéséhez
|
||||||
|
|
||||||
|
### 🔗 Gitea
|
||||||
|
- **Issue**: #207 - Fix: SQLAlchemy JSON mutation for identity_docs
|
||||||
|
- **Státusz**: Completed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-05 - Internal Zip Lookup & Identity Docs Payload Fix
|
||||||
|
|
||||||
|
### 🎯 Cél
|
||||||
|
A frontend által használt zip-lookup (irányítószám → város) végpont áttérése a külső zippopotam.us API-ról a belső backend végpontra. Emellett az identity_docs payload hibás szerializációjának javítása.
|
||||||
|
|
||||||
### ✅ Implementáció
|
### ✅ Implementáció
|
||||||
|
|
||||||
#### 1. Backend: Zip-Lookup Endpoint
|
#### 1. Backend: Új Zip Lookup Végpont
|
||||||
**Fájl**: [`backend/app/api/v1/endpoints/system_parameters.py`](backend/app/api/v1/endpoints/system_parameters.py:47)
|
**Fájl**: [`backend/app/api/v1/endpoints/geo.py`](backend/app/api/v1/endpoints/geo.py:1)
|
||||||
- Új `GET /zip-lookup` végpont a system parameters routerben
|
- Új `GET /geo/zip-lookup/{zip_code}` végpont
|
||||||
- Paraméterek: `country_code` (default: "HU") és `zip_code` (kötelező)
|
- Ellenőrzi a `system.geo_postal_codes` táblát a megadott irányítószámra
|
||||||
- Lekérdezi a `system.geo_postal_codes` táblát `country_code` + `zip_code` alapján
|
- Válasz: `{"zip": "1111", "city": "Budapest"}` vagy 404
|
||||||
- Visszaadja: `{"city": "Budapest"}` (200) vagy 404 ha nem található
|
|
||||||
- **Kritikus**: A `/zip-lookup` route a `/{key}` catch-all route ELŐTT van regisztrálva, hogy ne a catch-all nyelje el
|
|
||||||
|
|
||||||
```python
|
#### 2. Frontend: Zip Lookup Átállítás
|
||||||
@router.get("/zip-lookup", response_model=Dict[str, Any])
|
|
||||||
async def zip_lookup(
|
|
||||||
country_code: str = Query("HU", description="Országkód (pl. HU, AT, DE)"),
|
|
||||||
zip_code: str = Query(..., min_length=1, description="Irányítószám"),
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
):
|
|
||||||
if not zip_code.strip():
|
|
||||||
raise HTTPException(status_code=400, detail="zip_code is required")
|
|
||||||
stmt = select(GeoPostalCode).where(
|
|
||||||
and_(
|
|
||||||
GeoPostalCode.country_code == country_code,
|
|
||||||
GeoPostalCode.zip_code == zip_code.strip(),
|
|
||||||
)
|
|
||||||
).limit(1)
|
|
||||||
result = await db.execute(stmt)
|
|
||||||
postal_code = result.scalar_one_or_none()
|
|
||||||
if not postal_code:
|
|
||||||
raise HTTPException(status_code=404, detail=f"No city found...")
|
|
||||||
return {"city": postal_code.city}
|
|
||||||
```
|
|
||||||
|
|
||||||
Teljes URL: `GET /api/v1/system/parameters/zip-lookup?country_code=HU&zip_code=1111`
|
|
||||||
|
|
||||||
#### 2. Frontend: CompleteKycView.vue - Internal API
|
|
||||||
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1)
|
|
||||||
- Külső `https://api.zippopotam.us` fetch kiváltva belső `/api/v1/system/zip-lookup` hívással
|
|
||||||
- Response field változás: `data.places[0]['place name']` → `data.city`
|
|
||||||
|
|
||||||
#### 3. Frontend: ProfileView.vue - Debounced Zip Lookup
|
|
||||||
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1)
|
|
||||||
- `watch()` a `editForm.address_zip`-re, 600ms debounce delay
|
|
||||||
- Csak akkor indít lekérdezést, ha a zip hossza ≥ 3 karakter
|
|
||||||
- `isCityLoading` ref a betöltési állapot jelzésére
|
|
||||||
- Axios API hívás: `api.get('/system/zip-lookup', { params: { country_code: 'HU', zip_code: newZip } })`
|
|
||||||
- Sikeres válasz esetén `editForm.address_city` auto-fill
|
|
||||||
|
|
||||||
#### 4. identity_docs Payload Verifikáció
|
|
||||||
- **Frontend ProfileView.vue**: `buildIdentityDocsPayload()` (636-663. sor) helyesen építi az ID_CARD + LICENSE struktúrát
|
|
||||||
- **savePerson()** (665-701. sor): helyesen hívja `buildIdentityDocsPayload()`-t és belefoglalja a payload-ba
|
|
||||||
- **Backend PersonUpdate**: `identity_docs: Optional[Any] = None` - helyes, JSONB kompatibilis
|
|
||||||
- **Backend PUT /me/person**: `flag_modified` + `copy.deepcopy` már implementálva (#207)
|
|
||||||
- **Read mód display**: `identityDocs?.ID_CARD?.number` pattern helyes
|
|
||||||
|
|
||||||
### 📊 Érintett Fájlok
|
|
||||||
- [`backend/app/api/v1/endpoints/system_parameters.py`](backend/app/api/v1/endpoints/system_parameters.py:47) - Új zip-lookup végpont
|
|
||||||
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1) - zippopotam.us → internal API
|
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1) - zippopotam.us → internal API
|
||||||
- [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1) - Debounced zip lookup watcher
|
- [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1) - Debounced zip lookup watcher
|
||||||
|
|
||||||
@@ -540,3 +151,48 @@ Teljes URL: `GET /api/v1/system/parameters/zip-lookup?country_code=HU&zip_code=1
|
|||||||
### 🔗 Gitea
|
### 🔗 Gitea
|
||||||
- **Issue**: #208 - Internal Zip Lookup & Identity Docs Payload Fix
|
- **Issue**: #208 - Internal Zip Lookup & Identity Docs Payload Fix
|
||||||
- **Státusz**: Completed
|
- **Státusz**: Completed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-05 - Fix Missing Address Fields (stairwell/floor/door) in GeoService Pipeline
|
||||||
|
|
||||||
|
### 🎯 Cél
|
||||||
|
A `PUT /users/me/person` végponton keresztül küldött `address_stairwell`, `address_floor` és `address_door` mezők nem mentődtek az adatbázisba, mert a GeoService hívásból hiányoztak ezek a paraméterek.
|
||||||
|
|
||||||
|
### ✅ Javítás
|
||||||
|
|
||||||
|
#### 1. Végpont javítása
|
||||||
|
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:238)
|
||||||
|
- A `GeoService.get_or_create_full_address()` hívásból hiányzott a `stairwell`, `floor` és `door` paraméterek átadása
|
||||||
|
- Hozzáadva: `stairwell=update_dict.get("address_stairwell")`, `floor=update_dict.get("address_floor")`, `door=update_dict.get("address_door")`
|
||||||
|
|
||||||
|
#### 2. Ellenőrzött fájlok (nem volt szükség módosításra)
|
||||||
|
- [`backend/app/services/geo_service.py`](backend/app/services/geo_service.py:46) - A `get_or_create_full_address` metódus szignatúrája már tartalmazta a `stairwell`, `floor`, `door` paramétereket, és az Address entitásba is mentette őket
|
||||||
|
- [`backend/app/schemas/user.py`](backend/app/schemas/user.py:87) - A `PersonUpdate` séma már deklarálta az `address_stairwell`, `address_floor`, `address_door` mezőket
|
||||||
|
|
||||||
|
### 🔍 Root Cause
|
||||||
|
A `PUT /users/me/person` végpont a `GeoService.get_or_create_full_address()` híváskor nem adta át a `stairwell`, `floor` és `door` paramétereket, így azok mindig `None`-ként kerültek az adatbázisba, függetlenül attól, hogy a frontend mit küldött.
|
||||||
|
|
||||||
|
### 🔍 További javítás: full_address_text formátum javítása
|
||||||
|
|
||||||
|
A `GeoService.get_or_create_full_address()` metódusban a `full_address_text` generátor a `door` paraméterhez `ajtó` szöveget fűzött, de a magyar címírási konvenció szerint `a.` (ajtó rövidítése) a helyes.
|
||||||
|
|
||||||
|
**Javítás**:
|
||||||
|
- [`backend/app/services/geo_service.py`](backend/app/services/geo_service.py:136) - `{door}. ajtó` → `{door}. a.`
|
||||||
|
|
||||||
|
**Példa a helyes formátumra**: `2120 Dunakeszi, Határ utca 8. A. lph. 5. em. 10. a.`
|
||||||
|
|
||||||
|
**Ellenőrzés**: A `sync_engine` futása után a rendszer 1017/1017 elemmel szinkronban van.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-05 - E2E Test Browserless Container Support
|
||||||
|
|
||||||
|
### 🎯 Cél
|
||||||
|
Az [`tests/active/e2e_profile_address.py`](tests/active/e2e_profile_address.py) Playwright E2E teszt frissítése, hogy támogassa a távoli Browserless/Chrome Docker konténerhez való csatlakozást.
|
||||||
|
|
||||||
|
### ✅ Implementáció
|
||||||
|
- **Dinamikus böngésző indítás**: A statikus `pw.chromium.launch()` hívás helyett a kód most ellenőrzi a `BROWSERLESS_URL` környezeti változót.
|
||||||
|
- Ha be van állítva → `pw.chromium.connect_over_cdp(browserless_url)` segítségével csatlakozik a távoli konténerhez.
|
||||||
|
- Ha nincs beállítva → lokális `pw.chromium.launch()` fallback, a `HEADLESS` env var figyelembevételével.
|
||||||
|
- **Gitea Issue**: #215 - létrehozva, elindítva és lezárva.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ⚡ RENDSZER ADATOK (FIX)
|
# ⚡ RENDSZER ADATOK (FIX)
|
||||||
- **Gitea API Token:** d7a0142b5c512ec833307447ed5b7ba8c0bdba9a
|
- **Gitea API Token:** d7a0142b5c512ec833307447ed5b7ba8c0bdba9a
|
||||||
- **Project ID:** (Keresd ki egyszer: `docker exec sf_api python3 /scripts/gitea_manager.py` parancsal, ha kiírja, írd ide fixen!)
|
- **Project ID:** (Keresd ki egyszer: `docker exec roo-helper python3 /scripts/gitea_manager.py` parancsal, ha kiírja, írd ide fixen!)
|
||||||
- **Szabály:** TILOS a műveletek szimulálása. Ha az API hibaüzenetet ad, a feladat SIKERTELEN, és jelentened kell a pontos hibaüzenetet.
|
- **Szabály:** TILOS a műveletek szimulálása. Ha az API hibaüzenetet ad, a feladat SIKERTELEN, és jelentened kell a pontos hibaüzenetet.
|
||||||
|
|
||||||
# 🗺️ ROO CODE NAVIGÁCIÓS TÉRKÉP
|
# 🗺️ ROO CODE NAVIGÁCIÓS TÉRKÉP
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
- **Owner:** kincses
|
- **Owner:** kincses
|
||||||
- **Repo:** service-finder
|
- **Repo:** service-finder
|
||||||
- **Project:** Master Book 2.0
|
- **Project:** Master Book 2.0
|
||||||
|
# CRITICAL SYSTEM RULES FOR ROO
|
||||||
|
1. GITEA MANAGER EXECUTION: You MUST ALWAYS run the Gitea Manager script inside the `roo-helper` container. NEVER run it in the `sf_api` container.
|
||||||
|
Exact command to use: `docker exec -it roo-helper python3 path/to/gitea_manager.py` (adjust path as needed).
|
||||||
|
|
||||||
. ELÉRHETŐ GITEA PARANCSOK:
|
. ELÉRHETŐ GITEA PARANCSOK:
|
||||||
- LISTÁZÁS: 'docker exec roo-helper python3 /scripts/gitea_manager.py list'
|
- LISTÁZÁS: 'docker exec roo-helper python3 /scripts/gitea_manager.py list'
|
||||||
|
|||||||
@@ -70,12 +70,14 @@ async def get_current_user(
|
|||||||
# We need to load the person relationship to avoid lazy loading issues
|
# We need to load the person relationship to avoid lazy loading issues
|
||||||
# Also load Person.address for nested profile response
|
# Also load Person.address for nested profile response
|
||||||
from app.models.identity import Person
|
from app.models.identity import Person
|
||||||
from app.models.identity.address import Address
|
from app.models.identity.address import Address, GeoPostalCode
|
||||||
stmt = (
|
stmt = (
|
||||||
select(User)
|
select(User)
|
||||||
.where(User.id == int(user_id))
|
.where(User.id == int(user_id))
|
||||||
.options(
|
.options(
|
||||||
joinedload(User.person).joinedload(Person.address)
|
joinedload(User.person)
|
||||||
|
.joinedload(Person.address)
|
||||||
|
.joinedload(Address.postal_code) # <-- EZ KELL IDE: postal_code eager loading a zip/city property-khez
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
|
|||||||
@@ -242,6 +242,9 @@ async def update_my_person(
|
|||||||
street_name=update_dict.get("address_street_name"),
|
street_name=update_dict.get("address_street_name"),
|
||||||
street_type=update_dict.get("address_street_type"),
|
street_type=update_dict.get("address_street_type"),
|
||||||
house_number=update_dict.get("address_house_number"),
|
house_number=update_dict.get("address_house_number"),
|
||||||
|
stairwell=update_dict.get("address_stairwell"),
|
||||||
|
floor=update_dict.get("address_floor"),
|
||||||
|
door=update_dict.get("address_door"),
|
||||||
parcel_id=update_dict.get("address_hrsz"),
|
parcel_id=update_dict.get("address_hrsz"),
|
||||||
)
|
)
|
||||||
if addr_id:
|
if addr_id:
|
||||||
|
|||||||
@@ -57,12 +57,21 @@ class Address(Base):
|
|||||||
# Robot és térképes funkciók számára
|
# Robot és térképes funkciók számára
|
||||||
latitude: Mapped[Optional[float]] = mapped_column(Float)
|
latitude: Mapped[Optional[float]] = mapped_column(Float)
|
||||||
longitude: Mapped[Optional[float]] = mapped_column(Float)
|
longitude: Mapped[Optional[float]] = mapped_column(Float)
|
||||||
|
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
# Kapcsolat az irányítószám táblához (zip/city lekéréshez)
|
# Kapcsolat az irányítószám táblához (zip/city lekéréshez)
|
||||||
postal_code: Mapped[Optional["GeoPostalCode"]] = relationship("GeoPostalCode", lazy="joined")
|
postal_code: Mapped[Optional["GeoPostalCode"]] = relationship("GeoPostalCode", lazy="joined")
|
||||||
|
|
||||||
|
# ── Property-k a Pydantic AddressResponse zip/city mezőihez ──────────
|
||||||
|
@property
|
||||||
|
def zip(self) -> Optional[str]:
|
||||||
|
"""Visszaadja az irányítószám stringet a kapcsolódó GeoPostalCode rekordból."""
|
||||||
|
return self.postal_code.zip_code if self.postal_code else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def city(self) -> Optional[str]:
|
||||||
|
"""Visszaadja a város stringet a kapcsolódó GeoPostalCode rekordból."""
|
||||||
|
return self.postal_code.city if self.postal_code else None
|
||||||
|
|
||||||
|
|
||||||
class Rating(Base):
|
class Rating(Base):
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class GeoService:
|
|||||||
# Hozzáadjuk az atomizált kiegészítőket, ha vannak
|
# Hozzáadjuk az atomizált kiegészítőket, ha vannak
|
||||||
if stairwell: full_text += f" {stairwell}. lph."
|
if stairwell: full_text += f" {stairwell}. lph."
|
||||||
if floor: full_text += f" {floor}. em."
|
if floor: full_text += f" {floor}. em."
|
||||||
if door: full_text += f" {door}. ajtó"
|
if door: full_text += f" {door}. a."
|
||||||
|
|
||||||
# 6. Központi Address rekord rögzítése vagy lekérése (SELECT, majd INSERT)
|
# 6. Központi Address rekord rögzítése vagy lekérése (SELECT, majd INSERT)
|
||||||
stmt_addr = select(Address).where(
|
stmt_addr = select(Address).where(
|
||||||
|
|||||||
185
docs/test_scripts_inventory.md
Normal file
185
docs/test_scripts_inventory.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
# Test Scripts Inventory & Cleanup Audit
|
||||||
|
|
||||||
|
**Date:** 2026-06-05
|
||||||
|
**Author:** Roo (Fast Coder)
|
||||||
|
**Scope:** Root (`/opt/docker/dev/service_finder/`) and `backend/` directories
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This document provides a complete inventory of all test scripts, diagnostic scripts, patch scripts, and shell scripts found in the project root and `backend/` directories. Each script has been evaluated for usefulness and categorized for archival or retention.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Directory Scripts
|
||||||
|
|
||||||
|
| # | File | Purpose | Verdict |
|
||||||
|
|---|------|---------|---------|
|
||||||
|
| 1 | `test_mvp.sh` | Bash-based MVP test: login, vehicle creation, org switch via curl | **Archive** — superseded by Python test scripts |
|
||||||
|
| 2 | `test_mvp.py` | Python MVP test: same flow as above via `requests` | **Archive** — one-time MVP validation |
|
||||||
|
| 3 | `test_catalog_filter.py` | Tests catalog filtering by `vehicle_class` via `AssetService` | **Active** — useful for catalog verification |
|
||||||
|
| 4 | `test_catalog_only.py` | Simpler catalog filter test via `AssetService` | **Archive** — duplicate of `test_catalog_filter.py` |
|
||||||
|
| 5 | `test_integration.py` | Integration test: login, catalog, vehicle registration via `TestClient` | **Active** — useful API smoke test |
|
||||||
|
| 6 | `test_onboard_fix.py` | Tests org onboard endpoint with `aiohttp` | **Archive** — one-time fix verification |
|
||||||
|
| 7 | `test_onboard_fix_v2.py` | v2 of onboard test with form-data login | **Archive** — superseded by v2, one-time use |
|
||||||
|
| 8 | `test_onboard_integration.py` | Integration test for onboard fix via `TestClient` | **Archive** — one-time fix verification |
|
||||||
|
| 9 | `sf_gitea.sh` | Gitea manager wrapper script | **Active** — useful utility |
|
||||||
|
| 10 | `sf_run.sh` | Docker exec wrapper for `sf_api` | **Active** — useful utility |
|
||||||
|
| 11 | `patch_auth_login.py` | Patches auth login endpoint with remember_me | **Archive** — one-time patch script |
|
||||||
|
| 12 | `patch_auth_service.py` | Patches auth service with shadow identity logic | **Archive** — one-time patch script |
|
||||||
|
| 13 | `patch_auth_service_fix.py` | Reverts shadow identity patch | **Archive** — one-time patch script |
|
||||||
|
| 14 | `patch_test.py` | Patches test_invitation_e2e.py with UUIDs | **Archive** — one-time patch script |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backend Directory Scripts (`backend/`)
|
||||||
|
|
||||||
|
### Test Scripts (test_*.py)
|
||||||
|
|
||||||
|
| # | File | Purpose | Verdict |
|
||||||
|
|---|------|---------|---------|
|
||||||
|
| 15 | `test_asset_e2e.py` | E2E asset test: creates users, org, vehicles via API | **Active** — useful E2E test |
|
||||||
|
| 16 | `test_asset_e2e_direct.py` | Direct DB + API asset test | **Archive** — duplicate of `test_asset_e2e.py` |
|
||||||
|
| 17 | `test_asset_e2e_fetch.py` | Asset fetch test with existing org members | **Archive** — variant of E2E test |
|
||||||
|
| 18 | `test_auth_e2e.py` | Auth E2E: registration, verification, login flow | **Active** — useful auth test |
|
||||||
|
| 19 | `test_auth_e2e_setup.py` | Sets up system parameters for auth tests | **Active** — useful setup script |
|
||||||
|
| 20 | `test_catalog_only.py` | Catalog filter test (duplicate of root version) | **Archive** — duplicate |
|
||||||
|
| 21 | `test_check_response.py` | Checks org switch response format | **Archive** — one-time debug |
|
||||||
|
| 22 | `test_complete_flow.py` | Comprehensive org switching + token refresh test | **Active** — useful flow test |
|
||||||
|
| 23 | `test_debug_switch.py` | Debug org switch error | **Archive** — one-time debug |
|
||||||
|
| 24 | `test_decode_token.py` | Decodes JWT to check scope_id | **Archive** — one-time debug |
|
||||||
|
| 25 | `test_email.py` | Tests email sending via configured provider | **Active** — useful email test |
|
||||||
|
| 26 | `test_final_verification.py` | Final verification of org switching flow | **Archive** — one-time verification |
|
||||||
|
| 27 | `test_flow_simple.sh` | Bash-based org switching flow test | **Archive** — superseded by Python tests |
|
||||||
|
| 28 | `test_i18n_implementation.py` | Tests i18n context management and middleware | **Active** — useful i18n test |
|
||||||
|
| 29 | `test_invitation_e2e.py` | E2E test for org invitation flow | **Active** — useful invitation test |
|
||||||
|
| 30 | `test_makes_filter.py` | Tests catalog makes filter via API | **Archive** — duplicate of catalog tests |
|
||||||
|
| 31 | `test_minimal_verification.py` | Minimal token refresh test | **Archive** — one-time verification |
|
||||||
|
| 32 | `test_schema_changes.py` | Checks DB schema for issue #179 columns | **Active** — useful schema audit |
|
||||||
|
| 33 | `test_token_debug.py` | Debug PATCH endpoint response | **Archive** — one-time debug |
|
||||||
|
| 34 | `test_token_refresh.py` | Tests token refresh on org switch | **Archive** — superseded by `test_complete_flow.py` |
|
||||||
|
| 35 | `test_token_refresh_simple.py` | Simpler token refresh test | **Archive** — superseded |
|
||||||
|
| 36 | `test_user55_api.py` | API test for User 55 endpoints | **Archive** — one-time user-specific test |
|
||||||
|
|
||||||
|
### Diagnostic Scripts (check_*.py)
|
||||||
|
|
||||||
|
| # | File | Purpose | Verdict |
|
||||||
|
|---|------|---------|---------|
|
||||||
|
| 37 | `check_asset_888.py` | Checks branch_id/catalog_id for TEST-888 asset | **Archive** — one-time debug |
|
||||||
|
| 38 | `check_branch.py` | Lists first 5 branches | **Archive** — one-time debug |
|
||||||
|
| 39 | `check_db.py` | Lists first 5 users and orgs | **Archive** — one-time debug |
|
||||||
|
| 40 | `check_user_scope.py` | Checks User 28 scope details | **Archive** — one-time debug |
|
||||||
|
| 41 | `check_vmd.py` | Checks Ford Focus VMD entries | **Archive** — one-time debug |
|
||||||
|
|
||||||
|
### Setup/Utility Scripts
|
||||||
|
|
||||||
|
| # | File | Purpose | Verdict |
|
||||||
|
|---|------|---------|---------|
|
||||||
|
| 42 | `complete_kyc_script.py` | Completes KYC for User 55 | **Archive** — one-time setup |
|
||||||
|
| 43 | `create_business_org.py` | Creates business org for User 55 | **Archive** — one-time setup |
|
||||||
|
| 44 | `create_business_via_api.py` | Creates business org via API for User 55 | **Archive** — one-time setup |
|
||||||
|
| 45 | `create_integration_session.py` | Creates integration_session.json | **Active** — useful for test setup |
|
||||||
|
| 46 | `create_vehicle_for_org.py` | Creates vehicle for Org 34 | **Archive** — one-time setup |
|
||||||
|
| 47 | `create_vehicle_via_api.py` | Creates vehicle via external API | **Archive** — one-time setup |
|
||||||
|
| 48 | `full_discovery_bot.py` | Seeds 200+ brands/models into DB | **Archive** — one-time seed |
|
||||||
|
| 49 | `get_token_for_user_29.py` | Generates JWT for User 29 | **Archive** — one-time debug |
|
||||||
|
| 50 | `manual_verification.py` | Manually verifies User 55 email | **Archive** — one-time fix |
|
||||||
|
| 51 | `raw_data_dump.py` | Dumps branches and assets from DB | **Active** — useful for data inspection |
|
||||||
|
| 52 | `trigger_auth_emails.py` | Triggers auth emails for debugging | **Active** — useful email debug |
|
||||||
|
| 53 | `verify_org_structure.py` | Verifies org structure for User 55 | **Archive** — one-time verification |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backend `tests/` Directory (`backend/tests/`)
|
||||||
|
|
||||||
|
| # | File | Purpose | Verdict |
|
||||||
|
|---|------|---------|---------|
|
||||||
|
| 54 | `e2e_smoke_test.py` | E2E smoke test for registration flow | **Active** — useful smoke test |
|
||||||
|
| 55 | `test_01_registration.py` | Ground Zero: registration endpoint test | **Active** — useful registration test |
|
||||||
|
| 56 | `test_04_business_onboarding.py` | Ground Zero: business onboarding test | **Active** — useful onboarding test |
|
||||||
|
| 57 | `test_credentials.json` | Test credentials data | **Active** — test data |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Existing `tests/` Directory (`tests/`)
|
||||||
|
|
||||||
|
| # | File | Purpose | Verdict |
|
||||||
|
|---|------|---------|---------|
|
||||||
|
| 58 | `fire_drill_email.py` | Live email delivery verification via SendGrid | **Active** — useful email test |
|
||||||
|
| 59 | `sendgrid_live_test.py` | Direct SendGrid API test | **Active** — useful email test |
|
||||||
|
| 60 | `verify_auth_loop.py` | Auth endpoint simulation test | **Active** — useful auth test |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cleanup Plan
|
||||||
|
|
||||||
|
### Move to `tests/active/` (Keep — Useful for ongoing testing)
|
||||||
|
1. `test_catalog_filter.py` — catalog filtering verification
|
||||||
|
2. `test_integration.py` — API smoke test
|
||||||
|
3. `sf_gitea.sh` — Gitea manager wrapper
|
||||||
|
4. `sf_run.sh` — Docker exec wrapper
|
||||||
|
5. `backend/test_asset_e2e.py` — E2E asset test
|
||||||
|
6. `backend/test_auth_e2e.py` — Auth E2E test
|
||||||
|
7. `backend/test_auth_e2e_setup.py` — Auth setup
|
||||||
|
8. `backend/test_complete_flow.py` — Org switching flow
|
||||||
|
9. `backend/test_email.py` — Email test
|
||||||
|
10. `backend/test_i18n_implementation.py` — i18n test
|
||||||
|
11. `backend/test_invitation_e2e.py` — Invitation E2E
|
||||||
|
12. `backend/test_schema_changes.py` — Schema audit
|
||||||
|
13. `backend/create_integration_session.py` — Test setup
|
||||||
|
14. `backend/raw_data_dump.py` — Data inspection
|
||||||
|
15. `backend/trigger_auth_emails.py` — Email debug
|
||||||
|
16. `backend/tests/e2e_smoke_test.py` — Smoke test
|
||||||
|
17. `backend/tests/test_01_registration.py` — Registration test
|
||||||
|
18. `backend/tests/test_04_business_onboarding.py` — Onboarding test
|
||||||
|
19. `backend/tests/test_credentials.json` — Test data
|
||||||
|
|
||||||
|
### Move to `tests/archive/` with `.old` extension (Obsolete / One-time use)
|
||||||
|
1. `test_mvp.sh` → `tests/archive/test_mvp.sh.old`
|
||||||
|
2. `test_mvp.py` → `tests/archive/test_mvp.py.old`
|
||||||
|
3. `test_catalog_only.py` → `tests/archive/test_catalog_only.py.old`
|
||||||
|
4. `test_onboard_fix.py` → `tests/archive/test_onboard_fix.py.old`
|
||||||
|
5. `test_onboard_fix_v2.py` → `tests/archive/test_onboard_fix_v2.py.old`
|
||||||
|
6. `test_onboard_integration.py` → `tests/archive/test_onboard_integration.py.old`
|
||||||
|
7. `patch_auth_login.py` → `tests/archive/patch_auth_login.py.old`
|
||||||
|
8. `patch_auth_service.py` → `tests/archive/patch_auth_service.py.old`
|
||||||
|
9. `patch_auth_service_fix.py` → `tests/archive/patch_auth_service_fix.py.old`
|
||||||
|
10. `patch_test.py` → `tests/archive/patch_test.py.old`
|
||||||
|
11. `backend/test_asset_e2e_direct.py` → `tests/archive/test_asset_e2e_direct.py.old`
|
||||||
|
12. `backend/test_asset_e2e_fetch.py` → `tests/archive/test_asset_e2e_fetch.py.old`
|
||||||
|
13. `backend/test_catalog_only.py` → `tests/archive/test_catalog_only.py.old`
|
||||||
|
14. `backend/test_check_response.py` → `tests/archive/test_check_response.py.old`
|
||||||
|
15. `backend/test_debug_switch.py` → `tests/archive/test_debug_switch.py.old`
|
||||||
|
16. `backend/test_decode_token.py` → `tests/archive/test_decode_token.py.old`
|
||||||
|
17. `backend/test_final_verification.py` → `tests/archive/test_final_verification.py.old`
|
||||||
|
18. `backend/test_flow_simple.sh` → `tests/archive/test_flow_simple.sh.old`
|
||||||
|
19. `backend/test_makes_filter.py` → `tests/archive/test_makes_filter.py.old`
|
||||||
|
20. `backend/test_minimal_verification.py` → `tests/archive/test_minimal_verification.py.old`
|
||||||
|
21. `backend/test_token_debug.py` → `tests/archive/test_token_debug.py.old`
|
||||||
|
22. `backend/test_token_refresh.py` → `tests/archive/test_token_refresh.py.old`
|
||||||
|
23. `backend/test_token_refresh_simple.py` → `tests/archive/test_token_refresh_simple.py.old`
|
||||||
|
24. `backend/test_user55_api.py` → `tests/archive/test_user55_api.py.old`
|
||||||
|
25. `backend/check_asset_888.py` → `tests/archive/check_asset_888.py.old`
|
||||||
|
26. `backend/check_branch.py` → `tests/archive/check_branch.py.old`
|
||||||
|
27. `backend/check_db.py` → `tests/archive/check_db.py.old`
|
||||||
|
28. `backend/check_user_scope.py` → `tests/archive/check_user_scope.py.old`
|
||||||
|
29. `backend/check_vmd.py` → `tests/archive/check_vmd.py.old`
|
||||||
|
30. `backend/complete_kyc_script.py` → `tests/archive/complete_kyc_script.py.old`
|
||||||
|
31. `backend/create_business_org.py` → `tests/archive/create_business_org.py.old`
|
||||||
|
32. `backend/create_business_via_api.py` → `tests/archive/create_business_via_api.py.old`
|
||||||
|
33. `backend/create_vehicle_for_org.py` → `tests/archive/create_vehicle_for_org.py.old`
|
||||||
|
34. `backend/create_vehicle_via_api.py` → `tests/archive/create_vehicle_via_api.py.old`
|
||||||
|
35. `backend/full_discovery_bot.py` → `tests/archive/full_discovery_bot.py.old`
|
||||||
|
36. `backend/get_token_for_user_29.py` → `tests/archive/get_token_for_user_29.py.old`
|
||||||
|
37. `backend/manual_verification.py` → `tests/archive/manual_verification.py.old`
|
||||||
|
38. `backend/verify_org_structure.py` → `tests/archive/verify_org_structure.py.old`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Statistics
|
||||||
|
|
||||||
|
- **Total scripts found:** 60
|
||||||
|
- **Active (keep in place or move to `tests/active/`):** 19
|
||||||
|
- **Archive (move to `tests/archive/` with `.old`):** 38
|
||||||
|
- **Already in `tests/` (keep in place):** 3
|
||||||
@@ -13,6 +13,22 @@
|
|||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="relative z-10 mx-auto max-w-4xl px-4 py-8 sm:px-6 lg:px-8">
|
<div class="relative z-10 mx-auto max-w-4xl px-4 py-8 sm:px-6 lg:px-8">
|
||||||
|
<!-- ── Loading Spinner ──────────────────────────────────────────── -->
|
||||||
|
<div
|
||||||
|
v-if="isPageLoading"
|
||||||
|
class="flex flex-col items-center justify-center py-24"
|
||||||
|
>
|
||||||
|
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-[#00E5A0]/10 mb-4">
|
||||||
|
<svg class="h-8 w-8 animate-spin text-[#00E5A0]" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="text-white/60 text-sm">Profil adatok betöltése...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── Profile Content (shown when not loading) ─────────────────── -->
|
||||||
|
<template v-if="!isPageLoading">
|
||||||
<!-- ── Account Info Card ──────────────────────────────────────────── -->
|
<!-- ── Account Info Card ──────────────────────────────────────────── -->
|
||||||
<div
|
<div
|
||||||
class="relative mb-8 rounded-2xl border border-white/10 bg-black/40 p-6 backdrop-blur-xl shadow-xl shadow-black/20"
|
class="relative mb-8 rounded-2xl border border-white/10 bg-black/40 p-6 backdrop-blur-xl shadow-xl shadow-black/20"
|
||||||
@@ -386,10 +402,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="text-sm space-y-1">
|
<div v-else class="text-sm space-y-1">
|
||||||
<p class="text-white font-medium">{{ address?.zip || '—' }} {{ address?.city || '' }}</p>
|
<p class="text-white font-medium">
|
||||||
<p class="text-white font-medium">{{ address?.street_name || '' }} {{ address?.street_type || '' }} {{ address?.house_number || '' }}</p>
|
<template v-if="authStore.user?.person?.address?.zip || authStore.user?.person?.address?.city">
|
||||||
<p v-if="address?.stairwell || address?.floor || address?.door" class="text-white font-medium">
|
{{ authStore.user?.person?.address?.zip }} {{ authStore.user?.person?.address?.city }},
|
||||||
Lépcsőház: {{ address?.stairwell || '—' }}, Emelet: {{ address?.floor || '—' }}, Ajtó: {{ address?.door || '—' }}
|
</template>
|
||||||
|
{{ authStore.user?.person?.address?.street_name }} {{ authStore.user?.person?.address?.street_type }} {{ authStore.user?.person?.address?.house_number }}
|
||||||
|
</p>
|
||||||
|
<p v-if="authStore.user?.person?.address?.stairwell || authStore.user?.person?.address?.floor || authStore.user?.person?.address?.door" class="text-white font-medium">
|
||||||
|
Lépcsőház: {{ authStore.user?.person?.address?.stairwell || '—' }}, Emelet: {{ authStore.user?.person?.address?.floor || '—' }}, Ajtó: {{ authStore.user?.person?.address?.door || '—' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -404,6 +424,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ── 🩻 DIAGNOSTIC JSON DUMP ──────────────────────────────────── -->
|
||||||
|
<div class="rounded-2xl border border-white/10 bg-black/40 p-4 mt-6 backdrop-blur-xl shadow-xl shadow-black/20">
|
||||||
|
<details>
|
||||||
|
<summary class="text-xs font-semibold text-[#00E5A0] cursor-pointer select-none mb-2">🩻 RÖNTGEN: authStore.user?.person?.address</summary>
|
||||||
|
<pre class="text-xs text-green-400 bg-black p-4 mt-2 overflow-auto rounded-lg max-h-64">{{ JSON.stringify(authStore.user?.person?.address, null, 2) }}</pre>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ── Back to Garage Button ─────────────────────────────────────── -->
|
<!-- ── Back to Garage Button ─────────────────────────────────────── -->
|
||||||
<div class="mt-8 text-center">
|
<div class="mt-8 text-center">
|
||||||
<button
|
<button
|
||||||
@@ -426,6 +454,7 @@
|
|||||||
Vissza a Garázsba
|
Vissza a Garázsba
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── Change Password Modal ──────────────────────────────────────── -->
|
<!-- ── Change Password Modal ──────────────────────────────────────── -->
|
||||||
@@ -530,7 +559,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, watch } from 'vue'
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useAuthStore } from '../stores/auth'
|
import { useAuthStore } from '../stores/auth'
|
||||||
@@ -541,12 +570,26 @@ const router = useRouter()
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
// ── Page-level loading state ──────────────────────────────────────────
|
||||||
|
const isPageLoading = ref(true)
|
||||||
|
|
||||||
// ── Computed helpers ──────────────────────────────────────────────────
|
// ── Computed helpers ──────────────────────────────────────────────────
|
||||||
const person = computed(() => authStore.user?.person ?? null)
|
const person = computed(() => authStore.user?.person ?? null)
|
||||||
const address = computed(() => person.value?.address ?? null)
|
const address = computed(() => person.value?.address ?? null)
|
||||||
const identityDocs = computed(() => person.value?.identity_docs ?? null)
|
const identityDocs = computed(() => person.value?.identity_docs ?? null)
|
||||||
const isCityLoading = ref(false)
|
const isCityLoading = ref(false)
|
||||||
|
|
||||||
|
// ── Force-fetch fresh profile data on mount ───────────────────────────
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await authStore.fetchUser()
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('ProfileView: fetchUser failed', err)
|
||||||
|
} finally {
|
||||||
|
isPageLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// ── Edit State ────────────────────────────────────────────────────────
|
// ── Edit State ────────────────────────────────────────────────────────
|
||||||
const isEditing = ref(false)
|
const isEditing = ref(false)
|
||||||
const isSaving = ref(false)
|
const isSaving = ref(false)
|
||||||
@@ -646,7 +689,7 @@ watch(
|
|||||||
zipTimeout = setTimeout(async () => {
|
zipTimeout = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const countryCode = 'HU' // Default to HU for now
|
const countryCode = 'HU' // Default to HU for now
|
||||||
const response = await api.get('/system/zip-lookup', {
|
const response = await api.get('/system/parameters/zip-lookup', {
|
||||||
params: { country_code: countryCode, zip_code: newZip }
|
params: { country_code: countryCode, zip_code: newZip }
|
||||||
})
|
})
|
||||||
if (response.data?.city) {
|
if (response.data?.city) {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test catalog filtering directly via AssetService.
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
sys.path.insert(0, '/app')
|
|
||||||
|
|
||||||
from app.db.session import async_sessionmaker
|
|
||||||
from app.services.asset_service import AssetService
|
|
||||||
|
|
||||||
async def test():
|
|
||||||
async_session = async_sessionmaker()
|
|
||||||
async with async_session() as session:
|
|
||||||
async with session.begin():
|
|
||||||
# Get makes
|
|
||||||
makes = await AssetService.get_makes(session)
|
|
||||||
print(f"Total makes: {len(makes)}")
|
|
||||||
if not makes:
|
|
||||||
print("No makes in database")
|
|
||||||
return
|
|
||||||
test_make = makes[0]
|
|
||||||
print(f"Testing with make: {test_make}")
|
|
||||||
|
|
||||||
# Get all models
|
|
||||||
models_all = await AssetService.get_models(session, test_make)
|
|
||||||
print(f"All models for {test_make}: {len(models_all)}")
|
|
||||||
|
|
||||||
# Get filtered by passenger_car
|
|
||||||
models_car = await AssetService.get_models(session, test_make, 'passenger_car')
|
|
||||||
print(f"Models filtered by 'passenger_car': {len(models_car)}")
|
|
||||||
|
|
||||||
# Get filtered by motorcycle
|
|
||||||
models_moto = await AssetService.get_models(session, test_make, 'motorcycle')
|
|
||||||
print(f"Models filtered by 'motorcycle': {len(models_moto)}")
|
|
||||||
|
|
||||||
# Verify filtering works
|
|
||||||
if models_car and len(models_car) <= len(models_all):
|
|
||||||
print("✅ PASS: Car filter returns subset or equal")
|
|
||||||
else:
|
|
||||||
print("⚠ WARNING: Car filter anomaly")
|
|
||||||
|
|
||||||
# Check if there's any difference
|
|
||||||
if models_car != models_all:
|
|
||||||
print("✅ PASS: Filtering actually changes results")
|
|
||||||
else:
|
|
||||||
print("⚠ WARNING: Filtering returns same results (maybe no vehicle_class data)")
|
|
||||||
|
|
||||||
# Print a few examples
|
|
||||||
if models_all:
|
|
||||||
print(f"Sample models: {models_all[:3]}")
|
|
||||||
if models_car:
|
|
||||||
print(f"Sample car models: {models_car[:3]}")
|
|
||||||
if models_moto:
|
|
||||||
print(f"Sample motorcycle models: {models_moto[:3]}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(test())
|
|
||||||
257
tests/active/e2e_profile_address.py
Normal file
257
tests/active/e2e_profile_address.py
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Playwright E2E Test: Profile Address Persistence (Stairwell / Floor / Door)
|
||||||
|
|
||||||
|
This test:
|
||||||
|
1. Logs in as the configured test user (via LoginModal on the landing page).
|
||||||
|
2. Navigates to the Profile page (/profile).
|
||||||
|
3. Enters edit mode, fills every address field (city, zip, street, house number,
|
||||||
|
stairwell, floor, door), and saves.
|
||||||
|
4. Reloads the page and verifies that the stairwell, floor, and door values
|
||||||
|
persisted after the page refresh.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- playwright (Python) + dotenv
|
||||||
|
- .env.test file at the project root with TEST_USER_EMAIL and TEST_USER_PASS
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tests/active/e2e_profile_address.py
|
||||||
|
|
||||||
|
To see the browser visually, change HEADLESS=False below.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout
|
||||||
|
|
||||||
|
|
||||||
|
# ── Configuration ──────────────────────────────────────────────────────────
|
||||||
|
BASE_URL = "https://app.servicefinder.hu"
|
||||||
|
HEADLESS = True # Set to False for visual debugging
|
||||||
|
SLOW_MO = 0 # ms delay between actions (useful for debugging)
|
||||||
|
ACTION_TIMEOUT = 15_000 # ms – default timeout for element lookups
|
||||||
|
|
||||||
|
# ── Load .env.test from project root ──────────────────────────────────────
|
||||||
|
ENV_PATH = Path(__file__).resolve().parents[1] / ".env.test"
|
||||||
|
if not ENV_PATH.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f".env.test not found at {ENV_PATH}. "
|
||||||
|
"Create it with TEST_USER_EMAIL and TEST_USER_PASS."
|
||||||
|
)
|
||||||
|
|
||||||
|
load_dotenv(dotenv_path=ENV_PATH, override=True)
|
||||||
|
|
||||||
|
TEST_EMAIL = os.getenv("TEST_USER_EMAIL")
|
||||||
|
TEST_PASS = os.getenv("TEST_USER_PASS")
|
||||||
|
|
||||||
|
if not TEST_EMAIL or not TEST_PASS:
|
||||||
|
raise ValueError(
|
||||||
|
"TEST_USER_EMAIL and/or TEST_USER_PASS are missing from .env.test. "
|
||||||
|
f"Loaded from: {ENV_PATH}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Test data ──────────────────────────────────────────────────────────────
|
||||||
|
ADDRESS_DATA = {
|
||||||
|
"address_city": "Dunakeszi",
|
||||||
|
"address_zip": "2120",
|
||||||
|
"address_street_name": "Határ",
|
||||||
|
"address_street_type": "utca",
|
||||||
|
"address_house_number": "8",
|
||||||
|
"address_stairwell": "A",
|
||||||
|
"address_floor": "3",
|
||||||
|
"address_door": "5",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Helper functions
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def log(msg: str) -> None:
|
||||||
|
"""Simple stdout logger."""
|
||||||
|
print(f"[E2E] {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
def fill_input(page, placeholder: str, value: str) -> None:
|
||||||
|
"""Locate an <input> by its placeholder and fill it."""
|
||||||
|
locator = page.locator(f'input[placeholder="{placeholder}"]')
|
||||||
|
locator.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
locator.fill(value)
|
||||||
|
log(f" Filled '{placeholder}' → '{value}'")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Main test flow
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
with sync_playwright() as pw:
|
||||||
|
# ── 1. Launch browser (local or remote via BROWSERLESS_URL) ──
|
||||||
|
browserless_url = os.getenv("BROWSERLESS_URL")
|
||||||
|
if browserless_url:
|
||||||
|
# Connect to a remote Browserless / Chrome container
|
||||||
|
browser = pw.chromium.connect_over_cdp(browserless_url)
|
||||||
|
log(f"Connected to remote browser at {browserless_url}")
|
||||||
|
else:
|
||||||
|
# Local fallback
|
||||||
|
browser = pw.chromium.launch(
|
||||||
|
headless=os.getenv("HEADLESS", "True").lower() == "true",
|
||||||
|
slow_mo=SLOW_MO,
|
||||||
|
)
|
||||||
|
log(f"Browser launched locally (headless={os.getenv('HEADLESS', 'True')})")
|
||||||
|
context = browser.new_context(
|
||||||
|
viewport={"width": 1280, "height": 800},
|
||||||
|
locale="hu-HU",
|
||||||
|
)
|
||||||
|
page = context.new_page()
|
||||||
|
log(f"Browser launched (headless={HEADLESS})")
|
||||||
|
|
||||||
|
# ── 2. Open landing page & login ─────────────────────────────
|
||||||
|
log("Navigating to landing page …")
|
||||||
|
page.goto(BASE_URL, wait_until="networkidle", timeout=30_000)
|
||||||
|
|
||||||
|
# Click the "Garázs Nyitása" button to open the LoginModal
|
||||||
|
login_trigger = page.locator("button:has-text('Garázs Nyitása')")
|
||||||
|
login_trigger.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
login_trigger.click()
|
||||||
|
log("Login modal opened")
|
||||||
|
|
||||||
|
# Fill email & password
|
||||||
|
page.fill("#login-email", TEST_EMAIL)
|
||||||
|
page.fill("#login-password", TEST_PASS)
|
||||||
|
log("Login credentials entered")
|
||||||
|
|
||||||
|
# Click the submit button (the first .btn-premium inside the visible form)
|
||||||
|
submit_btn = page.locator("form button[type='submit']").first
|
||||||
|
submit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
submit_btn.click()
|
||||||
|
log("Login submitted – waiting for navigation …")
|
||||||
|
|
||||||
|
# Wait for redirect away from landing page (to /dashboard or /complete-kyc)
|
||||||
|
try:
|
||||||
|
page.wait_for_url(
|
||||||
|
lambda url: "/dashboard" in url or "/complete-kyc" in url,
|
||||||
|
timeout=15_000,
|
||||||
|
)
|
||||||
|
except PwTimeout:
|
||||||
|
# If still on landing, maybe login failed – dump state
|
||||||
|
log(f"URL after login attempt: {page.url}")
|
||||||
|
page.screenshot(path="/tmp/e2e_login_fail.png")
|
||||||
|
raise AssertionError(
|
||||||
|
"Login failed – did not redirect to dashboard. "
|
||||||
|
"Check credentials or UI state."
|
||||||
|
)
|
||||||
|
|
||||||
|
log(f"Login successful – current URL: {page.url}")
|
||||||
|
|
||||||
|
# ── 3. Navigate to Profile page ──────────────────────────────
|
||||||
|
log("Navigating to /profile …")
|
||||||
|
page.goto(f"{BASE_URL}/profile", wait_until="networkidle", timeout=20_000)
|
||||||
|
|
||||||
|
# Wait for the profile page to finish loading (spinner disappears)
|
||||||
|
page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT)
|
||||||
|
# Also wait until the loading spinner is gone
|
||||||
|
page.wait_for_function(
|
||||||
|
"() => !document.querySelector('.animate-spin')",
|
||||||
|
timeout=15_000,
|
||||||
|
)
|
||||||
|
log("Profile page loaded")
|
||||||
|
|
||||||
|
# ── 4. Enter edit mode ───────────────────────────────────────
|
||||||
|
edit_btn = page.locator("button:has-text('Szerkesztés')")
|
||||||
|
edit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
edit_btn.click()
|
||||||
|
log("Edit mode activated")
|
||||||
|
|
||||||
|
# ── 5. Fill in all address fields ────────────────────────────
|
||||||
|
log("Filling address form …")
|
||||||
|
fill_input(page, "Irányítószám", ADDRESS_DATA["address_zip"])
|
||||||
|
fill_input(page, "Város", ADDRESS_DATA["address_city"])
|
||||||
|
fill_input(page, "Utca", ADDRESS_DATA["address_street_name"])
|
||||||
|
fill_input(page, "Közterület jellege", ADDRESS_DATA["address_street_type"])
|
||||||
|
fill_input(page, "Házszám", ADDRESS_DATA["address_house_number"])
|
||||||
|
fill_input(page, "Lépcsőház", ADDRESS_DATA["address_stairwell"])
|
||||||
|
fill_input(page, "Emelet", ADDRESS_DATA["address_floor"])
|
||||||
|
fill_input(page, "Ajtó", ADDRESS_DATA["address_door"])
|
||||||
|
|
||||||
|
# ── 6. Save ──────────────────────────────────────────────────
|
||||||
|
save_btn = page.locator("button:has-text('Mentés')")
|
||||||
|
save_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
save_btn.click()
|
||||||
|
log("Save clicked – waiting for confirmation …")
|
||||||
|
|
||||||
|
# Wait for the success message to appear
|
||||||
|
success_locator = page.locator("text=A profil adatok és okmányok sikeresen frissítve!")
|
||||||
|
try:
|
||||||
|
success_locator.wait_for(state="visible", timeout=10_000)
|
||||||
|
log("Save succeeded – success message visible")
|
||||||
|
except PwTimeout:
|
||||||
|
# Check for error message
|
||||||
|
error_locator = page.locator(".text-red-400")
|
||||||
|
if error_locator.is_visible():
|
||||||
|
error_text = error_locator.text_content()
|
||||||
|
log(f"Save FAILED with error: {error_text}")
|
||||||
|
page.screenshot(path="/tmp/e2e_save_fail.png")
|
||||||
|
raise AssertionError(f"Save failed: {error_text}")
|
||||||
|
|
||||||
|
page.screenshot(path="/tmp/e2e_save_timeout.png")
|
||||||
|
raise AssertionError(
|
||||||
|
"Save timeout – neither success nor error message appeared."
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 7. Reload and verify persistence ─────────────────────────
|
||||||
|
log("Reloading page to verify persistence …")
|
||||||
|
page.reload(wait_until="networkidle")
|
||||||
|
page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT)
|
||||||
|
page.wait_for_function(
|
||||||
|
"() => !document.querySelector('.animate-spin')",
|
||||||
|
timeout=15_000,
|
||||||
|
)
|
||||||
|
log("Page reloaded")
|
||||||
|
|
||||||
|
# Read the display text in the read-only address section
|
||||||
|
# The read-only view shows: Lépcsőház: …, Emelet: …, Ajtó: …
|
||||||
|
page_body = page.locator("body").inner_text()
|
||||||
|
log("── Read-only address block ──")
|
||||||
|
|
||||||
|
# Use a regex to extract values
|
||||||
|
stairwell_match = re.search(r"Lépcsőház:\s*(\S+)", page_body)
|
||||||
|
floor_match = re.search(r"Emelet:\s*(\S+)", page_body)
|
||||||
|
door_match = re.search(r"Ajtó:\s*(\S+)", page_body)
|
||||||
|
|
||||||
|
stairwell_val = stairwell_match.group(1) if stairwell_match else ""
|
||||||
|
floor_val = floor_match.group(1) if floor_match else ""
|
||||||
|
door_val = door_match.group(1) if door_match else ""
|
||||||
|
|
||||||
|
log(f" Stairwell (Lépcsőház): '{stairwell_val}'")
|
||||||
|
log(f" Floor (Emelet): '{floor_val}'")
|
||||||
|
log(f" Door (Ajtó): '{door_val}'")
|
||||||
|
|
||||||
|
# ── 8. Assertion: all three values must be present ───────────
|
||||||
|
errors = []
|
||||||
|
if not stairwell_val or stairwell_val in ("—", "null", "None"):
|
||||||
|
errors.append(f"stairwell (expected '{ADDRESS_DATA['address_stairwell']}', got '{stairwell_val}')")
|
||||||
|
if not floor_val or floor_val in ("—", "null", "None"):
|
||||||
|
errors.append(f"floor (expected '{ADDRESS_DATA['address_floor']}', got '{floor_val}')")
|
||||||
|
if not door_val or door_val in ("—", "null", "None"):
|
||||||
|
errors.append(f"door (expected '{ADDRESS_DATA['address_door']}', got '{door_val}')")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
page.screenshot(path="/tmp/e2e_persistence_fail.png")
|
||||||
|
raise AssertionError(
|
||||||
|
"Hibás backend mentés! Az emelet/ajtó adatok elvesztek az oldal "
|
||||||
|
f"frissítése után.\n Missing/empty fields: {', '.join(errors)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
log("Sikeres E2E teszt: Az adatok megmaradtak a backend frissítés után is!")
|
||||||
|
log("ALL CHECKS PASSED.\n")
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user