Hotel Search App β QA Report
Scope of Testing
This QA report validates the Hotel Search App against the Architect's plan (plan.md) and the Builder's summary (build.md). Testing covers schema adherence, input validation, edge cases, adversarial inputs, security, and interface contract enforcement.
1. Plan vs. Build Compliance
| Plan Requirement |
Implemented? |
Notes |
| Free-form natural language input |
Yes |
Single Gradio Textbox, 5-line height |
SerpApi google_hotels engine |
Yes |
Correct engine parameter in API call |
| Date extraction (5+ formats) |
Yes |
MM/DD/YYYY, YYYY-MM-DD, Month Day Year, Month D-D Year, single dates |
| Price range extraction |
Yes |
Range, upper-bound, lower-bound patterns |
| Travel-agency link exclusion |
Yes |
20+ agency domains in blocklist |
| Top 15 results |
Yes |
properties[:15] enforced |
| Direct hotel website links |
Yes |
get_hotel_link with fallback to Google search |
| Hugging Face deployment ready |
Yes |
Single app.py, requirements.txt, README metadata |
| API key via env variable or UI |
Yes |
Checks UI field first, then SERPAPI_KEY / SERPAPI_API_KEY |
| Graceful error handling |
Yes |
Try/except wraps full pipeline; friendly HTML messages |
Verdict: All plan elements are implemented. No missing features.
2. Input Validation Tests
2.1 Empty Input
| Test |
Input |
Expected |
Result |
| Blank string |
"" |
Error prompt |
PASS β "Please enter a hotel description to search." |
| Whitespace only |
" " |
Error prompt |
PASS β Same message |
2.2 Missing API Key
| Test |
Input |
Key |
Expected |
Result |
| No key anywhere |
Any text |
"" (no env var) |
Key prompt |
PASS β "SerpApi key required" |
2.3 Adversarial / Gibberish Input
| Test |
Input |
Expected |
Result |
| Random characters |
"asdfghjkl 12345" |
No crash; search attempt or no-results |
PASS β Falls back to raw text as query |
| SQL injection attempt |
"'; DROP TABLE hotels; --" |
No crash; input treated as text |
PASS β Regex simply won't match; text sent to SerpApi as query |
| XSS attempt |
"<script>alert('xss')</script>" |
No script execution |
PASS β Gradio HTML component sanitizes output |
| Extremely long input |
5000-character string |
No crash |
PASS β First 120 chars used as query if no location found |
3. Date Parsing Edge Cases
| Input Fragment |
Extracted Check-in |
Extracted Check-out |
Result |
"March 15 to March 18, 2026" |
2026-03-15 |
2026-03-18 |
PASS |
"3/15/2026 - 3/18/2026" |
2026-03-15 |
2026-03-18 |
PASS |
"2026-03-15 to 2026-03-18" |
2026-03-15 |
2026-03-18 |
PASS |
"March 15, 2026" (single date) |
2026-03-15 |
2026-03-16 |
PASS β defaults to 1 night |
| No dates provided |
Tomorrow |
Tomorrow + 1 |
PASS β sensible defaults |
Past date "Jan 1, 2020" |
Adjusted to tomorrow |
Tomorrow + 1 |
PASS β past-date guard works |
"March 15-18, 2026" (range) |
2026-03-15 |
2026-03-18 |
PASS β compact range regex |
4. Price Parsing Edge Cases
| Input Fragment |
min_price |
max_price |
Result |
"$100 to $200" |
100 |
200 |
PASS |
"under $200" |
None |
200 |
PASS |
"budget of $150" |
None |
150 |
PASS |
"above $100" |
100 |
None |
PASS |
"at least $50" |
50 |
None |
PASS |
| No price mentioned |
None |
None |
PASS β no price filter applied |
5. Location Extraction Tests
| Input Fragment |
Extracted Location |
Result |
"hotel in Austin, TX" |
Austin, TX |
PASS |
"near Times Square" |
Times Square |
PASS |
"hotels in San Francisco" |
San Francisco |
PASS |
"close to downtown Nashville" |
downtown Nashville |
PASS |
| No location keyword |
Falls back to first sentence |
PASS β graceful fallback |
6. Travel-Agency Link Filtering
Verified that the blocklist covers: Expedia, Booking.com, Hotels.com, Trivago, Kayak, Priceline, Orbitz, Travelocity, Agoda, Trip.com, Hotwire, CheapTickets, LastMinute, eDreams, Opodo, Wotif, Zuji, MakeMyTrip, Goibibo, Yatra.
- If all results happen to be travel-agency links, the filter relaxes (threshold < 3) to avoid showing zero results. This is a pragmatic trade-off documented in
build.md.
7. Security Review
| Check |
Status |
Notes |
| No hard-coded API keys |
PASS |
Key from UI (masked) or env var only |
No eval() or exec() on user input |
PASS |
Input only used in regex and as SerpApi query param |
| No shell command execution |
PASS |
No subprocess or os.system calls |
External links use rel='noopener noreferrer' |
PASS |
Prevents reverse tabnapping |
| Stack traces hidden from user |
PASS |
Generic error message with exception text only |
Gradio type="password" for API key |
PASS |
Input is masked in UI |
8. Interface Contract Verification
- Parser output dict matches the JSON schema defined in
plan.md.
- SerpApi response is consumed via
results.get("properties", []) with safe .get() access throughout.
- HTML output is well-formed; tested with missing fields (no image, no rating, no amenities).
9. Findings & Recommendations
Issues Found (non-blocking)
- Relative date phrases like "next weekend" or "this Friday" are not parsed. The app defaults to tomorrow, which is acceptable but could be improved.
- Feature matching against results is not performed post-search. The required/desired/avoid features are extracted but only influence the search query indirectly. SerpApi handles the location and price filtering, but amenity-based re-ranking is not implemented.
- No automated test suite. Manual tests pass but a
pytest suite would strengthen CI/CD.
Recommendations
- Add a lightweight NLP layer (or LLM call) for better feature classification and relative date parsing in a future version.
- Add unit tests for
parse_user_input covering the edge cases above.
- Consider rate-limiting or caching SerpApi calls to avoid quota exhaustion during heavy use.