From ef522b456fd1f1bcd723df595bd154579113733d Mon Sep 17 00:00:00 2001 From: jacobyoby Date: Tue, 8 Sep 2026 23:30:28 -0700 Subject: [PATCH] Wire CI proofs: issue-932 repro, sitemap url-root assertion, 404 on unset url root - Add issue_932_repro.sh expect-fail/pass steps to e2e workflow - Install geocode.py alongside util.py/email_crypto.py in the base install step - seo_check.sh now accepts optional url_root param and asserts every starts with it - Test config sets url root to https://law.example.org (distinct from localhost) - sitemap route returns 404 when url root is unset (relative URLs are invalid per sitemaps.org) --- .github/workflows/e2e-issue-981.yml | 16 ++++++++++++---- .github/workflows/e2e/seo_check.sh | 19 ++++++++++++++++--- .../docassemble/webapp/main/views.py | 13 +++++++++++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.github/workflows/e2e-issue-981.yml b/.github/workflows/e2e-issue-981.yml index 15869bc41..2314dd9a4 100644 --- a/.github/workflows/e2e-issue-981.yml +++ b/.github/workflows/e2e-issue-981.yml @@ -85,7 +85,7 @@ jobs: fi echo "KEY=$KEY" >> "$GITHUB_ENV" - - name: Issue-15 setup - install the SEO test interview and configure dispatch + social.og (no image) + - name: Issue-15 setup - install the SEO test interview and configure dispatch + social.og (no image) + url root run: | D=$(docker exec da bash -c "ls -d /usr/share/docassemble/local*/lib/python3*/site-packages/docassemble/demo/data/questions") docker cp .github/workflows/e2e/test_seo.yml da:"$D/test_seo.yml" @@ -97,13 +97,14 @@ jobs: c=yaml.safe_load(open(p)) or {} c.setdefault('dispatch', {})['seo']='docassemble.demo:data/questions/test_seo.yml' og=c.setdefault('social', {}).setdefault('og', {}); og.pop('image', None); og.setdefault('locale','en_US') + c['url root']='https://law.example.org' yaml.safe_dump(c, open(p,'w'), default_flow_style=False) PYEOF docker exec da bash -c "supervisorctl restart uwsgi" sleep 30 - name: Issue-15 control - SEO head tags and sitemap must be absent on the unpatched release - run: bash .github/workflows/e2e/seo_check.sh http://localhost:8080 expect-fail + run: bash .github/workflows/e2e/seo_check.sh http://localhost:8080 expect-fail https://law.example.org - name: NFC control - stock PDF fill must lose the combining accent (NFD stays NFD) run: | @@ -118,6 +119,9 @@ jobs: - name: Issue-18 control - a11y fixes must be absent on the unpatched release run: bash .github/workflows/e2e/a11y_check.sh http://localhost:8080 expect-fail + - name: Issue-932 control - unpatched release must crash when geopy is broken + run: bash .github/workflows/e2e/issue_932_repro.sh http://localhost:8080 da expect-fail + - name: Control - unpatched release must reject the plain dict if: env.ALREADY_FIXED != '1' run: bash .github/workflows/e2e/issue_981_repro.sh http://localhost:8080 "$KEY" expect-fail @@ -147,12 +151,13 @@ jobs: docker cp .github/workflows/e2e/test_issue_968.yml da:"$D/test_issue_968.yml" bash .github/workflows/e2e/issue_968_repro.sh http://localhost:8080 da expect-fail - - name: Install this branch's util.py and email_crypto.py, restart + - name: Install this branch's util.py, email_crypto.py, and geocode.py, restart run: | U=$(docker exec da bash -c "ls /usr/share/docassemble/local*/lib/python3*/site-packages/docassemble/base/util.py") docker cp docassemble_base/docassemble/base/util.py da:"$U" B=$(docker exec da bash -c "ls -d /usr/share/docassemble/local*/lib/python3*/site-packages/docassemble/base") docker cp docassemble_base/docassemble/base/email_crypto.py da:"$B/email_crypto.py" + docker cp docassemble_base/docassemble/base/geocode.py da:"$B/geocode.py" docker exec da bash -c "supervisorctl restart uwsgi" sleep 30 @@ -162,6 +167,9 @@ jobs: PY=$(docker exec da bash -c "ls /usr/share/docassemble/local*/bin/python3 | head -1") docker exec da "$PY" /tmp/email_crypto_check.py + - name: Issue-932 - patched server must import fine when geopy is broken + run: bash .github/workflows/e2e/issue_932_repro.sh http://localhost:8080 da expect-pass + - name: Issue-968 - patched server must report the real attribute run: bash .github/workflows/e2e/issue_968_repro.sh http://localhost:8080 da expect-pass @@ -219,7 +227,7 @@ jobs: docker cp docassemble_webapp/docassemble/webapp/main/views.py da:"$WB/main/views.py" docker exec da bash -c "supervisorctl restart uwsgi" sleep 30 - bash .github/workflows/e2e/seo_check.sh http://localhost:8080 expect-pass + bash .github/workflows/e2e/seo_check.sh http://localhost:8080 expect-pass https://law.example.org - name: Issue-19 - install FontAwesome CSS build; JS bundle gone, page weight must drop run: | diff --git a/.github/workflows/e2e/seo_check.sh b/.github/workflows/e2e/seo_check.sh index 37e398e57..e4429e28b 100755 --- a/.github/workflows/e2e/seo_check.sh +++ b/.github/workflows/e2e/seo_check.sh @@ -1,12 +1,13 @@ #!/usr/bin/env bash # Fail-first SEO check for fork issue #15. # -# Usage: seo_check.sh expect-fail|expect-pass +# Usage: seo_check.sh expect-fail|expect-pass [url_root] # # Requires the test interview test_seo.yml installed in docassemble.demo and # the server config to carry: # dispatch: {seo: docassemble.demo:data/questions/test_seo.yml} # social: {og: {locale: en_US}} # og configured WITHOUT an image +# url root: # must differ from base_url for meaningful check # # Assertions against the live interview page and sitemap: # 1. carries the interview's metadata description, @@ -15,11 +16,12 @@ # 2. points at the clean interview entry URL. # 3. og:title is emitted even though no og:image is configured (ungated). # 4. /sitemap.xml lists the dispatch entry. +# 5. Every in sitemap.xml starts with url_root (when url_root is given). # # expect-fail: every assertion must FAIL on the unpatched release. expect-pass: # every assertion must hold. set -euo pipefail -base="$1"; mode="$2" +base="$1"; mode="$2"; url_root="${3:-}" yaml="docassemble.demo:data/questions/test_seo.yml" page=$(curl -sL "$base/interview?i=$yaml") @@ -27,7 +29,8 @@ desc=$(echo "$page" | grep -c 'name="description" content="A test interview whos desc_total=$(echo "$page" | grep -c 'name="description"' || true) canon=$(echo "$page" | grep -c 'rel="canonical" href="' || true) og=$(echo "$page" | grep -c 'name="og:title"' || true) -sitemap=$(curl -s "$base/sitemap.xml" | grep -cE '.*/start/seo/?' || true) +sitemap_body=$(curl -s "$base/sitemap.xml") +sitemap=$(echo "$sitemap_body" | grep -cE '.*/start/seo/?' || true) echo "description=$desc (total description tags=$desc_total) canonical=$canon og:title=$og sitemap=$sitemap" @@ -43,6 +46,16 @@ case "$mode" in echo "FAIL: SEO fix not fully present" exit 1 fi + # When url_root is provided, verify every entry starts with it + if [ -n "$url_root" ]; then + loc_count=$(echo "$sitemap_body" | grep -cE '' || true) + loc_with_root=$(echo "$sitemap_body" | grep -cE "${url_root}/" || true) + if [ "$loc_count" != "$loc_with_root" ] || [ "$loc_count" = "0" ]; then + echo "FAIL: sitemap entries do not all start with url root '$url_root' ($loc_with_root of $loc_count)" + exit 1 + fi + echo "pass: all $loc_count sitemap entries start with $url_root" + fi echo "pass: description, canonical, ungated og:title, and sitemap entry all present" ;; *) echo "mode must be expect-fail or expect-pass"; exit 2 ;; esac diff --git a/docassemble_webapp/docassemble/webapp/main/views.py b/docassemble_webapp/docassemble/webapp/main/views.py index b05f73aca..261624367 100644 --- a/docassemble_webapp/docassemble/webapp/main/views.py +++ b/docassemble_webapp/docassemble/webapp/main/views.py @@ -414,7 +414,16 @@ def favicon_browserconfig(): def sitemap(): """List the public, listed dispatch interviews so search engines can find every court form without crawling for them. Unlisted interviews - and anything not in `dispatch` are omitted.""" + and anything not in `dispatch` are omitted. + + Returns 404 when `url root` is not configured in the server config. + Sitemaps require absolute URLs per the sitemaps.org protocol; emitting + relative paths would produce an invalid sitemap that search engines + would reject. Returning 404 (rather than 500) signals that the sitemap + is intentionally unavailable, not broken.""" + url_root = daconfig.get('url root', '').rstrip('/') + if not url_root: + return ('Sitemap unavailable: url root not configured', 404) urls = [] for key, yaml_filename in sorted(daconfig.get('dispatch', {}).items()): try: @@ -425,7 +434,7 @@ def sitemap(): continue # Built from url root, not the request scheme/host, so a plain-http hop # behind a TLS-terminating proxy cannot emit http:// entries. - urls.append(daconfig.get('url root', '').rstrip('/') + url_for('interview.redirect_to_interview', dispatch=key)) + urls.append(url_root + url_for('interview.redirect_to_interview', dispatch=key)) body = '\n\n' for url in urls: body += ' ' + url.replace('&', '&') + '\n'