transloadit 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d73cc9f7b8291b8c4d208ec3f4bdd624a115bc31de35803b7651f7f86d09dee
4
- data.tar.gz: d5298dfe9ba4df6723e087267f6a1ee1c96ab890c4888d1116a3bfba53d9a2eb
3
+ metadata.gz: c48cbed2048f296ca130e4cbb69790fea92b7a02fab75f4aa9c0e6811b89063e
4
+ data.tar.gz: 2b9752757ce8a2575cb6b3c446a19aec436c4003e715869e835e8906fb768b85
5
5
  SHA512:
6
- metadata.gz: c789384262485d41d6d4a6c188f2c860efc155721197f026980c80cfc6aa61cda8018ea9a346200885fdd2f663ad2a7cfdad8df89299ed5c07209e4e1b240023
7
- data.tar.gz: e19fc6c2f00aebd20639fbacfc787da14999d4900b7b53cb0cf5f19776160fffbe385863636c88c3cb842d5d98622478f4b5808997aaad054105e5c42cef5925
6
+ metadata.gz: 2d05074cb025387b2f805c14797ff93b9abd5b28f77b51bc7a6a69811d66deb2dad2260a0ad6f0ad7ea5ff4313e076d1867332528a5b136f6694267d56987eee
7
+ data.tar.gz: e078c99468b14f03ddc1f1c48a7abab1f92492194d8fc17bb6208a696f0a0cfb9a638a4002f7a4905ef9cd714e6ba0159f4f7f6e275693527478d5ea1ff3eae6
data/.dockerignore ADDED
@@ -0,0 +1,9 @@
1
+ .git
2
+ .gitignore
3
+ .docker-cache
4
+ .bundle
5
+ coverage
6
+ pkg
7
+ node_modules
8
+ vendor/bundle
9
+ .env
data/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ *.jpg binary
2
+
@@ -31,4 +31,40 @@ jobs:
31
31
  ruby-version: ${{ matrix.ruby }}
32
32
  bundler-cache: true
33
33
  - run: bundle exec standardrb
34
- - run: COVERAGE=0 TEST_NODE_PARITY=1 bundle exec rake test
34
+ - name: Run tests
35
+ env:
36
+ COVERAGE: ${{ matrix.ruby == '3.3' && '1' || '0' }}
37
+ TEST_NODE_PARITY: ${{ matrix.ruby == '3.3' && '1' || '0' }}
38
+ run: bundle exec rake test
39
+ - name: Upload coverage to Codecov
40
+ if: matrix.ruby == '3.3'
41
+ uses: codecov/codecov-action@v5
42
+ with:
43
+ token: ${{ secrets.CODECOV_TOKEN }}
44
+ files: ./coverage/coverage.json
45
+ fail_ci_if_error: true
46
+ e2e:
47
+ needs: ci
48
+ if: >
49
+ github.event_name != 'pull_request' ||
50
+ github.event.pull_request.head.repo.full_name == github.repository
51
+ runs-on: ubuntu-latest
52
+ env:
53
+ TRANSLOADIT_KEY: ${{ secrets.TRANSLOADIT_KEY }}
54
+ TRANSLOADIT_SECRET: ${{ secrets.TRANSLOADIT_SECRET }}
55
+ steps:
56
+ - uses: actions/checkout@v3
57
+ - uses: ruby/setup-ruby@v1
58
+ with:
59
+ ruby-version: 3.3
60
+ bundler-cache: true
61
+ - name: Ensure e2e credentials are configured
62
+ if: ${{ env.TRANSLOADIT_KEY == '' || env.TRANSLOADIT_SECRET == '' }}
63
+ run: |
64
+ echo "TRANSLOADIT_KEY and TRANSLOADIT_SECRET must be configured in repository secrets to run the e2e job." >&2
65
+ exit 1
66
+ - name: Run end-to-end upload test
67
+ env:
68
+ RUBY_SDK_E2E: 1
69
+ if: ${{ env.TRANSLOADIT_KEY != '' && env.TRANSLOADIT_SECRET != '' }}
70
+ run: bundle exec ruby -Itest test/integration/test_e2e_upload.rb
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ transloadit-*.gem
15
15
  env.sh
16
16
  .env
17
17
  vendor/bundle/
18
+ .docker-cache/
19
+ .cache/rubocop_cache/04e06e0faf5ad652d8bcbcfd85bac5f6c32e711e/3031a80880d8a984708138f0d003f77c4bad2648
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### 3.1.1 / 2025-10-28
2
+
3
+ - Add optional live end-to-end upload harness and CI job for parity verification, defaulted in Docker tests (kvz)
4
+ - Restore missing `require "uri"` to prevent `NameError` when loading `Transloadit::Request` (kvz)
5
+
1
6
  ### 3.1.0 / 2024-11-24
2
7
 
3
8
  - Add Smart CDN signature support via `signed_smart_cdn_url` method (kvz)
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,95 @@
1
+ # Contributing
2
+
3
+ Thanks for helping improve the Transloadit Ruby SDK! This guide covers local development, testing, and publishing new releases.
4
+
5
+ ## Local Development
6
+
7
+ After cloning the repository, install dependencies and run the test suite:
8
+
9
+ ```bash
10
+ bundle install
11
+ bundle exec rake test
12
+ ```
13
+
14
+ To exercise the signature parity suite against the Node.js CLI, make sure `npx transloadit` is available and run:
15
+
16
+ ```bash
17
+ TEST_NODE_PARITY=1 bundle exec rake test
18
+ ```
19
+
20
+ You can warm the CLI cache ahead of time:
21
+
22
+ ```bash
23
+ TRANSLOADIT_KEY=... TRANSLOADIT_SECRET=... \
24
+ npx --yes transloadit smart_sig --help
25
+ TRANSLOADIT_KEY=... TRANSLOADIT_SECRET=... \
26
+ npx --yes transloadit sig --algorithm sha384 --help
27
+ ```
28
+
29
+ Set `COVERAGE=0` to skip coverage instrumentation if desired:
30
+
31
+ ```bash
32
+ COVERAGE=0 bundle exec rake test
33
+ ```
34
+
35
+ ## Docker Workflow
36
+
37
+ The repository ships with a helper that runs tests inside a reproducible Docker image:
38
+
39
+ ```bash
40
+ ./scripts/test-in-docker.sh
41
+ ```
42
+
43
+ Pass a custom command to run alternatives (Bundler still installs first):
44
+
45
+ ```bash
46
+ ./scripts/test-in-docker.sh bundle exec ruby -Itest test/unit/transloadit/test_request.rb
47
+ ```
48
+
49
+ The script forwards environment variables such as `TEST_NODE_PARITY` and credentials from `.env`, so you can combine parity checks and integration tests. End-to-end uploads are enabled by default; unset them by running:
50
+
51
+ ```bash
52
+ RUBY_SDK_E2E=0 ./scripts/test-in-docker.sh
53
+ ```
54
+
55
+ ## Live End-to-End Test
56
+
57
+ To exercise the optional live upload:
58
+
59
+ ```bash
60
+ RUBY_SDK_E2E=1 TRANSLOADIT_KEY=... TRANSLOADIT_SECRET=... \
61
+ ./scripts/test-in-docker.sh bundle exec ruby -Itest test/integration/test_e2e_upload.rb
62
+ ```
63
+
64
+ The test uploads `chameleon.jpg`, resizes it, and asserts on a real assembly response.
65
+
66
+ ## Releasing to RubyGems
67
+
68
+ 1. Update the version and changelog:
69
+ - Bump `lib/transloadit/version.rb`.
70
+ - Add a corresponding entry to `CHANGELOG.md`.
71
+ 2. Run the full test suite (including Docker, parity, and e2e checks as needed).
72
+ 3. Commit the release changes and tag:
73
+ ```bash
74
+ git commit -am "Release X.Y.Z"
75
+ git tag -a vX.Y.Z -m "Release X.Y.Z"
76
+ ```
77
+ 4. Push the commit and tag:
78
+ ```bash
79
+ git push origin main
80
+ git push origin vX.Y.Z
81
+ ```
82
+ 5. Publish the gem using the helper script:
83
+ ```bash
84
+ GEM_HOST_API_KEY=... ./scripts/notify-registry.sh
85
+ ```
86
+ 6. Draft a GitHub release from the new tag and publish the generated notes.
87
+
88
+ ### RubyGems Credentials
89
+
90
+ - You must belong to the `transloadit` organization on RubyGems with permission to push the `transloadit` gem.
91
+ - Generate an API key with **Push Rubygems** permissions at <https://rubygems.org/profile/edit>. Copy the token and keep it secure.
92
+ - Export the token as `GEM_HOST_API_KEY` in your environment before running `./scripts/notify-registry.sh`. The script refuses to run if the variable is missing.
93
+
94
+ That’s it! Thank you for contributing.
95
+
data/Dockerfile ADDED
@@ -0,0 +1,19 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM ruby:3.3 AS base
4
+
5
+ RUN apt-get update \
6
+ && apt-get install -y --no-install-recommends \
7
+ build-essential \
8
+ git \
9
+ curl \
10
+ ca-certificates \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Install Node.js for parity checks
14
+ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
15
+ && apt-get install -y --no-install-recommends nodejs \
16
+ && npm install -g npm@latest \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ WORKDIR /workspace
data/Gemfile CHANGED
@@ -1,3 +1,8 @@
1
1
  source "https://rubygems.org"
2
2
 
3
+ gem "rake"
4
+ gem "minitest"
5
+ gem "simplecov"
6
+ gem "simplecov-cobertura"
7
+
3
8
  gemspec
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  [![Build Status](https://github.com/transloadit/ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/transloadit/ruby-sdk/actions/workflows/ci.yml)
2
- [![Code Climate](https://codeclimate.com/github/transloadit/ruby-sdk.png)](https://codeclimate.com/github/transloadit/ruby-sdk)
2
+ [![Coverage](https://codecov.io/gh/transloadit/ruby-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/transloadit/ruby-sdk)
3
3
 
4
- ## ruby-sdk
4
+ # Transloadit Ruby SDK
5
5
 
6
6
  A **Ruby** Integration for [Transloadit](https://transloadit.com)'s file uploading and encoding service
7
7
 
@@ -447,7 +447,7 @@ transloadit.assembly(:tries => 0).create! open('/PATH/TO/FILE.mpg')
447
447
 
448
448
  ## Example
449
449
 
450
- A small sample tutorial of using the Transloadit ruby-sdk to optimize an image, encode MP3 audio, add ID3 tags,
450
+ A small sample tutorials of using the Transloadit Ruby SDK to optimize an image, encode MP3 audio, add ID3 tags,
451
451
  and more can be found [here](https://github.com/transloadit/ruby-sdk/tree/main/examples).
452
452
 
453
453
  ## Documentation
@@ -467,21 +467,4 @@ If you still need support for Ruby 2.x, 2.0.1 is the last version that supports
467
467
 
468
468
  ## Contributing
469
469
 
470
- ### Running tests
471
-
472
- ```bash
473
- bundle install
474
- bundle exec rake test
475
- ```
476
-
477
- To also test parity against the Node.js reference implementation, run:
478
-
479
- ```bash
480
- TEST_NODE_PARITY=1 bundle exec rake test
481
- ```
482
-
483
- To disable coverage reporting, run:
484
-
485
- ```bash
486
- COVERAGE=0 bundle exec rake test
487
- ```
470
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for local development, testing, and release instructions.
data/chameleon.jpg ADDED
Binary file
@@ -52,6 +52,7 @@ class Transloadit::Response < Delegator
52
52
  #
53
53
  def extend!(mod)
54
54
  extend(mod)
55
+
55
56
  self
56
57
  end
57
58
 
@@ -1,3 +1,3 @@
1
1
  class Transloadit
2
- VERSION = "3.1.0"
2
+ VERSION = "3.1.1"
3
3
  end
data/lib/transloadit.rb CHANGED
@@ -2,8 +2,8 @@ require "multi_json"
2
2
  require "date"
3
3
  require "json"
4
4
  require "openssl"
5
- require "uri"
6
5
  require "cgi"
6
+ require "uri"
7
7
 
8
8
  #
9
9
  # Implements the Transloadit REST API in Ruby. Check the {file:README.md README}
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ IMAGE_NAME=${IMAGE_NAME:-transloadit-ruby-sdk-dev}
5
+
6
+ err() {
7
+ echo "notify-registry: $*" >&2
8
+ }
9
+
10
+ if ! command -v docker >/dev/null 2>&1; then
11
+ err "Docker is required to publish the gem."
12
+ exit 1
13
+ fi
14
+
15
+ if [[ -z "${GEM_HOST_API_KEY:-}" ]]; then
16
+ err "GEM_HOST_API_KEY environment variable is not set. Generate a RubyGems API key with push permissions and export it before running this script."
17
+ exit 1
18
+ fi
19
+
20
+ if ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
21
+ err "Docker image '$IMAGE_NAME' not found. Building it now..."
22
+ docker build -t "$IMAGE_NAME" -f Dockerfile .
23
+ fi
24
+
25
+ version=$(
26
+ docker run --rm \
27
+ -v "$PWD":/workspace \
28
+ -w /workspace \
29
+ "$IMAGE_NAME" \
30
+ ruby -Ilib -e 'require "transloadit/version"; puts Transloadit::VERSION'
31
+ )
32
+
33
+ gem_file="transloadit-${version}.gem"
34
+
35
+ err "Building ${gem_file}..."
36
+ docker run --rm \
37
+ --user "$(id -u):$(id -g)" \
38
+ -e HOME=/workspace \
39
+ -v "$PWD":/workspace \
40
+ -w /workspace \
41
+ "$IMAGE_NAME" \
42
+ bash -lc "set -euo pipefail; rm -f ${gem_file}; gem build transloadit.gemspec >/dev/null"
43
+
44
+ err "Pushing ${gem_file} to RubyGems..."
45
+ docker run --rm \
46
+ --user "$(id -u):$(id -g)" \
47
+ -e HOME=/workspace \
48
+ -e GEM_HOST_API_KEY="$GEM_HOST_API_KEY" \
49
+ -v "$PWD":/workspace \
50
+ -w /workspace \
51
+ "$IMAGE_NAME" \
52
+ bash -lc "set -euo pipefail; gem push ${gem_file}"
53
+
54
+ err "Removing local ${gem_file}..."
55
+ rm -f "${gem_file}"
56
+
57
+ echo "notify-registry: Successfully pushed ${gem_file} to RubyGems."
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ IMAGE_NAME=${IMAGE_NAME:-transloadit-ruby-sdk-dev}
5
+ CACHE_DIR=.docker-cache
6
+
7
+ ensure_docker() {
8
+ if ! command -v docker >/dev/null 2>&1; then
9
+ echo "Docker is required to run this script." >&2
10
+ exit 1
11
+ fi
12
+
13
+ if ! docker info >/dev/null 2>&1; then
14
+ if [[ -z "${DOCKER_HOST:-}" && -S "$HOME/.colima/default/docker.sock" ]]; then
15
+ export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"
16
+ fi
17
+ fi
18
+
19
+ if ! docker info >/dev/null 2>&1; then
20
+ echo "Docker daemon is not reachable. Start Docker (or Colima) and retry." >&2
21
+ exit 1
22
+ fi
23
+ }
24
+
25
+ configure_platform() {
26
+ if [[ -z "${DOCKER_PLATFORM:-}" ]]; then
27
+ local arch
28
+ arch=$(uname -m)
29
+ if [[ "$arch" == "arm64" || "$arch" == "aarch64" ]]; then
30
+ DOCKER_PLATFORM=linux/amd64
31
+ fi
32
+ fi
33
+ }
34
+
35
+ ensure_docker
36
+ configure_platform
37
+
38
+ if [[ $# -eq 0 ]]; then
39
+ RUN_CMD='set -e; bundle install --jobs 4 --retry 3; bundle exec rake test'
40
+ else
41
+ printf -v USER_CMD '%q ' "$@"
42
+ RUN_CMD="set -e; bundle install --jobs 4 --retry 3; ${USER_CMD}"
43
+ fi
44
+
45
+ mkdir -p "$CACHE_DIR/bundle" "$CACHE_DIR/npm-cache"
46
+
47
+ BUILD_ARGS=()
48
+ if [[ -n "${DOCKER_PLATFORM:-}" ]]; then
49
+ BUILD_ARGS+=(--platform "$DOCKER_PLATFORM")
50
+ fi
51
+ BUILD_ARGS+=(-t "$IMAGE_NAME" -f Dockerfile .)
52
+
53
+ docker build "${BUILD_ARGS[@]}"
54
+
55
+ DOCKER_ARGS=(
56
+ --rm
57
+ --user "$(id -u):$(id -g)"
58
+ -e HOME=/workspace
59
+ -e BUNDLE_PATH=/workspace/$CACHE_DIR/bundle
60
+ -e BUNDLE_APP_CONFIG=/workspace/$CACHE_DIR/bundle-config
61
+ -e BUNDLE_CACHE_PATH=/workspace/$CACHE_DIR/bundle-cache
62
+ -e npm_config_cache=/workspace/$CACHE_DIR/npm-cache
63
+ -e TEST_NODE_PARITY="${TEST_NODE_PARITY:-0}"
64
+ -e RUBY_SDK_E2E="${RUBY_SDK_E2E:-1}"
65
+ -v "$PWD":/workspace
66
+ -w /workspace
67
+ )
68
+
69
+ if [[ -n "${DOCKER_PLATFORM:-}" ]]; then
70
+ DOCKER_ARGS+=(--platform "$DOCKER_PLATFORM")
71
+ fi
72
+
73
+ if [[ -f .env ]]; then
74
+ DOCKER_ARGS+=(--env-file "$PWD/.env")
75
+ fi
76
+
77
+ PASSTHROUGH_ENV_VARS=(
78
+ TRANSLOADIT_KEY
79
+ TRANSLOADIT_SECRET
80
+ TRANSLOADIT_TEMPLATE_ID
81
+ )
82
+
83
+ for var in "${PASSTHROUGH_ENV_VARS[@]}"; do
84
+ if [[ -n "${!var:-}" ]]; then
85
+ DOCKER_ARGS+=(-e "$var=${!var}")
86
+ fi
87
+ done
88
+
89
+ exec docker run "${DOCKER_ARGS[@]}" "$IMAGE_NAME" bash -lc "$RUN_CMD"
@@ -0,0 +1,97 @@
1
+ require "test_helper"
2
+ require "webmock"
3
+
4
+ describe "Transloadit end-to-end upload" do
5
+ before do
6
+ skip "Set RUBY_SDK_E2E=1 to run live upload tests" unless e2e_enabled?
7
+
8
+ @key = ENV["TRANSLOADIT_KEY"]
9
+ @secret = ENV["TRANSLOADIT_SECRET"]
10
+ skip "TRANSLOADIT_KEY and TRANSLOADIT_SECRET must be set to run live upload tests" if blank?(@key) || blank?(@secret)
11
+
12
+ @fixture_path = File.expand_path("../../chameleon.jpg", __dir__)
13
+ skip "chameleon.jpg fixture missing; run tests from the repository root" unless File.file?(@fixture_path)
14
+ end
15
+
16
+ it "uploads and processes the chameleon image" do
17
+ with_live_http do
18
+ transloadit = Transloadit.new(key: @key, secret: @secret)
19
+
20
+ resize_step = transloadit.step(
21
+ "resize",
22
+ "/image/resize",
23
+ use: ":original",
24
+ width: 128,
25
+ height: 128,
26
+ resize_strategy: "fit",
27
+ format: "png"
28
+ )
29
+
30
+ response = File.open(@fixture_path, "rb") do |upload|
31
+ transloadit.assembly.create!(
32
+ upload,
33
+ wait: true,
34
+ steps: resize_step
35
+ )
36
+ end
37
+
38
+ response.reload_until_finished!(tries: 120) unless response.finished?
39
+
40
+ _(response.completed?).must_equal true, "Assembly did not complete successfully: #{response.body.inspect}"
41
+
42
+ uploads = response["uploads"] || []
43
+ refute_empty uploads, "Expected uploads in the assembly response"
44
+
45
+ upload_info = uploads.first
46
+ basename = upload_info["basename"]
47
+ _(basename).must_equal File.basename(@fixture_path, ".*") if basename
48
+
49
+ filename = upload_info["name"]
50
+ _(filename).must_equal File.basename(@fixture_path) if filename
51
+
52
+ results = (response["results"] || {})["resize"] || []
53
+ refute_empty results, "Expected resize results in assembly response"
54
+
55
+ first_result = results.first
56
+ ssl_url = first_result["ssl_url"]
57
+ refute_nil ssl_url, "Missing ssl_url in resize result: #{first_result.inspect}"
58
+ _(ssl_url).must_match(/\Ahttps:\/\//)
59
+
60
+ meta = first_result["meta"] || {}
61
+ width = integer_if_present(meta["width"])
62
+ height = integer_if_present(meta["height"])
63
+ refute_nil width, "Missing width metadata: #{meta.inspect}"
64
+ refute_nil height, "Missing height metadata: #{meta.inspect}"
65
+ assert width.positive? && width <= 128, "Unexpected width #{width.inspect}"
66
+ assert height.positive? && height <= 128, "Unexpected height #{height.inspect}"
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def with_live_http
73
+ VCR.turn_off!(ignore_cassettes: true)
74
+ WebMock.allow_net_connect!
75
+ yield
76
+ ensure
77
+ WebMock.disable_net_connect!(allow_localhost: true)
78
+ VCR.turn_on!
79
+ end
80
+
81
+ def e2e_enabled?
82
+ flag = ENV["RUBY_SDK_E2E"]
83
+ return false if blank?(flag)
84
+
85
+ %w[1 true yes on].include?(flag.to_s.strip.downcase)
86
+ end
87
+
88
+ def integer_if_present(value)
89
+ return nil if blank?(value)
90
+
91
+ value.to_i
92
+ end
93
+
94
+ def blank?(value)
95
+ value.respond_to?(:empty?) ? value.empty? : !value
96
+ end
97
+ end
data/test/test_helper.rb CHANGED
@@ -3,12 +3,20 @@ $:.unshift File.expand_path("../../lib", __FILE__)
3
3
 
4
4
  if ENV["COVERAGE"] != "0"
5
5
  require "simplecov"
6
- SimpleCov.start { add_filter "/test/" }
6
+ SimpleCov.start do
7
+ add_filter "/test/"
8
+ enable_coverage :branch
9
+ end
10
+
11
+ require "simplecov-cobertura"
12
+ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
7
13
  end
8
14
 
9
15
  require "minitest/autorun"
10
16
  require "transloadit"
11
17
  require "vcr"
18
+ require "open3"
19
+ require "json"
12
20
 
13
21
  VCR.configure do |c|
14
22
  c.cassette_library_dir = "test/fixtures/cassettes"
@@ -19,3 +27,48 @@ end
19
27
  def values_from_post_body(body)
20
28
  Addressable::URI.parse("?" + CGI.unescape(body)).query_values
21
29
  end
30
+
31
+ module TransloaditCliHelpers
32
+ TRANSLOADIT_CLI_PACKAGE = ENV.fetch("TRANSLOADIT_CLI_PACKAGE", "transloadit@4.0.5")
33
+
34
+ def run_transloadit_cli(command, payload, key:, secret:, algorithm: nil)
35
+ return nil unless ENV["TEST_NODE_PARITY"] == "1"
36
+
37
+ env = {
38
+ "TRANSLOADIT_KEY" => key,
39
+ "TRANSLOADIT_SECRET" => secret,
40
+ "TRANSLOADIT_AUTH_KEY" => key,
41
+ "TRANSLOADIT_AUTH_SECRET" => secret
42
+ }
43
+
44
+ args = [
45
+ "npm", "exec", "--yes", "--package", TRANSLOADIT_CLI_PACKAGE, "--",
46
+ "transloadit", command
47
+ ]
48
+ args += ["--algorithm", algorithm] if algorithm
49
+
50
+ stdout, stderr, status = Open3.capture3(env, *args, stdin_data: JSON.dump(payload))
51
+ raise "transloadit CLI #{command} failed: #{stderr}" unless status.success?
52
+
53
+ stdout.strip
54
+ end
55
+
56
+ def run_transloadit_smart_sig(payload, key:, secret:)
57
+ cli_payload = {
58
+ workspace: payload.fetch(:workspace),
59
+ template: payload.fetch(:template),
60
+ input: payload.fetch(:input)
61
+ }
62
+ cli_payload[:url_params] = payload[:url_params] if payload.key?(:url_params)
63
+ cli_payload[:expire_at_ms] = payload[:expire_at_ms] if payload.key?(:expire_at_ms)
64
+
65
+ run_transloadit_cli("smart_sig", cli_payload, key: key, secret: secret)
66
+ end
67
+
68
+ def run_transloadit_sig(payload, key:, secret:, algorithm: nil)
69
+ output = run_transloadit_cli("sig", payload, key: key, secret: secret, algorithm: algorithm)
70
+ output && JSON.parse(output)
71
+ end
72
+ end
73
+
74
+ Minitest::Test.include(TransloaditCliHelpers)
@@ -108,7 +108,10 @@ describe Transloadit do
108
108
  end
109
109
 
110
110
  it "must produce Transloadit-compatible JSON output" do
111
- _(@transloadit.to_json).must_equal MultiJson.dump(@transloadit.to_hash)
111
+ fixed_time = Time.utc(2025, 10, 28, 0, 0, 0)
112
+ Time.stub :now, fixed_time do
113
+ _(@transloadit.to_json).must_equal MultiJson.dump(@transloadit.to_hash)
114
+ end
112
115
  end
113
116
  end
114
117
 
@@ -1,4 +1,7 @@
1
1
  require "test_helper"
2
+ require "multi_json"
3
+ require "rbconfig"
4
+ require "tmpdir"
2
5
 
3
6
  describe Transloadit::Request do
4
7
  it "must allow initialization" do
@@ -75,4 +78,86 @@ describe Transloadit::Request do
75
78
  end
76
79
  end
77
80
  end
81
+
82
+ it "loads request when URI was not previously required" do
83
+ lib_path = File.expand_path("../../../lib", __dir__)
84
+
85
+ Dir.mktmpdir do |stub_dir|
86
+ File.write(File.join(stub_dir, "rest-client.rb"), <<~RUBY)
87
+ module RestClient
88
+ class Response; end
89
+
90
+ class Resource
91
+ def initialize(*); end
92
+ def [](*); self; end
93
+ def get(*); Response.new; end
94
+ def post(*); Response.new; end
95
+ def put(*); Response.new; end
96
+ def delete(*); Response.new; end
97
+ end
98
+
99
+ module Exceptions
100
+ class OpenTimeout < StandardError; end
101
+ end
102
+ end
103
+ RUBY
104
+
105
+ File.write(File.join(stub_dir, "multi_json.rb"), <<~RUBY)
106
+ require "json"
107
+
108
+ module MultiJson
109
+ def self.dump(value)
110
+ JSON.dump(value)
111
+ end
112
+
113
+ def self.load(json)
114
+ JSON.parse(json)
115
+ end
116
+ end
117
+ RUBY
118
+
119
+ script = <<~RUBY
120
+ $LOAD_PATH.unshift #{stub_dir.inspect}
121
+ $LOAD_PATH.unshift #{lib_path.inspect}
122
+
123
+ begin
124
+ require "transloadit/request"
125
+ Transloadit::Request.new("/")
126
+ rescue StandardError => e
127
+ warn e.full_message
128
+ exit 1
129
+ end
130
+ RUBY
131
+
132
+ stdout, stderr, status = Open3.capture3(RbConfig.ruby, "-e", script)
133
+ error_output = stderr.empty? ? stdout : stderr
134
+ assert status.success?, "Expected transloadit/request to load without NameError, got: #{error_output}"
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "signature parity" do
140
+ it "matches transloadit CLI sig output" do
141
+ skip "Parity testing not enabled" unless ENV["TEST_NODE_PARITY"] == "1"
142
+
143
+ expires = "2025-01-02T00:00:00.000Z"
144
+ params = {
145
+ auth: {key: "cli-key", expires: expires},
146
+ steps: {encode: {robot: "/video/encode"}}
147
+ }
148
+
149
+ cli_result = run_transloadit_sig(params, key: "cli-key", secret: "cli-secret", algorithm: "sha384")
150
+ refute_nil cli_result
151
+
152
+ cli_params_json = cli_result["params"]
153
+ request = Transloadit::Request.new("/", "cli-secret")
154
+ ruby_signature = request.send(:signature, cli_params_json)
155
+
156
+ assert_equal cli_result["signature"], ruby_signature
157
+
158
+ cli_params = JSON.parse(cli_params_json)
159
+ assert_equal "cli-key", cli_params.dig("auth", "key")
160
+ assert_equal expires, cli_params.dig("auth", "expires")
161
+ assert_equal "/video/encode", cli_params.dig("steps", "encode", "robot")
162
+ end
78
163
  end
@@ -1,6 +1,4 @@
1
1
  require "test_helper"
2
- require "json"
3
- require "open3"
4
2
 
5
3
  describe Transloadit do
6
4
  before do
@@ -13,15 +11,6 @@ describe Transloadit do
13
11
  @expire_at = 1732550672867
14
12
  end
15
13
 
16
- def run_node_script(params)
17
- return unless ENV["TEST_NODE_PARITY"] == "1"
18
- script_path = File.expand_path("./node-smartcdn-sig", __dir__)
19
- json_input = JSON.dump(params)
20
- stdout, stderr, status = Open3.capture3("tsx #{script_path}", stdin_data: json_input)
21
- raise "Node script failed: #{stderr}" unless status.success?
22
- stdout.strip
23
- end
24
-
25
14
  describe "#signed_smart_cdn_url" do
26
15
  it "requires workspace" do
27
16
  assert_raises ArgumentError, "workspace is required" do
@@ -81,8 +70,8 @@ describe Transloadit do
81
70
  url = @transloadit.signed_smart_cdn_url(**params)
82
71
  assert_equal expected_url, url
83
72
 
84
- if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
85
- assert_equal expected_url, node_url
73
+ if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
74
+ assert_equal expected_url, cli_url
86
75
  end
87
76
  end
88
77
 
@@ -98,8 +87,8 @@ describe Transloadit do
98
87
  url = @transloadit.signed_smart_cdn_url(**params)
99
88
  assert_equal expected_url, url
100
89
 
101
- if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
102
- assert_equal expected_url, node_url
90
+ if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
91
+ assert_equal expected_url, cli_url
103
92
  end
104
93
  end
105
94
 
@@ -119,8 +108,8 @@ describe Transloadit do
119
108
  url = @transloadit.signed_smart_cdn_url(**params)
120
109
  assert_equal expected_url, url
121
110
 
122
- if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
123
- assert_equal expected_url, node_url
111
+ if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
112
+ assert_equal expected_url, cli_url
124
113
  end
125
114
  end
126
115
 
@@ -140,8 +129,8 @@ describe Transloadit do
140
129
  url = @transloadit.signed_smart_cdn_url(**params)
141
130
  assert_equal expected_url, url
142
131
 
143
- if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
144
- assert_equal expected_url, node_url
132
+ if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
133
+ assert_equal expected_url, cli_url
145
134
  end
146
135
  end
147
136
 
@@ -161,8 +150,8 @@ describe Transloadit do
161
150
  url = @transloadit.signed_smart_cdn_url(**params)
162
151
  assert_equal expected_url, url
163
152
 
164
- if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
165
- assert_equal expected_url, node_url
153
+ if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
154
+ assert_equal expected_url, cli_url
166
155
  end
167
156
  end
168
157
  end
data/transloadit.gemspec CHANGED
@@ -7,8 +7,8 @@ Gem::Specification.new do |gem|
7
7
  gem.version = Transloadit::VERSION
8
8
  gem.platform = Gem::Platform::RUBY
9
9
 
10
- gem.authors = ["Stephen Touset", "Robin Mehner"]
11
- gem.email = %w[stephen@touset.org robin@coding-robin.de]
10
+ gem.authors = ["Stephen Touset", "Robin Mehner", "Kevin van Zonneveld"]
11
+ gem.email = %w[stephen@touset.org robin@coding-robin.de kevin@transloadit.com]
12
12
  gem.homepage = "https://github.com/transloadit/ruby-sdk/"
13
13
  gem.license = "MIT"
14
14
 
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transloadit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Touset
8
8
  - Robin Mehner
9
+ - Kevin van Zonneveld
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 1980-01-01 00:00:00.000000000 Z
13
+ date: 2025-10-29 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: rest-client
@@ -170,20 +171,26 @@ description: The transloadit gem allows you to automate uploading files through
170
171
  email:
171
172
  - stephen@touset.org
172
173
  - robin@coding-robin.de
174
+ - kevin@transloadit.com
173
175
  executables: []
174
176
  extensions: []
175
177
  extra_rdoc_files: []
176
178
  files:
179
+ - ".dockerignore"
180
+ - ".gitattributes"
177
181
  - ".github/workflows/ci.yml"
178
182
  - ".gitignore"
179
183
  - ".standard.yml"
180
184
  - ".vscode/ruby-sdk.code-workspace"
181
185
  - CHANGELOG.md
186
+ - CONTRIBUTING.md
187
+ - Dockerfile
182
188
  - Gemfile
183
189
  - LICENSE
184
190
  - Makefile
185
191
  - README.md
186
192
  - Rakefile
193
+ - chameleon.jpg
187
194
  - examples/README.md
188
195
  - examples/basic/assets/APU_Shutdown.mp3
189
196
  - examples/basic/assets/Computers_are_in_Control.flac
@@ -205,6 +212,8 @@ files:
205
212
  - lib/transloadit/step.rb
206
213
  - lib/transloadit/template.rb
207
214
  - lib/transloadit/version.rb
215
+ - scripts/notify-registry.sh
216
+ - scripts/test-in-docker.sh
208
217
  - test/fixtures/cassettes/cancel_assembly.yml
209
218
  - test/fixtures/cassettes/create_template.yml
210
219
  - test/fixtures/cassettes/delete_template.yml
@@ -226,9 +235,9 @@ files:
226
235
  - test/fixtures/cassettes/replay_assembly_notification.yml
227
236
  - test/fixtures/cassettes/submit_assembly.yml
228
237
  - test/fixtures/cassettes/update_template.yml
238
+ - test/integration/test_e2e_upload.rb
229
239
  - test/test_helper.rb
230
240
  - test/unit/test_transloadit.rb
231
- - test/unit/transloadit/node-smartcdn-sig.ts
232
241
  - test/unit/transloadit/test_api.rb
233
242
  - test/unit/transloadit/test_assembly.rb
234
243
  - test/unit/transloadit/test_request.rb
@@ -1,89 +0,0 @@
1
- #!/usr/bin/env tsx
2
- // Reference Smart CDN (https://transloadit.com/services/content-delivery/) Signature implementation
3
- // And CLI tester to see if our SDK's implementation
4
- // matches Node's
5
-
6
- /// <reference types="node" />
7
-
8
- import { createHash, createHmac } from 'crypto'
9
-
10
- interface SmartCDNParams {
11
- workspace: string
12
- template: string
13
- input: string
14
- expire_at_ms?: number
15
- auth_key?: string
16
- auth_secret?: string
17
- url_params?: Record<string, any>
18
- }
19
-
20
- function signSmartCDNUrl(params: SmartCDNParams): string {
21
- const {
22
- workspace,
23
- template,
24
- input,
25
- expire_at_ms,
26
- auth_key = 'my-key',
27
- auth_secret = 'my-secret',
28
- url_params = {},
29
- } = params
30
-
31
- if (!workspace) throw new Error('workspace is required')
32
- if (!template) throw new Error('template is required')
33
- if (input === null || input === undefined)
34
- throw new Error('input must be a string')
35
-
36
- const workspaceSlug = encodeURIComponent(workspace)
37
- const templateSlug = encodeURIComponent(template)
38
- const inputField = encodeURIComponent(input)
39
-
40
- const expireAt = expire_at_ms ?? Date.now() + 60 * 60 * 1000 // 1 hour default
41
-
42
- const queryParams: Record<string, string[]> = {}
43
-
44
- // Handle url_params
45
- Object.entries(url_params).forEach(([key, value]) => {
46
- if (value === null || value === undefined) return
47
- if (Array.isArray(value)) {
48
- value.forEach((val) => {
49
- if (val === null || val === undefined) return
50
- ;(queryParams[key] ||= []).push(String(val))
51
- })
52
- } else {
53
- queryParams[key] = [String(value)]
54
- }
55
- })
56
-
57
- queryParams.auth_key = [auth_key]
58
- queryParams.exp = [String(expireAt)]
59
-
60
- // Sort parameters to ensure consistent ordering
61
- const sortedParams = Object.entries(queryParams)
62
- .sort()
63
- .map(([key, values]) =>
64
- values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`)
65
- )
66
- .flat()
67
- .join('&')
68
-
69
- const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${sortedParams}`
70
- const signature = createHmac('sha256', auth_secret)
71
- .update(stringToSign)
72
- .digest('hex')
73
-
74
- const finalParams = `${sortedParams}&sig=${encodeURIComponent(
75
- `sha256:${signature}`
76
- )}`
77
- return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${finalParams}`
78
- }
79
-
80
- // Read JSON from stdin
81
- let jsonInput = ''
82
- process.stdin.on('data', (chunk) => {
83
- jsonInput += chunk
84
- })
85
-
86
- process.stdin.on('end', () => {
87
- const params = JSON.parse(jsonInput)
88
- console.log(signSmartCDNUrl(params))
89
- })