Skip to content

Commit 072adad

Browse files
committed
Add Docker-based test harness with Transloadit CLI parity
1 parent 8c4d9f1 commit 072adad

File tree

7 files changed

+275
-104
lines changed

7 files changed

+275
-104
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ jobs:
2020
- uses: actions/setup-node@v4
2121
with:
2222
node-version: '20'
23-
- name: Install tsx
24-
run: npm install -g tsx
23+
- name: Install Transloadit CLI
24+
run: npm install -g transloadit
2525

2626
- name: Set up Python
2727
uses: actions/setup-python@v4

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ htmlcov/
4343
.cache
4444
nosetests.xml
4545
coverage.xml
46+
coverage.json
4647
*.cover
4748
.hypothesis/
4849

@@ -98,6 +99,9 @@ ENV/
9899
# mkdocs documentation
99100
/site
100101

102+
# Docker/local build helpers
103+
.docker-cache/
104+
101105
# mypy
102106
.mypy_cache/
103107

Dockerfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# syntax=docker/dockerfile:1
2+
3+
ARG PYTHON_VERSION=3.12
4+
FROM python:${PYTHON_VERSION}-slim AS base
5+
6+
ENV DEBIAN_FRONTEND=noninteractive \
7+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
8+
PYTHONDONTWRITEBYTECODE=1 \
9+
POETRY_VIRTUALENVS_IN_PROJECT=true
10+
11+
RUN apt-get update \
12+
&& apt-get install -y --no-install-recommends \
13+
curl \
14+
gnupg \
15+
ca-certificates \
16+
build-essential \
17+
git \
18+
&& rm -rf /var/lib/apt/lists/*
19+
20+
# Install Node.js 20 (for Smart CDN parity tests) and supporting CLI tooling
21+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
22+
&& apt-get update \
23+
&& apt-get install -y --no-install-recommends nodejs \
24+
&& npm install -g transloadit \
25+
&& rm -rf /var/lib/apt/lists/*
26+
27+
# Install Poetry so we match the GitHub Actions toolchain
28+
RUN pip install --no-cache-dir --upgrade pip \
29+
&& pip install --no-cache-dir poetry
30+
31+
WORKDIR /workspace

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ See [readthedocs](https://transloadit.readthedocs.io) for full API documentation
4848

4949
### Running tests
5050

51+
You can mirror our GitHub Actions setup locally by running the test matrix inside Docker:
52+
53+
```bash
54+
scripts/test-in-docker.sh
55+
```
56+
57+
This script will:
58+
59+
- build images for the Python versions we test in CI (3.9–3.13)
60+
- install Poetry, Node.js 20, and the Transloadit CLI
61+
- pass credentials from `.env` (if present) so end-to-end tests can run against real Transloadit accounts
62+
63+
Signature parity tests use `npx transloadit smart_sig` under the hood, matching the reference implementation used by our other SDKs.
64+
65+
Pass `--python 3.12` (or set `PYTHON_VERSIONS`) to restrict the matrix, or append a custom command after `--`, for example `scripts/test-in-docker.sh -- pytest -k smartcdn`.
66+
5167
If you have a global installation of `poetry`, you can run the tests with:
5268

5369
```bash
@@ -72,4 +88,4 @@ Generate a coverage report with:
7288
poetry run pytest --cov=transloadit --cov-report=html tests
7389
```
7490

75-
Then view the coverage report locally by opening `htmlcov/index.html` in your browser.
91+
Then view the coverage report locally by opening `htmlcov/index.html` in your browser.

scripts/test-in-docker.sh

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
IMAGE_PREFIX=${IMAGE_PREFIX:-transloadit-python-sdk-dev}
5+
CACHE_ROOT=${CACHE_ROOT:-.docker-cache}
6+
POETRY_CACHE_DIR="$CACHE_ROOT/pypoetry"
7+
PIP_CACHE_DIR="$CACHE_ROOT/pip"
8+
NPM_CACHE_DIR="$CACHE_ROOT/npm"
9+
HOME_DIR="$CACHE_ROOT/home"
10+
DEFAULT_MATRIX=("3.9" "3.10" "3.11" "3.12" "3.13")
11+
declare -a PYTHON_MATRIX=()
12+
declare -a CUSTOM_COMMAND=()
13+
14+
usage() {
15+
cat <<'EOF'
16+
Usage: scripts/test-in-docker.sh [options] [-- command ...]
17+
18+
Options:
19+
-p, --python VERSION Only run for the given Python version (repeatable)
20+
-h, --help Show this help
21+
22+
Environment:
23+
PYTHON_VERSIONS Space-separated Python versions to run (default CI matrix)
24+
SKIP_POETRY_RUN Set to 1 to run the custom command without "poetry run"
25+
IMAGE_NAME Override the Docker image name prefix
26+
CACHE_ROOT Override the cache directory (default: .docker-cache)
27+
28+
Examples:
29+
scripts/test-in-docker.sh
30+
scripts/test-in-docker.sh --python 3.12
31+
scripts/test-in-docker.sh -- pytest tests/test_client.py
32+
SKIP_POETRY_RUN=1 scripts/test-in-docker.sh -- python -m pytest -k smartcdn
33+
EOF
34+
}
35+
36+
ensure_docker() {
37+
if ! command -v docker >/dev/null 2>&1; then
38+
echo "Docker is required to run this script." >&2
39+
exit 1
40+
fi
41+
42+
if ! docker info >/dev/null 2>&1; then
43+
if [[ -z "${DOCKER_HOST:-}" && -S "$HOME/.colima/default/docker.sock" ]]; then
44+
export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"
45+
fi
46+
fi
47+
48+
if ! docker info >/dev/null 2>&1; then
49+
echo "Docker daemon is not reachable. Start Docker (or Colima) and retry." >&2
50+
exit 1
51+
fi
52+
}
53+
54+
configure_platform() {
55+
if [[ -z "${DOCKER_PLATFORM:-}" ]]; then
56+
local arch
57+
arch=$(uname -m)
58+
if [[ "$arch" == "arm64" || "$arch" == "aarch64" ]]; then
59+
DOCKER_PLATFORM=linux/amd64
60+
fi
61+
fi
62+
}
63+
64+
parse_python_versions() {
65+
local -a cli_versions=()
66+
local -a custom_cmd=()
67+
68+
while [[ $# -gt 0 ]]; do
69+
case "$1" in
70+
-p|--python)
71+
if [[ $# -lt 2 ]]; then
72+
echo "Missing value for $1" >&2
73+
exit 1
74+
fi
75+
cli_versions+=("$2")
76+
shift 2
77+
;;
78+
--python=*)
79+
cli_versions+=("${1#*=}")
80+
shift
81+
;;
82+
-h|--help)
83+
usage
84+
exit 0
85+
;;
86+
--)
87+
shift
88+
custom_cmd=("$@")
89+
break
90+
;;
91+
*)
92+
custom_cmd+=("$1")
93+
shift
94+
;;
95+
esac
96+
done
97+
98+
if [[ ${#cli_versions[@]} -gt 0 ]]; then
99+
PYTHON_MATRIX=("${cli_versions[@]}")
100+
elif [[ -n "${PYTHON_VERSIONS:-}" ]]; then
101+
read -r -a PYTHON_MATRIX <<< "$PYTHON_VERSIONS"
102+
else
103+
PYTHON_MATRIX=("${DEFAULT_MATRIX[@]}")
104+
fi
105+
106+
if [[ ${#PYTHON_MATRIX[@]} -eq 0 ]]; then
107+
PYTHON_MATRIX=("${DEFAULT_MATRIX[@]}")
108+
fi
109+
110+
CUSTOM_COMMAND=("${custom_cmd[@]}")
111+
}
112+
113+
build_image_for_version() {
114+
local version=$1
115+
local image_name=$2
116+
117+
local -a build_args=()
118+
if [[ -n "${DOCKER_PLATFORM:-}" ]]; then
119+
build_args+=(--platform "$DOCKER_PLATFORM")
120+
fi
121+
build_args+=(-t "$image_name" --build-arg "PYTHON_VERSION=$version" -f Dockerfile .)
122+
123+
echo "==> Building image $image_name (Python $version)"
124+
docker build "${build_args[@]}"
125+
}
126+
127+
run_for_version() {
128+
local version=$1
129+
local image_name=$2
130+
131+
local -a docker_args=(
132+
--rm
133+
--user "$(id -u):$(id -g)"
134+
-v "$PWD":/workspace
135+
-w /workspace
136+
)
137+
138+
if [[ -n "${DOCKER_PLATFORM:-}" ]]; then
139+
docker_args+=(--platform "$DOCKER_PLATFORM")
140+
fi
141+
142+
mkdir -p "$POETRY_CACHE_DIR" "$PIP_CACHE_DIR" "$NPM_CACHE_DIR" "$HOME_DIR"
143+
144+
local container_home="/workspace/$HOME_DIR"
145+
docker_args+=(
146+
-e "HOME=$container_home"
147+
-e "PIP_CACHE_DIR=/workspace/$PIP_CACHE_DIR"
148+
-e "POETRY_CACHE_DIR=/workspace/$POETRY_CACHE_DIR"
149+
-e "NPM_CONFIG_CACHE=/workspace/$NPM_CACHE_DIR"
150+
-e "PYTHON_VERSION_UNDER_TEST=$version"
151+
-v "$PWD/$POETRY_CACHE_DIR":/workspace/"$POETRY_CACHE_DIR"
152+
-v "$PWD/$PIP_CACHE_DIR":/workspace/"$PIP_CACHE_DIR"
153+
-v "$PWD/$NPM_CACHE_DIR":/workspace/"$NPM_CACHE_DIR"
154+
-v "$PWD/$HOME_DIR":/workspace/"$HOME_DIR"
155+
)
156+
157+
if [[ -f .env ]]; then
158+
docker_args+=(--env-file "$PWD/.env")
159+
fi
160+
161+
local -a passthrough_envs=(TRANSLOADIT_KEY TRANSLOADIT_SECRET TRANSLOADIT_HOST TRANSLOADIT_REGION TRANSLOADIT_TEMPLATE_ID)
162+
for var in "${passthrough_envs[@]}"; do
163+
if [[ -n "${!var:-}" ]]; then
164+
docker_args+=(-e "$var=${!var}")
165+
fi
166+
done
167+
168+
if [[ "$version" == "3.12" && ${#CUSTOM_COMMAND[@]} -eq 0 ]]; then
169+
docker_args+=(-e TEST_NODE_PARITY=1)
170+
fi
171+
172+
local run_cmd
173+
if [[ ${#CUSTOM_COMMAND[@]} -gt 0 ]]; then
174+
printf -v user_cmd '%q ' "${CUSTOM_COMMAND[@]}"
175+
if [[ "${SKIP_POETRY_RUN:-0}" == "1" ]]; then
176+
run_cmd="set -euo pipefail; poetry install; ${user_cmd}"
177+
else
178+
run_cmd="set -euo pipefail; poetry install; poetry run ${user_cmd}"
179+
fi
180+
else
181+
if [[ "$version" == "3.12" ]]; then
182+
run_cmd='set -euo pipefail; poetry install; poetry run pytest --cov=transloadit --cov-report=xml --cov-report=json --cov-report=html --cov-report=term-missing --cov-fail-under=65 tests'
183+
else
184+
run_cmd='set -euo pipefail; poetry install; poetry run pytest tests'
185+
fi
186+
fi
187+
188+
echo "==> Running Python $version: $run_cmd"
189+
docker run "${docker_args[@]}" "$image_name" bash -lc "$run_cmd"
190+
}
191+
192+
main() {
193+
parse_python_versions "$@"
194+
ensure_docker
195+
configure_platform
196+
197+
mkdir -p "$CACHE_ROOT"
198+
199+
for version in "${PYTHON_MATRIX[@]}"; do
200+
image_name="${IMAGE_NAME:-$IMAGE_PREFIX}-${version//./}"
201+
build_image_for_version "$version" "$image_name"
202+
run_for_version "$version" "$image_name"
203+
done
204+
}
205+
206+
main "$@"

tests/node-smartcdn-sig.ts

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)