Update DocuSign auth docs and coverage
This commit is contained in:
parent
3be3903986
commit
3b27a0fd5b
17
README.md
17
README.md
|
|
@ -10,7 +10,7 @@ It downloads templates via the Adobe Sign API, converts them to DocuSign format,
|
||||||
1. **Authenticates** with Adobe Sign via OAuth (one-time browser flow, tokens saved to `.env`)
|
1. **Authenticates** with Adobe Sign via OAuth (one-time browser flow, tokens saved to `.env`)
|
||||||
2. **Downloads** templates — PDF, metadata, and form field definitions
|
2. **Downloads** templates — PDF, metadata, and form field definitions
|
||||||
3. **Converts** each template to a DocuSign `envelopeTemplate` JSON, mapping all field types, coordinates, recipient roles, and conditional field logic
|
3. **Converts** each template to a DocuSign `envelopeTemplate` JSON, mapping all field types, coordinates, recipient roles, and conditional field logic
|
||||||
4. **Authenticates** with DocuSign via JWT grant (one-time browser consent, then fully automated)
|
4. **Authenticates** with DocuSign via OAuth Authorization Code Grant (one-time browser login, then refresh-token based)
|
||||||
5. **Uploads** the converted template to DocuSign via the REST API
|
5. **Uploads** the converted template to DocuSign via the REST API
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -19,7 +19,7 @@ It downloads templates via the Adobe Sign API, converts them to DocuSign format,
|
||||||
|
|
||||||
- Python 3.10+
|
- Python 3.10+
|
||||||
- An Adobe Sign OAuth app (EU2 shard) with scopes: `library_read:self library_write:self user_read:self`
|
- An Adobe Sign OAuth app (EU2 shard) with scopes: `library_read:self library_write:self user_read:self`
|
||||||
- A DocuSign developer account with an integration key and RSA keypair
|
- A DocuSign developer account with an OAuth app client ID and client secret
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -46,14 +46,13 @@ python3 src/adobe_auth.py
|
||||||
Opens a browser. After authorizing, paste the redirect URL back into the terminal.
|
Opens a browser. After authorizing, paste the redirect URL back into the terminal.
|
||||||
Tokens are saved to `.env` and auto-refreshed on subsequent runs.
|
Tokens are saved to `.env` and auto-refreshed on subsequent runs.
|
||||||
|
|
||||||
**4. Grant consent for DocuSign** (one-time per user):
|
**4. Authorize DocuSign** (one-time per user):
|
||||||
```bash
|
```bash
|
||||||
python3 src/docusign_auth.py --consent
|
python3 src/docusign_auth.py --authorize
|
||||||
```
|
```
|
||||||
Opens a browser for the DocuSign OAuth consent screen. After approving, paste the
|
Opens a browser for the DocuSign OAuth screen. After approving, paste the
|
||||||
redirect URL back into the terminal. This grants the `impersonation` scope required
|
redirect URL back into the terminal. The app stores both access and refresh
|
||||||
for JWT grant. After this runs once, all subsequent API calls use JWT automatically —
|
tokens in `.env`, and later API calls refresh access tokens automatically.
|
||||||
no further browser interaction needed.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -274,7 +273,7 @@ src/
|
||||||
adobe_api.py # Adobe Sign API client (auto token refresh)
|
adobe_api.py # Adobe Sign API client (auto token refresh)
|
||||||
download_templates.py # List and download templates from Adobe Sign
|
download_templates.py # List and download templates from Adobe Sign
|
||||||
compose_docusign_template.py # Core conversion: Adobe Sign → DocuSign JSON
|
compose_docusign_template.py # Core conversion: Adobe Sign → DocuSign JSON
|
||||||
docusign_auth.py # DocuSign JWT auth + one-time consent flow
|
docusign_auth.py # DocuSign auth-code + refresh-token helper
|
||||||
upload_docusign_template.py # Upsert upload: PUT if exists, POST if not
|
upload_docusign_template.py # Upsert upload: PUT if exists, POST if not
|
||||||
migrate_template.py # End-to-end CLI runner (download → convert → upload)
|
migrate_template.py # End-to-end CLI runner (download → convert → upload)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ Then open [http://localhost:8000](http://localhost:8000).
|
||||||
|
|
||||||
- [ ] Top bar shows two disconnected chips (red dot): "Adobe Sign" and "DocuSign"
|
- [ ] Top bar shows two disconnected chips (red dot): "Adobe Sign" and "DocuSign"
|
||||||
- [ ] Click "Adobe Sign" chip → connects via `.env` refresh token → chip turns green
|
- [ ] Click "Adobe Sign" chip → connects via `.env` refresh token → chip turns green
|
||||||
- [ ] Click "DocuSign" chip → connects via JWT grant → chip turns green
|
- [ ] Click "DocuSign" chip → redirects through OAuth if needed, then chip turns green
|
||||||
- [ ] Disconnecting either chip → chip turns red → templates clear
|
- [ ] Disconnecting either chip → chip turns red → templates clear
|
||||||
|
|
||||||
## 4. Templates View
|
## 4. Templates View
|
||||||
|
|
|
||||||
|
|
@ -96,11 +96,10 @@ def test_adobe_exchange_rejects_missing_code():
|
||||||
|
|
||||||
|
|
||||||
def test_docusign_connect_stores_token():
|
def test_docusign_connect_stores_token():
|
||||||
"""GET /api/auth/docusign/connect uses JWT grant from .env → session connected."""
|
"""GET /api/auth/docusign/connect uses cached OAuth credentials → session connected."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import web.routers.auth as auth_module
|
|
||||||
|
|
||||||
with patch("docusign_auth.get_access_token", return_value="ds-jwt-token"):
|
with patch("docusign_auth.get_access_token", return_value="ds-oauth-token"):
|
||||||
resp = client.get("/api/auth/docusign/connect")
|
resp = client.get("/api/auth/docusign/connect")
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
@ -113,6 +112,19 @@ def test_docusign_connect_stores_token():
|
||||||
assert status_resp.json()["docusign"] is True
|
assert status_resp.json()["docusign"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_docusign_connect_requests_authorization_when_refresh_token_missing():
|
||||||
|
"""GET /api/auth/docusign/connect returns an auth URL when first-time authorization is needed."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
with patch("docusign_auth.get_access_token", side_effect=RuntimeError("No DocuSign refresh token found")), \
|
||||||
|
patch("docusign_auth.build_authorization_url", return_value="https://example.com/oauth"):
|
||||||
|
resp = client.get("/api/auth/docusign/connect")
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json()["authorization_required"] is True
|
||||||
|
assert resp.json()["authorization_url"] == "https://example.com/oauth"
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
def test_disconnect_clears_token():
|
def test_disconnect_clears_token():
|
||||||
"""After disconnect, status shows platform as disconnected."""
|
"""After disconnect, status shows platform as disconnected."""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue