diff --git a/roles/upload-pypi/tasks/main.yaml b/roles/upload-pypi/tasks/main.yaml
index 10e67f25a..b3917705f 100644
--- a/roles/upload-pypi/tasks/main.yaml
+++ b/roles/upload-pypi/tasks/main.yaml
@@ -1,3 +1,8 @@
+# NOTE(ianw) 2022-07 : If you modify this, see the comments about
+# testing in test-playbooks/python/upload-pypi.yaml.  Once the change
+# is finalised, you should do one run that uploads the sandbox project
+# to test.pypi.org to validate the full path.
+
 - name: Validate password/token combo
   fail:
     msg: 'Specify either username/password or api_token'
diff --git a/test-playbooks/python/upload-pypi.yaml b/test-playbooks/python/upload-pypi.yaml
index 8587f1d8d..ee2e0ffe4 100644
--- a/test-playbooks/python/upload-pypi.yaml
+++ b/test-playbooks/python/upload-pypi.yaml
@@ -28,3 +28,43 @@
       fail:
         msg: 'upload-pypi did not fail as it should'
       when: _role_failed is not true
+
+    - name: Import sandbox project
+      shell: |
+        git clone https://opendev.org/opendev/sandbox
+        pushd sandbox
+        python3 setup.py bdist_wheel
+      args:
+        executable: '/bin/bash'
+
+    # NOTE(ianw) 2022-07 : You can not upload the same release twice,
+    # so this is disabled by default.  Before we commit changes to the
+    # upload-pypi role, we should do one test run with this
+    # uncommented to validate the entire path, then re-comment it for
+    # the final commit.
+    - name: Run full upload
+      set_fact:
+        _run_full_upload: false
+
+    # This value is restricted to uploading the sandbox package to
+    # test.pypi.org; it is not kept in plain text to avoid things that
+    # grep for accidentally committed tokens finding it, but it is not
+    # particularly secret.  OpenDev admins can revoke it from the
+    # "openstackci" test.pypi.org user if there is an issue.
+    - name: Set API token
+      set_fact:
+        _api_token: |
+            cHlwaS1BZ0VOZEdWemRDNXdlWEJwTG05eVp3SWtOMk0wTmpBMU9HSXRORGszTVMwME9ERTRMV0kx
+            Tm1NdFpEUTNNalJsWmpneE16TTRBQUk0ZXlKd1pYSnRhWE56YVc5dWN5STZJSHNpY0hKdmFtVmpk
+            SE1pT2lCYkluTmhibVJpYjNnaVhYMHNJQ0oyWlhKemFXOXVJam9nTVgwQUFBWWdmTGN1cHNaeWw3
+            NkI0Ri1Bd3FDR19VZHlNSWVMQzRHWHZTUjhSalEyQlJnCg==
+
+    - name: Upload sandbox
+      include_role:
+        name: upload-pypi
+      vars:
+        pypi_info:
+          api_token: '{{ _api_token | b64decode }}'
+          repository: testpypi
+        pypi_path: '{{ ansible_user_dir }}/sandbox/dist'
+      when: _run_full_upload
diff --git a/zuul-tests.d/python-jobs.yaml b/zuul-tests.d/python-jobs.yaml
index 782f21426..2f4175995 100644
--- a/zuul-tests.d/python-jobs.yaml
+++ b/zuul-tests.d/python-jobs.yaml
@@ -412,6 +412,7 @@
     description: Test the upload-pypi role
     files:
       - roles/upload-pypi/.*
+      - test-playbooks/python/upload-pypi.yaml
     run: test-playbooks/python/upload-pypi.yaml
 
 # -* AUTOGENERATED *-