## Implementation Plan: Add URL format validation to `SeoRunForm` ### 1. Change location and content **File:** `devplacepy/models.py` **Target:** `SeoRunForm` class (approx. lines 466–472), specifically the `url_scheme` field validator method. **Change:** Replace the existing minimal validator with one that mirrors `IsslopRunForm`’s logic but restricted to `http://` and `https://` only. The validator must: - Strip whitespace. - Reject inputs that don’t start with a valid scheme (`http://`, `https://`). - Raise `ValueError` with a clear message (e.g., `"URL must start with http:// or https://"`). - *Optionally* auto-prepend `https://` if the input looks like a hostname (no scheme at all). This is not strictly required by the ticket but matches downstream `_normalize_url` behaviour and is less disruptive. The plan here includes it for consistency; if omitted, reject relative paths explicitly. **Concrete diff (recommended approach – reject unless valid scheme present):** ```python # At top of models.py, add (if not already present) SEO_URL_SCHEME_PATTERN = re.compile(r"^https?://", re.IGNORECASE) # Inside SeoRunForm, replace the existing url_scheme method: @field_validator("url") @classmethod def url_scheme(cls, value): text = value.strip() if not SEO_URL_SCHEME_PATTERN.match(text): raise ValueError("URL must start with http:// or https://") return text ``` If auto-prepend is desired (align with `_normalize_url`), use: ```python @field_validator("url") @classmethod def url_scheme(cls, value): text = value.strip() if not text: raise ValueError("URL cannot be blank") if not re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", text): text = "https://" + text if not re.match(r"^https?://", text): raise ValueError("URL must be an http(s) source location") return text ``` **Recommendation:** The simpler reject-only approach is sufficient and avoids introducing auto-correction that might confuse users. The ticket only asks for rejection. Choose the first snippet. ### 2. Test updates **File:** `tests/api/tools/seo.py` Add three new test functions (or parameterized tests) covering: - Input `"ahttps://devplace.net/sitem"` → HTTP 422 - Input `"/feed"` → HTTP 422 - Input `"javascript:void(0)"` → HTTP 422 - (Optional) Input `"devplace.net"` without scheme → confirm rejection (or acceptance if auto-prepend is enabled – adjust accordingly) - Existing happy path must still pass. After adding tests, run `make test-unit` inside the project’s virtual environment (ensuring `PIP_REQUIRE_VIRTUALENV=0` or using `.venv`). ### 3. Execution environment Because earlier attempts failed due to PEP 668 restrictions, **all commands must be run inside an activated virtual environment**: ```bash python3 -m venv .venv source .venv/bin/activate pip install -e '.[dev]' -q ``` Then execute tests and linting: ```bash make test-unit ruff check . ``` Do **not** use `pip install` with `--break-system-packages` or outside a venv. ### 4. Merge conflict avoidance Before committing, ensure the branch is rebased on the latest `main` (or target branch) and that no uncommitted changes exist for the files listed in the merge conflict report. Only modify `devplacepy/models.py` and `tests/api/tools/seo.py`. If conflicts arise, pause and resolve manually (no automatic merge). --- ## Definition of Done - [ ] `SeoRunForm` in `devplacepy/models.py` rejects URLs that do not start with `http://` or `https://`, returning a validation error with a clear message. - [ ] The fix does not require a database migration or change to any other service. - [ ] New unit tests in `tests/api/tools/seo.py` verify that: - `"ahttps://devplace.net/sitem"` returns 422 - `"/feed"` returns 422 - `"javascript:..."` returns 422 - A valid URL `"https://valid.com"` still succeeds (200) - [ ] All existing tests in `tests/api/tools/seo.py` continue to pass. - [ ] The project’s test command (`make test-unit`) exits with code 0 when run inside the project’s virtual environment. - [ ] The project’s lint command (`ruff check .`) exits with code 0. - [ ] No merge conflicts exist with the base branch for any of the changed files. - [ ] A commit is created with a message like `"fix: validate SEO URLs require http/https scheme"` referencing the ticket.