# 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 | `""` | 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) 1. **Relative date phrases** like "next weekend" or "this Friday" are not parsed. The app defaults to tomorrow, which is acceptable but could be improved. 2. **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. 3. **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.