adobe-to-docusign-migrator/docs/IMPLEMENTATION-PLAN.md

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*