Language-agnostic harness that exercises the Dropbox Sign OpenAPI SDKs. Each
supported SDK lives inside a dedicated Docker image; the ./run script feeds
a normalised JSON payload to the image and returns a normalised JSON response,
which makes it easy to drive the same scenario through every SDK from a single
test suite.
Supported SDKs: dotnet, java, node, php, python, ruby (csharp is
accepted as an alias for dotnet).
See [./demo](./demo) for an example of how to embed the harness in your own
test code.
# 1. Build all SDK images (or just the ones you care about, e.g. ./build python)
./build all
# 2. Point at production and drop in a Dropbox Sign API key tied to a
# test account. Keep tests in test_mode (see "Safety" below).
export API_KEY="YOUR_DROPBOX_SIGN_API_KEY"
export SERVER="api.hellosign.com"
# 3. Run the pytest suite against one SDK...
LANGUAGE=python pytest -svra tests/
# ...or fan the same tests out across every SDK in one go
LANGUAGES=python,node,php,ruby,java,dotnet pytest -svra tests/Firing a single request (no pytest) instead:
./run \
--sdk=python \
--auth_type=apikey \
--auth_key="$API_KEY" \
--server=api.hellosign.com \
--json="${PWD}/tests/fixtures/accounts/accountCreate-example_01.json"Safety: the harness talks to the real Dropbox Sign API. Use an API key bound to a dedicated test account, and keep
test_mode: truein payloads that create signature requests or unclaimed drafts. Rotate the key if it ever lands in shell history, CI logs, or a screenshot.
The pytest suite and helper utilities read the following variables. ./run
itself only takes CLI flags — it does not read env vars.
| Variable | Scope | Required | Default | Meaning |
|---|---|---|---|---|
API_KEY |
pytest | yes | — | Dropbox Sign API key used for every request issued by the suite. |
SERVER |
pytest | yes | — | API host (no scheme). Production is api.hellosign.com. |
LANGUAGES |
pytest | yes* | — | Comma-separated list of SDKs to exercise, e.g. python,node. Turns pytest tests/ into a matrix run. |
LANGUAGE |
pytest | yes* | — | Single SDK; kept for backwards compatibility. Set exactly one of LANGUAGES / LANGUAGE. |
CLIENT_ID |
pytest | no | — | API-App client_id for embedded / unclaimed-draft tests. When unset the fixture uses the first app returned by /v3/api_app/list. |
SDK_TESTER_RATE_LIMIT_RETRIES |
pytest | no | 3 |
How many times helpers_hsapi.run retries on HTTP 429 before giving up. |
SDK_TESTER_RATE_LIMIT_BACKOFF |
pytest | no | 7 |
Fallback backoff in seconds when the API does not return Retry-After / X-Ratelimit-Reset. |
* Either LANGUAGES or LANGUAGE must be set. conftest.py fails loudly
rather than defaulting to an arbitrary SDK.
- Docker (Docker Desktop, Colima, OrbStack, etc.) running locally.
- Bash (any version — the scripts no longer rely on Bash 4-only syntax).
- Python 3 with
pytest+requests, if you want to run the pytest suite:pip3 install pytest requests
./build produces one Docker image per SDK (dropbox/sign-<sdk>:latest).
./build [options] SDK
Options:
| Flag | Meaning |
|---|---|
--sdk-ref REF |
Git ref (branch/tag/commit) for SDKs sourced from git (python, ruby, node, dotnet). For java this is the Maven Central version (defaults to the latest <release> published on Maven Central). For php it is the Composer version constraint. |
--local PATH |
Build against a local SDK source directory instead of fetching it from git / a package registry. Mutually exclusive with all. |
--help |
Show help and exit. |
SDK is one of dotnet java node php python ruby or all (or csharp as an
alias for dotnet). Examples:
./build all # build every SDK from defaults
./build python # build just python
./build --sdk-ref=v1.4.0 node # pin node to a specific upstream ref
./build --sdk-ref=2.6.0 java # pin java to a specific Maven Central release
./build --local ../dropbox-sign-python python # build against a local SDK| SDK | Default source | Definition file |
|---|---|---|
| python | github.com/hellosign/dropbox-sign-python @ main |
adapters/python/requirements.txt |
| ruby | github.com/hellosign/dropbox-sign-ruby @ main |
adapters/ruby/Gemfile |
| node | github:hellosign/dropbox-sign-node#main |
adapters/node/package.json |
| dotnet | github.com/hellosign/dropbox-sign-dotnet @ main (cloned inside the image) |
adapters/dotnet/Dockerfile |
| php | Packagist dropbox/sign ^1.0.0 |
adapters/php/composer.json |
| java | Maven Central com.dropbox.sign:dropbox-sign (latest <release>) |
adapters/java/pom.xml |
--sdk-ref rewrites the relevant descriptor in a staged build context (under
.build-ctx/<sdk>/, git-ignored) so the originals in adapters/<sdk>/ are never
touched. --local does the same plus copies your local SDK into the context
at ./sdk-src/ and rewrites the descriptor to reference /sdk-src inside the
container (for Java it mvn installs the local SDK into the image-local Maven
repo and pins the tester's pom.xml to the version in the local pom.xml).
For Java, ./build java with no --sdk-ref fetches
https://repo1.maven.org/maven2/com/dropbox/sign/dropbox-sign/maven-metadata.xml
and pins the staged pom.xml to the <release> value it reports — so the
default tracks "latest published Maven Central release" the same way the git
based SDKs track main. If that fetch fails (offline, Maven Central outage)
the build falls back to the <version> baked into adapters/java/pom.xml and
prints a warning.
./build always runs with --no-cache. If your local SDK tree has a large
node_modules/, vendor/, target/, build/, dist/, bin/, obj/,
venv/, .venv/, __pycache__/, or *.egg-info/ folder, it is excluded from
the copy so the build context stays small.
./run [options]
| Flag | Required | Description |
|---|---|---|
--sdk=<sdk> |
yes | dotnet, java, node, php, python, ruby (or csharp). |
--auth_type=<type> |
yes | apikey or oauth. |
--auth_key=<value> |
yes | API key (for apikey) or OAuth bearer token (for oauth). |
--json=<path or b64> |
yes | Either a path to a JSON file or a base64-encoded JSON string. |
--server=<host> |
no | API host (no scheme). Defaults to api.hellosign.com. |
--uploads_dir=<path> |
no | Directory mounted at /file_uploads. Defaults to ./tests/file_uploads. |
--dev_mode |
no | Mount the local adapters/<sdk>/requester.* over the one baked into the image (see note below). |
--help |
no | Show help and exit. |
./run mounts --uploads_dir at /file_uploads in the container; any file
names used under files in your JSON payload must live in that directory.
Using a JSON file:
./run \
--sdk=php \
--auth_type=apikey \
--auth_key=$HS_API_KEY \
--json="${PWD}/tests/fixtures/accounts/accountCreate-example_01.json"Using a base64-encoded JSON string:
./run \
--sdk=node \
--auth_type=apikey \
--auth_key=$HS_API_KEY \
--json="$(printf '{"operationId":"accountCreate","parameters":{},"data":{"email_address":"test_user@example.com"},"files":{}}' | base64)"Prefer environment variables over pasting secrets on the command line; values on the command line are visible in your shell history and in
psoutput.
--dev_mode mounts the local adapters/<sdk>/requester.* script over the one
baked into the image so you can iterate on the per-SDK requester code without
rebuilding. It does not remount the SDK itself — to iterate on the SDK
source, rebuild the image with --local:
./build --local ../dropbox-sign-python pythonDev mode also enables the XDEBUG_SESSION=xdebug cookie on outbound requests,
which is handy when debugging the API backend. Edits to the repo-root
[data.json](./data.json) (git-ignored) are picked up by dev mode.
conftest.py exposes fixtures the tests under tests/ consume
(container_bin, sdk_language, uploads_dir, auth_type, auth_key,
server). The first one is a parametrized fixture that lets a single pytest
invocation fan out across multiple SDKs.
See Environment variables above for the full list of
variables the suite understands. Auth type is pinned to apikey in
conftest.py.
Run every test against a single SDK:
LANGUAGE=python \
API_KEY=$HS_API_KEY \
SERVER=api.hellosign.com \
pytest -svra tests/Run every test against every SDK (matrix) in one process:
LANGUAGES=python,node,php,ruby,java,dotnet \
API_KEY=$HS_API_KEY \
SERVER=api.hellosign.com \
pytest -svra tests/Tests come out as test_create_account_success[python],
test_create_account_success[node], etc. Filter a subset with -k:
LANGUAGES=python,node,php,ruby,java,dotnet \
API_KEY=$HS_API_KEY \
SERVER=api.hellosign.com \
pytest -svra tests/test_account.py -k "python or node"Run a single test on a single SDK:
LANGUAGE=python \
API_KEY=$HS_API_KEY \
SERVER=api.hellosign.com \
pytest -svra tests/test_signature_request.py::test_signature_request_sendRun the matrix with per-SDK reports in parallel processes (& + wait), one
pytest per SDK:
for lang in python node php ruby java dotnet; do
LANGUAGE=$lang API_KEY=$HS_API_KEY SERVER=api.hellosign.com \
pytest -svra tests/ --junitxml=reports/${lang}.xml &
done
waitTests hit the real API. Use an API key bound to a dedicated test account and keep payloads that create signature requests or unclaimed drafts in
test_mode: trueso real documents are never dispatched.
The --json file (or base64 blob) passed to ./run must conform to:
{
"operationId": "<string>",
"data": {},
"parameters": {},
"files": {}
}Must match an operationId in the
OpenAPI spec's paths object.
For example:
paths:
/account/create:
post:
tags: [Account]
summary: 'Create Account'
operationId: accountCreateRequest body. Must be an object (may be empty).
Query / path parameters. For example, GET /account takes an account_id
query parameter.
Files to upload, if the endpoint supports them. Every filename must live in
--uploads_dir (default ./tests/file_uploads). For endpoints that accept several
files, use a nested array:
"files": { "files": ["pdf-sample.pdf", "pdf-sample-2.pdf"] }Get an account (query parameter, no body):
{
"operationId": "accountGet",
"parameters": { "account_id": "test_account_id" },
"data": {},
"files": {}
}Create an API App with a file upload:
{
"operationId": "apiAppCreate",
"parameters": {},
"data": {
"name": "My Production App",
"callback_url": "https://example.com/callback",
"domains": ["example.com"],
"oauth": {
"callback_url": "https://example.com/oauth",
"scopes": ["basic_account_info", "request_signature"]
}
},
"files": { "custom_logo_file": "pdf-sample.pdf" }
}Send a signature request with multiple file uploads:
{
"operationId": "signatureRequestSend",
"parameters": {},
"data": {
"cc_email_addresses": ["test_user@example.com"],
"signers": [
{
"email_address": "test_user@example.com",
"name": "Test Signer",
"order": 0
}
],
"subject": "The NDA we talked about",
"test_mode": true,
"title": "NDA with Acme Co."
},
"files": { "files": ["pdf-sample.pdf", "pdf-sample-2.pdf"] }
}More examples live under [./tests/fixtures](./tests/fixtures).
Every response — success or error — comes back as:
{
"body": {},
"status_code": 0,
"headers": {}
}Header names are always lowercased. A successful response looks like:
{
"body": {
"account": {
"account_id": "test_account_id",
"email_address": "test_user@example.com",
"is_locked": false,
"is_paid_hs": false,
"is_paid_hf": false,
"quotas": { "api_signature_requests_left": 0, "documents_left": 3, "templates_left": 0 },
"locale": "en-US"
}
},
"status_code": 200,
"headers": { "content-type": "application/json" }
}An error response looks like:
{
"body": {
"error": { "error_msg": "Unauthorized api key", "error_name": "unauthorized" }
},
"status_code": 401,
"headers": { "content-type": "application/json" }
}**${SELECTED_SDK,,}: bad substitution**— the scripts used Bash 4 syntax that macOS's system Bash 3.2 does not support. The current./builduses portabletrinstead; pull the latest version if you see this.- Node image build fails on axios
.d.ts— TypeScript is too old. The image now uses TypeScript 5.x withskipLibCheck: trueto insulate against third-party typing churn. - Java image build fails with
com.github.hellosign:dropbox-sign-java:main-SNAPSHOTnot found — the harness now pulls the publishedcom.dropbox.sign:dropbox-signartifact from Maven Central rather than JitPack. By default./build javaauto-resolves the latest<release>frommaven-metadata.xml; to pin a specific version pass./build --sdk-ref=<version> java. - Java build prints
could not resolve latest Java version from Maven Central— themaven-metadata.xmlfetch failed (offline, DNS, proxy). The build falls back to the<version>pinned inadapters/java/pom.xml; if that is too old, re-run with an explicit--sdk-ref=<version> javaonce the network is back. - .NET build complains about incompatible target framework — the upstream
SDK targets
net8.0; the image usesmcr.microsoft.com/dotnet/sdk:8.0. If upstream bumps its target, bump the base image inadapters/dotnet/Dockerfile. **LANGUAGES/LANGUAGEmissing** —conftest.pynow fails loudly rather than defaulting to an arbitrary SDK. Set one of them before running pytest.
Unless otherwise noted:
Copyright (c) 2023 Dropbox, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.