268 lines
10 KiB
Markdown
268 lines
10 KiB
Markdown
# Implementation Plan — Adobe → DocuSign Migrator v2
|
|
|
|
*Created: 2026-04-17*
|
|
|
|
---
|
|
|
|
## Objective
|
|
|
|
Extend the CLI migration pipeline with:
|
|
1. **Idempotent upload** — update the most recently modified DocuSign template with the same name instead of always creating a new one.
|
|
2. **Web UI** — browser-based app that lets users authenticate to both platforms, browse templates side-by-side, and run migrations with live feedback.
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
adobe-to-docusign-migrator/
|
|
├── src/ # Core pipeline (existing)
|
|
│ ├── compose_docusign_template.py
|
|
│ ├── upload_docusign_template.py ← Phase 1: upsert logic added
|
|
│ ├── adobe_api.py
|
|
│ ├── docusign_auth.py
|
|
│ └── ...
|
|
├── web/ # New — FastAPI web app
|
|
│ ├── app.py # FastAPI entrypoint
|
|
│ ├── config.py # Env/settings
|
|
│ ├── session.py # Session middleware
|
|
│ ├── routers/
|
|
│ │ ├── auth.py # Adobe + DocuSign OAuth
|
|
│ │ ├── templates.py # Listing + status API
|
|
│ │ └── migrate.py # Migration trigger + history
|
|
│ └── static/
|
|
│ ├── index.html # Main SPA page
|
|
│ ├── app.js # Vanilla JS app
|
|
│ └── style.css
|
|
├── tests/
|
|
│ ├── test_mapping.py # Existing field-mapping unit tests
|
|
│ ├── test_upload_upsert.py # Phase 1: upsert logic
|
|
│ ├── test_api_health.py # Phase 2: health endpoint
|
|
│ ├── test_api_auth.py # Phase 3: auth endpoints
|
|
│ ├── test_api_templates.py # Phase 4: template listing
|
|
│ ├── test_api_migrate.py # Phase 5: migration API
|
|
│ └── test_e2e.py # Phase 7: full pipeline e2e
|
|
└── docs/
|
|
├── IMPLEMENTATION-PLAN.md ← this file
|
|
└── agent-harness/
|
|
└── EXECUTION-BOARD.md
|
|
```
|
|
|
|
---
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Choice | Reason |
|
|
|---|---|---|
|
|
| Backend | FastAPI | Matches existing Python; async; auto-generates OpenAPI docs |
|
|
| Sessions | `starlette-sessions` + `itsdangerous` | Lightweight, no DB needed |
|
|
| Frontend | Vanilla HTML/CSS/JS | No build tooling; straightforward for this scope |
|
|
| Testing | `pytest` + `httpx` + `respx` | FastAPI's recommended test stack; respx for mocking HTTP |
|
|
| HTTP mocking | `respx` | Intercepts `httpx` calls for Adobe/DocuSign API mocks |
|
|
|
|
---
|
|
|
|
## Phase 1 — Idempotent Upload
|
|
|
|
**Goal:** `upload_docusign_template.py` should update the most recently modified DocuSign template with the same name, rather than always creating a new one.
|
|
|
|
**Logic:**
|
|
1. After loading the template JSON, extract its `name`.
|
|
2. Call `GET /v2.1/accounts/{accountId}/templates?search_text={name}` to list matches.
|
|
3. Filter to exact name matches; sort by `lastModified` descending; take the first.
|
|
4. If found: `PUT /v2.1/accounts/{accountId}/templates/{templateId}` (update).
|
|
5. If not found: `POST /v2.1/accounts/{accountId}/templates` (create).
|
|
6. Print `Updated template {id}` or `Created template {id}`.
|
|
7. Add `--force-create` flag to bypass upsert and always create.
|
|
|
|
**Tests (`tests/test_upload_upsert.py`):**
|
|
- `test_creates_when_no_match` — no existing templates; POST called.
|
|
- `test_updates_most_recent_when_match` — two existing templates with same name; PUT called on newer one.
|
|
- `test_force_create_bypasses_upsert` — `--force-create` always POSTs.
|
|
- `test_partial_name_match_ignored` — template name contains search term but isn't exact; still creates.
|
|
|
|
---
|
|
|
|
## Phase 2 — FastAPI Backend Foundation
|
|
|
|
**Goal:** Runnable FastAPI app with health endpoint, env config, and session middleware.
|
|
|
|
**Endpoints:**
|
|
- `GET /health` → `{"status": "ok", "version": "2.0"}`
|
|
|
|
**Tests (`tests/test_api_health.py`):**
|
|
- `test_health_returns_200`
|
|
- `test_health_response_shape`
|
|
|
|
---
|
|
|
|
## Phase 3 — Auth Endpoints
|
|
|
|
**Goal:** Users can connect to Adobe Sign and DocuSign from the browser; tokens stored in server-side session.
|
|
|
|
**Adobe Sign (OAuth 2.0 Authorization Code):**
|
|
- `GET /api/auth/adobe/start` → redirect to Adobe Sign OAuth URL
|
|
- `GET /api/auth/adobe/callback?code=...` → exchange code for tokens; store in session
|
|
- `GET /api/auth/adobe/disconnect` → clear Adobe tokens from session
|
|
|
|
**DocuSign (OAuth 2.0 Authorization Code — demo sandbox):**
|
|
- `GET /api/auth/docusign/start` → redirect to DocuSign OAuth URL
|
|
- `GET /api/auth/docusign/callback?code=...` → exchange code for tokens; store in session
|
|
- `GET /api/auth/docusign/disconnect` → clear DocuSign tokens from session
|
|
|
|
**Status:**
|
|
- `GET /api/auth/status` → `{"adobe": true/false, "docusign": true/false}`
|
|
|
|
**Tests (`tests/test_api_auth.py`):**
|
|
- `test_status_unauthenticated` — both false on fresh session.
|
|
- `test_adobe_callback_stores_token` — mock token exchange; session updated.
|
|
- `test_docusign_callback_stores_token` — same for DocuSign.
|
|
- `test_disconnect_clears_token` — after disconnect, status shows false.
|
|
|
|
---
|
|
|
|
## Phase 4 — Template Listing API
|
|
|
|
**Goal:** Expose Adobe and DocuSign template lists; compute per-template migration status.
|
|
|
|
**Endpoints:**
|
|
- `GET /api/templates/adobe` → list of Adobe Sign library documents
|
|
- `GET /api/templates/docusign` → list of DocuSign templates
|
|
- `GET /api/templates/status` → merged view: each Adobe template tagged as:
|
|
- `not_migrated` — no DocuSign template with same name
|
|
- `migrated` — at least one DocuSign template with exact name match
|
|
- `needs_update` — Adobe template modified after the matched DocuSign template
|
|
|
|
**Tests (`tests/test_api_templates.py`):**
|
|
- `test_adobe_list_requires_auth` — 401 if not authenticated.
|
|
- `test_adobe_list_returns_templates` — mock Adobe API; correct shape.
|
|
- `test_docusign_list_returns_templates` — mock DocuSign API.
|
|
- `test_status_not_migrated` — Adobe template with no DS match → `not_migrated`.
|
|
- `test_status_migrated` — name match exists → `migrated`.
|
|
- `test_status_needs_update` — Adobe modified after DS template → `needs_update`.
|
|
|
|
---
|
|
|
|
## Phase 5 — Migration API
|
|
|
|
**Goal:** Trigger migration of selected Adobe templates and retrieve history.
|
|
|
|
**Endpoints:**
|
|
- `POST /api/migrate` — body: `{"adobe_template_ids": ["id1", "id2"]}`
|
|
- Downloads each template, runs `compose_docusign_template.py`, uploads via upsert
|
|
- Returns `{"results": [{"adobe_id": "...", "docusign_id": "...", "status": "created|updated|failed", "error": null}]}`
|
|
- `GET /api/migrate/history` — reads `migration-output/.history.json`; returns past runs
|
|
|
|
**History record schema:**
|
|
```json
|
|
{
|
|
"timestamp": "2026-04-17T10:30:00Z",
|
|
"adobe_template_name": "NDA",
|
|
"adobe_template_id": "CBJ...",
|
|
"docusign_template_id": "7dfd...",
|
|
"action": "created|updated",
|
|
"status": "success|failed",
|
|
"error": null
|
|
}
|
|
```
|
|
|
|
**Tests (`tests/test_api_migrate.py`):**
|
|
- `test_migrate_requires_auth` — 401 if not authenticated.
|
|
- `test_migrate_single_template_creates` — mock Adobe download + DS upload (no existing); returns created.
|
|
- `test_migrate_single_template_updates` — mock with existing DS template; returns updated.
|
|
- `test_migrate_records_history` — after run, history file updated.
|
|
- `test_history_returns_past_runs` — GET history returns written records.
|
|
- `test_migrate_handles_partial_failure` — one template fails; others succeed; partial results returned.
|
|
|
|
---
|
|
|
|
## Phase 6 — Frontend
|
|
|
|
**Goal:** Single-page app served at `/`; no build step.
|
|
|
|
**Layout:**
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Adobe Sign → DocuSign Migrator [auth]│
|
|
├───────────────────────┬─────────────────────────────┤
|
|
│ Adobe Sign Templates │ DocuSign Templates │
|
|
│ [connect] │ [connect] │
|
|
│ ───────────────── │ ───────────────────────── │
|
|
│ ● NDA [●] │ ● NDA │
|
|
│ ○ Sales Agmt [○] │ ● David Tag Demo │
|
|
│ ○ Rob Test [○] │ │
|
|
│ │ │
|
|
│ [Migrate Selected] │ │
|
|
└───────────────────────┴─────────────────────────────┘
|
|
```
|
|
|
|
**Status badges:**
|
|
- Green dot = Migrated
|
|
- Yellow dot = Needs Update
|
|
- Red dot = Not Migrated
|
|
|
|
**Migrate flow:**
|
|
1. User checks templates to migrate.
|
|
2. Clicks "Migrate Selected."
|
|
3. Progress shown inline per template (spinner → check/error).
|
|
4. History section at bottom shows past runs.
|
|
|
|
---
|
|
|
|
## Phase 7 — End-to-End & Regression Tests
|
|
|
|
**Goal:** Ensure the full pipeline works together and existing behaviour doesn't regress.
|
|
|
|
**`tests/test_e2e.py`:**
|
|
- `test_full_migration_flow` — using TestClient + respx mocks:
|
|
1. Connect Adobe (mock OAuth callback)
|
|
2. Connect DocuSign (mock OAuth callback)
|
|
3. GET /api/templates/status → at least one `not_migrated`
|
|
4. POST /api/migrate → status `created`
|
|
5. GET /api/templates/status → now `migrated`
|
|
6. POST /api/migrate again → status `updated`
|
|
7. GET /api/migrate/history → two entries
|
|
|
|
**`tests/test_regression.py`:**
|
|
- Runs `compose_docusign_template.py` on all fixtures in `sample-templates/`
|
|
- Validates output against expected JSON snapshots in `tests/fixtures/expected/`
|
|
- Any field type regression fails the test
|
|
- Run via `pytest tests/test_regression.py` — no live API calls needed
|
|
|
|
---
|
|
|
|
## Running Tests
|
|
|
|
```bash
|
|
# All tests
|
|
pytest tests/ -v
|
|
|
|
# Unit only (no live API)
|
|
pytest tests/ -v -m "not integration"
|
|
|
|
# Regression only
|
|
pytest tests/test_regression.py -v
|
|
|
|
# With coverage
|
|
pytest tests/ --cov=src --cov=web --cov-report=term-missing
|
|
```
|
|
|
|
---
|
|
|
|
## Dependencies to Add
|
|
|
|
```
|
|
fastapi
|
|
uvicorn[standard]
|
|
starlette-sessions
|
|
itsdangerous
|
|
httpx
|
|
respx
|
|
pytest-asyncio
|
|
pytest-cov
|
|
```
|
|
|
|
---
|
|
|
|
*Last updated: 2026-04-17*
|