transloadit 3.0.2 → 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 +4 -4
- data/.dockerignore +9 -0
- data/.gitattributes +2 -0
- data/.github/workflows/ci.yml +49 -6
- data/.gitignore +4 -0
- data/.vscode/ruby-sdk.code-workspace +13 -0
- data/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +95 -0
- data/Dockerfile +19 -0
- data/Gemfile +5 -0
- data/Makefile +18 -0
- data/README.md +89 -44
- data/chameleon.jpg +0 -0
- data/lib/transloadit/assembly.rb +2 -2
- data/lib/transloadit/response.rb +1 -0
- data/lib/transloadit/step.rb +1 -1
- data/lib/transloadit/template.rb +1 -1
- data/lib/transloadit/version.rb +1 -1
- data/lib/transloadit.rb +54 -1
- data/scripts/notify-registry.sh +57 -0
- data/scripts/test-in-docker.sh +89 -0
- data/test/integration/test_e2e_upload.rb +97 -0
- data/test/test_helper.rb +55 -2
- data/test/unit/test_transloadit.rb +4 -1
- data/test/unit/transloadit/test_assembly.rb +1 -1
- data/test/unit/transloadit/test_request.rb +85 -0
- data/test/unit/transloadit/test_smart_cdn.rb +158 -0
- data/transloadit.gemspec +2 -2
- metadata +16 -3
data/lib/transloadit.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
require "multi_json"
|
|
2
2
|
require "date"
|
|
3
|
+
require "json"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "cgi"
|
|
6
|
+
require "uri"
|
|
3
7
|
|
|
4
8
|
#
|
|
5
9
|
# Implements the Transloadit REST API in Ruby. Check the {file:README.md README}
|
|
@@ -11,6 +15,7 @@ class Transloadit
|
|
|
11
15
|
autoload :Exception, "transloadit/exception"
|
|
12
16
|
autoload :Request, "transloadit/request"
|
|
13
17
|
autoload :Response, "transloadit/response"
|
|
18
|
+
autoload :SmartCDN, "transloadit/smart_cdn"
|
|
14
19
|
autoload :Step, "transloadit/step"
|
|
15
20
|
autoload :Template, "transloadit/template"
|
|
16
21
|
autoload :VERSION, "transloadit/version"
|
|
@@ -83,7 +88,7 @@ class Transloadit
|
|
|
83
88
|
# Creates a Transloadit::Template instance ready to interact with its corresponding REST API.
|
|
84
89
|
#
|
|
85
90
|
# See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#template-api]
|
|
86
|
-
# for
|
|
91
|
+
# for further information on Templates and available endpoints.
|
|
87
92
|
#
|
|
88
93
|
def template(options = {})
|
|
89
94
|
Transloadit::Template.new(self, options)
|
|
@@ -130,6 +135,54 @@ class Transloadit
|
|
|
130
135
|
MultiJson.dump(to_hash)
|
|
131
136
|
end
|
|
132
137
|
|
|
138
|
+
# @param workspace [String] Workspace slug
|
|
139
|
+
# @param template [String] Template slug or template ID
|
|
140
|
+
# @param input [String] Input value that is provided as `${fields.input}` in the template
|
|
141
|
+
# @param url_params [Hash] Additional parameters for the URL query string (optional)
|
|
142
|
+
# @param expire_at_ms [Integer] Expiration time as Unix timestamp in milliseconds (optional)
|
|
143
|
+
# @return [String] Signed Smart CDN URL
|
|
144
|
+
def signed_smart_cdn_url(
|
|
145
|
+
workspace:,
|
|
146
|
+
template:,
|
|
147
|
+
input:,
|
|
148
|
+
expire_at_ms: nil,
|
|
149
|
+
url_params: {}
|
|
150
|
+
)
|
|
151
|
+
raise ArgumentError, "workspace is required" if workspace.nil? || workspace.empty?
|
|
152
|
+
raise ArgumentError, "template is required" if template.nil? || template.empty?
|
|
153
|
+
raise ArgumentError, "input is required" if input.nil?
|
|
154
|
+
|
|
155
|
+
workspace_slug = CGI.escape(workspace)
|
|
156
|
+
template_slug = CGI.escape(template)
|
|
157
|
+
input_field = CGI.escape(input)
|
|
158
|
+
|
|
159
|
+
expire_at = expire_at_ms || (Time.now.to_i * 1000 + 60 * 60 * 1000) # 1 hour default
|
|
160
|
+
|
|
161
|
+
query_params = {}
|
|
162
|
+
url_params.each do |key, value|
|
|
163
|
+
next if value.nil?
|
|
164
|
+
Array(value).each do |val|
|
|
165
|
+
next if val.nil?
|
|
166
|
+
(query_params[key.to_s] ||= []) << val.to_s
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
query_params["auth_key"] = [key]
|
|
171
|
+
query_params["exp"] = [expire_at.to_s]
|
|
172
|
+
|
|
173
|
+
# Sort parameters to ensure consistent ordering
|
|
174
|
+
sorted_params = query_params.sort.flat_map do |key, values|
|
|
175
|
+
values.map { |v| "#{CGI.escape(key)}=#{CGI.escape(v)}" }
|
|
176
|
+
end.join("&")
|
|
177
|
+
|
|
178
|
+
string_to_sign = "#{workspace_slug}/#{template_slug}/#{input_field}?#{sorted_params}"
|
|
179
|
+
|
|
180
|
+
signature = OpenSSL::HMAC.hexdigest("sha256", secret, string_to_sign)
|
|
181
|
+
|
|
182
|
+
final_params = "#{sorted_params}&sig=#{CGI.escape("sha256:#{signature}")}"
|
|
183
|
+
"https://#{workspace_slug}.tlcdn.com/#{template_slug}/#{input_field}?#{final_params}"
|
|
184
|
+
end
|
|
185
|
+
|
|
133
186
|
private
|
|
134
187
|
|
|
135
188
|
#
|
|
@@ -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
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
|
2
2
|
$:.unshift File.expand_path("../../lib", __FILE__)
|
|
3
3
|
|
|
4
|
-
if ENV["COVERAGE"] != "
|
|
4
|
+
if ENV["COVERAGE"] != "0"
|
|
5
5
|
require "simplecov"
|
|
6
|
-
SimpleCov.start
|
|
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
|
-
|
|
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
|
|
|
@@ -286,7 +286,7 @@ describe Transloadit::Assembly do
|
|
|
286
286
|
end
|
|
287
287
|
|
|
288
288
|
describe "when replaying assembly notification" do
|
|
289
|
-
it "must replay notification of
|
|
289
|
+
it "must replay notification of specified assembly" do
|
|
290
290
|
VCR.use_cassette "replay_assembly_notification" do
|
|
291
291
|
response = @assembly.replay_notification "2ea5d21063ad11e6bc93e53395ce4e7d"
|
|
292
292
|
|
|
@@ -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
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
describe Transloadit do
|
|
4
|
+
before do
|
|
5
|
+
@auth_key = "my-key"
|
|
6
|
+
@auth_secret = "my-secret"
|
|
7
|
+
@transloadit = Transloadit.new(key: @auth_key, secret: @auth_secret)
|
|
8
|
+
@workspace = "my-app"
|
|
9
|
+
@template = "test-smart-cdn"
|
|
10
|
+
@input = "inputs/prinsengracht.jpg"
|
|
11
|
+
@expire_at = 1732550672867
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "#signed_smart_cdn_url" do
|
|
15
|
+
it "requires workspace" do
|
|
16
|
+
assert_raises ArgumentError, "workspace is required" do
|
|
17
|
+
@transloadit.signed_smart_cdn_url(
|
|
18
|
+
workspace: nil,
|
|
19
|
+
template: @template,
|
|
20
|
+
input: @input
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
assert_raises ArgumentError, "workspace is required" do
|
|
25
|
+
@transloadit.signed_smart_cdn_url(
|
|
26
|
+
workspace: "",
|
|
27
|
+
template: @template,
|
|
28
|
+
input: @input
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "requires template" do
|
|
34
|
+
assert_raises ArgumentError, "template is required" do
|
|
35
|
+
@transloadit.signed_smart_cdn_url(
|
|
36
|
+
workspace: @workspace,
|
|
37
|
+
template: nil,
|
|
38
|
+
input: @input
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
assert_raises ArgumentError, "template is required" do
|
|
43
|
+
@transloadit.signed_smart_cdn_url(
|
|
44
|
+
workspace: @workspace,
|
|
45
|
+
template: "",
|
|
46
|
+
input: @input
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "requires input" do
|
|
52
|
+
assert_raises ArgumentError, "input is required" do
|
|
53
|
+
@transloadit.signed_smart_cdn_url(
|
|
54
|
+
workspace: @workspace,
|
|
55
|
+
template: @template,
|
|
56
|
+
input: nil
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "allows empty input string" do
|
|
62
|
+
params = {
|
|
63
|
+
workspace: @workspace,
|
|
64
|
+
template: @template,
|
|
65
|
+
input: "",
|
|
66
|
+
expire_at_ms: @expire_at
|
|
67
|
+
}
|
|
68
|
+
expected_url = "https://my-app.tlcdn.com/test-smart-cdn/?auth_key=my-key&exp=1732550672867&sig=sha256%3Ad5e13df4acde8d4aaa0f34534489e54098b5128c54392600ed96dd77669a533e"
|
|
69
|
+
|
|
70
|
+
url = @transloadit.signed_smart_cdn_url(**params)
|
|
71
|
+
assert_equal expected_url, url
|
|
72
|
+
|
|
73
|
+
if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
|
|
74
|
+
assert_equal expected_url, cli_url
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "uses instance credentials" do
|
|
79
|
+
params = {
|
|
80
|
+
workspace: @workspace,
|
|
81
|
+
template: @template,
|
|
82
|
+
input: @input,
|
|
83
|
+
expire_at_ms: @expire_at
|
|
84
|
+
}
|
|
85
|
+
expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&sig=sha256%3A8620fc2a22aec6081cde730b7f3f29c0d8083f58a68f62739e642b3c03709139"
|
|
86
|
+
|
|
87
|
+
url = @transloadit.signed_smart_cdn_url(**params)
|
|
88
|
+
assert_equal expected_url, url
|
|
89
|
+
|
|
90
|
+
if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
|
|
91
|
+
assert_equal expected_url, cli_url
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "includes empty width parameter" do
|
|
96
|
+
params = {
|
|
97
|
+
workspace: @workspace,
|
|
98
|
+
template: @template,
|
|
99
|
+
input: @input,
|
|
100
|
+
expire_at_ms: @expire_at,
|
|
101
|
+
url_params: {
|
|
102
|
+
width: "",
|
|
103
|
+
height: 200
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&width=&sig=sha256%3Aebf562722c504839db97165e657583f74192ac4ab580f1a0dd67d3d868b4ced3"
|
|
107
|
+
|
|
108
|
+
url = @transloadit.signed_smart_cdn_url(**params)
|
|
109
|
+
assert_equal expected_url, url
|
|
110
|
+
|
|
111
|
+
if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
|
|
112
|
+
assert_equal expected_url, cli_url
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "handles nil values in parameters" do
|
|
117
|
+
params = {
|
|
118
|
+
workspace: @workspace,
|
|
119
|
+
template: @template,
|
|
120
|
+
input: @input,
|
|
121
|
+
expire_at_ms: @expire_at,
|
|
122
|
+
url_params: {
|
|
123
|
+
width: nil,
|
|
124
|
+
height: 200
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&sig=sha256%3Ad6897a0cb527a14eaab13c54b06f53527797c553d8b7e5d0b1a5df237212f083"
|
|
128
|
+
|
|
129
|
+
url = @transloadit.signed_smart_cdn_url(**params)
|
|
130
|
+
assert_equal expected_url, url
|
|
131
|
+
|
|
132
|
+
if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
|
|
133
|
+
assert_equal expected_url, cli_url
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "handles array values in parameters" do
|
|
138
|
+
params = {
|
|
139
|
+
workspace: @workspace,
|
|
140
|
+
template: @template,
|
|
141
|
+
input: @input,
|
|
142
|
+
expire_at_ms: @expire_at,
|
|
143
|
+
url_params: {
|
|
144
|
+
tags: ["landscape", "amsterdam", nil, ""],
|
|
145
|
+
height: 200
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&tags=landscape&tags=amsterdam&tags=&sig=sha256%3Aff46eb0083d64b250b2e4510380e333f67da855b2401493dee7a706a47957d3f"
|
|
149
|
+
|
|
150
|
+
url = @transloadit.signed_smart_cdn_url(**params)
|
|
151
|
+
assert_equal expected_url, url
|
|
152
|
+
|
|
153
|
+
if (cli_url = run_transloadit_smart_sig(params, key: @auth_key, secret: @auth_secret))
|
|
154
|
+
assert_equal expected_url, cli_url
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
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
|
|