diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..2341a2f5 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Uploadcare API credentials +# Copy this file to .env and fill in your actual keys (for local development only). +# Never commit real credentials. See MIGRATING_V5.md for v5 client-first setup details. +# Get your keys from: https://app.uploadcare.com/projects/-/api-keys/ + +UPLOADCARE_PUBLIC_KEY=your_public_key_here +UPLOADCARE_SECRET_KEY=your_secret_key_here diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index 6ded99cc..366d42d4 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Release Gem if: contains(github.ref, 'refs/tags/v') diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index f13405ce..4c2d5831 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -2,7 +2,7 @@ # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. -# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# This workflow installs Ruby, runs tests, and runs style checks in a dedicated job. # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby name: Ruby @@ -16,14 +16,12 @@ jobs: strategy: matrix: ruby-version: - - 3.0 - - 3.1 - - 3.2 - 3.3 - 3.4 + - 4.0 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -31,26 +29,24 @@ jobs: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - name: Run tests - run: bundle exec rake + # Keep test invocation on rake tasks so repository-local pre/post hooks stay centralized. + run: bundle exec rake spec env: UPLOADCARE_PUBLIC_KEY: demopublickey - UPLOADCARE_SECRET_KEY: demoprivatekey + UPLOADCARE_SECRET_KEY: demosecretkey style-check: runs-on: ubuntu-latest - continue-on-error: true strategy: matrix: ruby-version: - - 3.4 + - 4.0 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Install Rubocop - run: gem install rubocop - name: Check codestyle - run: rubocop + run: bundle exec rubocop diff --git a/.gitignore b/.gitignore index b8eac599..49c40cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,12 @@ Gemfile.lock .ruby-version project_files *.gem +.vscode/ +.DS_Store .claude/ +.kiro/ +.agents/ + +# Environment variables (API keys) +.env +.env.local diff --git a/.rubocop.yml b/.rubocop.yml index e1fb9093..7682b8a8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,18 @@ +plugins: + - rubocop-rake + - rubocop-rspec + AllCops: NewCops: enable - TargetRubyVersion: 3.0 + # RuboCop currently supports up to 3.5; CI still tests this gem on Ruby 4.0. + TargetRubyVersion: 3.4 + SuggestExtensions: false + Exclude: + - 'examples/**/*' + - 'tmp/**/*' + - 'spec/support/**/*' + - 'api_examples/support/**/*' + - 'vendor/bundle/**/*' Layout/LineLength: Max: 120 @@ -10,31 +22,106 @@ Layout/LineLength: Lint/IneffectiveAccessModifier: Enabled: false -Style/HashTransformKeys: - Exclude: - - 'lib/uploadcare/entity/decorator/paginator.rb' - - 'lib/uploadcare/client/conversion/video_conversion_client.rb' - - 'lib/uploadcare/entity/file.rb' - Metrics/BlockLength: + Max: 30 Exclude: - 'bin/' - 'spec/**/*' - 'uploadcare-ruby.gemspec' + - 'api_examples/support/**/*.rb' + +Metrics/ClassLength: + Max: 200 + Exclude: + - 'spec/**/*' Metrics/ModuleLength: + Max: 100 Exclude: - 'spec/**/*' + - 'api_examples/support/**/*.rb' Metrics/MethodLength: - Max: 20 + Max: 30 + Exclude: + - 'api_examples/support/**/*.rb' + +Metrics/AbcSize: + Max: 28 + Exclude: + - 'api_examples/support/**/*.rb' + +Metrics/CyclomaticComplexity: + Max: 10 + Exclude: + - 'api_examples/support/**/*.rb' + +Metrics/PerceivedComplexity: + Max: 11 + Exclude: + - 'api_examples/support/**/*.rb' + +Metrics/ParameterLists: + Max: 8 + Exclude: + - 'spec/**/*' Style/Documentation: Enabled: false +Style/ClassAndModuleChildren: + EnforcedStyle: compact + Style/OptionalBooleanParameter: AllowedMethods: ['create'] Style/FrozenStringLiteralComment: Exclude: - 'api_examples/**/*' + +Style/ArgumentsForwarding: + Enabled: false + +Gemspec/RequiredRubyVersion: + Enabled: false + +RSpec/ContextWording: + Prefixes: + - when + - with + - without + - if + - unless + - for + +# Kept intentionally for API-integration style specs where strict unit-style cops add noise. +RSpec/AnyInstance: + Enabled: false + +RSpec/DescribedClass: + Enabled: false + +RSpec/ExampleLength: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/MultipleMemoizedHelpers: + Enabled: false + +RSpec/NestedGroups: + Enabled: false + +# Existing test suite uses classic doubles/spies heavily; keep these disabled until specs are migrated. +RSpec/DescribeClass: + Enabled: false + +RSpec/MessageSpies: + Enabled: false + +RSpec/StubbedMock: + Enabled: false + +RSpec/VerifiedDoubles: + Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index a701f17f..f340b393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,70 @@ # Changelog +## 5.0.0 — 2026-05-17 + +v5 is stable. +No API changes since `5.0.0.rc1`. +This cut removes the prerelease suffix, ports the relevant upstream release-readiness updates, and publishes the v5 line as stable. + +Please review [`MIGRATING_V5.md`](./MIGRATING_V5.md) before upgrading from v4.x. + +### Added + +* New client-first public API centered on `Uploadcare::Client` +* Full endpoint-parity access through `client.api.rest` and `client.api.upload` +* Canonical endpoint examples for the REST API and Upload API under `api_examples/` +* Updated workflow-oriented examples under `examples/` +* Context7 configuration tuned for the v5 client-first API and example layout +* Multi-account configuration support through client-scoped `Uploadcare::Configuration` +* Documented internal API surface through YARD for maintainers and integrators +* Ruby 4.0 support in the test matrix + +### Changed + +* Replaced the older flatter API shape with a layered API: + * convenience layer for application code + * raw parity layer for exact endpoint access +* Replaced `dry-configurable` with a plain Ruby configuration object +* Replaced legacy HTTP and autoloading patterns with Faraday and Zeitwerk +* Standardized resource and collection return types across the convenience layer +* Reworked README, migration docs, and examples to match the current v5 API + +### Fixed + +* Upload IO normalization for both path-backed files and generic readable streams +* Client/config scoping so resources do not silently fall back to the wrong account context +* Video conversion `store` parameter normalization +* Document conversion boolean option normalization to match video conversion behavior +* REST request signing so the resolved `Content-Type` is the same value used for both signing and transmission +* REST query signing to use the same parameter encoding style as request transmission +* Multipart upload retry semantics so `max_retries` means retries after the initial attempt +* Multipart upload part retries/timeouts now honor configuration (`max_upload_retries`, `upload_timeout`) +* Multipart upload worker cancellation after first parallel upload error to avoid unnecessary in-flight uploads +* Multipart upload start payload no longer sends unsupported `part_size` to `/multipart/start/` +* Upload-from-URL polling now supports exponential backoff with a configurable cap +* Example cleanup to avoid leaking temporary files and groups in the demo project +* Standalone example loading and script execution +* File metadata resource initialization now correctly assigns instance UUID +* Upload API batch uploads now avoid filename collisions without mutating caller-visible filenames +* REST authenticator now uses deterministic protocol-required digests (`MD5` body digest and `SHA1` HMAC digest) +* Upload API debug logger now avoids emitting request/response headers and bodies by default +* Thread-safe lazy memoization for client/accessor/API endpoint objects and CNAME cache internals +* Test fixture generation now closes source and destination file handles correctly + +### Removed + +* Support for Ruby versions below `3.3` +* Legacy configuration and transport patterns that were no longer aligned with the v5 architecture + +### Risk & Rollout Notes + +* Ruby support baseline is now `>= 3.3`; verify application/runtime images before upgrading. +* Recommended rollout: wire explicit `Uploadcare::Client` instances first, then migrate call sites incrementally. +* Validate large-file uploads because multipart retry and cancellation behavior was hardened. +* Validate custom REST signing integrations if your app reimplements signing outside this gem. +* After deploy, monitor upload errors, multipart worker failures, and REST signing errors for 24-72 hours. +* Keep rollback simple by pinning to the latest v4 release if your app depends on removed internal APIs. + ## 4.5.0 — 2025-07-25 ### Added * **CDN Subdomain Support**: Added support for automatic subdomain generation to improve CDN performance and caching. diff --git a/Gemfile b/Gemfile index d3f8c60d..c5b52939 100644 --- a/Gemfile +++ b/Gemfile @@ -2,12 +2,29 @@ source 'https://rubygems.org' -gem 'byebug' -gem 'rake' -gem 'rspec' -gem 'rubocop' -gem 'vcr' -gem 'webmock' +group :development, :test do + # Ruby 3.4+ stopped shipping some stdlib components as default gems. + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4') + gem 'base64', require: false + gem 'cgi', require: false + end + + # Ruby 4.1+ removes tsort from default gems. + gem('tsort', require: false) if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('4.1') + + gem 'byebug' + gem 'dotenv', '~> 3.2' # For running examples with .env file + gem 'rake' + gem 'redcarpet' + gem 'rspec' + gem 'rubocop' + gem 'rubocop-rake' + gem 'rubocop-rspec' + gem 'simplecov', require: false + gem 'vcr' + gem 'webmock' + gem 'yard' +end # Specify your gem's dependencies in uploadcare-ruby.gemspec gemspec diff --git a/MIGRATING_V5.md b/MIGRATING_V5.md new file mode 100644 index 00000000..8cf594d6 --- /dev/null +++ b/MIGRATING_V5.md @@ -0,0 +1,290 @@ +# Migrating From v4.x to v5 + +Version 5 moves the gem to a client-first API with a clearer split between the convenience layer and the endpoint-parity API. + +The main migration rule is simple: + +- use `Uploadcare::Client` for application code +- use `client.files`, `client.groups`, `client.uploads`, and the other domain accessors for normal workflows +- use `client.api.rest` and `client.api.upload` when you need exact Uploadcare endpoint behavior + +## What Changed + +Version 5 introduces: + +- a client-scoped configuration model +- first-class multi-account support +- a convenience layer that returns resources and raises typed exceptions +- a raw API layer that mirrors the REST and Upload APIs and returns `Uploadcare::Result` +- a modernized internal structure built around Zeitwerk and Faraday + +Version 5 also raises the minimum supported Ruby version to `3.3`. +The CI matrix for v5 verifies `3.3`, `3.4`, and `4.0`. + +If you run examples locally with `.env`, use it only for developer machines and never commit real keys. +The examples expect `UPLOADCARE_PUBLIC_KEY` and `UPLOADCARE_SECRET_KEY` to come from environment variables. + +## Recommended Migration Order + +1. Introduce explicit `Uploadcare::Client` instances in your application. +2. Move app-facing code to `client.files`, `client.groups`, `client.uploads`, `client.project`, `client.webhooks`, `client.file_metadata`, `client.addons`, and `client.conversions`. +3. Keep `client.api.rest` and `client.api.upload` only where you need raw endpoint parity. +4. Audit return-type and error-handling assumptions. +5. Remove any app code that depends on internal transport classes. + +## Configuration + +### Before + +Typical v4 code relied more heavily on the process-wide configuration singleton. + +```ruby +Uploadcare.configure do |config| + config.public_key = ENV.fetch("UPLOADCARE_PUBLIC_KEY") + config.secret_key = ENV.fetch("UPLOADCARE_SECRET_KEY") +end +``` + +### After + +Global configuration still exists, but the preferred style is explicit clients. + +```ruby +client = Uploadcare::Client.new( + public_key: ENV.fetch("UPLOADCARE_PUBLIC_KEY"), + secret_key: ENV.fetch("UPLOADCARE_SECRET_KEY") +) +``` + +You can also build and copy configuration objects directly: + +```ruby +base = Uploadcare::Configuration.new( + public_key: ENV.fetch("UPLOADCARE_PUBLIC_KEY"), + secret_key: ENV.fetch("UPLOADCARE_SECRET_KEY") +) + +primary = Uploadcare::Client.new(config: base) +secondary = Uploadcare::Client.new( + config: base.with(public_key: "other-public-key", secret_key: "other-secret-key") +) +``` + +This is the intended approach for multi-tenant Rails apps and for integrations that need more than one Uploadcare project in one process. + +## Public API Mapping + +### Uploads + +```ruby +# v4-ish +Uploadcare::Uploader.upload(object: file, store: true) + +# v5 +client.files.upload(file, store: true) +# or +client.uploads.upload(file, store: true) +``` + +```ruby +# v4-ish +Uploadcare::Uploader.upload(object: url, store: true) + +# v5 +client.files.upload_from_url(url, store: true) +``` + +```ruby +# v4-ish +Uploadcare::Uploader.upload_from_url(url: url, async: true) + +# v5 +client.uploads.upload_from_url(url: url, async: true) +``` + +```ruby +# v4-ish +Uploadcare::Uploader.upload_from_url_status(token: token) + +# v5 +client.uploads.upload_from_url_status(token: token) +``` + +### Files + +```ruby +# v4-ish +Uploadcare::File.info(uuid: uuid) + +# v5 +client.files.find(uuid: uuid) +``` + +```ruby +# v4-ish +Uploadcare::File.list(options: { limit: 100 }) + +# v5 +client.files.list(limit: 100) +``` + +```ruby +# v4-ish +Uploadcare::File.batch_store(uuids: uuids) + +# v5 +client.files.batch_store(uuids: uuids) +``` + +```ruby +# v4-ish +Uploadcare::File.batch_delete(uuids: uuids) + +# v5 +client.files.batch_delete(uuids: uuids) +``` + +### Groups + +```ruby +# v4-ish +Uploadcare::Group.create(uuids: uuids) + +# v5 +client.groups.create(uuids: uuids) +``` + +```ruby +# v4-ish +Uploadcare::Group.info(group_id: group_id) + +# v5 +client.groups.find(group_id: group_id) +``` + +### Metadata + +```ruby +# v4-ish +Uploadcare::FileMetadata.update(uuid: uuid, key: "key", value: "value") + +# v5 +client.file_metadata.update(uuid: uuid, key: "key", value: "value") +``` + +### Webhooks + +```ruby +# v4-ish +Uploadcare::Webhook.create(target_url: url) + +# v5 +client.webhooks.create(target_url: url) +``` + +### Add-ons + +```ruby +# v4-ish +Uploadcare::Addons.check_remove_bg_status(request_id: request_id) + +# v5 +client.addons.remove_bg_status(request_id: request_id) +``` + +## Top-Level Constants + +These top-level resource constants still exist: + +- `Uploadcare::File` +- `Uploadcare::Group` +- `Uploadcare::Project` +- `Uploadcare::Webhook` +- `Uploadcare::FileMetadata` +- `Uploadcare::DocumentConversion` +- `Uploadcare::VideoConversion` + +They remain useful for compatibility and for direct resource-oriented usage, but the main application entry point is now `Uploadcare::Client`. + +## Errors and Results + +### Convenience Layer + +The convenience layer unwraps results and raises typed exceptions. + +```ruby +file = client.files.find(uuid: uuid) +``` + +Typical exceptions include: + +- `Uploadcare::Exception::RequestError` +- `Uploadcare::Exception::InvalidRequestError` +- `Uploadcare::Exception::NotFoundError` +- `Uploadcare::Exception::UploadError` +- `Uploadcare::Exception::MultipartUploadError` +- `Uploadcare::Exception::UploadTimeoutError` +- `Uploadcare::Exception::ThrottleError` + +### Raw API Layer + +The raw API layer returns `Uploadcare::Result`. + +```ruby +result = client.api.rest.files.info(uuid: uuid) + +if result.success? + puts result.success +else + warn result.error_message +end +``` + +If your v4 code assumed direct hashes everywhere, decide explicitly whether it belongs on the convenience layer or the raw API layer. + +## Return Type Changes + +### Files and Groups + +Convenience methods now return resource objects and paginated collections: + +- `client.files.find` -> `Uploadcare::File` +- `client.files.list` -> `Uploadcare::Collections::Paginated` +- `client.groups.find` -> `Uploadcare::Group` + +### Batch Operations + +Batch operations return `Uploadcare::Collections::BatchResult`. + +```ruby +result = client.files.batch_store(uuids: uuids) + +result.status +result.result +result.problems +``` + +### Conversions + +Document and video conversions are intentionally not perfectly symmetrical: + +- `client.conversions.documents.convert` returns the API response hash +- `client.conversions.videos.convert` returns a `Uploadcare::VideoConversion` resource + +Audit conversion code carefully if you rely on exact return types. + +## Internal APIs + +Version 5 keeps the internal transport layer available and documented, but it is not the recommended primary surface for application code. + +If you need it: + +- `client.api.rest` +- `client.api.upload` + +If you are wrapping the gem from another library, prefer these explicit raw API entry points over reaching into lower-level internals. + +## See Also + +- [README.md](./README.md) +- [CHANGELOG.md](./CHANGELOG.md) diff --git a/README.md b/README.md index 215834c3..2099e13a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Ruby integration for Uploadcare +# Uploadcare Ruby SDK ![license](https://img.shields.io/badge/license-MIT-brightgreen.svg) [![Build Status][actions-img]][actions-badge] @@ -6,948 +6,634 @@ [actions-badge]: https://github.com/uploadcare/uploadcare-ruby/actions/workflows/ruby.yml [actions-img]: https://github.com/uploadcare/uploadcare-ruby/actions/workflows/ruby.yml/badge.svg -[coverals-img]: https://coveralls.io/repos/github/uploadcare/uploadcare-ruby/badge.svg?branch=main -[coverals]: https://coveralls.io/github/uploadcare/uploadcare-ruby?branch=main [stack-img]: https://img.shields.io/badge/tech-stack-0690fa.svg?style=flat [stack]: https://stackshare.io/uploadcare/stacks/ -Uploadcare Ruby integration handles uploads and further operations with files by -wrapping Upload and REST APIs. +`uploadcare-ruby` is a framework-agnostic client for the Uploadcare Upload API and REST API. +The gem is built around: + +- explicit `Uploadcare::Client` instances +- client-scoped configuration for multi-account use +- a small convenience layer for common workflows +- full endpoint coverage through `client.api.rest` and `client.api.upload` + +- [Requirements](#requirements) +- [Compatibility](#compatibility) - [Installation](#installation) -- [Usage](#usage) - - [Uploading files](#uploading-files) - - [Uploading and storing a single file](#uploading-and-storing-a-single-file) - - [Multiple ways to upload files](#multiple-ways-to-upload-files) - - [Uploading options](#uploading-options) - - [File management](#file-management) - - [File](#file) - - [FileList](#filelist) - - [Pagination](#pagination) - - [Custom File Metadata](#custom-file-metadata) - - [Group](#group) - - [GroupList](#grouplist) - - [Webhook](#webhook) - - [Add-Ons](#add-ons) - - [Project](#project) - - [Conversion](#conversion) -- [Useful links](#useful-links) +- [Design](#design) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Multi-Account Usage](#multi-account-usage) +- [Uploads](#uploads) +- [Files](#files) +- [Groups](#groups) +- [Project](#project) +- [Metadata](#metadata) +- [Webhooks](#webhooks) +- [Add-ons](#add-ons) +- [Conversions](#conversions) +- [Secure Delivery](#secure-delivery) +- [Errors and Results](#errors-and-results) +- [Request Options](#request-options) +- [Raw API Access](#raw-api-access) +- [Examples](#examples) +- [Upgrading from v4.x](#upgrading-from-v4x) ## Requirements -- ruby 3.0+ +- Ruby 3.3+ ## Compatibility -Note that `uploadcare-ruby` **3.x** is not backward compatible with -**[2.x](https://github.com/uploadcare/uploadcare-ruby/tree/v2.x)**. +This gem is intended for plain Ruby applications and for framework integrations built on top of it. + +- Use explicit `Uploadcare::Client` instances when you need multiple accounts in one process. +- Use `Uploadcare.configure` and `Uploadcare.client` when one global default client is enough. +- Use `client.api.rest` and `client.api.upload` when you want endpoint-level parity with the official API references. ## Installation -Add this line to your application's Gemfile: +Add the gem to your Gemfile: ```ruby gem "uploadcare-ruby" ``` -And then execute: - - $ bundle - -You can also use it outside of Rails or other Apps. - -Install the gem directly: - - $ gem install uploadcare-ruby - -Then in your Ruby code: - -```ruby -require "uploadcare" - -Uploadcare.config.public_key = "your_public_key" -Uploadcare.config.secret_key = "your_secret_key" - -# Example usage -uuid = "file_uuid" -puts Uploadcare::File.info(uuid).inspect -``` - -If you use `api_struct` gem in your project, replace it with `uploadcare-api_struct`: +Then install: -```ruby -gem 'uploadcare-api_struct' +```bash +bundle ``` -and run `bundle install` - -If already not, create your project in [Uploadcare dashboard](https://app.uploadcare.com/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-ruby) and copy -its [API keys](https://app.uploadcare.com/projects/-/api-keys/) from there. - -Set your Uploadcare keys in config file or through environment variables: +Set credentials with environment variables: ```bash export UPLOADCARE_PUBLIC_KEY=your_public_key -export UPLOADCARE_SECRET_KEY=your_private_key +export UPLOADCARE_SECRET_KEY=your_secret_key ``` -Or configure your app yourself if you are using different way of storing keys. -Gem configuration is available in `Uploadcare.configuration`. Full list of -settings can be seen in [`lib/uploadcare.rb`](lib/uploadcare.rb) +## Design -```ruby -# your_config_initializer_file.rb -Uploadcare.config.public_key = "your_public_key" -Uploadcare.config.secret_key = "your_private_key" -``` +The gem has two public layers: -## Usage +### Convenience layer -This section contains practical usage examples. Please note, everything that -follows gets way more clear once you've looked through our -[docs](https://uploadcare.com/docs/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-ruby) -and [Upload](https://uploadcare.com/api-refs/upload-api/) and [REST](https://uploadcare.com/api-refs/rest-api/) API refs. +This is the default API you should use in applications: -You can also find an example project [here](https://github.com/uploadcare/uploadcare-rails-example). +- `client.files` +- `client.groups` +- `client.uploads` +- `client.project` +- `client.webhooks` +- `client.file_metadata` +- `client.addons` +- `client.conversions` -In examples we’re going to use `demo.ucarecd.net` domain. Check your project's subdomain in the [Dashboard](https://app.uploadcare.com/projects/-/settings/#delivery). +This layer returns resources and collections, and it raises typed exceptions on failure. -### Uploading files +### Raw parity layer -#### Uploading and storing a single file +This layer mirrors Uploadcare’s REST and Upload APIs: -Using Uploadcare is simple, and here are the basics of handling files. +- `client.api.rest` +- `client.api.upload` -```ruby -@file_to_upload = File.open("your-file.png") +This layer returns `Uploadcare::Result` objects so you can inspect success and failure explicitly. -@uc_file = Uploadcare::Uploader.upload(@file_to_upload, store: "auto") +That split keeps app code clean without losing full API coverage. -@uc_file.uuid -# => "dc99200d-9bd6-4b43-bfa9-aa7bfaefca40" +## Quick Start -# URL for the file, can be used with your website or app right away -@uc_file.original_file_url -# => "https://demo.ucarecd.net/dc99200d-9bd6-4b43-bfa9-aa7bfaefca40/your-file.png" +```ruby +require "uploadcare" -# CDN URL for the file -@uc_file.cdn_url -# => "https://demo.ucarecd.net/dc99200d-9bd6-4b43-bfa9-aa7bfaefca40/" -# -# With subdomains enabled: -# Uploadcare.config.use_subdomains = true -# => "https://a1b2c3d4e5.ucarecdn.net/dc99200d-9bd6-4b43-bfa9-aa7bfaefca40/" -``` +client = Uploadcare::Client.new( + public_key: ENV.fetch("UPLOADCARE_PUBLIC_KEY"), + secret_key: ENV.fetch("UPLOADCARE_SECRET_KEY") +) -The `store` option can have these possible values: +file = File.open("photo.jpg", "rb") do |io| + client.files.upload(io, store: true) +end -- `true`: mark the uploaded file as stored. -- `false`: do not mark the uploaded file as stored and remove it after 24 hours. -- `"auto"`: defers the choice of storage behavior to the [auto-store setting](https://app.uploadcare.com/projects/-/settings/#storage) for your Uploadcare project. This is the default behavior. +puts file.uuid +puts file.cdn_url +``` -Your might then want to store or delete the uploaded file. +You can also configure a default global client: ```ruby -# that's how you store a file, if you have uploaded the file using store: false and changed your mind later -@uc_file.store -# => # # "c6e31082-6bdc-4cb3-bef5-14dd10574d72" +Uploadcare.configure do |config| + config.public_key = "public_key" + config.secret_key = "secret_key" + config.auth_type = "Uploadcare" + config.use_subdomains = false +end ``` -After the request for uploading-from-URL is sent, you can check the progress of the upload by sending the `get_upload_from_url_status` request: +Or build configuration objects directly: ```ruby -Uploadcare::Uploader.get_upload_from_url_status("1251ee66-3631-4416-a2fb-96ba59f5a515") -# => Success({:size=>453543, :total=>453543, :done=>453543, :uuid=>"5c51a7fe-e45d-42a2-ba5e-79957ff4bdab", :file_id=>"5c51a7fe-e45d-42a2-ba5e-79957ff4bdab", :original_filename=>"2250", :is_image=>true, :is_stored=>false, :image_info=>{:dpi=>[96, 96], :width=>2250, :format=>"JPEG", :height=>2250, :sequence=>false, :color_mode=>"RGB", :orientation=>nil, :geo_location=>nil, :datetime_original=>nil}, :video_info=>nil, :content_info=>{:mime=>{:mime=>"image/jpeg", :type=>"image", :subtype=>"jpeg"}, :image=>{:dpi=>[96, 96], :width=>2250, :format=>"JPEG", :height=>2250, :sequence=>false, :color_mode=>"RGB", :orientation=>nil, :geo_location=>nil, :datetime_original=>nil}}, :is_ready=>true, :filename=>"2250", :mime_type=>"image/jpeg", :metadata=>{}, :status=>"success"}) +base_config = Uploadcare::Configuration.new( + public_key: "public_key", + secret_key: "secret_key" +) + +client = Uploadcare::Client.new(config: base_config) ``` -In case of the `async` option is disabled, uploadcare-ruby tries to request the upload status several times (depending on the `max_request_tries` config param) and then returns uploaded file attributes. +Configuration objects are copyable: ```ruby -# multipart upload - can be useful for files bigger than 10 mb -Uploadcare::Uploader.multipart_upload(File.open("big_file.bin"), store: true) +account_a = Uploadcare::Client.new(config: base_config.with(public_key: "pk-a", secret_key: "sk-a")) +account_b = Uploadcare::Client.new(config: base_config.with(public_key: "pk-b", secret_key: "sk-b")) ``` -For the multipart upload you can pass a block to add some additional logic after each file chunk is uploaded. -For example to track file uploading progress you can do something like this: +Common configuration options: + +- `public_key` +- `secret_key` +- `auth_type` +- `multipart_size_threshold` +- `multipart_chunk_size` +- `upload_threads` +- `upload_timeout` +- `max_upload_retries` +- `sign_uploads` +- `upload_signature_lifetime` +- `use_subdomains` +- `cdn_base_postfix` +- `default_cdn_base` + +CDN helpers: ```ruby -file = File.open("big_file.bin") -progress = 0 -Uploadcare::Uploader.multipart_upload(file, store: true) do |options| - progress += (100.0 / options[:links_count]) - puts "PROGRESS = #{progress}" +Uploadcare.configure do |config| + config.use_subdomains = true + config.cdn_base_postfix = "https://ucarecd.net/" + config.default_cdn_base = "https://ucarecdn.com/" end -``` -Output of the code above looks like: - -```console -PROGRESS = 4.545454545454546 -PROGRESS = 9.090909090909092 -PROGRESS = 13.636363636363637 -... -``` - -Options available in a block: - -- **:chunk_size** - size of each chunk in bytes; -- **:object** - file object which is going to be uploaded; -- **:offset** - offset from the beginning of a File object in bytes; -- **:link_id** - index of a link provided by Uploadcare API. Might be treated as index of a chunk; -- **:links** - array of links for uploading file's chunks; -- **:links_count** - count of the array of links. - -#### Uploading options - -You can override [auto-store setting](https://app.uploadcare.com/projects/-/settings/#storage) from your Uploadcare project for each upload request: - -```ruby -@api.upload(files, store: true) # mark the uploaded file as stored. -@api.upload(files, store: false) # do not mark the uploaded file as stored and remove it after 24 hours. -@api.upload_from_url(url, store: "auto") # defers the choice of storage behavior to the auto-store setting. -``` - -You can upload file with custom metadata, for example `subsystem` and `pet`: - -```ruby -@api.upload(files, metadata: { subsystem: 'my_subsystem', pet: 'cat' } ) -@api.upload_from_url(url, metadata: { subsystem: 'my_subsystem', pet: 'cat' }) -``` - -### File management - -Entities are representations of objects in Uploadcare cloud. - -#### File - -File entity contains its metadata. It also supports `include` param to include additional fields to the file object, such as: "appdata". - -```ruby -@file = Uploadcare::File.file("FILE_UUID", include: "appdata") -{ - "datetime_removed"=>nil, - "datetime_stored"=>"2018-11-26T12:49:10.477888Z", - "datetime_uploaded"=>"2018-11-26T12:49:09.945335Z", - "is_image"=>true, - "is_ready"=>true, - "mime_type"=>"image/jpeg", - "original_file_url"=>"https://demo.ucarecd.net/FILE_UUID/pineapple.jpg", - "original_filename"=>"pineapple.jpg", - "size"=>642, - "url"=>"https://api.uploadcare.com/files/FILE_UUID/", - "uuid"=>"FILE_UUID", - "variations"=>nil, - "content_info"=>{ - "mime"=>{ - "mime"=>"image/jpeg", - "type"=>"image", - "subtype"=>"jpeg" - }, - "image"=>{ - "format"=>"JPEG", - "width"=>500, - "height"=>500, - "sequence"=>false, - "orientation"=>6, - "geo_location"=>{ - "latitude"=>55.62013611111111, - "longitude"=>37.66299166666666 - }, - "datetime_original"=>"2018-08-20T08:59:50", - "dpi"=>[72, 72] - } - }, - "metadata"=>{ - "subsystem"=>"uploader", - "pet"=>"cat" - }, - "appdata"=>{ - "uc_clamav_virus_scan"=>{ - "data"=>{ - "infected"=>true, - "infected_with"=>"Win.Test.EICAR_HDB-1" - }, - "version"=>"0.104.2", - "datetime_created"=>"2021-09-21T11:24:33.159663Z", - "datetime_updated"=>"2021-09-21T11:24:33.159663Z" - }, - "remove_bg"=>{ - "data"=>{ - "foreground_type"=>"person" - }, - "version"=>"1.0", - "datetime_created"=>"2021-07-25T12:24:33.159663Z", - "datetime_updated"=>"2021-07-25T12:24:33.159663Z" - }, - "aws_rekognition_detect_labels"=>{ - "data"=>{ - "LabelModelVersion"=>"2.0", - "Labels"=>[ - { - "Confidence"=>93.41645812988281, - "Instances"=>[], - "Name"=>"Home Decor", - "Parents"=>[] - }, - { - "Confidence"=>70.75951385498047, - "Instances"=>[], - "Name"=>"Linen", - "Parents"=>[{ "Name"=>"Home Decor" }] - }, - { - "Confidence"=>64.7123794555664, - "Instances"=>[], - "Name"=>"Sunlight", - "Parents"=>[] - }, - { - "Confidence"=>56.264793395996094, - "Instances"=>[], - "Name"=>"Flare", - "Parents"=>[{ "Name"=>"Light" }] - }, - { - "Confidence"=>50.47153854370117, - "Instances"=>[], - "Name"=>"Tree", - "Parents"=>[{ "Name"=>"Plant" }] - } - ] - }, - "version"=>"2016-06-27", - "datetime_created"=>"2021-09-21T11:25:31.259763Z", - "datetime_updated"=>"2021-09-21T11:27:33.359763Z" - } - } -} - -@file.local_copy # copy file to local storage - -@file.remote_copy # copy file to remote storage - -@file.store # stores file, returns updated metadata - -@file.delete #deletes file. Returns updated metadata -``` - -The File object is also can be converted if it is a document or a video file. Imagine, you have a document file: - -```ruby -@file = Uploadcare::File.file("FILE_UUID") -``` - -To convert it to an another file, just do: - -```ruby -@converted_file = @file.convert_document({ format: "png", page: "1" }, store: true) -# => { -# "uuid"=>""} -# ...other file info... -# } -# OR -# Failure({:"/document/-/format/png/-/page/1/"=>"the target_format is not a supported 'to' format for this source file. -> png"}) -``` - -Same works for video files: - -```ruby -@converted_file = @file.convert_video( - { - format: "ogg", - quality: "best", - cut: { start_time: "0:0:0.1", length: "end" }, - size: { resize_mode: "change_ratio", width: "600", height: "400" }, - thumb: { N: 1, number: 2 } - }, - store: true -) -# => { -# "uuid"=>""} -# ...other file info... -# } -# OR -# Failure({:"/video/-/size/600x400/preserve_ratio/-/quality/best/-/format/ogg/-/cut/0:0:0.1/end/-/thumbs~1/2/"=>"CDN Path error: Failed to parse remainder \"/preserve_ratio\" of \"size/600x400/preserve_ratio\""}) +Uploadcare.configuration.custom_cname +Uploadcare.configuration.cdn_base ``` -More about file conversion [here](#conversion). -Metadata of deleted files is stored permanently. +## Multi-Account Usage -#### FileList - -`Uploadcare::FileList` represents the whole collection of files (or it's -subset) and provides a way to iterate through it, making pagination transparent. -FileList objects can be created using `Uploadcare::FileList.file_list` method. +The gem is designed to support multiple Uploadcare projects in the same process: ```ruby -@list = Uploadcare::FileList.file_list -# Returns instance of Uploadcare::Entity::FileList - -# load last page of files -@files = @list.files -# load all files -@all_files = @list.load -``` +primary = Uploadcare::Client.new(public_key: "pk-1", secret_key: "sk-1") +secondary = Uploadcare::Client.new(public_key: "pk-2", secret_key: "sk-2") -This method accepts some options to control which files should be fetched and -how they should be fetched: - -- **:limit** — Controls page size. Accepts values from 1 to 1000, defaults to 100. -- **:stored** — Can be either `true` or `false`. When true, file list will contain only stored files. When false — only not stored. -- **:removed** — Can be either `true` or `false`. When true, file list will contain only removed files. When false — all except removed. Defaults to false. -- **:ordering** — Controls the order of returned files. Available values: `datetime_uploaded`, `-datetime_uploaded`. Defaults to `datetime_uploaded`. More info can be found [here](https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/filesList). -- **:from** — Specifies the starting point for a collection. Resulting collection will contain files from the given value and to the end in a direction set by an **ordering** option. When files are ordered by `datetime_updated` in any direction, accepts either a `DateTime` object or an ISO 8601 string. When files are ordered by size, accepts non-negative integers (size in bytes). More info can be found [here](https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/filesList). +primary_file = primary.files.find(uuid: "uuid-1") +secondary_file = secondary.files.find(uuid: "uuid-2") +``` -Options used to create a file list can be accessed through `#options` method. -Note that, once set, they don't affect file fetching process anymore and are -stored just for your convenience. That is why they are frozen. +You can also derive temporary variants from an existing client: ```ruby -options = { - limit: 10, - stored: true, - ordering: "-datetime_uploaded", - from: "2017-01-01T00:00:00", -} -@list = @api.file_list(options) +admin_client = primary.with(secret_key: "different-secret") ``` -To simply get all associated objects: +Resource objects retain their client context, so subsequent instance operations stay bound to the correct account. -```ruby -@list.all # => returns Array of Files -``` +## Uploads -#### Pagination +### Smart upload -Initially, `FileList` is a paginated collection. It can be navigated using following methods: +`client.uploads.upload` accepts: + +- an IO or file object +- an array of IO or file objects +- an HTTP or HTTPS URL string ```ruby - @file_list = Uploadcare::FileList.file_list - # Let's assume there are 250 files in cloud. By default, UC loads 100 files. To get next 100 files, do: - @next_page = @file_list.next_page - # To get previous page: - @previous_page = @file_list.previous_page +file = File.open("photo.jpg", "rb") do |io| + client.uploads.upload(io, store: true) +end + +remote_file = client.uploads.upload("https://example.com/image.jpg", store: true) ``` -Alternatively, it's possible to iterate through full list of groups or files with `each`: +### Single file upload ```ruby -@list.each do |file| - p file.url +file = File.open("photo.jpg", "rb") do |io| + client.files.upload(io, store: true, metadata: { subsystem: "avatars" }) end ``` -#### Custom File Metadata - -File metadata is additional, arbitrary data, associated with uploaded file. -As an example, you could store unique file identifier from your system. +### Multiple file upload ```ruby -# Get file's metadata keys and values. -Uploadcare::FileMetadata.index('FILE_UUID') +files = [ + File.open("photo-1.jpg", "rb"), + File.open("photo-2.jpg", "rb") +] -# Get the value of a single metadata key. -Uploadcare::FileMetadata.show('FILE_UUID', 'KEY') +uploaded = client.uploads.upload(files, store: true) -# Update the value of a single metadata key. If the key does not exist, it will be created. -Uploadcare::FileMetadata.update('FILE_UUID', 'KEY', 'VALUE') - -# Delete a file's metadata key. -Uploadcare::FileMetadata.delete('FILE_UUID', 'KEY') +files.each(&:close) ``` -#### Group +### Upload from URL -Groups are structures intended to organize sets of separate files. Each group is -assigned UUID. Note, group UUIDs include a `~#{files_count}` part at the end. -That's a requirement of our API. +Synchronous: ```ruby -# group can be created from an array of Uploadcare files (UUIDs) -@file = "134dc30c-093e-4f48-a5b9-966fe9cb1d01" -@file2 = "134dc30c-093e-4f48-a5b9-966fe9cb1d02" -@files_ary = [@file, @file2] -@group = Uploadcare::Group.create @files - -# group can be stored by group ID. It means that all files of a group will be stored on Uploadcare servers permanently -Uploadcare::Group.store(group.id) - -# get a file group by its ID. -Uploadcare::Group.rest_info(group.id) - -# group can be deleted by group ID. -Uploadcare::Group.delete(group.id) -# Note: This operation only removes the group object itself. All the files that were part of the group are left as is. - -# Returns group's CDN URL -@group.cdn_url -# => "https://demo.ucarecd.net/group-id~2/" - -# Returns CDN URLs of all files from group without API requesting -@group.file_cdn_urls -# => 'https://demo.ucarecd.net/0513dda0-582f-447d-846f-096e5df9e2bb~2/nth/0/' +file = client.files.upload_from_url("https://example.com/image.jpg", store: true) ``` -#### GroupList - -`GroupList` is a list of `Group` +Async: ```ruby -@group_list = Uploadcare::GroupList.list -# To get an array of groups: -@groups = @group_list.all +job = client.uploads.upload_from_url(url: "https://example.com/image.jpg", async: true, store: true) +status = client.uploads.upload_from_url_status(token: job.fetch("token")) ``` -This is a paginated list, so [pagination](#Pagination) methods apply - -#### Webhook +When async mode is enabled, the convenience layer returns the raw status token hash because the file does not exist yet. -https://uploadcare.com/docs/api_reference/rest/webhooks/ +Polling options for synchronous URL uploads: -You can use webhooks to provide notifications about your uploads to target urls. -This gem lets you create and manage webhooks. +- `poll_interval` (default: `1`) initial status polling interval in seconds +- `poll_max_interval` (default: `10`) maximum polling interval in seconds +- `poll_timeout` (default: `300`) maximum total polling time in seconds -Each webhook payload can be signed with a secret (the `signing_secret` option) to ensure that the request comes from the expected sender. -More info about secure webhooks [here](https://uploadcare.com/docs/security/secure-webhooks/). +### Multipart upload with progress ```ruby -Uploadcare::Webhook.create(target_url: "https://example.com/listen", event: "file.uploaded", is_active: true, signing_secret: "some-secret") -Uploadcare::Webhook.update(, target_url: "https://newexample.com/listen/new", event: "file.uploaded", is_active: true, signing_secret: "some-secret") -Uploadcare::Webhook.delete("https://example.com/listen") -Uploadcare::Webhook.list -``` +File.open("large-video.mp4", "rb") do |io| + file = client.uploads.multipart_upload(file: io, store: true, threads: 4) do |progress| + uploaded = progress[:uploaded] + total = progress[:total] + puts "#{uploaded}/#{total}" + end -##### Webhook signature verification - -The gem has a helper class to verify a webhook signature from headers — -`Uploadcare::Param::WebhookSignatureVerifier`. This class accepts three -important options: + puts file.uuid +end +``` -- **:webhook_body** — this option represents parameters received in the webhook - request in the JSON format. - **NOTE**: if you're using Rails, you should exclude options `controller`, - `action` and `post` from the `webhook_body`. -- **:signing_secret** — the secret that was set while creating/updating a - webhook. This option can be specified as an ENV var with the name - `UC_SIGNING_SECRET` — then no need to send it to the verifier class. -- **:x_uc_signature_header** — the content of the `X-Uc-Signature` HTTP header - in the webhook request. +### Signed uploads -Using the `Uploadcare::Param::WebhookSignatureVerifier` class example: +You can enable signed uploads globally: ```ruby - webhook_body = '{...}' +client = Uploadcare::Client.new( + public_key: "public", + secret_key: "secret", + sign_uploads: true +) +``` -signing_secret = "12345X" -x_uc_signature_header = "v1=9b31c7dd83fdbf4a2e12b19d7f2b9d87d547672a325b9492457292db4f513c70" +Or pass explicit signature data per request: -Uploadcare::Param::WebhookSignatureVerifier.valid?(signing_secret: signing_secret, x_uc_signature_header: x_uc_signature_header, webhook_body: webhook_body) +```ruby +File.open("photo.jpg", "rb") do |io| + client.files.upload(io, signature: "signature", expire: 1_900_000_000) +end ``` -You can write your verifier. Example code: +### Upload options -```ruby -webhook_body_json = '{...}' +Common upload options: -signing_secret = ENV['UC_SIGNING_SECRET'] -x_uc_signature_header = "v1=f4d859ed2fe47b9a4fcc81693d34e58ad12366a841e58a7072c1530483689cc0" +- `store: true | false | "auto"` +- `metadata: { key: value }` +- `signature: "..."` +- `expire: unix_timestamp` +- `async: true` for URL uploads +- `threads:` and `part_size:` for multipart uploads -digest = OpenSSL::Digest.new('sha256') +If you prefer the older top-level style, the same flows can still be written through the global client: -calculated_signature = "v1=#{OpenSSL::HMAC.hexdigest(digest, signing_secret.force_encoding("utf-8"), webhook_body_json.force_encoding("utf-8"))}" +```ruby +Uploadcare.configure do |config| + config.public_key = ENV.fetch("UPLOADCARE_PUBLIC_KEY") + config.secret_key = ENV.fetch("UPLOADCARE_SECRET_KEY") +end -if calculated_signature == x_uc_signature_header - puts "WebHook signature matches!" -else - puts "WebHook signature mismatch!" +file = File.open("photo.jpg", "rb") do |io| + Uploadcare.files.upload(io, store: true) end ``` -#### Add-Ons - -An `Add-On` is an application implemented by Uploadcare that accepts uploaded files as an input and can produce other files and/or appdata as an output. +## Files -##### AWS Rekognition +### Find a file ```ruby -# Execute AWS Rekognition Add-On for a given target to detect labels in an image. -# Note: Detected labels are stored in the file's appdata. -Uploadcare::Addons.ws_rekognition_detect_labels('FILE_UUID') - -# Check the status of AWS Rekognition. -Uploadcare::Addons.ws_rekognition_detect_labels_status('RETURNED_ID_FROM_WS_REKOGNITION_DETECT_LABELS') +file = client.files.find(uuid: "file-uuid") ``` -##### AWS Rekognition Moderation +### List files ```ruby -# Execute AWS Rekognition Moderation Add-On for a given target to detect moderation labels in an image. -# Note: Detected moderation labels are stored in the file's appdata. +files = client.files.list(limit: 100) +files.each { |file| puts file.uuid } +``` -Uploadcare::Addons.ws_rekognition_detect_moderation_labels('FILE_UUID') +List responses are `Uploadcare::Collections::Paginated`: -# Check the status of an Add-On execution request that had been started using the Execute Add-On operation. -Uploadcare::Addons.ws_rekognition_detect_moderation_labels_status('RETURNED_ID_FROM_WS_REKOGNITION_DETECT_MODERATION_LABELS') +```ruby +files.next_page +files.previous_page +files.all ``` -##### ClamAV +Filters and API parameters can still be passed through: ```ruby -# ClamAV virus checking Add-On for a given target. -Uploadcare::Addons.uc_clamav_virus_scan('FILE_UUID') +files = client.files.list(stored: true, removed: false, limit: 100) +``` -# Check and purge infected file. -Uploadcare::Addons.uc_clamav_virus_scan('FILE_UUID', purge_infected: true ) +### Resource operations -# Check the status of an Add-On execution request that had been started using the Execute Add-On operation. -Uploadcare::Addons.uc_clamav_virus_scan_status('RETURNED_ID_FROM_UC_CLAMAV_VIRUS_SCAN') +```ruby +file.store +file.delete +file.reload +file.reload(params: { include: "appdata" }) ``` -##### Remove.bg +### Batch operations ```ruby -# Execute remove.bg background image removal Add-On for a given target. -Uploadcare::Addons.remove_bg('FILE_UUID') - -# You can pass optional parameters. -# See the full list of parameters here: https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/removeBgExecute -Uploadcare::Addons.remove_bg('FILE_UUID', crop: true, type_level: '2') +result = client.files.batch_store(uuids: ["uuid-1", "uuid-2"]) -# Check the status of an Add-On execution request that had been started using the Execute Add-On operation. -Uploadcare::Addons.remove_bg_status('RETURNED_ID_FROM_REMOVE_BG') +puts result.status +puts result.result.map(&:uuid) +puts result.problems ``` -#### Project +The same shape applies to `client.files.batch_delete`. -`Project` provides basic info about the connected Uploadcare project. That -object is also an Hashie::Mash, so every methods out of -[these](https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/projectInfo) will work. +### Copy operations ```ruby -@project = Uploadcare::Project.project -# => # +copied = client.files.copy_to_local(source: file.uuid, options: { store: true }) +remote_url = client.files.copy_to_remote(source: file.uuid, target: "custom_storage") +``` -@project.name -# => "demo" +Instance-level variants are also available: -@project.collaborators -# => [] -# while that one was empty, it usually goes like this: -# [{"email": collaborator@gmail.com, "name": "Collaborator"}, {"email": collaborator@gmail.com, "name": "Collaborator"}] +```ruby +copied = file.copy_to_local(options: { store: true }) +remote_url = file.copy_to_remote(target: "custom_storage") ``` -#### Conversion +## Groups -##### Video - -After each video file upload you obtain a file identifier in UUID format. -Then you can use this file identifier to convert your video in multiple ways: +Create a group: ```ruby -Uploadcare::VideoConverter.convert( - [ - { - uuid: "dc99200d-9bd6-4b43-bfa9-aa7bfaefca40", - size: { resize_mode: "change_ratio", width: "600", height: "400" }, - quality: "best", - format: "ogg", - cut: { start_time: "0:0:0.0", length: "0:0:1.0" }, - thumbs: { N: 2, number: 1 } - } - ], - store: false -) +group = client.groups.create(uuids: ["uuid-1", "uuid-2"]) ``` -This method accepts options to set properties of an output file: +Find and list groups: -- **uuid** — the file UUID-identifier. -- **size**: - - **resize_mode** - size operation to apply to a video file. Can be `preserve_ratio (default)`, `change_ratio`, `scale_crop` or `add_padding`. - - **width** - width for a converted video. - - **height** - height for a converted video. - -``` - NOTE: you can choose to provide a single dimension (width OR height). - The value you specify for any of the dimensions should be a non-zero integer divisible by 4 +```ruby +group = client.groups.find(group_id: "group-uuid~2") +groups = client.groups.list(limit: 50) ``` -- **quality** - sets the level of video quality that affects file sizes and hence loading times and volumes of generated traffic. Can be `normal (default)`, `better`, `best`, `lighter`, `lightest`. -- **format** - format for a converted video. Can be `mp4 (default)`, `webm`, `ogg`. -- **cut**: - - **start_time** - defines the starting point of a fragment to cut based on your input file timeline. - - **length** - defines the duration of that fragment. -- **thumbs**: - - **N** - quantity of thumbnails for your video - non-zero integer ranging from 1 to 50; defaults to 1. - - **number** - zero-based index of a particular thumbnail in a created set, ranging from 1 to (N - 1). -- **store** - a flag indicating if Uploadcare should store your transformed outputs. +Delete a group: ```ruby -# Response -{ - :result => [ - { - :original_source=>"dc99200d-9bd6-4b43-bfa9-aa7bfaefca40/video/-/size/600x400/change_ratio/-/quality/best/-/format/ogg/-/cut/0:0:0.0/0:0:1.0/-/thumbs~2/1/", - :token=>911933811, - :uuid=>"6f9b88bd-625c-4d60-bfde-145fa3813d95", - :thumbnails_group_uuid=>"cf34c5a1-8fcc-4db2-9ec5-62c389e84468~2" - } - ], - :problems=>{} -} +group.delete ``` -Params in the response: +Useful group helpers: + +```ruby +group.cdn_url +group.file_cdn_urls +``` -- **result** - info related to your transformed output(-s): - - **original_source** - built path for a particular video with all the conversion operations and parameters. - - **token** - a processing job token that can be used to get a [job status](https://uploadcare.com/docs/transformations/video-encoding/#status) (see below). - - **uuid** - UUID of your processed video file. - - **thumbnails_group_uuid** - holds :uuid-thumb-group, a UUID of a [file group](https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/groupsList) with thumbnails for an output video, based on the thumbs [operation](https://uploadcare.com/docs/transformations/video-encoding/#operation-thumbs) parameters. -- **problems** - problems related to your processing job, if any. +## Project -To convert multiple videos just add params as a hash for each video to the first argument of the `Uploadcare::VideoConverter#convert` method: +Fetch the current project: ```ruby -Uploadcare::VideoConverter.convert( - [ - { video_one_params }, { video_two_params }, ... - ], - store: false -) +project = client.project.current + +puts project.name +puts project.pub_key +puts project.collaborators ``` -To check a status of a video processing job you can simply use appropriate method of `Uploadcare::VideoConverter`: +## Metadata ```ruby -token = 911933811 -Uploadcare::VideoConverter.status(token) +client.file_metadata.update(uuid: file.uuid, key: "category", value: "avatar") +client.file_metadata.show(uuid: file.uuid, key: "category") +client.file_metadata.index(uuid: file.uuid) +client.file_metadata.delete(uuid: file.uuid, key: "category") ``` -`token` here is a processing job token, obtained in a response of a convert video request. +`Uploadcare::FileMetadata` is also available as a resource if you need to hold metadata state locally. + +## Webhooks ```ruby -# Response -{ - :status => "finished", - :error => nil, - :result => { - :uuid => "dc99200d-9bd6-4b43-bfa9-aa7bfaefca40", - :thumbnails_group_uuid => "0f181f24-7551-42e5-bebc-14b15d9d3838~2" - } -} +webhook = client.webhooks.create( + target_url: "https://example.com/uploadcare", + event: "file.uploaded", + is_active: true +) + +client.webhooks.list +client.webhooks.update(id: webhook.id, is_active: false) +client.webhooks.delete(target_url: webhook.target_url) ``` -Params in the response: +## Add-ons + +```ruby +execution = client.addons.aws_rekognition_detect_labels(uuid: file.uuid) +client.addons.aws_rekognition_detect_labels_status(request_id: execution.request_id) -- **status** - processing job status, can have one of the following values: - - _pending_ — video file is being prepared for conversion. - - _processing_ — video file processing is in progress. - - _finished_ — the processing is finished. - - _failed_ — we failed to process the video, see error for details. - - _canceled_ — video processing was canceled. -- **error** - holds a processing error if we failed to handle your video. -- **result** - repeats the contents of your processing output. -- **thumbnails_group_uuid** - holds :uuid-thumb-group, a UUID of a file group with thumbnails for an output video, based on the thumbs operation parameters. -- **uuid** - a UUID of your processed video file. +scan = client.addons.uc_clamav_virus_scan(uuid: file.uuid) +client.addons.uc_clamav_virus_scan_status(request_id: scan.request_id) -More examples and options can be found [here](https://uploadcare.com/docs/transformations/video-encoding/#video-encoding). +background = client.addons.remove_bg(uuid: file.uuid) +client.addons.remove_bg_status(request_id: background.request_id) +``` -##### Document +These methods return `Uploadcare::AddonExecution` resources. -After each document file upload you obtain a file identifier in UUID format. +## Conversions -You can use file identifier to determine the document format and possible conversion formats. +Document conversions: ```ruby -Uploadcare::DocumentConverter.info("dc99200d-9bd6-4b43-bfa9-aa7bfaefca40") - -# Response -{:error=>nil, :format=>{ - :name=>"jpg", - :conversion_formats=>[ - {:name=>"avif"}, {:name=>"bmp"}, {:name=>"gif"}, {:name=>"ico"}, {:name=>"pcx"}, {:name=>"pdf"}, {:name=>"png"}, {:name=>"ps"}, {:name=>"svg"}, {:name=>"tga"}, {:name=>"thumbnail"}, {:name=>"tiff"}, {:name=>"wbmp"}, {:name=>"webp"} - ] -}} +info = client.conversions.documents.info(uuid: file.uuid) +job = client.conversions.documents.convert(uuid: file.uuid, format: :pdf) +status = client.conversions.documents.status(token: job.fetch("result").first.fetch("token")) ``` -Then you can use this file identifier to convert your document to a new format: +Video conversions: ```ruby -Uploadcare::DocumentConverter.convert( - [ - { - uuid: "dc99200d-9bd6-4b43-bfa9-aa7bfaefca40", - format: "pdf" - } - ], - store: false -) +job = client.conversions.videos.convert(uuid: file.uuid, format: :webm, quality: :normal) +status = client.conversions.videos.status(token: job.result.first.fetch("token")) ``` -or create an image of a particular page (if using image format): +Document conversion `convert` returns the API response hash. + +Video conversion `convert` returns a `Uploadcare::VideoConversion` resource. + +## Secure Delivery + +The gem includes signed URL generators for delivery workflows. ```ruby -Uploadcare::DocumentConverter.convert( - [ - { - uuid: "a4b9db2f-1591-4f4c-8f68-94018924525d", - format: "png", - page: 1 - } - ], - store: false +generator = Uploadcare::SignedUrlGenerators::AkamaiGenerator.new( + cdn_host: "example.com", + secret_key: "your_hex_encoded_akamai_secret" ) -``` -This method accepts options to set properties of an output file: +signed_url = generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3") +``` -- **uuid** — the file UUID-identifier. -- **format** - defines the target format you want a source file converted to. The supported values are: `pdf` (default), `doc`, `docx`, `xls`, `xlsx`, `odt`, `ods`, `rtf`, `txt`, `jpg`, `png`. In case the format operation was not found, your input document will be converted to `pdf`. -- **page** - a page number of a multi-paged document to either `jpg` or `png`. The method will not work for any other target formats. +Custom ACL and wildcard examples: ```ruby -# Response -{ - :result => [ - { - :original_source=>"a4b9db2f-1591-4f4c-8f68-94018924525d/document/-/format/png/-/page/1/", - :token=>21120220 - :uuid=>"88fe5ada-90f1-422a-a233-3a0f3a7cf23c" - } - ], - :problems=>{} -} +generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", "/*") +generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", wildcard: true) ``` -Params in the response: +## Errors and Results + +The convenience layer raises exceptions: -- **result** - info related to your transformed output(-s): - - **original_source** - source file identifier including a target format, if present. - - **token** - a processing job token that can be used to get a [job status](https://uploadcare.com/docs/transformations/document-conversion/#status) (see below). - - **uuid** - UUID of your processed document file. -- **problems** - problems related to your processing job, if any. +- `Uploadcare::Exception::RequestError` +- `Uploadcare::Exception::InvalidRequestError` +- `Uploadcare::Exception::NotFoundError` +- `Uploadcare::Exception::UploadError` +- `Uploadcare::Exception::MultipartUploadError` +- `Uploadcare::Exception::UploadTimeoutError` +- `Uploadcare::Exception::ThrottleError` -To convert multiple documents just add params as a hash for each document to the first argument of the `Uploadcare::DocumentConverter#convert` method: +Example: ```ruby -Uploadcare::DocumentConverter.convert( - [ - { doc_one_params }, { doc_two_params }, ... - ], - store: false -) +begin + client.files.find(uuid: "missing") +rescue Uploadcare::Exception::NotFoundError => e + warn e.message +end ``` -To check a status of a document processing job you can simply use appropriate method of `Uploadcare::DocumentConverter`: +The raw API layer returns `Uploadcare::Result`: ```ruby -token = 21120220 -Uploadcare::DocumentConverter.status(token) +result = client.api.rest.files.info(uuid: "file-uuid") + +if result.success? + puts result.success +else + warn result.error_message +end ``` -`token` here is a processing job token, obtained in a response of a convert document request. +## Request Options + +Most API calls accept `request_options:` and pass them to the HTTP layer. + +Example: ```ruby -# Response -{ - :status => "finished", - :error => nil, - :result => { - :uuid => "a4b9db2f-1591-4f4c-8f68-94018924525d" - } -} +client.files.find(uuid: "file-uuid", request_options: { timeout: 10 }) ``` -Params in the response: +Use this when you need per-request timeout control without changing the client’s default configuration. -- **status** - processing job status, can have one of the following values: - - _pending_ — document file is being prepared for conversion. - - _processing_ — document file processing is in progress. - - _finished_ — the processing is finished. - - _failed_ — we failed to process the document, see error for details. - - _canceled_ — document processing was canceled. -- **error** - holds a processing error if we failed to handle your document. -- **result** - repeats the contents of your processing output. -- **uuid** - a UUID of your processed document file. +## Raw API Access -More examples and options can be found [here](https://uploadcare.com/docs/transformations/document-conversion/#document-conversion) +The gem exposes full endpoint-level access through `client.api`. -## Secure delivery +REST API: -You can use custom domain and CDN provider to deliver files with authenticated URLs (see [original documentation](https://uploadcare.com/docs/security/secure_delivery/)). +```ruby +client.api.rest.files.list(params: { limit: 10 }) +client.api.rest.files.info(uuid: "file-uuid") +client.api.rest.project.show +client.api.rest.webhooks.list +``` -To generate authenticated URL from the library, you should choose `Uploadcare::SignedUrlGenerators::AkamaiGenerator` (or create your own generator implementation): +Upload API: ```ruby -generator = Uploadcare::SignedUrlGenerators::AkamaiGenerator.new(cdn_host: 'example.com', secret_key: 'secret_key') -# Optional parameters: ttl: 300, algorithm: 'sha256' -generator.generate_url(uuid, acl = optional) +File.open("photo.jpg", "rb") do |io| + client.api.upload.files.direct(file: io, store: true) +end -generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3") -# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649405263~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=a989cae5342f17013677f5a0e6577fc5594cc4e238fb4c95eda36634eb47018b +client.api.upload.files.from_url(source_url: "https://example.com/image.jpg", async: true) +client.api.upload.groups.create(files: ["uuid-1", "uuid-2"]) +``` -# You can pass in ACL as a second parameter to generate_url. See https://uploadcare.com/docs/security/secure-delivery/#authenticated-urls for supported acl formats -generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", '/*/') -# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649405263~acl=/*/~hmac=3ce1152c6af8864b36d4dc721f08ca3cf0b3a20278d7f849e82c6c930d48ccc1 +Use this layer when you want exact control over the documented endpoints or when you are wrapping the gem from another library. -# Optionally you can use wildcard: true to generate a wildcard acl token -generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", wildcard: true) -# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1714233449~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/*~hmac=a568ee2a85dd90a8a8a1ef35ea0cc0ef0acb84fe81990edd3a06eacf10a52b4e +The raw layer is part of the public surface, but it is intentionally less promoted than `client.files`, `client.groups`, and the other convenience accessors. + +## Examples + +- [api_examples/README.md](./api_examples/README.md): one canonical script per documented REST and Upload API endpoint +- [examples/README.md](./examples/README.md): workflow-oriented demos built on the public client API -# You can also pass in a custom ttl and algorithm to AkamaiGenerator -generator = Uploadcare::SignedUrlGenerators::AkamaiGenerator.new(cdn_host: 'example.com', secret_key: 'secret_key', ttl: 10) -generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3") -# This generates a URL that expires in 10 seconds -# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1714233277~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=f25343104aeced3004d2cc4d49807d8d7c732300b54b154c319da5283a871a71 +Run examples with project-managed Ruby: + +```bash +mise exec -- ruby api_examples/rest_api/get_project.rb +mise exec -- ruby examples/simple_upload.rb spec/fixtures/kitten.jpeg ``` -## Useful links +## Upgrading from v4.x + +See: -- [Development](https://github.com/uploadcare/uploadcare-ruby/blob/main/DEVELOPMENT.md) -- [Uploadcare documentation](https://uploadcare.com/docs/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-ruby) -- [Upload API reference](https://uploadcare.com/api-refs/upload-api/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-ruby) -- [REST API reference](https://uploadcare.com/api-refs/rest-api/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-ruby) -- [Changelog](./CHANGELOG.md) -- [Contributing guide](https://github.com/uploadcare/.github/blob/master/CONTRIBUTING.md) -- [Security policy](https://github.com/uploadcare/uploadcare-ruby/security/policy) -- [Support](https://github.com/uploadcare/.github/blob/master/SUPPORT.md) +- [MIGRATING_V5.md](./MIGRATING_V5.md) +- [api_examples/README.md](./api_examples/README.md) diff --git a/Rakefile b/Rakefile index 82bb534a..49647511 100644 --- a/Rakefile +++ b/Rakefile @@ -5,4 +5,8 @@ require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task default: :spec +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +task default: %i[spec rubocop] diff --git a/api_examples/README.md b/api_examples/README.md new file mode 100644 index 00000000..61a5d452 --- /dev/null +++ b/api_examples/README.md @@ -0,0 +1,77 @@ +# API Examples + +Each file in this directory maps to one documented Uploadcare API endpoint. + +Run examples with project-managed Ruby and real credentials: + +```bash +mise exec -- ruby api_examples/rest_api/get_project.rb +mise exec -- ruby api_examples/upload_api/post_base.rb +``` + +Required environment variables: + +- `UPLOADCARE_PUBLIC_KEY` +- `UPLOADCARE_SECRET_KEY` + +Optional environment variables: + +- `UPLOADCARE_AUTH_TYPE` +- `UPLOADCARE_REMOTE_STORAGE` for `post_files_remote_copy.rb` + +Verification: + +- Verified against a real Uploadcare demo account on `2026-03-16` +- All canonical scripts in `api_examples/rest_api` and `api_examples/upload_api` executed successfully + +## REST API 0.7 + +| Endpoint | Example file | Notes | +| --- | --- | --- | +| `GET /files/` | `api_examples/rest_api/get_files.rb` | Uses `client.files.list` | +| `PUT /files/{uuid}/storage/` | `api_examples/rest_api/put_files_uuid_storage.rb` | Uses `file.store` | +| `DELETE /files/{uuid}/storage/` | `api_examples/rest_api/delete_files_uuid_storage.rb` | Uses `file.delete` | +| `GET /files/{uuid}/` | `api_examples/rest_api/get_files_uuid.rb` | Uses `client.files.find` | +| `PUT /files/storage/` | `api_examples/rest_api/put_files_storage.rb` | Uses `client.files.batch_store` | +| `DELETE /files/storage/` | `api_examples/rest_api/delete_files_storage.rb` | Uses `client.files.batch_delete` | +| `POST /files/local_copy/` | `api_examples/rest_api/post_files_local_copy.rb` | Uses `client.files.copy_to_local` | +| `POST /files/remote_copy/` | `api_examples/rest_api/post_files_remote_copy.rb` | Uses `client.files.copy_to_remote`; requires `UPLOADCARE_REMOTE_STORAGE` | +| `GET /files/{uuid}/metadata/` | `api_examples/rest_api/get_files_uuid_metadata.rb` | Uses `client.file_metadata.index` | +| `GET /files/{uuid}/metadata/{key}/` | `api_examples/rest_api/get_files_uuid_metadata_key.rb` | Uses `client.file_metadata.show` | +| `PUT /files/{uuid}/metadata/{key}/` | `api_examples/rest_api/put_files_uuid_metadata_key.rb` | Uses `client.file_metadata.update` | +| `DELETE /files/{uuid}/metadata/{key}/` | `api_examples/rest_api/delete_files_uuid_metadata_key.rb` | Uses `client.file_metadata.delete` | +| `GET /groups/` | `api_examples/rest_api/get_groups.rb` | Uses `client.groups.list` | +| `GET /groups/{uuid}/` | `api_examples/rest_api/get_groups_uuid.rb` | Uses `client.groups.find` | +| `DELETE /groups/{uuid}/` | `api_examples/rest_api/delete_groups_uuid.rb` | Uses `group.delete` | +| `POST /addons/aws_rekognition_detect_labels/execute/` | `api_examples/rest_api/post_addons_aws_rekognition_detect_labels_execute.rb` | Uses `client.addons.aws_rekognition_detect_labels` | +| `GET /addons/aws_rekognition_detect_labels/execute/status/` | `api_examples/rest_api/get_addons_aws_rekognition_detect_labels_execute_status.rb` | Uses `client.addons.*` and checks status | +| `POST /addons/aws_rekognition_detect_moderation_labels/execute/` | `api_examples/rest_api/post_addons_aws_rekognition_detect_moderation_labels_execute.rb` | Uses `client.addons.aws_rekognition_detect_moderation_labels` | +| `GET /addons/aws_rekognition_detect_moderation_labels/execute/status/` | `api_examples/rest_api/get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb` | Uses `client.addons.*` and checks status | +| `POST /addons/uc_clamav_virus_scan/execute/` | `api_examples/rest_api/post_addons_uc_clamav_virus_scan_execute.rb` | Uses `client.addons.uc_clamav_virus_scan` | +| `GET /addons/uc_clamav_virus_scan/execute/status/` | `api_examples/rest_api/get_addons_uc_clamav_virus_scan_execute_status.rb` | Uses `client.addons.*` and checks status | +| `POST /addons/remove_bg/execute/` | `api_examples/rest_api/post_addons_remove_bg_execute.rb` | Uses `client.addons.remove_bg` | +| `GET /addons/remove_bg/execute/status/` | `api_examples/rest_api/get_addons_remove_bg_execute_status.rb` | Uses `client.addons.*` and checks status | +| `GET /project/` | `api_examples/rest_api/get_project.rb` | Uses `client.project.current` | +| `GET /webhooks/` | `api_examples/rest_api/get_webhooks.rb` | Uses `client.webhooks.list` | +| `POST /webhooks/` | `api_examples/rest_api/post_webhooks.rb` | Uses `client.webhooks.create` | +| `PUT /webhooks/{id}/` | `api_examples/rest_api/put_webhooks_id.rb` | Uses `client.webhooks.update` | +| `DELETE /webhooks/unsubscribe/` | `api_examples/rest_api/delete_webhooks_unsubscribe.rb` | Uses `client.webhooks.delete` | +| `GET /convert/document/{uuid}/` | `api_examples/rest_api/get_convert_document_uuid.rb` | Uses `client.conversions.documents.info` | +| `POST /convert/document/` | `api_examples/rest_api/post_convert_document.rb` | Uses `client.conversions.documents.convert` | +| `GET /convert/document/status/{token}/` | `api_examples/rest_api/get_convert_document_status_token.rb` | Uses `client.conversions.documents.convert` then `status` | +| `POST /convert/video/` | `api_examples/rest_api/post_convert_video.rb` | Uses `client.conversions.videos.convert` | +| `GET /convert/video/status/{token}/` | `api_examples/rest_api/get_convert_video_status_token.rb` | Uses `client.conversions.videos.convert` then `status` | + +## Upload API + +| Endpoint | Example file | Notes | +| --- | --- | --- | +| `POST /base/` | `api_examples/upload_api/post_base.rb` | Uses raw upload API | +| `POST /multipart/start/` | `api_examples/upload_api/post_multipart_start.rb` | Starts and completes a real multipart upload | +| `PUT ` | `api_examples/upload_api/put_multipart_part.rb` | Uploads one part via gem multipart helper | +| `POST /multipart/complete/` | `api_examples/upload_api/post_multipart_complete.rb` | Completes a real multipart upload | +| `POST /from_url/` | `api_examples/upload_api/post_from_url.rb` | Uses raw upload API | +| `GET /from_url/status/` | `api_examples/upload_api/get_from_url_status.rb` | Starts async upload then checks status | +| `GET /info/` | `api_examples/upload_api/get_info.rb` | Uses raw upload API | +| `POST /group/` | `api_examples/upload_api/post_group.rb` | Uses raw upload API | +| `GET /group/info/` | `api_examples/upload_api/get_group_info.rb` | Uses raw upload API | diff --git a/api_examples/rest_api/delete_files_storage.rb b/api_examples/rest_api/delete_files_storage.rb old mode 100644 new mode 100755 index e3054757..b5b8db83 --- a/api_examples/rest_api/delete_files_storage.rb +++ b/api_examples/rest_api/delete_files_storage.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuids = %w[21975c81-7f57-4c7a-aef9-acfe28779f78 cbaf2d73-5169-4b2b-a543-496cf2813dff] -puts Uploadcare::FileList.batch_delete(uuids) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/delete_files_uuid_metadata_key.rb b/api_examples/rest_api/delete_files_uuid_metadata_key.rb old mode 100644 new mode 100755 index 2d1f5d4c..b5b8db83 --- a/api_examples/rest_api/delete_files_uuid_metadata_key.rb +++ b/api_examples/rest_api/delete_files_uuid_metadata_key.rb @@ -1,5 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -puts Uploadcare::FileMetadata.delete('1bac376c-aa7e-4356-861b-dd2657b5bfd2', 'pet') +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/delete_files_uuid_storage.rb b/api_examples/rest_api/delete_files_uuid_storage.rb old mode 100644 new mode 100755 index 8837391b..b5b8db83 --- a/api_examples/rest_api/delete_files_uuid_storage.rb +++ b/api_examples/rest_api/delete_files_uuid_storage.rb @@ -1,5 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -puts Uploadcare::File.delete('1bac376c-aa7e-4356-861b-dd2657b5bfd2') +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/delete_groups_uuid.rb b/api_examples/rest_api/delete_groups_uuid.rb old mode 100644 new mode 100755 index 203527b7..b5b8db83 --- a/api_examples/rest_api/delete_groups_uuid.rb +++ b/api_examples/rest_api/delete_groups_uuid.rb @@ -1,5 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -puts Uploadcare::Group.delete('c5bec8c7-d4b6-4921-9e55-6edb027546bc~1') +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/delete_webhooks_unsubscribe.rb b/api_examples/rest_api/delete_webhooks_unsubscribe.rb old mode 100644 new mode 100755 index c1c0f8da..b5b8db83 --- a/api_examples/rest_api/delete_webhooks_unsubscribe.rb +++ b/api_examples/rest_api/delete_webhooks_unsubscribe.rb @@ -1,5 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -puts Uploadcare::Webhook.delete('https://yourwebhook.com') +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_addons_aws_rekognition_detect_labels_execute_status.rb b/api_examples/rest_api/get_addons_aws_rekognition_detect_labels_execute_status.rb old mode 100644 new mode 100755 index 0d43b9ae..b5b8db83 --- a/api_examples/rest_api/get_addons_aws_rekognition_detect_labels_execute_status.rb +++ b/api_examples/rest_api/get_addons_aws_rekognition_detect_labels_execute_status.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -request_id = 'd1fb31c6-ed34-4e21-bdc3-4f1485f58e21' -result = Uploadcare::Addons.ws_rekognition_detect_labels_status(request_id) -puts result.status +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb b/api_examples/rest_api/get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb old mode 100644 new mode 100755 index 979d4094..b5b8db83 --- a/api_examples/rest_api/get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb +++ b/api_examples/rest_api/get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -request_id = 'd1fb31c6-ed34-4e21-bdc3-4f1485f58e21' -result = Uploadcare::Addons.ws_rekognition_detect_moderation_labels_status(request_id) -puts result.status +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_addons_remove_bg_execute_status.rb b/api_examples/rest_api/get_addons_remove_bg_execute_status.rb old mode 100644 new mode 100755 index 784ef10f..b5b8db83 --- a/api_examples/rest_api/get_addons_remove_bg_execute_status.rb +++ b/api_examples/rest_api/get_addons_remove_bg_execute_status.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -request_id = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -result = Uploadcare::Addons.remove_bg_status(request_id) -puts result.status +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_addons_uc_clamav_virus_scan_execute_status.rb b/api_examples/rest_api/get_addons_uc_clamav_virus_scan_execute_status.rb old mode 100644 new mode 100755 index 3f2b1400..b5b8db83 --- a/api_examples/rest_api/get_addons_uc_clamav_virus_scan_execute_status.rb +++ b/api_examples/rest_api/get_addons_uc_clamav_virus_scan_execute_status.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -request_id = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -result = Uploadcare::Addons.uc_clamav_virus_scan_status(request_id) -puts result.status +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_convert_document_status_token.rb b/api_examples/rest_api/get_convert_document_status_token.rb old mode 100644 new mode 100755 index 77b7fa68..b5b8db83 --- a/api_examples/rest_api/get_convert_document_status_token.rb +++ b/api_examples/rest_api/get_convert_document_status_token.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -token = 32_921_143 -puts Uploadcare::DocumentConverter.status(token) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_convert_document_uuid.rb b/api_examples/rest_api/get_convert_document_uuid.rb old mode 100644 new mode 100755 index 7b0eeba6..b5b8db83 --- a/api_examples/rest_api/get_convert_document_uuid.rb +++ b/api_examples/rest_api/get_convert_document_uuid.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '740e1b8c-1ad8-4324-b7ec-112c79d8eac2' -puts Uploadcare::DocumentConverter.info(uuid) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_convert_video_status_token.rb b/api_examples/rest_api/get_convert_video_status_token.rb old mode 100644 new mode 100755 index c4295d12..b5b8db83 --- a/api_examples/rest_api/get_convert_video_status_token.rb +++ b/api_examples/rest_api/get_convert_video_status_token.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -token = 1_201_016_744 -puts Uploadcare::VideoConverter.status(token) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_files.rb b/api_examples/rest_api/get_files.rb old mode 100644 new mode 100755 index e83aa207..b5b8db83 --- a/api_examples/rest_api/get_files.rb +++ b/api_examples/rest_api/get_files.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -list = Uploadcare::FileList.file_list(stored: true, removed: false, limit: 100) -list.each { |file| puts file.inspect } +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_files_uuid.rb b/api_examples/rest_api/get_files_uuid.rb old mode 100644 new mode 100755 index 900fde2a..b5b8db83 --- a/api_examples/rest_api/get_files_uuid.rb +++ b/api_examples/rest_api/get_files_uuid.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -puts Uploadcare::File.info(uuid).inspect +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_files_uuid_metadata.rb b/api_examples/rest_api/get_files_uuid_metadata.rb old mode 100644 new mode 100755 index 7701512e..b5b8db83 --- a/api_examples/rest_api/get_files_uuid_metadata.rb +++ b/api_examples/rest_api/get_files_uuid_metadata.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -puts Uploadcare::FileMetadata.show(uuid, 'pet') +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_files_uuid_metadata_key.rb b/api_examples/rest_api/get_files_uuid_metadata_key.rb old mode 100644 new mode 100755 index b7d0542e..b5b8db83 --- a/api_examples/rest_api/get_files_uuid_metadata_key.rb +++ b/api_examples/rest_api/get_files_uuid_metadata_key.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -puts Uploadcare::FileMetadata.index(uuid).inspect +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_groups.rb b/api_examples/rest_api/get_groups.rb old mode 100644 new mode 100755 index c2e61e96..b5b8db83 --- a/api_examples/rest_api/get_groups.rb +++ b/api_examples/rest_api/get_groups.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -groups = Uploadcare::GroupList.list(limit: 10) -groups.each { |group| puts group.inspect } +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_groups_uuid.rb b/api_examples/rest_api/get_groups_uuid.rb old mode 100644 new mode 100755 index e5e91ddd..b5b8db83 --- a/api_examples/rest_api/get_groups_uuid.rb +++ b/api_examples/rest_api/get_groups_uuid.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = 'c5bec8c7-d4b6-4921-9e55-6edb027546bc~1' -puts Uploadcare::Group.info(uuid).inspect +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_project.rb b/api_examples/rest_api/get_project.rb old mode 100644 new mode 100755 index c6c0413c..b5b8db83 --- a/api_examples/rest_api/get_project.rb +++ b/api_examples/rest_api/get_project.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -project_info = Uploadcare::Project.show -puts project_info.inspect +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/get_webhooks.rb b/api_examples/rest_api/get_webhooks.rb old mode 100644 new mode 100755 index 01dd5d33..b5b8db83 --- a/api_examples/rest_api/get_webhooks.rb +++ b/api_examples/rest_api/get_webhooks.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -webhooks = Uploadcare::Webhook.list -webhooks.each { |webhook| puts webhook.inspect } +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_addons_aws_rekognition_detect_labels_execute.rb b/api_examples/rest_api/post_addons_aws_rekognition_detect_labels_execute.rb old mode 100644 new mode 100755 index 0df40b2f..b5b8db83 --- a/api_examples/rest_api/post_addons_aws_rekognition_detect_labels_execute.rb +++ b/api_examples/rest_api/post_addons_aws_rekognition_detect_labels_execute.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -Uploadcare::Addons.ws_rekognition_detect_labels(uuid) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_addons_aws_rekognition_detect_moderation_labels_execute.rb b/api_examples/rest_api/post_addons_aws_rekognition_detect_moderation_labels_execute.rb old mode 100644 new mode 100755 index e685541d..b5b8db83 --- a/api_examples/rest_api/post_addons_aws_rekognition_detect_moderation_labels_execute.rb +++ b/api_examples/rest_api/post_addons_aws_rekognition_detect_moderation_labels_execute.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -Uploadcare::Addons.ws_rekognition_detect_moderation_labels(uuid) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_addons_remove_bg_execute.rb b/api_examples/rest_api/post_addons_remove_bg_execute.rb old mode 100644 new mode 100755 index a78d7081..b5b8db83 --- a/api_examples/rest_api/post_addons_remove_bg_execute.rb +++ b/api_examples/rest_api/post_addons_remove_bg_execute.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -Uploadcare::Addons.remove_bg(uuid, crop: true) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_addons_uc_clamav_virus_scan_execute.rb b/api_examples/rest_api/post_addons_uc_clamav_virus_scan_execute.rb old mode 100644 new mode 100755 index 340e660f..b5b8db83 --- a/api_examples/rest_api/post_addons_uc_clamav_virus_scan_execute.rb +++ b/api_examples/rest_api/post_addons_uc_clamav_virus_scan_execute.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -Uploadcare::Addons.uc_clamav_virus_scan(uuid, purge_infected: true) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_convert_document.rb b/api_examples/rest_api/post_convert_document.rb old mode 100644 new mode 100755 index cc710529..b5b8db83 --- a/api_examples/rest_api/post_convert_document.rb +++ b/api_examples/rest_api/post_convert_document.rb @@ -1,9 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -document_params = { uuid: '1bac376c-aa7e-4356-861b-dd2657b5bfd2', format: :pdf } -options = { store: '1' } -# for multipage conversion -# options = { store: '1', save_in_group: '1' } -Uploadcare::DocumentConverter.convert(document_params, options) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_convert_video.rb b/api_examples/rest_api/post_convert_video.rb old mode 100644 new mode 100755 index 2414d3d3..b5b8db83 --- a/api_examples/rest_api/post_convert_video.rb +++ b/api_examples/rest_api/post_convert_video.rb @@ -1,11 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -video_params = { - uuid: '1bac376c-aa7e-4356-861b-dd2657b5bfd2', - format: :mp4, - quality: :lighter -} -options = { store: true } -Uploadcare::VideoConverter.convert(video_params, options) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_files_local_copy.rb b/api_examples/rest_api/post_files_local_copy.rb old mode 100644 new mode 100755 index 4860110a..b5b8db83 --- a/api_examples/rest_api/post_files_local_copy.rb +++ b/api_examples/rest_api/post_files_local_copy.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -source = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -copied_file = Uploadcare::File.local_copy(source, store: true) -puts copied_file.uuid +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_files_remote_copy.rb b/api_examples/rest_api/post_files_remote_copy.rb old mode 100644 new mode 100755 index f60c8beb..b5b8db83 --- a/api_examples/rest_api/post_files_remote_copy.rb +++ b/api_examples/rest_api/post_files_remote_copy.rb @@ -1,8 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -source_object = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -target = 'custom_storage_connected_to_the_project' -copied_file_url = Uploadcare::File.remote_copy(source_object, target, make_public: true) -puts copied_file_url +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/post_webhooks.rb b/api_examples/rest_api/post_webhooks.rb old mode 100644 new mode 100755 index 49d63f2c..b5b8db83 --- a/api_examples/rest_api/post_webhooks.rb +++ b/api_examples/rest_api/post_webhooks.rb @@ -1,10 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -options = { - target_url: 'https://yourwebhook.com', - event: 'file.uploaded', - is_active: true -} -Uploadcare::Webhook.create(**options) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/put_files_storage.rb b/api_examples/rest_api/put_files_storage.rb old mode 100644 new mode 100755 index 6553abac..b5b8db83 --- a/api_examples/rest_api/put_files_storage.rb +++ b/api_examples/rest_api/put_files_storage.rb @@ -1,9 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuids = %w[ - b7a301d1-1bd0-473d-8d32-708dd55addc0 - 1bac376c-aa7e-4356-861b-dd2657b5bfd2 -] -Uploadcare::FileList.batch_store(uuids) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/put_files_uuid_metadata_key.rb b/api_examples/rest_api/put_files_uuid_metadata_key.rb old mode 100644 new mode 100755 index 48f447f6..b5b8db83 --- a/api_examples/rest_api/put_files_uuid_metadata_key.rb +++ b/api_examples/rest_api/put_files_uuid_metadata_key.rb @@ -1,8 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -key = 'pet' -value = 'dog' -Uploadcare::FileMetadata.update(uuid, key, value) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/put_files_uuid_storage.rb b/api_examples/rest_api/put_files_uuid_storage.rb old mode 100644 new mode 100755 index c3343c89..b5b8db83 --- a/api_examples/rest_api/put_files_uuid_storage.rb +++ b/api_examples/rest_api/put_files_uuid_storage.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '1bac376c-aa7e-4356-861b-dd2657b5bfd2' -Uploadcare::File.store(uuid) +require_relative '../support/run_rest_example' diff --git a/api_examples/rest_api/put_webhooks_id.rb b/api_examples/rest_api/put_webhooks_id.rb old mode 100644 new mode 100755 index b06a6cc3..b5b8db83 --- a/api_examples/rest_api/put_webhooks_id.rb +++ b/api_examples/rest_api/put_webhooks_id.rb @@ -1,12 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -webhook_id = 1_473_151 -options = { - target_url: 'https://yourwebhook.com', - event: 'file.uploaded', - is_active: true, - signing_secret: 'webhook-secret' -} -Uploadcare::Webhook.update(webhook_id, options) +require_relative '../support/run_rest_example' diff --git a/api_examples/support/example_helper.rb b/api_examples/support/example_helper.rb new file mode 100755 index 00000000..d414c9f6 --- /dev/null +++ b/api_examples/support/example_helper.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require 'json' +require 'dotenv/load' +require 'tempfile' +require 'securerandom' + +require_relative '../../lib/uploadcare' + +module ApiExamples +end + +module ApiExamples::ExampleHelper + SAMPLE_IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/3/3f/JPEG_example_flower.jpg' + SAMPLE_VIDEO_URL = 'https://samplelib.com/lib/preview/mp4/sample-5s.mp4' + + module_function + + def client + @client ||= Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY'), + auth_type: ENV.fetch('UPLOADCARE_AUTH_TYPE', 'Uploadcare.Simple') + ) + end + + def run + output(yield(client)) + rescue StandardError => e + warn("#{e.class}: #{e.message}") + exit 1 + end + + def output(value) + puts JSON.pretty_generate(value) + end + + def unwrap(result) + Uploadcare::Result.unwrap(result) + end + + def skip(message) + output('skipped' => true, 'reason' => message) + exit 0 + end + + def fixture_path(name) + File.expand_path("../../spec/fixtures/#{name}", __dir__) + end + + def with_uploaded_file(path: fixture_path('kitten.jpeg'), store: true, metadata: {}) + handle = File.open(path, 'rb') + file = client.files.upload(handle, store: store, metadata: metadata) + yield file + ensure + handle&.close + safe_delete_file(file) + end + + def with_uploaded_files(paths: [fixture_path('kitten.jpeg'), fixture_path('another_kitten.jpeg')], store: true) + handles = paths.map { |path| File.open(path, 'rb') } + files = client.files.upload(handles, store: store) + yield files + ensure + handles&.each(&:close) + Array(files).each { |file| safe_delete_file(file) } + end + + def with_fixture_file(name) + handle = File.open(fixture_path(name), 'rb') + response = yield handle + response + ensure + handle&.close + safe_delete_file(uploaded_uuid_from_base_response(response)) if response + end + + def with_uploaded_group + with_uploaded_files do |files| + group = client.groups.create(uuids: files.map(&:uuid)) + yield group, files + ensure + safe_delete_group(group) + end + end + + def with_webhook + webhook = client.webhooks.create( + target_url: "https://example.com/uploadcare-webhook/#{SecureRandom.hex(4)}", + is_active: true + ) + yield webhook + ensure + safe_delete_webhook(webhook) + end + + def with_uploaded_pdf + pdf = Tempfile.new(['uploadcare-example', '.pdf']) + pdf.write(minimal_pdf) + pdf.rewind + file = client.files.upload(pdf, store: true) + yield file + ensure + pdf&.close! + safe_delete_file(file) + end + + def with_uploaded_video + file = client.files.upload_from_url(SAMPLE_VIDEO_URL, store: true) + yield file + ensure + safe_delete_file(file) + end + + def multipart_part_size + 5 * 1024 * 1024 + end + + def with_multipart_session + file = File.open(fixture_path('big.jpeg'), 'rb') + response = unwrap( + client.api.upload.files.multipart_start( + filename: File.basename(file.path), + size: file.size, + content_type: 'image/jpeg', + part_size: multipart_part_size, + store: true + ) + ) + yield file, response + ensure + file&.close + safe_delete_file(response['completed_uuid']) if response.is_a?(Hash) && response['completed_uuid'] + end + + def upload_multipart_part(file:, session:, index:) + file.seek(index * multipart_part_size) + data = file.read(multipart_part_size) + return nil unless data && !data.empty? + + client.api.upload.upload_part_to_url(session.fetch('parts').fetch(index), data) + data.bytesize + end + + def finish_multipart_upload(file:, session:, skip_indices: []) + session.fetch('parts').each_index do |index| + next if skip_indices.include?(index) + + upload_multipart_part(file: file, session: session, index: index) + end + result = unwrap(client.api.upload.files.multipart_complete(uuid: session.fetch('uuid'))) + session['completed_uuid'] = result['uuid'] + result + end + + def uploaded_uuid_from_base_response(response) + response.values.first + end + + def conversion_token(response) + result = if response.respond_to?(:result) + response.result + else + response.fetch('result') + end + + Array(result).first.fetch('token') + end + + def safe_delete_file(file) + return unless file + + uuid = if file.respond_to?(:uuid) + file.uuid + elsif file.is_a?(Hash) + file['uuid'] || file[:uuid] + else + file + end + return if uuid.to_s.empty? + + client.api.rest.files.delete(uuid: uuid) + rescue StandardError + nil + end + + def safe_delete_group(group) + group_id = if group.respond_to?(:id) + group.id + elsif group.is_a?(Hash) + group['id'] || group[:id] + else + group + end + return if group_id.to_s.empty? + + client.api.rest.groups.delete(uuid: group_id) + rescue StandardError + nil + end + + def safe_delete_webhook(webhook) + return unless webhook&.target_url + + client.api.rest.webhooks.delete(target_url: webhook.target_url) + rescue StandardError + nil + end + + def minimal_pdf + <<~PDF + %PDF-1.4 + 1 0 obj + << /Type /Catalog /Pages 2 0 R >> + endobj + 2 0 obj + << /Type /Pages /Kids [3 0 R] /Count 1 >> + endobj + 3 0 obj + << /Type /Page /Parent 2 0 R /MediaBox [0 0 300 144] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> + endobj + 4 0 obj + << /Length 44 >> + stream + BT + /F1 18 Tf + 72 100 Td + (Uploadcare PDF) Tj + ET + endstream + endobj + 5 0 obj + << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> + endobj + xref + 0 6 + 0000000000 65535 f + 0000000009 00000 n + 0000000058 00000 n + 0000000115 00000 n + 0000000241 00000 n + 0000000334 00000 n + trailer + << /Size 6 /Root 1 0 R >> + startxref + 404 + %%EOF + PDF + end +end diff --git a/api_examples/support/run_rest_example.rb b/api_examples/support/run_rest_example.rb new file mode 100755 index 00000000..59030d04 --- /dev/null +++ b/api_examples/support/run_rest_example.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require_relative 'example_helper' + +module ApiExamples::RunRestExample + module_function + + def call + ApiExamples::ExampleHelper.run do |client| + case File.basename($PROGRAM_NAME) + when 'get_project.rb' + client.project.current + when 'get_files.rb' + client.files.list(limit: 2) + when 'get_files_uuid.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.files.find(uuid: file.uuid) + end + when 'put_files_uuid_storage.rb' + ApiExamples::ExampleHelper.with_uploaded_file(store: false, &:store) + when 'delete_files_uuid_storage.rb' + ApiExamples::ExampleHelper.with_uploaded_file(&:delete) + when 'put_files_storage.rb' + ApiExamples::ExampleHelper.with_uploaded_files(store: false) do |files| + client.files.batch_store(uuids: files.map(&:uuid)) + end + when 'delete_files_storage.rb' + ApiExamples::ExampleHelper.with_uploaded_files do |files| + client.files.batch_delete(uuids: files.map(&:uuid)) + end + when 'post_files_local_copy.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + copied = client.files.copy_to_local(source: file.uuid, options: { store: true }) + copied + ensure + ApiExamples::ExampleHelper.safe_delete_file(copied) + end + when 'post_files_remote_copy.rb' + target = ENV.fetch('UPLOADCARE_REMOTE_STORAGE', nil) + if target.to_s.empty? + ApiExamples::ExampleHelper.skip('Set UPLOADCARE_REMOTE_STORAGE to a configured custom storage name.') + end + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.files.copy_to_remote(source: file.uuid, target: target) + end + when 'get_groups.rb' + client.groups.list(limit: 2) + when 'get_groups_uuid.rb' + ApiExamples::ExampleHelper.with_uploaded_group do |group, _files| + client.groups.find(group_id: group.id) + end + when 'delete_groups_uuid.rb' + ApiExamples::ExampleHelper.with_uploaded_group do |group, _files| + group.delete + end + when 'get_webhooks.rb' + client.webhooks.list + when 'post_webhooks.rb' + ApiExamples::ExampleHelper.with_webhook do |webhook| + { + 'id' => webhook.id, + 'target_url' => webhook.target_url, + 'is_active' => webhook.is_active, + 'event' => webhook.event + } + end + when 'put_webhooks_id.rb' + ApiExamples::ExampleHelper.with_webhook do |webhook| + client.webhooks.update(id: webhook.id, is_active: false) + end + when 'delete_webhooks_unsubscribe.rb' + ApiExamples::ExampleHelper.with_webhook do |webhook| + client.webhooks.delete(target_url: webhook.target_url) + { 'target_url' => webhook.target_url, 'deleted' => true } + end + when 'get_files_uuid_metadata.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.file_metadata.update(uuid: file.uuid, key: 'color', value: 'orange') + client.file_metadata.index(uuid: file.uuid) + end + when 'get_files_uuid_metadata_key.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.file_metadata.update(uuid: file.uuid, key: 'color', value: 'orange') + client.file_metadata.show(uuid: file.uuid, key: 'color') + end + when 'put_files_uuid_metadata_key.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.file_metadata.update(uuid: file.uuid, key: 'color', value: 'orange') + end + when 'delete_files_uuid_metadata_key.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.file_metadata.update(uuid: file.uuid, key: 'color', value: 'orange') + client.file_metadata.delete(uuid: file.uuid, key: 'color') + { 'uuid' => file.uuid, 'key' => 'color', 'deleted' => true } + end + when 'post_addons_aws_rekognition_detect_labels_execute.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.addons.aws_rekognition_detect_labels(uuid: file.uuid) + end + when 'get_addons_aws_rekognition_detect_labels_execute_status.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + response = client.addons.aws_rekognition_detect_labels(uuid: file.uuid) + client.addons.aws_rekognition_detect_labels_status(request_id: response.request_id) + end + when 'post_addons_aws_rekognition_detect_moderation_labels_execute.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.addons.aws_rekognition_detect_moderation_labels(uuid: file.uuid) + end + when 'get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + response = client.addons.aws_rekognition_detect_moderation_labels(uuid: file.uuid) + client.addons.aws_rekognition_detect_moderation_labels_status(request_id: response.request_id) + end + when 'post_addons_uc_clamav_virus_scan_execute.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.addons.uc_clamav_virus_scan(uuid: file.uuid) + end + when 'get_addons_uc_clamav_virus_scan_execute_status.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + response = client.addons.uc_clamav_virus_scan(uuid: file.uuid) + client.addons.uc_clamav_virus_scan_status(request_id: response.request_id) + end + when 'post_addons_remove_bg_execute.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + client.addons.remove_bg(uuid: file.uuid) + end + when 'get_addons_remove_bg_execute_status.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + response = client.addons.remove_bg(uuid: file.uuid) + client.addons.remove_bg_status(request_id: response.request_id) + end + when 'get_convert_document_uuid.rb' + ApiExamples::ExampleHelper.with_uploaded_pdf do |file| + client.conversions.documents.info(uuid: file.uuid) + end + when 'post_convert_document.rb' + ApiExamples::ExampleHelper.with_uploaded_pdf do |file| + client.conversions.documents.convert(uuid: file.uuid, format: :jpg) + end + when 'get_convert_document_status_token.rb' + ApiExamples::ExampleHelper.with_uploaded_pdf do |file| + response = client.conversions.documents.convert(uuid: file.uuid, format: :jpg) + client.conversions.documents.status(token: ApiExamples::ExampleHelper.conversion_token(response)) + end + when 'post_convert_video.rb' + ApiExamples::ExampleHelper.with_uploaded_video do |file| + client.conversions.videos.convert(uuid: file.uuid, format: :webm, quality: :normal) + end + when 'get_convert_video_status_token.rb' + ApiExamples::ExampleHelper.with_uploaded_video do |file| + response = client.conversions.videos.convert(uuid: file.uuid, format: :webm, quality: :normal) + client.conversions.videos.status(token: ApiExamples::ExampleHelper.conversion_token(response)) + end + else + raise "No REST API example mapped for #{File.basename($PROGRAM_NAME)}" + end + end + end +end + +ApiExamples::RunRestExample.call diff --git a/api_examples/support/run_upload_example.rb b/api_examples/support/run_upload_example.rb new file mode 100755 index 00000000..ff7cd406 --- /dev/null +++ b/api_examples/support/run_upload_example.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require_relative 'example_helper' + +module ApiExamples::RunUploadExample + module_function + + def call + ApiExamples::ExampleHelper.run do |client| + case File.basename($PROGRAM_NAME) + when 'post_base.rb' + run_base_upload(client) + when 'post_from_url.rb' + run_url_upload(client) + when 'get_from_url_status.rb' + response = ApiExamples::ExampleHelper.unwrap( + client.api.upload.files.from_url( + source_url: ApiExamples::ExampleHelper::SAMPLE_IMAGE_URL, + async: true, + store: true + ) + ) + ApiExamples::ExampleHelper.unwrap(client.api.upload.files.from_url_status(token: response.fetch('token'))) + when 'get_info.rb' + ApiExamples::ExampleHelper.with_uploaded_file do |file| + ApiExamples::ExampleHelper.unwrap(client.api.upload.files.info(file_id: file.uuid)) + end + when 'post_group.rb' + ApiExamples::ExampleHelper.with_uploaded_files do |files| + group = ApiExamples::ExampleHelper.unwrap(client.api.upload.groups.create(files: files.map(&:uuid))) + group + ensure + ApiExamples::ExampleHelper.safe_delete_group(group) + end + when 'get_group_info.rb' + ApiExamples::ExampleHelper.with_uploaded_group do |group, _files| + ApiExamples::ExampleHelper.unwrap(client.api.upload.groups.info(group_id: group.id)) + end + when 'post_multipart_start.rb' + ApiExamples::ExampleHelper.with_multipart_session do |file, session| + completed_file = ApiExamples::ExampleHelper.finish_multipart_upload(file: file, session: session) + { + 'uuid' => session.fetch('uuid'), + 'parts' => session.fetch('parts').length, + 'completed_file' => completed_file.fetch('uuid') + } + end + when 'put_multipart_part.rb' + ApiExamples::ExampleHelper.with_multipart_session do |file, session| + uploaded = ApiExamples::ExampleHelper.upload_multipart_part(file: file, session: session, index: 0) + completed = ApiExamples::ExampleHelper.finish_multipart_upload( + file: file, + session: session, + skip_indices: [0] + ) + { + 'uuid' => session.fetch('uuid'), + 'uploaded_bytes' => uploaded, + 'completed_file' => completed.fetch('uuid') + } + end + when 'post_multipart_complete.rb' + ApiExamples::ExampleHelper.with_multipart_session do |file, session| + ApiExamples::ExampleHelper.finish_multipart_upload(file: file, session: session) + end + else + raise "No Upload API example mapped for #{File.basename($PROGRAM_NAME)}" + end + end + end + + def run_base_upload(client) + ApiExamples::ExampleHelper.with_fixture_file('kitten.jpeg') do |handle| + ApiExamples::ExampleHelper.unwrap(client.api.upload.files.direct(file: handle, store: true)) + end + end + + def run_url_upload(client) + response = ApiExamples::ExampleHelper.unwrap( + client.api.upload.files.from_url(source_url: ApiExamples::ExampleHelper::SAMPLE_IMAGE_URL, store: true) + ) + response + ensure + ApiExamples::ExampleHelper.safe_delete_file(response['uuid']) if response && response['uuid'] + end +end + +ApiExamples::RunUploadExample.call diff --git a/api_examples/upload_api/get_from_url_status.rb b/api_examples/upload_api/get_from_url_status.rb old mode 100644 new mode 100755 index 162b41a3..a396cd06 --- a/api_examples/upload_api/get_from_url_status.rb +++ b/api_examples/upload_api/get_from_url_status.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -token = '945ebb27-1fd6-46c6-a859-b9893712d650' -puts Uploadcare::Uploader.get_upload_from_url_status(token) +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/get_group_info.rb b/api_examples/upload_api/get_group_info.rb old mode 100644 new mode 100755 index 4fb48eb1..a396cd06 --- a/api_examples/upload_api/get_group_info.rb +++ b/api_examples/upload_api/get_group_info.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '0d712319-b970-4602-850c-bae1ced521a6~1' -info = Uploadcare::Group.info(uuid) -puts info.inspect +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/get_info.rb b/api_examples/upload_api/get_info.rb old mode 100644 new mode 100755 index 7c0893d7..a396cd06 --- a/api_examples/upload_api/get_info.rb +++ b/api_examples/upload_api/get_info.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuid = '740e1b8c-1ad8-4324-b7ec-112c79d8eac2' -info = Uploadcare::File.info(uuid) -puts info.inspect +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/post_base.rb b/api_examples/upload_api/post_base.rb old mode 100644 new mode 100755 index b9494662..a396cd06 --- a/api_examples/upload_api/post_base.rb +++ b/api_examples/upload_api/post_base.rb @@ -1,7 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -File.open('image.png') do |source_file| - Uploadcare::Uploader.upload(source_file, store: 'auto') -end +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/post_from_url.rb b/api_examples/upload_api/post_from_url.rb old mode 100644 new mode 100755 index 4dd1c810..a396cd06 --- a/api_examples/upload_api/post_from_url.rb +++ b/api_examples/upload_api/post_from_url.rb @@ -1,6 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -source_url = 'https://source.unsplash.com/featured' -Uploadcare::Uploader.upload(source_url, store: 'auto') +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/post_group.rb b/api_examples/upload_api/post_group.rb old mode 100644 new mode 100755 index 79ada754..a396cd06 --- a/api_examples/upload_api/post_group.rb +++ b/api_examples/upload_api/post_group.rb @@ -1,9 +1,4 @@ -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' +#!/usr/bin/env ruby +# frozen_string_literal: true -uuids = [ - 'd6d34fa9-addd-472c-868d-2e5c105f9fcd', - 'b1026315-8116-4632-8364-607e64fca723/-/resize/x800/' -] -Uploadcare::Group.create(uuids) +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/post_multipart_complete.rb b/api_examples/upload_api/post_multipart_complete.rb old mode 100644 new mode 100755 index f431eb64..a396cd06 --- a/api_examples/upload_api/post_multipart_complete.rb +++ b/api_examples/upload_api/post_multipart_complete.rb @@ -1,9 +1,4 @@ -# Uploadcare lib provides high level API for multipart uploads that does everything for you +#!/usr/bin/env ruby +# frozen_string_literal: true -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' - -File.open('image.png') do |source_file| - Uploadcare::Uploader.upload(source_file, store: 'auto') -end +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/post_multipart_start.rb b/api_examples/upload_api/post_multipart_start.rb old mode 100644 new mode 100755 index f431eb64..a396cd06 --- a/api_examples/upload_api/post_multipart_start.rb +++ b/api_examples/upload_api/post_multipart_start.rb @@ -1,9 +1,4 @@ -# Uploadcare lib provides high level API for multipart uploads that does everything for you +#!/usr/bin/env ruby +# frozen_string_literal: true -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' - -File.open('image.png') do |source_file| - Uploadcare::Uploader.upload(source_file, store: 'auto') -end +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/put_multipart_part.rb b/api_examples/upload_api/put_multipart_part.rb new file mode 100755 index 00000000..a396cd06 --- /dev/null +++ b/api_examples/upload_api/put_multipart_part.rb @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../support/run_upload_example' diff --git a/api_examples/upload_api/put_presigned_url_x.rb b/api_examples/upload_api/put_presigned_url_x.rb deleted file mode 100644 index f431eb64..00000000 --- a/api_examples/upload_api/put_presigned_url_x.rb +++ /dev/null @@ -1,9 +0,0 @@ -# Uploadcare lib provides high level API for multipart uploads that does everything for you - -require 'uploadcare' -Uploadcare.config.public_key = 'YOUR_PUBLIC_KEY' -Uploadcare.config.secret_key = 'YOUR_SECRET_KEY' - -File.open('image.png') do |source_file| - Uploadcare::Uploader.upload(source_file, store: 'auto') -end diff --git a/bin/console b/bin/console index 5763de1b..d6e9fc9e 100755 --- a/bin/console +++ b/bin/console @@ -2,7 +2,7 @@ # frozen_string_literal: true require 'bundler/setup' -require 'uploadcare/ruby' +require 'uploadcare' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/context7.json b/context7.json index dbca8336..d61b65e5 100644 --- a/context7.json +++ b/context7.json @@ -7,7 +7,9 @@ "branch": "main", "folders": [ "lib", - "api_examples" + "api_examples", + "examples", + "docs" ], "excludeFolders": [ ".github", @@ -47,10 +49,13 @@ ], "rules": [ "Install the gem as `uploadcare-ruby` and require it with `require \"uploadcare\"`.", - "Configure credentials with `Uploadcare.config.public_key` and `Uploadcare.config.secret_key` or the UPLOADCARE_PUBLIC_KEY and UPLOADCARE_SECRET_KEY environment variables.", + "Prefer explicit `Uploadcare::Client` instances for application code, especially when an app uses more than one Uploadcare project.", + "Configure credentials with `Uploadcare::Client.new(public_key: ..., secret_key: ...)`, `Uploadcare.configure`, or the UPLOADCARE_PUBLIC_KEY and UPLOADCARE_SECRET_KEY environment variables.", "Never hardcode real Uploadcare API keys in examples or application code; use environment variables or an application secrets store.", - "Use `Uploadcare::Uploader.upload` for automatic upload method selection and `Uploadcare::Uploader.multipart_upload` for explicit large-file multipart uploads.", - "Prefer public entity helpers such as `Uploadcare::File`, `Uploadcare::FileList`, `Uploadcare::Group`, `Uploadcare::Webhook`, and conversion methods over internal clients." + "Use `client.files`, `client.groups`, `client.uploads`, `client.project`, `client.webhooks`, `client.file_metadata`, `client.addons`, and `client.conversions` for normal application workflows.", + "Use `client.api.rest` and `client.api.upload` when exact REST API or Upload API endpoint parity is needed.", + "Use `client.uploads.upload` for automatic upload method selection and `client.uploads.multipart_upload` for explicit multipart uploads.", + "Use `MIGRATING_V5.md` when upgrading applications from uploadcare-ruby v4.x to v5." ], "previousVersions": [ { diff --git a/docs/release-notes-5.0.0.md b/docs/release-notes-5.0.0.md new file mode 100644 index 00000000..1ae1d8e6 --- /dev/null +++ b/docs/release-notes-5.0.0.md @@ -0,0 +1,47 @@ +# uploadcare-ruby 5.0.0 + +v5 is stable. +No API changes since `5.0.0.rc1`. +What to do now: run the full test suite in staging, validate large multipart uploads, deploy, and monitor for 24-72 hours. + +Please review [MIGRATING_V5.md](../MIGRATING_V5.md) before upgrading from v4.x. + +## Highlights + +- Use `Uploadcare::Client` as the primary API; avoid internal helpers. +- Use `client.api.rest` and `client.api.upload` when you need exact endpoint behavior. +- Create one client per project for multi-account setups. +- Faraday and Zeitwerk reduce internal coupling; audit code that relied on old internals. +- Follow the canonical API examples, workflow examples, migration docs, and Context7 rules for v5. +- CI covers Ruby 3.3, 3.4, and 4.0. + +## Important Fixes Included + +- REST signing uses deterministic protocol-required digests (`MD5` and `SHA1`) +- REST query signing uses the same nested parameter encoding as request transmission +- Multipart upload retries/timeouts now honor configuration (`max_upload_retries`, `upload_timeout`) +- Multipart upload worker cancellation now stops remaining queued work after first worker error +- Upload-from-URL polling now supports exponential backoff with configurable cap +- Multipart start payload no longer sends unsupported `part_size` to `/multipart/start/` +- Upload API batch uploads avoid duplicate filename key collisions without mutating caller-visible filenames +- `FileMetadata` resource instance initialization correctly assigns `uuid` + +## Upgrade Notes + +- Requirement: Ruby `>= 3.3`. +- Do this first: run the full test suite against v5 in staging. +- Example check: `bundle update uploadcare-ruby && bundle exec rspec`. +- Multi-account apps: create one `Uploadcare::Client` per project. +- Audit code for removed internal APIs; replace them with `client.files`, `client.groups`, `client.uploads`, or `client.api`. +- Test large-file uploads because multipart retry and cancellation behavior changed. +- Re-validate custom REST signing if your app reimplements signing outside this gem. +- Rollback: pin to the latest v4 release and keep rollback trivial. + +## Risk Notes + +- v5 uses Faraday and Zeitwerk with simpler internal dependencies; audit any app code that relied on old internals. +- Post-deploy: monitor upload errors, multipart worker failures, and REST signing errors for 24-72 hours. + +## Full Changelog + +See [CHANGELOG.md](../CHANGELOG.md). diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..035c829d --- /dev/null +++ b/examples/README.md @@ -0,0 +1,39 @@ +# Workflow Examples + +This directory contains higher-level workflow demos built on the public client API. + +For endpoint-by-endpoint coverage, use [api_examples/README.md](../api_examples/README.md). + +## Setup + +```bash +export UPLOADCARE_PUBLIC_KEY=your_public_key +export UPLOADCARE_SECRET_KEY=your_secret_key +``` + +Run scripts with project-managed Ruby: + +```bash +mise exec -- ruby examples/simple_upload.rb spec/fixtures/kitten.jpeg +``` + +## Scripts + +- `examples/simple_upload.rb` + Upload one file and print its UUID and CDN URL. +- `examples/upload_with_progress.rb` + Upload one large file with multipart progress reporting. +- `examples/batch_upload.rb` + Upload multiple files in one call. +- `examples/large_file_upload.rb` + Force multipart upload and show throughput details. +- `examples/url_upload.rb` + Upload a remote URL and show async polling as a follow-up example. +- `examples/group_creation.rb` + Upload multiple files, create a group, and print group details. + +## Notes + +- These are workflow demos. +- The canonical API inventory lives in `api_examples/`. +- The examples assume real Uploadcare credentials and network access. diff --git a/examples/batch_upload.rb b/examples/batch_upload.rb new file mode 100755 index 00000000..163a32c1 --- /dev/null +++ b/examples/batch_upload.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/uploadcare' +require 'dotenv/load' + +file_paths = ARGV + +if file_paths.empty? + puts 'Usage: ruby batch_upload.rb ...' + puts 'Example: ruby batch_upload.rb photo1.jpg photo2.jpg photo3.jpg' + exit 1 +end + +file_paths.each do |path| + unless File.exist?(path) + puts "Error: File not found: #{path}" + exit 1 + end +end + +puts "Batch Upload - #{file_paths.length} files" +puts '=' * 50 +puts + +files = file_paths.map { |path| File.open(path, 'rb') } + +begin + client = Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY') + ) + + results = client.uploads.upload(files, store: true) + + puts '✓ Batch upload complete!' + puts + puts 'Results:' + puts '-' * 50 + + results.each_with_index do |file, index| + puts "#{index + 1}. #{file.original_filename}" + puts " UUID: #{file.uuid}" + puts " CDN URL: #{file.cdn_url}" + puts + end + + puts "Successfully uploaded #{results.length} files" +rescue StandardError => e + puts "✗ Batch upload failed: #{e.message}" + exit 1 +ensure + files.each(&:close) +end diff --git a/examples/group_creation.rb b/examples/group_creation.rb new file mode 100755 index 00000000..64ce586d --- /dev/null +++ b/examples/group_creation.rb @@ -0,0 +1,88 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/uploadcare' +require 'dotenv/load' + +file_paths = ARGV + +if file_paths.empty? + script_name = File.basename($PROGRAM_NAME) + puts "Usage: ruby examples/#{script_name} ..." + puts "Example: ruby examples/#{script_name} photo1.jpg photo2.jpg photo3.jpg" + exit 1 +end + +file_paths.each do |path| + unless File.exist?(path) + puts "Error: File not found: #{path}" + exit 1 + end +end + +puts "Group Creation - #{file_paths.length} files" +puts '=' * 50 +puts + +begin + client = Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY') + ) + + puts 'Step 1: Uploading files...' + uuids = [] + + file_paths.each_with_index do |path, index| + uploaded = File.open(path, 'rb') do |file| + client.files.upload(file, store: true) + end + + uuids << uploaded.uuid + puts " #{index + 1}. #{File.basename(path)} → #{uploaded.uuid}" + end + + puts + puts "✓ Uploaded #{uuids.length} files" + puts + + puts 'Step 2: Creating group...' + group = client.groups.create(uuids: uuids) + + puts '✓ Group created!' + puts + puts 'Group Details:' + puts '-' * 50 + puts "Group ID: #{group.id}" + puts "Files count: #{group.files_count}" + puts "CDN URL: #{group.cdn_url}" + puts "Created at: #{group.datetime_created}" + puts + + puts 'Step 3: Retrieving group info...' + info = client.groups.find(group_id: group.id) + + puts '✓ Group info retrieved' + puts + puts 'Files in group:' + puts '-' * 50 + + Array(info.files).each_with_index do |file, index| + file_resource = Uploadcare::Resources::File.new(file, client) + puts "#{index + 1}. #{file_resource.original_filename}" + puts " UUID: #{file_resource.uuid}" + puts " Size: #{(file_resource.size / 1024.0).round(2)} KB" + puts " URL: #{file_resource.cdn_url}" + puts + end + + puts 'Group URL:' + puts group.cdn_url + puts + puts 'You can access individual files in the group:' + puts "#{group.cdn_url}nth/0/" + puts "#{group.cdn_url}nth/1/" +rescue StandardError => e + puts "✗ Group creation failed: #{e.message}" + exit 1 +end diff --git a/examples/large_file_upload.rb b/examples/large_file_upload.rb new file mode 100755 index 00000000..3b2f0901 --- /dev/null +++ b/examples/large_file_upload.rb @@ -0,0 +1,88 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/uploadcare' +require 'dotenv/load' + +file_path = ARGV[0] +threads = (ARGV[1] || 4).to_i + +unless file_path && File.exist?(file_path) + puts 'Usage: ruby large_file_upload.rb [threads]' + puts 'Example: ruby large_file_upload.rb large_video.mp4 4' + puts + puts 'threads: Number of parallel upload threads (default: 4)' + exit 1 +end + +file_size = File.size(file_path) +file_size_mb = (file_size / 1024.0 / 1024.0).round(2) + +if file_size < 10_000_000 + puts 'Warning: File is < 10MB. Multipart upload is recommended for files >= 10MB' + puts 'The upload will still work but may use base upload instead.' + puts +end + +puts 'Large File Upload' +puts '=' * 50 +puts "File: #{file_path}" +puts "Size: #{file_size_mb} MB" +puts "Threads: #{threads}" +puts + +begin + client = Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY') + ) + + start_time = Time.now + result = nil + + File.open(file_path, 'rb') do |file| + result = client.uploads.multipart_upload( + file: file, + store: true, + threads: threads, + metadata: { + source: 'large_file_example', + upload_method: 'multipart' + } + ) do |progress| + uploaded_mb = (progress[:uploaded] / 1024.0 / 1024.0).round(2) + total_mb = (progress[:total] / 1024.0 / 1024.0).round(2) + percentage = ((progress[:uploaded].to_f / progress[:total]) * 100).round + part = progress[:part] + total_parts = progress[:total_parts] + + bar_length = 30 + filled = (bar_length * percentage / 100).to_i + bar = ('#' * filled) + ('.' * (bar_length - filled)) + + print "\r#{bar} #{percentage}% | Part #{part}/#{total_parts} | #{uploaded_mb}/#{total_mb} MB" + $stdout.flush + end + end + + elapsed = Time.now - start_time + + puts + puts + puts '✓ Upload successful!' + puts + puts 'Upload Details:' + puts '-' * 50 + puts "UUID: #{result.uuid}" + puts "Size: #{file_size_mb} MB" + puts "Time: #{elapsed.round(2)} seconds" + puts "Speed: #{(file_size_mb / elapsed).round(2)} MB/s" + puts "Threads: #{threads}" + puts 'Method: Multipart upload' + puts + puts "CDN URL: #{result.cdn_url}" +rescue StandardError => e + puts + puts "✗ Upload failed: #{e.message}" + exit 1 +end diff --git a/examples/simple_upload.rb b/examples/simple_upload.rb new file mode 100755 index 00000000..6ee1b5de --- /dev/null +++ b/examples/simple_upload.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/uploadcare' +require 'dotenv/load' + +file_path = ARGV[0] + +unless file_path && File.exist?(file_path) + puts 'Usage: ruby simple_upload.rb ' + puts 'Example: ruby simple_upload.rb photo.jpg' + exit 1 +end + +puts "Uploading: #{file_path}" +puts "Size: #{(File.size(file_path) / 1024.0).round(2)} KB" +puts + +begin + client = Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY') + ) + + uploaded_file = File.open(file_path, 'rb') do |file| + client.files.upload(file, store: true) + end + + puts '✓ Upload successful!' + puts + puts "UUID: #{uploaded_file.uuid}" + puts "Filename: #{uploaded_file.original_filename}" + puts "CDN URL: #{uploaded_file.cdn_url}" + puts + puts 'The file has been stored and is ready to use.' +rescue StandardError => e + puts "✗ Upload failed: #{e.message}" + exit 1 +end diff --git a/examples/upload_with_progress.rb b/examples/upload_with_progress.rb new file mode 100755 index 00000000..72faa3e0 --- /dev/null +++ b/examples/upload_with_progress.rb @@ -0,0 +1,84 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/uploadcare' +require 'dotenv/load' + +file_path = ARGV[0] +script_name = File.basename($PROGRAM_NAME) +multipart_threshold = 10 * 1024 * 1024 + +unless file_path && File.exist?(file_path) + puts "Usage: ruby examples/#{script_name} " + puts "Example: ruby examples/#{script_name} large_video.mp4" + puts + puts 'Note: Progress tracking works best with files >= 10 MiB' + exit 1 +end + +file_size = File.size(file_path) +file_size_mb = (file_size / 1024.0 / 1024.0).round(2) + +puts "Uploading: #{file_path}" +puts "Size: #{file_size_mb} MB" +puts + +if file_size < multipart_threshold + puts 'Note: File is < 10 MiB, so the upload may complete without multipart progress updates' + puts +end + +file = nil + +begin + client = Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY') + ) + + file = File.open(file_path, 'rb') + start_time = Time.now + + result = client.uploads.upload(file, store: true) do |progress| + uploaded_mb = (progress[:uploaded] / 1024.0 / 1024.0).round(2) + total_mb = (progress[:total] / 1024.0 / 1024.0).round(2) + percentage = ((progress[:uploaded].to_f / progress[:total]) * 100).round + part = progress[:part] + total_parts = progress[:total_parts] + + elapsed = Time.now - start_time + speed_mbps = uploaded_mb / elapsed + remaining_mb = total_mb - uploaded_mb + eta_seconds = remaining_mb / speed_mbps if speed_mbps.positive? + + bar_length = 40 + filled = (bar_length * percentage / 100).to_i + bar = ('#' * filled) + ('.' * (bar_length - filled)) + + print "\r#{bar} #{percentage}% | " + print "#{uploaded_mb}/#{total_mb} MB | " + print "Part #{part}/#{total_parts} | " + print "Speed: #{speed_mbps.round(2)} MB/s" + print " | ETA: #{eta_seconds.to_i}s" if eta_seconds + $stdout.flush + end + + elapsed = Time.now - start_time + + puts + puts + puts '✓ Upload successful!' + puts + puts "UUID: #{result.uuid}" + puts "Filename: #{result.original_filename}" + puts "Total time: #{elapsed.round(2)} seconds" + puts "Average speed: #{(file_size_mb / elapsed).round(2)} MB/s" + puts + puts "CDN URL: #{result.cdn_url}" +rescue StandardError => e + puts + puts "✗ Upload failed: #{e.message}" + exit 1 +ensure + file&.close +end diff --git a/examples/url_upload.rb b/examples/url_upload.rb new file mode 100755 index 00000000..7c4bfaef --- /dev/null +++ b/examples/url_upload.rb @@ -0,0 +1,56 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/uploadcare' +require 'dotenv/load' + +url = ARGV[0] + +unless url&.match?(%r{^https?://}) + puts 'Usage: ruby url_upload.rb ' + puts 'Example: ruby url_upload.rb https://example.com/image.jpg' + exit 1 +end + +puts 'URL Upload' +puts '=' * 50 +puts "URL: #{url}" +puts + +begin + client = Uploadcare::Client.new( + public_key: ENV.fetch('UPLOADCARE_PUBLIC_KEY'), + secret_key: ENV.fetch('UPLOADCARE_SECRET_KEY') + ) + + puts 'Starting upload...' + result = client.files.upload_from_url(url, store: true) + + puts '✓ Upload successful!' + puts + puts "UUID: #{result.uuid}" + puts "Filename: #{result.original_filename}" + puts "Size: #{(result.size / 1024.0).round(2)} KB" + puts "MIME type: #{result.mime_type}" + puts + puts "CDN URL: #{result.cdn_url}" + puts + puts 'Advanced Usage:' + puts + puts '# Async mode (returns immediately with token):' + puts 'client = Uploadcare::Client.new(public_key: "...", secret_key: "...")' + puts "response = client.uploads.upload_from_url(url: '#{url}', async: true)" + puts "token = response['token']" + puts + puts '# Check status later:' + puts 'status = client.uploads.upload_from_url_status(token: token)' + puts "puts status['status']" +rescue StandardError => e + puts "✗ Upload failed: #{e.message}" + puts + puts 'Common issues:' + puts '- URL must be publicly accessible' + puts '- URL must return a valid file' + puts '- Some file types may not be supported' + exit 1 +end diff --git a/lib/uploadcare.rb b/lib/uploadcare.rb index e34e3770..f01c4923 100644 --- a/lib/uploadcare.rb +++ b/lib/uploadcare.rb @@ -1,70 +1,88 @@ # frozen_string_literal: true -# Gem version -require 'ruby/version' +require 'zeitwerk' +require 'faraday' -# Exceptions -require 'exception/throttle_error' -require 'exception/request_error' -require 'exception/retry_error' -require 'exception/auth_error' -require 'exception/configuration_error' +module Uploadcare + @loader = Zeitwerk::Loader.for_gem -# Entities -require 'entity/entity' -require 'entity/file' -require 'entity/file_list' -require 'entity/group' -require 'entity/group_list' -require 'entity/project' -require 'entity/uploader' -require 'entity/webhook' + @loader.setup -# Param -require 'param/webhook_signature_verifier' + require_relative 'uploadcare/cname_generator' -# General api -require 'api/api' + class << self + # Configure the global Uploadcare instance. + # + # @yield [config] Configuration block + # @yieldparam config [Uploadcare::Configuration] The configuration object + def configure + yield configuration if block_given? + ensure + @client = nil + end -# SignedUrlGenerators -require 'signed_url_generators/akamai_generator' -require 'signed_url_generators/base_generator' + # Access the global configuration. + # + # @return [Uploadcare::Configuration] + def configuration + @configuration ||= Configuration.new + end -# CNAME generator -require 'cname_generator' + # Access a global client instance, or create one with overrides. + # + # @param config [Uploadcare::Configuration, nil] Custom configuration + # @param options [Hash] Configuration overrides + # @return [Uploadcare::Client] + def client(config: nil, **options) + if options.empty? && (config.nil? || config.equal?(configuration)) + return (@client ||= Client.new(config: configuration)) + end -# Ruby wrapper for Uploadcare API -# -# @see https://uploadcare.com/docs/api_reference -module Uploadcare - extend Dry::Configurable + Client.new(config: config || configuration, **options) + end + + # @return [Uploadcare::Client::FilesAccessor] + def files + client.files + end - setting :public_key, default: ENV.fetch('UPLOADCARE_PUBLIC_KEY', '') - setting :secret_key, default: ENV.fetch('UPLOADCARE_SECRET_KEY', '') - setting :auth_type, default: 'Uploadcare' - setting :multipart_size_threshold, default: 100 * 1024 * 1024 - setting :rest_api_root, default: 'https://api.uploadcare.com' - setting :upload_api_root, default: 'https://upload.uploadcare.com' - setting :max_request_tries, default: 100 - setting :base_request_sleep, default: 1 # seconds - setting :max_request_sleep, default: 60.0 # seconds - setting :sign_uploads, default: false - setting :upload_signature_lifetime, default: 30 * 60 # seconds - setting :max_throttle_attempts, default: 5 - setting :upload_threads, default: 2 # used for multiupload only ATM - setting :framework_data, default: '' - setting :file_chunk_size, default: 100 - setting :logger, default: Logger.new($stdout) - setting :default_cdn_base, default: ENV.fetch('UPLOADCARE_DEFAULT_CDN_BASE', 'https://ucarecdn.com/') - setting :cdn_base_postfix, default: ENV.fetch('UPLOADCARE_CDN_BASE', 'https://ucarecd.net/') - # Enable automatic *.ucarecdn.net subdomains and CNAME generation - setting :use_subdomains, default: false - setting :custom_cname, default: -> { CnameGenerator.generate_cname } # CNAME domain - setting :cdn_base, default: lambda { - if config.use_subdomains && config.public_key - CnameGenerator.cdn_base_postfix - else - config.default_cdn_base + # @return [Uploadcare::Client::GroupsAccessor] + def groups + client.groups end - } + + # @return [Uploadcare::Operations::UploadRouter] + def uploads + client.uploads + end + + # @return [Uploadcare::Client::ProjectAccessor] + def project + client.project + end + + # Eager-load the gem namespace through Zeitwerk. + # + # @return [void] + def eager_load! + @loader.eager_load + end + end + + # Top-level aliases for the public resource classes. + File = Resources::File + # Alias for the group resource. + Group = Resources::Group + # Alias for the project resource. + Project = Resources::Project + # Alias for the webhook resource. + Webhook = Resources::Webhook + # Alias for the file metadata resource. + FileMetadata = Resources::FileMetadata + # Alias for the add-on execution resource. + AddonExecution = Resources::AddonExecution + # Alias for the document conversion resource. + DocumentConversion = Resources::DocumentConversion + # Alias for the video conversion resource. + VideoConversion = Resources::VideoConversion end diff --git a/lib/uploadcare/api/api.rb b/lib/uploadcare/api/api.rb deleted file mode 100644 index 8834cd3b..00000000 --- a/lib/uploadcare/api/api.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -Gem.find_files('client/**/*.rb').each { |path| require path } -Gem.find_files('entity/**/*.rb').each { |path| require path } - -module Uploadcare - # End-user interface - # - # It delegates methods to other classes: - # * To class methods of Entity objects - # * To instance methods of Client objects - # @see Uploadcare::Entity - # @see Uploadcare::Client - class Api - extend Forwardable - include Entity - - def_delegator File, :file - def_delegators FileList, :file_list, :store_files, :delete_files - def_delegators Group, :group - def_delegators Project, :project - def_delegators Uploader, :upload, :upload_files, :upload_url - def_delegators Webhook, :create_webhook, :list_webhooks, :delete_webhook, :update_webhook - end -end diff --git a/lib/uploadcare/api/rest.rb b/lib/uploadcare/api/rest.rb new file mode 100644 index 00000000..8db7eb92 --- /dev/null +++ b/lib/uploadcare/api/rest.rb @@ -0,0 +1,254 @@ +# frozen_string_literal: true + +require 'faraday' +require 'uri' + +# Base client for the Uploadcare REST API. +# +# Provides authenticated HTTP methods (GET, POST, PUT, DELETE) for all REST API +# endpoints. Includes automatic error handling and throttle retry logic. +# +# Endpoint classes are accessed via lazy-loaded accessors: +# rest = Uploadcare::Api::Rest.new(config: config) +# rest.files.list +# rest.groups.info(uuid: "...") +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/ +class Uploadcare::Api::Rest + include Uploadcare::Internal::ErrorHandler + include Uploadcare::Internal::ThrottleHandler + + # Verb name used when deciding whether params belong in the query string. + HTTP_GET = 'GET' + + # @return [Uploadcare::Configuration] + attr_reader :config + + # @return [Faraday::Connection] + attr_reader :connection + + # @return [Uploadcare::Internal::Authenticator] + attr_reader :authenticator + + # Initialize a new REST API client. + # + # @param config [Uploadcare::Configuration] Configuration object (defaults to global config) + def initialize(config: Uploadcare.configuration) + @config = config + @memo_mutex = Mutex.new + @connection = Faraday.new(url: config.rest_api_root) do |conn| + conn.request :json + conn.response :json, content_type: /\bjson$/ + conn.response :raise_error + end + @authenticator = Uploadcare::Internal::Authenticator.new(config: config) + end + + # --- Endpoint accessors --- + + # @return [Uploadcare::Api::Rest::Files] File operations endpoint + def files + memoized(:@files) { Uploadcare::Api::Rest::Files.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::Groups] Group operations endpoint + def groups + memoized(:@groups) { Uploadcare::Api::Rest::Groups.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::Project] Project information endpoint + def project + memoized(:@project) { Uploadcare::Api::Rest::Project.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::Webhooks] Webhook operations endpoint + def webhooks + memoized(:@webhooks) { Uploadcare::Api::Rest::Webhooks.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::FileMetadata] File metadata operations endpoint + def file_metadata + memoized(:@file_metadata) { Uploadcare::Api::Rest::FileMetadata.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::Addons] Add-on operations endpoint + def addons + memoized(:@addons) { Uploadcare::Api::Rest::Addons.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::DocumentConversions] Document conversion endpoint + def document_conversions + memoized(:@document_conversions) { Uploadcare::Api::Rest::DocumentConversions.new(rest: self) } + end + + # @return [Uploadcare::Api::Rest::VideoConversions] Video conversion endpoint + def video_conversions + memoized(:@video_conversions) { Uploadcare::Api::Rest::VideoConversions.new(rest: self) } + end + + # --- HTTP methods --- + + # Make an HTTP request to the REST API. + # + # @param method [Symbol] HTTP method (:get, :post, :put, :delete) + # @param path [String] API endpoint path + # @param params [Hash, Array, String] Request parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options (timeout, etc.) + # @return [Hash, Array, nil] Parsed JSON response body + # @raise [Uploadcare::Exception::RequestError] on API errors + def make_request(method:, path:, params: {}, headers: {}, request_options: {}) + handle_throttling(max_attempts: request_options[:max_throttle_attempts]) do + response = connection.public_send(method, path) do |req| + prepare_request(req, method, path, params, headers, request_options) + end + response.body + end + rescue Faraday::Error => e + handle_error(e) + end + + # Make a POST request wrapped in a Result. + # + # @param path [String] API endpoint path + # @param params [Hash] Request body parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def post(path:, params: {}, headers: {}, request_options: {}) + request(method: :post, path: path, params: params, headers: headers, request_options: request_options) + end + + # Make a GET request wrapped in a Result. + # + # @param path [String] API endpoint path + # @param params [Hash] Query parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def get(path:, params: {}, headers: {}, request_options: {}) + request(method: :get, path: path, params: params, headers: headers, request_options: request_options) + end + + # Make a PUT request wrapped in a Result. + # + # @param path [String] API endpoint path + # @param params [Hash] Request body parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def put(path:, params: {}, headers: {}, request_options: {}) + request(method: :put, path: path, params: params, headers: headers, request_options: request_options) + end + + # Make a DELETE request wrapped in a Result. + # + # @param path [String] API endpoint path + # @param params [Hash] Request body parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def delete(path:, params: {}, headers: {}, request_options: {}) + request(method: :delete, path: path, params: params, headers: headers, request_options: request_options) + end + + # Wraps a request in a Result object. + # + # @param method [Symbol] HTTP method + # @param path [String] API path + # @param params [Hash] Request parameters + # @param headers [Hash] Request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def request(method:, path:, params: {}, headers: {}, request_options: {}) + Uploadcare::Result.capture do + make_request(method: method, path: path, params: params, headers: headers, request_options: request_options) + end + end + + private + + def prepare_request(req, method, path, params, headers, request_options) + upcase_method_name = method.to_s.upcase + uri = build_request_uri(path, params, upcase_method_name) + + prepare_headers(req, upcase_method_name, uri, params, headers) + prepare_body_or_params(req, upcase_method_name, params) + apply_request_options(req, request_options) + end + + def build_request_uri(path, params, method) + if method == HTTP_GET && !params.nil? && params.is_a?(Hash) && !params.empty? + build_uri(path, params) + else + path + end + end + + def prepare_headers(req, method, uri, params, headers) + body_content = body_content_for_signature(method, params) + content_type = extract_content_type(headers) || authenticator.default_headers['Content-Type'] + auth_headers = authenticator.headers(method, uri, body_content, content_type) + normalized_headers = normalize_content_type_header(headers, content_type) + req.headers.merge!(auth_headers) + req.headers.merge!(normalized_headers) + end + + def body_content_for_signature(method, params) + return '' if method == HTTP_GET + return '' if params.nil? || (params.respond_to?(:empty?) && params.empty?) + return params if params.is_a?(String) + + params.to_json + end + + def prepare_body_or_params(req, method, params) + if method == HTTP_GET + req.params.update(params) unless params.nil? || params.empty? + else + return if params.nil? || params.empty? + + req.body = params + end + end + + def apply_request_options(req, request_options) + return if request_options.nil? || request_options.empty? + + req.options.timeout = request_options[:timeout] if request_options[:timeout] + req.options.open_timeout = request_options[:open_timeout] if request_options[:open_timeout] + end + + def extract_content_type(headers) + headers['Content-Type'] || headers['content-type'] || headers[:content_type] || headers[:'Content-Type'] + end + + def normalize_content_type_header(headers, content_type) + normalized_headers = headers.dup + normalized_headers.delete('content-type') + normalized_headers.delete(:content_type) + normalized_headers.delete(:'Content-Type') + normalized_headers['Content-Type'] = content_type if content_type + normalized_headers + end + + def build_uri(path, query_params = {}) + if query_params.empty? + path + else + separator = path.include?('?') ? '&' : '?' + params_encoder = connection.options.params_encoder || Faraday::Utils.default_params_encoder + encoded_query = params_encoder.encode(query_params) + "#{path}#{separator}#{encoded_query}" + end + end + + def memoized(ivar) + cached = instance_variable_get(ivar) + return cached if cached + + @memo_mutex.synchronize do + instance_variable_get(ivar) || instance_variable_set(ivar, yield) + end + end +end diff --git a/lib/uploadcare/api/rest/addons.rb b/lib/uploadcare/api/rest/addons.rb new file mode 100644 index 00000000..a6c22a10 --- /dev/null +++ b/lib/uploadcare/api/rest/addons.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +# REST API endpoint for add-on operations. +# +# Supports AWS Rekognition (labels & moderation), ClamAV virus scan, and Remove.bg. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons +class Uploadcare::Api::Rest::Addons + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # Execute AWS Rekognition label detection. + # + # @param uuid [String] File UUID to process + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with request_id + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionExecute + def aws_rekognition_detect_labels(uuid:, request_options: {}) + rest.post(path: '/addons/aws_rekognition_detect_labels/execute/', + params: { target: uuid }, headers: {}, request_options: request_options) + end + + # Check AWS Rekognition label detection status. + # + # @param request_id [String] Request ID from execution + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with status + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionExecutionStatus + def aws_rekognition_detect_labels_status(request_id:, request_options: {}) + rest.get(path: '/addons/aws_rekognition_detect_labels/execute/status/', + params: { request_id: request_id }, headers: {}, request_options: request_options) + end + + # Execute AWS Rekognition moderation label detection. + # + # @param uuid [String] File UUID to process + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with request_id + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionDetectModerationLabelsExecute + def aws_rekognition_detect_moderation_labels(uuid:, request_options: {}) + rest.post(path: '/addons/aws_rekognition_detect_moderation_labels/execute/', + params: { target: uuid }, headers: {}, request_options: request_options) + end + + # Check AWS Rekognition moderation label detection status. + # + # @param request_id [String] Request ID from execution + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with status + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionDetectModerationLabelsExecutionStatus + def aws_rekognition_detect_moderation_labels_status(request_id:, request_options: {}) + rest.get(path: '/addons/aws_rekognition_detect_moderation_labels/execute/status/', + params: { request_id: request_id }, headers: {}, request_options: request_options) + end + + # Execute ClamAV virus scan. + # + # @param uuid [String] File UUID to process + # @param params [Hash] Optional scan parameters + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with request_id + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/ucClamavVirusScanExecute + def uc_clamav_virus_scan(uuid:, params: {}, request_options: {}) + body = { target: uuid }.merge(params) + rest.post(path: '/addons/uc_clamav_virus_scan/execute/', params: body, headers: {}, + request_options: request_options) + end + + # Check ClamAV virus scan status. + # + # @param request_id [String] Request ID from execution + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with status + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/ucClamavVirusScanExecutionStatus + def uc_clamav_virus_scan_status(request_id:, request_options: {}) + rest.get(path: '/addons/uc_clamav_virus_scan/execute/status/', + params: { request_id: request_id }, headers: {}, request_options: request_options) + end + + # Execute Remove.bg background removal. + # + # @param uuid [String] File UUID to process + # @param params [Hash] Optional parameters for the add-on + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with request_id + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/removeBgExecute + def remove_bg(uuid:, params: {}, request_options: {}) + rest.post(path: '/addons/remove_bg/execute/', + params: { target: uuid, params: params }, headers: {}, request_options: request_options) + end + + # Check Remove.bg execution status. + # + # @param request_id [String] Request ID from execution + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with status and result + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/removeBgExecutionStatus + def remove_bg_status(request_id:, request_options: {}) + rest.get(path: '/addons/remove_bg/execute/status/', + params: { request_id: request_id }, headers: {}, request_options: request_options) + end +end diff --git a/lib/uploadcare/api/rest/document_conversions.rb b/lib/uploadcare/api/rest/document_conversions.rb new file mode 100644 index 00000000..6c84a223 --- /dev/null +++ b/lib/uploadcare/api/rest/document_conversions.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# REST API endpoint for document conversion operations. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion +class Uploadcare::Api::Rest::DocumentConversions + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # Get document format information and possible conversion formats. + # + # @param uuid [String] Document file UUID + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Document format info + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion/operation/documentConvertInfo + def info(uuid:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid.to_s) + rest.get(path: "/convert/document/#{encoded_uuid}/", params: {}, headers: {}, + request_options: request_options) + end + + # Convert a document to a specified format. + # + # @param paths [Array] Conversion paths (e.g., ["uuid/document/-/format/pdf/"]) + # @param options [Hash] Optional parameters (:store, :save_in_group) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Conversion details with result and problems + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion/operation/documentConvert + def convert(paths:, options: {}, request_options: {}) + body = { paths: paths } + body[:store] = normalize_bool_param(options[:store]) if options.key?(:store) + body[:save_in_group] = normalize_bool_param(options[:save_in_group]) if options.key?(:save_in_group) + body.compact! + + rest.post(path: '/convert/document/', params: body, headers: {}, request_options: request_options) + end + + # Get document conversion job status. + # + # @param token [String, Integer] Conversion job token + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Job status and result + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion/operation/documentConvertStatus + def status(token:, request_options: {}) + rest.get(path: "/convert/document/status/#{token}/", params: {}, headers: {}, + request_options: request_options) + end + + private + + def normalize_bool_param(value) + normalized = value.is_a?(String) ? value.strip.downcase : value + + case normalized + when true, 1, '1', 'true' then '1' + when false, 0, '0', 'false' then '0' + else value + end + end +end diff --git a/lib/uploadcare/api/rest/file_metadata.rb b/lib/uploadcare/api/rest/file_metadata.rb new file mode 100644 index 00000000..14d578f9 --- /dev/null +++ b/lib/uploadcare/api/rest/file_metadata.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'uri' + +# REST API endpoint for file metadata operations. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata +class Uploadcare::Api::Rest::FileMetadata + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # Get all metadata for a file. + # + # @param uuid [String] File UUID + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash of metadata key-value pairs + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata/operation/_fileMetadata + def index(uuid:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid) + rest.get(path: "/files/#{encoded_uuid}/metadata/", params: {}, headers: {}, + request_options: request_options) + end + + # Get the value of a specific metadata key. + # + # @param uuid [String] File UUID + # @param key [String] Metadata key + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Metadata value + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata/operation/fileMetadata + def show(uuid:, key:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid) + encoded_key = URI.encode_www_form_component(key) + rest.get(path: "/files/#{encoded_uuid}/metadata/#{encoded_key}/", params: {}, headers: {}, + request_options: request_options) + end + + # Update or create a metadata key for a file. + # + # @param uuid [String] File UUID + # @param key [String] Metadata key + # @param value [String] Metadata value + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Updated value + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata/operation/updateFileMetadataKey + def update(uuid:, key:, value:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid) + encoded_key = URI.encode_www_form_component(key) + rest.put(path: "/files/#{encoded_uuid}/metadata/#{encoded_key}/", params: value.to_json, headers: {}, + request_options: request_options) + end + + # Delete a metadata key for a file. + # + # @param uuid [String] File UUID + # @param key [String] Metadata key to delete + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata/operation/deleteFileMetadata + def delete(uuid:, key:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid) + encoded_key = URI.encode_www_form_component(key) + rest.request(method: :delete, path: "/files/#{encoded_uuid}/metadata/#{encoded_key}/", + params: {}, headers: {}, request_options: request_options) + end +end diff --git a/lib/uploadcare/api/rest/files.rb b/lib/uploadcare/api/rest/files.rb new file mode 100644 index 00000000..892156cc --- /dev/null +++ b/lib/uploadcare/api/rest/files.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +# REST API endpoint for file operations. +# +# Provides methods for listing, retrieving, storing, deleting, and copying files. +# +# @example +# rest = Uploadcare::Api::Rest.new(config: config) +# rest.files.list(params: { limit: 10 }) +# rest.files.info(uuid: "file-uuid") +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File +class Uploadcare::Api::Rest::Files + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # List files with optional filtering and pagination. + # + # @param params [Hash] Query parameters (limit, ordering, etc.) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Paginated file list + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/filesList + def list(params: {}, request_options: {}) + rest.get(path: '/files/', params: params, headers: {}, request_options: request_options) + end + + # Get file information by UUID. + # + # @param uuid [String] The file UUID + # @param params [Hash] Optional parameters (e.g., include: "appdata") + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] File details + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/fileInfo + def info(uuid:, params: {}, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid.to_s) + rest.get(path: "/files/#{encoded_uuid}/", params: params, headers: {}, request_options: request_options) + end + + # Store a file by UUID, making it permanently available. + # + # @param uuid [String] The file UUID + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Updated file details + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/filesStore + def store(uuid:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid.to_s) + rest.put(path: "/files/#{encoded_uuid}/storage/", params: {}, headers: {}, request_options: request_options) + end + + # Delete a file by UUID. + # + # @param uuid [String] The file UUID + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Deleted file details + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/deleteFileStorage + def delete(uuid:, request_options: {}) + encoded_uuid = URI.encode_www_form_component(uuid.to_s) + rest.request(method: :delete, path: "/files/#{encoded_uuid}/storage/", params: {}, headers: {}, + request_options: request_options) + end + + # Batch store files by UUIDs. + # + # @param uuids [Array] List of file UUIDs to store + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with 'result' and 'problems' keys + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/filesStoring + def batch_store(uuids:, request_options: {}) + rest.put(path: '/files/storage/', params: uuids, headers: {}, request_options: request_options) + end + + # Batch delete files by UUIDs. + # + # @param uuids [Array] List of file UUIDs to delete + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with 'result' and 'problems' keys + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/filesDelete + def batch_delete(uuids:, request_options: {}) + rest.request(method: :delete, path: '/files/storage/', params: uuids, headers: {}, + request_options: request_options) + end + + # Copy a file to local storage. + # + # @param source [String] CDN URL or UUID of the file to copy + # @param options [Hash] Optional parameters (:store, :metadata) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with 'type' and 'result' keys + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/createLocalCopy + def local_copy(source:, options: {}, request_options: {}) + params = { source: source }.merge(options) + rest.post(path: '/files/local_copy/', params: params, headers: {}, request_options: request_options) + end + + # Copy a file to remote storage. + # + # @param source [String] CDN URL or UUID of the file to copy + # @param target [String] Name of the custom storage + # @param options [Hash] Optional parameters (:make_public, :pattern) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Hash with 'type' and 'result' keys + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/createRemoteCopy + def remote_copy(source:, target:, options: {}, request_options: {}) + params = { source: source, target: target }.merge(options) + rest.post(path: '/files/remote_copy/', params: params, headers: {}, request_options: request_options) + end +end diff --git a/lib/uploadcare/api/rest/groups.rb b/lib/uploadcare/api/rest/groups.rb new file mode 100644 index 00000000..6956e29f --- /dev/null +++ b/lib/uploadcare/api/rest/groups.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'uri' + +# REST API endpoint for group operations. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group +class Uploadcare::Api::Rest::Groups + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # List groups with optional filtering and pagination. + # + # @param params [Hash] Query parameters (limit, ordering, etc.) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Paginated group list + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group/operation/groupsList + def list(params: {}, request_options: {}) + rest.get(path: '/groups/', params: params, headers: {}, request_options: request_options) + end + + # Get group information by UUID. + # + # @param uuid [String] Group UUID (formatted as UUID~size) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Group details + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group/operation/groupInfo + def info(uuid:, request_options: {}) + encoded_uuid = URI::DEFAULT_PARSER.escape(uuid.to_s, /[^A-Za-z0-9\-._~]/) + rest.get(path: "/groups/#{encoded_uuid}/", params: {}, headers: {}, request_options: request_options) + end + + # Delete a group by UUID. + # + # @param uuid [String] Group UUID (formatted as UUID~size) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group/operation/deleteGroup + def delete(uuid:, request_options: {}) + encoded_uuid = URI::DEFAULT_PARSER.escape(uuid.to_s, /[^A-Za-z0-9\-._~]/) + rest.request(method: :delete, path: "/groups/#{encoded_uuid}/", params: {}, headers: {}, + request_options: request_options) + end +end diff --git a/lib/uploadcare/api/rest/project.rb b/lib/uploadcare/api/rest/project.rb new file mode 100644 index 00000000..670ec7d2 --- /dev/null +++ b/lib/uploadcare/api/rest/project.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# REST API endpoint for project information. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Project +class Uploadcare::Api::Rest::Project + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # Get current project information. + # + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Project details (name, pub_key, autostore_enabled, collaborators) + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Project + def show(request_options: {}) + rest.get(path: '/project/', params: {}, headers: {}, request_options: request_options) + end +end diff --git a/lib/uploadcare/api/rest/video_conversions.rb b/lib/uploadcare/api/rest/video_conversions.rb new file mode 100644 index 00000000..d4f69a1d --- /dev/null +++ b/lib/uploadcare/api/rest/video_conversions.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# REST API endpoint for video conversion operations. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Video +class Uploadcare::Api::Rest::VideoConversions + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # Convert a video to a specified format. + # + # @param paths [Array] Conversion paths (e.g., ["uuid/video/-/format/mp4/-/quality/normal/"]) + # @param options [Hash] Optional parameters (:store) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Conversion details + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Video/operation/convertVideo + def convert(paths:, options: {}, request_options: {}) + params = { paths: paths } + params[:store] = normalize_bool_param(options[:store]) if options.key?(:store) + params.merge!(options.except(:store)) + params.compact! + rest.post(path: '/convert/video/', params: params, headers: {}, request_options: request_options) + end + + # Get video conversion job status. + # + # @param token [String, Integer] Conversion job token + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Job status and result + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion/operation/videoConvertStatus + def status(token:, request_options: {}) + rest.get(path: "/convert/video/status/#{token}/", params: {}, headers: {}, + request_options: request_options) + end + + private + + def normalize_bool_param(value) + normalized = value.is_a?(String) ? value.strip.downcase : value + + case normalized + when true, 1, '1', 'true' then '1' + when false, 0, '0', 'false' then '0' + else value + end + end +end diff --git a/lib/uploadcare/api/rest/webhooks.rb b/lib/uploadcare/api/rest/webhooks.rb new file mode 100644 index 00000000..38fa5ddd --- /dev/null +++ b/lib/uploadcare/api/rest/webhooks.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# REST API endpoint for webhook operations. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook +class Uploadcare::Api::Rest::Webhooks + # @return [Uploadcare::Api::Rest] Parent REST client + attr_reader :rest + + # @param rest [Uploadcare::Api::Rest] Parent REST client + def initialize(rest:) + @rest = rest + end + + # List all project webhooks. + # + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Array of webhook hashes + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook/operation/webhooksList + def list(request_options: {}) + rest.get(path: '/webhooks/', params: {}, headers: {}, request_options: request_options) + end + + # Create a new webhook. + # + # @param options [Hash] Webhook options + # @option options [String] :target_url Webhook target URL (required) + # @option options [String] :event Event type (default: "file.uploaded") + # @option options [Boolean] :is_active Active flag (default: true) + # @option options [String] :signing_secret Signing secret + # @option options [String] :version Webhook payload version + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Created webhook attributes + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook/operation/webhookCreate + def create(options: {}, request_options: {}) + payload = { + target_url: options[:target_url], + event: options[:event] || 'file.uploaded', + is_active: options[:is_active].nil? || options[:is_active] + } + payload.merge!({ signing_secret: options[:signing_secret] }.compact) + payload.merge!({ version: options[:version] }.compact) + + rest.post(path: '/webhooks/', params: payload, headers: {}, request_options: request_options) + end + + # Update a webhook. + # + # @param id [Integer] Webhook ID + # @param options [Hash] Webhook options to update + # @option options [String] :target_url Target URL + # @option options [String] :event Event type + # @option options [Boolean] :is_active Active flag + # @option options [String] :signing_secret Signing secret + # @option options [String] :version Webhook payload version + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Updated webhook attributes + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook/operation/updateWebhook + def update(id:, options: {}, request_options: {}) + rest.put(path: "/webhooks/#{id}/", params: options, headers: {}, request_options: request_options) + end + + # Delete a webhook by target URL. + # + # @param target_url [String] Target URL of the webhook to delete + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook/operation/webhookUnsubscribe + def delete(target_url:, request_options: {}) + payload = { target_url: target_url } + rest.request(method: :delete, path: '/webhooks/unsubscribe/', params: payload, headers: {}, + request_options: request_options) + end +end diff --git a/lib/uploadcare/api/upload.rb b/lib/uploadcare/api/upload.rb new file mode 100644 index 00000000..3c36d563 --- /dev/null +++ b/lib/uploadcare/api/upload.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true + +require 'faraday' +require 'faraday/multipart' +require 'ipaddr' +require 'mime/types' +require 'resolv' +require 'securerandom' +require 'uri' +require 'addressable/uri' + +# Base client for the Uploadcare Upload API. +# +# Provides HTTP methods for upload endpoints using multipart/form-data encoding. +# Authentication is handled via public key in request parameters (no HMAC signing). +# +# Endpoint classes are accessed via lazy-loaded accessors: +# upload = Uploadcare::Api::Upload.new(config: config) +# upload.files.direct(file: file_obj) +# upload.groups.create(files: ["uuid1", "uuid2"]) +# +# @see https://uploadcare.com/api-refs/upload-api/ +class Uploadcare::Api::Upload + include Uploadcare::Internal::ErrorHandler + include Uploadcare::Internal::ThrottleHandler + + # @return [Uploadcare::Configuration] + attr_reader :config + + # @return [Faraday::Connection] + attr_reader :connection + + # Initialize a new Upload API client. + # + # @param config [Uploadcare::Configuration] Configuration object + def initialize(config: Uploadcare.configuration) + @config = config + @memo_mutex = Mutex.new + @connection = Faraday.new(url: config.upload_api_root) do |conn| + conn.request :multipart + conn.request :url_encoded + conn.response :json, content_type: /\bjson$/ + conn.response :raise_error + conn.response :logger, config.logger, bodies: false, headers: false if ENV['DEBUG'] + conn.adapter Faraday.default_adapter + end + end + + # --- Endpoint accessors --- + + # @return [Uploadcare::Api::Upload::Files] File upload operations + def files + memoized(:@files) { Uploadcare::Api::Upload::Files.new(upload: self) } + end + + # @return [Uploadcare::Api::Upload::Groups] Group operations via Upload API + def groups + memoized(:@groups) { Uploadcare::Api::Upload::Groups.new(upload: self) } + end + + # --- HTTP methods --- + + # Make a GET request to the Upload API wrapped in a Result. + # + # @param path [String] API endpoint path + # @param params [Hash] Query parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def get(path:, params: {}, headers: {}, request_options: {}) + Uploadcare::Result.capture do + make_request(:get, path, params, headers, request_options) + end + end + + # Make a POST request to the Upload API wrapped in a Result. + # + # @param path [String] API endpoint path + # @param params [Hash] Request body parameters + # @param headers [Hash] Additional request headers + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] + def post(path:, params: {}, headers: {}, request_options: {}) + Uploadcare::Result.capture do + make_request(:post, path, params, headers, request_options) + end + end + + # Upload binary data to a presigned URL (for multipart uploads). + # + # @param presigned_url [String] Presigned URL from multipart_start + # @param part_data [String, IO] Binary data for this part + # @param max_retries [Integer] Maximum retry attempts (default: 3) + # @param timeout [Integer, nil] Request timeout in seconds + # @param open_timeout [Integer, nil] Open timeout in seconds + # @return [Boolean] true on success + # @raise [Uploadcare::Exception::MultipartUploadError] on failure after retries + def upload_part_to_url(presigned_url, part_data, max_retries: 3, timeout: nil, open_timeout: nil) + uri = validated_presigned_uri(presigned_url) + retries = 0 + begin + conn = Faraday.new(url: "#{uri.scheme}://#{uri.host}") do |f| + f.adapter Faraday.default_adapter + end + + data = part_data.respond_to?(:read) ? part_data.read : part_data + response = upload_part_request( + conn: conn, request_uri: uri.request_uri, data: data, timeout: timeout, open_timeout: open_timeout + ) + raise_multipart_upload_error("Failed to upload part: HTTP #{response.status}") unless success_response?(response) + + true + rescue StandardError => e + retry_part_upload_or_raise!(error: e, retries: retries, max_retries: max_retries) + retries += 1 + retry + end + end + + protected + + def make_request(method, path, params = {}, headers = {}, request_options = {}) + handle_throttling(max_attempts: request_options[:max_throttle_attempts]) do + response = connection.public_send(method, path) do |req| + prepare_request(req, method, path, params, headers, request_options) + end + handle_response(response) + end + rescue Faraday::Error => e + handle_error(e) + end + + def handle_response(response) + return handle_error_response(response) unless success_response?(response) + + parse_success_response(response) + rescue JSON::ParserError => e + handle_json_error(e, response) + end + + private + + def prepare_request(req, method, path, params, headers, request_options) + upcase_method_name = method.to_s.upcase + uri = path + uri = build_request_uri(path, params, upcase_method_name) if upcase_method_name == 'GET' + + prepare_headers(req, upcase_method_name, uri, headers) + prepare_body_or_params(req, upcase_method_name, params) + apply_request_options(req, request_options) + end + + def build_request_uri(path, params, method) + return path unless method == 'GET' && params.is_a?(Hash) && !params.empty? + + uri = Addressable::URI.parse(path) + uri.query_values = params + uri.to_s + end + + def prepare_headers(req, _method, _uri, headers) + req.headers['User-Agent'] ||= Uploadcare::Internal::UserAgent.call(config: config) + req.headers.merge!(headers) + end + + def prepare_body_or_params(req, method, params) + if method == 'GET' + req.params.update(params) unless params.empty? + else + req.body = params unless params.empty? + end + end + + def apply_request_options(req, request_options) + return if request_options.nil? || request_options.empty? + + req.options.timeout = request_options[:timeout] if request_options[:timeout] + req.options.open_timeout = request_options[:open_timeout] if request_options[:open_timeout] + end + + def success_response?(response) + response.status >= 200 && response.status < 300 + end + + def handle_error_response(response) + raise Uploadcare::Exception::UploadError, "Upload API error: #{response.status} #{response.body}" + end + + def parse_success_response(response) + return {} if response.body.nil? || (response.body.is_a?(String) && response.body.strip.empty?) + return response.body if response.body.is_a?(Hash) + + JSON.parse(response.body) + end + + def handle_json_error(error, response) + config.logger&.error("Invalid JSON response: #{error.message}") + success_response?(response) ? {} : response.body + end + + def validated_presigned_uri(url) + uri = URI.parse(url.to_s) + raise ArgumentError, 'presigned_url must use HTTPS' unless uri.is_a?(URI::HTTPS) + raise ArgumentError, 'presigned_url host is required' if uri.host.to_s.empty? + raise ArgumentError, 'presigned_url cannot target localhost' if local_hostname?(uri.host) + raise ArgumentError, 'presigned_url cannot target a private address' if private_host?(uri.host) + + uri + rescue URI::InvalidURIError => e + raise ArgumentError, "Invalid presigned_url: #{e.message}" + end + + def local_hostname?(host) + normalized_host = host.to_s.downcase + normalized_host == 'localhost' || normalized_host.end_with?('.localhost', '.local') + end + + def private_host?(host) + return private_ip?(host) if ip_literal?(host) + + Resolv.getaddresses(host).any? { |address| private_ip?(address) } + rescue Resolv::ResolvError, SocketError + false + end + + def ip_literal?(host) + IPAddr.new(host) + true + rescue IPAddr::InvalidAddressError + false + end + + def private_ip?(address) + ip = IPAddr.new(address) + return true if ip.loopback? + return true if ip.link_local? + + ip.private? + rescue IPAddr::InvalidAddressError + false + end + + def upload_part_request(conn:, request_uri:, data:, timeout:, open_timeout:) + conn.put(request_uri) do |req| + req.headers['Content-Type'] = 'application/octet-stream' + req.options.timeout = timeout if timeout + req.options.open_timeout = open_timeout if open_timeout + req.body = data + end + end + + def retry_part_upload_or_raise!(error:, retries:, max_retries:) + if retries >= max_retries + raise_multipart_upload_error("Failed to upload part after #{max_retries} retries: #{error.message}") + end + + sleep(2**retries) + end + + def raise_multipart_upload_error(message) + raise Uploadcare::Exception::MultipartUploadError, message + end + + def memoized(ivar) + cached = instance_variable_get(ivar) + return cached if cached + + @memo_mutex.synchronize do + instance_variable_get(ivar) || instance_variable_set(ivar, yield) + end + end +end diff --git a/lib/uploadcare/api/upload/files.rb b/lib/uploadcare/api/upload/files.rb new file mode 100644 index 00000000..e9e4ea89 --- /dev/null +++ b/lib/uploadcare/api/upload/files.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +# Upload API endpoint for file upload operations. +# rubocop:disable Metrics/ClassLength +class Uploadcare::Api::Upload::Files + # @return [Uploadcare::Api::Upload] Parent Upload client + attr_reader :upload + + # @param upload [Uploadcare::Api::Upload] Parent Upload client + def initialize(upload:) + @upload = upload + end + + # Upload a file directly (POST /base/). + # + # @param file [File, IO] File object to upload + # @param options [Hash] Upload options (:store, :metadata, :signature, :expire) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Upload response with file UUID + # @raise [ArgumentError] if file is not a valid IO object + # @see https://uploadcare.com/api-refs/upload-api/#operation/baseUpload + def direct(file:, request_options: {}, **options) + Uploadcare::Result.capture do + prepared_file = Uploadcare::Internal::UploadIo.wrap(file) + params = build_upload_params(prepared_file, options) + Uploadcare::Result.unwrap(upload.post(path: 'base/', params: params, request_options: request_options)) + ensure + prepared_file&.close! + end + end + + # Upload multiple files directly (POST /base/). + # + # @param files [Array] Files to upload + # @param options [Hash] Upload options (:store, :metadata) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Upload response hash mapping filenames to UUIDs + # @see https://uploadcare.com/api-refs/upload-api/#operation/baseUpload + def direct_many(files:, request_options: {}, **options) + Uploadcare::Result.capture do + raise ArgumentError, 'files must be an array' unless files.is_a?(Array) + raise ArgumentError, 'files cannot be empty' if files.empty? + + prepared_files = [] + params = Uploadcare::Internal::UploadParamsGenerator.call( + options: options, config: upload.config + ) + files.each do |file| + prepared_file = Uploadcare::Internal::UploadIo.wrap(file) + prepared_files << prepared_file + form_data_for(prepared_file, params, field_index: prepared_files.length - 1) + end + Uploadcare::Result.unwrap(upload.post(path: '/base/', params: params, request_options: request_options)) + ensure + prepared_files&.each(&:close!) + end + end + + # Upload a file from URL (POST /from_url/). + # + # @param source_url [String] URL of the file to upload + # @param options [Hash] Upload options + # @option options [Boolean] :async Return immediately with token (default: false) + # @option options [String, Boolean] :store Whether to store the file + # @option options [Hash] :metadata Custom metadata + # @option options [Integer] :poll_interval Polling interval in seconds (default: 1) + # @option options [Integer] :poll_timeout Max polling time in seconds (default: 300) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Upload response (file info or token) + # @see https://uploadcare.com/api-refs/upload-api/#operation/fromUrlUpload + def from_url(source_url:, request_options: {}, **options) + Uploadcare::Result.capture do + validate_url(source_url) + async_mode = options.fetch(:async, false) + params = build_from_url_params(source_url, options) + response = Uploadcare::Result.unwrap( + upload.post(path: 'from_url/', params: params, request_options: request_options) + ) + if async_mode + response + else + poll_upload_status(token: response['token'], options: options, request_options: request_options) + end + end + end + + # Get upload-from-URL status (GET /from_url/status/). + # + # @param token [String] Upload token from async upload + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Status response + # @see https://uploadcare.com/api-refs/upload-api/#operation/fromUrlUploadStatus + def from_url_status(token:, request_options: {}) + Uploadcare::Result.capture do + raise ArgumentError, 'token cannot be empty' if token.to_s.strip.empty? + + Uploadcare::Result.unwrap( + upload.get(path: 'from_url/status/', params: { token: token }, request_options: request_options) + ) + end + end + + # Start a multipart upload (POST /multipart/start/). + # + # @param filename [String] Original filename + # @param size [Integer] File size in bytes + # @param content_type [String] MIME type + # @param options [Hash] Upload options (:store, :metadata) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Response with UUID and presigned URLs + # @see https://uploadcare.com/api-refs/upload-api/#operation/multipartUploadStart + def multipart_start(filename:, size:, content_type:, request_options: {}, **options) + Uploadcare::Result.capture do + raise ArgumentError, 'filename cannot be empty' if filename.to_s.strip.empty? + raise ArgumentError, 'size must be a positive integer' unless size.is_a?(Integer) && size.positive? + raise ArgumentError, 'content_type cannot be empty' if content_type.to_s.strip.empty? + + params = build_multipart_start_params(filename, size, content_type, options) + Uploadcare::Result.unwrap( + upload.post(path: 'multipart/start/', params: params, request_options: request_options) + ) + end + end + + # Complete a multipart upload (POST /multipart/complete/). + # + # @param uuid [String] Upload UUID from multipart_start + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Final file information + # @see https://uploadcare.com/api-refs/upload-api/#operation/multipartUploadComplete + def multipart_complete(uuid:, request_options: {}) + Uploadcare::Result.capture do + raise ArgumentError, 'uuid cannot be empty' if uuid.to_s.strip.empty? + + params = { + 'UPLOADCARE_PUB_KEY' => upload.config.public_key, + 'uuid' => uuid + } + Uploadcare::Result.unwrap( + upload.post(path: 'multipart/complete/', params: params, request_options: request_options) + ) + end + end + + # Get file info from Upload API (GET /info/). + # + # @param file_id [String] File UUID + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] File information + # @see https://uploadcare.com/api-refs/upload-api/#operation/filesInfo + def info(file_id:, request_options: {}) + Uploadcare::Result.capture do + raise ArgumentError, 'file_id cannot be empty' if file_id.to_s.strip.empty? + + Uploadcare::Result.unwrap( + upload.get(path: 'info/', params: { pub_key: upload.config.public_key, file_id: file_id }, + request_options: request_options) + ) + end + end + + private + + def validate_url(url) + raise ArgumentError, 'URL cannot be empty' if url.to_s.strip.empty? + + uri = URI.parse(url) + raise ArgumentError, 'URL must be HTTP or HTTPS' unless %w[http https].include?(uri.scheme) + rescue URI::InvalidURIError => e + raise ArgumentError, "Invalid URL: #{e.message}" + end + + def build_upload_params(file, options) + params = Uploadcare::Internal::UploadParamsGenerator.call( + options: options, config: upload.config + ) + form_data_for(file, params) + end + + def form_data_for(file, params, field_index: nil) + file_path = file.path + filename = file.respond_to?(:original_filename) ? file.original_filename : ::File.basename(file_path) + mime = MIME::Types.type_for(file.path).first&.content_type || 'application/octet-stream' + + field_name = unique_form_field_name(filename, params, field_index) + params[field_name] = Faraday::Multipart::FilePart.new(file_path, mime, filename) + params + end + + def build_from_url_params(source_url, options) + params = { + 'pub_key' => upload.config.public_key, + 'source_url' => source_url + } + store = store_value(options[:store]) + params['store'] = store unless store.nil? + params['check_URL_duplicates'] = options[:check_URL_duplicates].to_s if options.key?(:check_URL_duplicates) + params['save_URL_duplicates'] = options[:save_URL_duplicates].to_s if options.key?(:save_URL_duplicates) + metadata_params = generate_metadata_params(options[:metadata]) + params.merge!(metadata_params) if metadata_params.any? + params.merge!(signature_params(options)) + params + end + + def build_multipart_start_params(filename, size, content_type, options) + params = { + 'UPLOADCARE_PUB_KEY' => upload.config.public_key, + 'filename' => filename, + 'size' => size.to_s, + 'content_type' => content_type + } + store = store_value(options[:store]) + params['UPLOADCARE_STORE'] = store unless store.nil? + metadata_params = generate_metadata_params(options[:metadata]) + params.merge!(metadata_params) if metadata_params.any? + params.merge!(signature_params(options)) + params + end + + def poll_upload_status(token:, options: {}, request_options: {}) + initial_poll_interval = options.fetch(:poll_interval, 1).to_f + max_poll_interval = options.fetch(:poll_max_interval, 10).to_f + poll_timeout = options.fetch(:poll_timeout, 300) + poll_attempt = 0 + start_time = Time.now + + loop do + status = Uploadcare::Result.unwrap(from_url_status(token: token, request_options: request_options)) + + case status['status'] + when 'success' + return status + when 'error' + raise Uploadcare::Exception::UploadError, "Upload from URL failed: #{status['error']}" + when 'waiting', 'progress' + elapsed = Time.now - start_time + if elapsed > poll_timeout + raise Uploadcare::Exception::UploadTimeoutError, + "Upload from URL polling timed out after #{poll_timeout} seconds" + end + + sleep_duration = next_poll_sleep( + initial: initial_poll_interval, + max_interval: max_poll_interval, + attempt: poll_attempt + ) + sleep(sleep_duration) + poll_attempt += 1 + else + raise Uploadcare::Exception::UnknownStatusError, "Unknown upload status: #{status['status']}" + end + end + end + + def store_value(store) + return nil if store.nil? + + case store + when true then '1' + when false then '0' + else store.to_s + end + end + + def generate_metadata_params(metadata = nil) + return {} if metadata.nil? || !metadata.is_a?(Hash) + + metadata.each_with_object({}) do |(key, value), result| + result["metadata[#{key}]"] = value.to_s + end + end + + def signature_params(options = {}) + return {} if options.nil? + + if options[:signature] + params = { 'signature' => options[:signature] } + params['expire'] = options[:expire].to_s if options[:expire] + return params + end + + return {} unless upload.config.sign_uploads + + result = Uploadcare::Internal::SignatureGenerator.call(config: upload.config) + if result.is_a?(Hash) + sig = result[:signature] || result['signature'] + exp = result[:expire] || result['expire'] + p = {} + p['signature'] = sig if sig + p['expire'] = exp if exp + p + else + { 'signature' => result } + end + end + + def unique_form_field_name(filename, params, field_index) + return filename unless params.key?(filename) + + candidate = "__uploadcare_form_#{field_index || 0}__#{filename}" + suffix = 0 + while params.key?(candidate) + suffix += 1 + candidate = "__uploadcare_form_#{field_index || 0}_#{suffix}__#{filename}" + end + candidate + end + + def next_poll_sleep(initial:, max_interval:, attempt:) + [initial.to_f * (2**attempt), max_interval.to_f].min + end +end +# rubocop:enable Metrics/ClassLength diff --git a/lib/uploadcare/api/upload/groups.rb b/lib/uploadcare/api/upload/groups.rb new file mode 100644 index 00000000..eb14b013 --- /dev/null +++ b/lib/uploadcare/api/upload/groups.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +# Upload API endpoint for group operations. +# +# @see https://uploadcare.com/api-refs/upload-api/#operation/createFilesGroup +class Uploadcare::Api::Upload::Groups + # @return [Uploadcare::Api::Upload] Parent Upload client + attr_reader :upload + + # @param upload [Uploadcare::Api::Upload] Parent Upload client + def initialize(upload:) + @upload = upload + end + + # Create a file group from UUIDs (POST /group/). + # + # @param files [Array] Array of file UUIDs or objects responding to #uuid + # @param options [Hash] Group creation options (:signature, :expire) + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Group information + # @raise [ArgumentError] if files is empty or not an array + # @see https://uploadcare.com/api-refs/upload-api/#operation/createFilesGroup + def create(files:, request_options: {}, **options) + Uploadcare::Result.capture do + raise ArgumentError, 'files must be an array' unless files.is_a?(Array) + raise ArgumentError, 'files cannot be empty' if files.empty? + + params = build_group_params(files, options) + Uploadcare::Result.unwrap( + upload.post(path: 'group/', params: params, headers: {}, request_options: request_options) + ) + end + end + + # Get group info (GET /group/info/). + # + # @param group_id [String] Group UUID + # @param request_options [Hash] Request options + # @return [Uploadcare::Result] Group information + # @raise [ArgumentError] if group_id is empty + # @see https://uploadcare.com/api-refs/upload-api/#operation/filesGroupInfo + def info(group_id:, request_options: {}) + Uploadcare::Result.capture do + raise ArgumentError, 'group_id cannot be empty' if group_id.to_s.strip.empty? + + Uploadcare::Result.unwrap( + upload.get(path: 'group/info/', + params: { pub_key: upload.config.public_key, group_id: group_id }, + request_options: request_options) + ) + end + end + + private + + def build_group_params(files, options) + params = { 'pub_key' => upload.config.public_key } + + files.each_with_index do |file, index| + uuid = file.respond_to?(:uuid) ? file.uuid : file.to_s + params["files[#{index}]"] = uuid + end + + if options[:signature] || options['signature'] + params['signature'] = + (options[:signature] || options['signature']).to_s + end + params['expire'] = (options[:expire] || options['expire']).to_s if options[:expire] || options['expire'] + + params + end +end diff --git a/lib/uploadcare/client.rb b/lib/uploadcare/client.rb new file mode 100644 index 00000000..db501c59 --- /dev/null +++ b/lib/uploadcare/client.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +# Primary entry point for interacting with the Uploadcare API. +# +# Each Client instance owns its own configuration and provides domain-scoped +# accessors for all API operations. Multiple clients can coexist for +# multi-account support. +# +# @example Basic usage +# client = Uploadcare::Client.new(public_key: "pk", secret_key: "sk") +# file = client.files.upload(File.open("photo.jpg"), store: true) +# file = client.files.find(uuid: "file-uuid") +# files = client.files.list(limit: 100) +# +# @example Multi-account +# account_a = Uploadcare::Client.new(public_key: "a", secret_key: "x") +# account_b = Uploadcare::Client.new(public_key: "b", secret_key: "y") +# +# @example Raw API access +# client.api.rest.files.list(params: { limit: 10 }) +# client.api.upload.files.direct(file: file_obj) +class Uploadcare::Client + attr_reader :config + + # Build a client bound to a specific configuration. + # + # @param config [Uploadcare::Configuration, nil] Base configuration to clone + # @param options [Hash] Per-client configuration overrides + def initialize(config: nil, **options) + base_config = config || Uploadcare.configuration + @config = base_config.with(**options) + @memo_mutex = Mutex.new + end + + # Build a new client derived from this client. + # + # @param options [Hash] Configuration overrides + # @return [Uploadcare::Client] + def with(**options) + self.class.new(config: config, **options) + end + + # Access the raw endpoint-parity API. + # + # @return [Uploadcare::Client::Api] + def api + memoized(:@api) { Api.new(config: config) } + end + + # Access file operations and upload helpers. + # + # @return [Uploadcare::Client::FilesAccessor] + def files + memoized(:@files) { FilesAccessor.new(client: self) } + end + + # Access group operations. + # + # @return [Uploadcare::Client::GroupsAccessor] + def groups + memoized(:@groups) { GroupsAccessor.new(client: self) } + end + + # Access upload routing helpers. + # + # @return [Uploadcare::Operations::UploadRouter] + def uploads + memoized(:@uploads) { Uploadcare::Operations::UploadRouter.new(client: self) } + end + + # Access project operations. + # + # @return [Uploadcare::Client::ProjectAccessor] + def project + memoized(:@project) { ProjectAccessor.new(client: self) } + end + + # Access webhook operations. + # + # @return [Uploadcare::Client::WebhooksAccessor] + def webhooks + memoized(:@webhooks) { WebhooksAccessor.new(client: self) } + end + + # Access add-on execution helpers. + # + # @return [Uploadcare::Client::AddonsAccessor] + def addons + memoized(:@addons) { AddonsAccessor.new(client: self) } + end + + # Access file metadata operations. + # + # @return [Uploadcare::Client::FileMetadataAccessor] + def file_metadata + memoized(:@file_metadata) { FileMetadataAccessor.new(client: self) } + end + + # Access conversion helpers. + # + # @return [Uploadcare::Client::ConversionsAccessor] + def conversions + memoized(:@conversions) { ConversionsAccessor.new(client: self) } + end + + # Upload a source through the convenience upload router. + # + # @param source [IO, Array, String] File object, file array, or URL + # @param request_options [Hash] Per-request HTTP options + # @param options [Hash] Upload options + # @yield [Hash] Multipart progress callback + # @return [Uploadcare::Resources::File, Array, Hash] + def upload(source, request_options: {}, **options, &block) + uploads.upload(source, request_options: request_options, **options, &block) + end + + private + + def memoized(ivar) + cached = instance_variable_get(ivar) + return cached if cached + + @memo_mutex.synchronize do + instance_variable_get(ivar) || instance_variable_set(ivar, yield) + end + end +end diff --git a/lib/uploadcare/client/addons_accessor.rb b/lib/uploadcare/client/addons_accessor.rb new file mode 100644 index 00000000..e22ee465 --- /dev/null +++ b/lib/uploadcare/client/addons_accessor.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# High-level add-on execution helpers scoped to a client instance. +class Uploadcare::Client::AddonsAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param uuid [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def aws_rekognition_detect_labels(uuid:, request_options: {}) + Uploadcare::Resources::AddonExecution.aws_rekognition_detect_labels( + uuid: uuid, client: client, request_options: request_options + ) + end + + # @param request_id [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def aws_rekognition_detect_labels_status(request_id:, request_options: {}) + Uploadcare::Resources::AddonExecution.aws_rekognition_detect_labels_status( + request_id: request_id, client: client, request_options: request_options + ) + end + + # @param uuid [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def aws_rekognition_detect_moderation_labels(uuid:, request_options: {}) + Uploadcare::Resources::AddonExecution.aws_rekognition_detect_moderation_labels( + uuid: uuid, client: client, request_options: request_options + ) + end + + # @param request_id [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def aws_rekognition_detect_moderation_labels_status(request_id:, request_options: {}) + Uploadcare::Resources::AddonExecution.aws_rekognition_detect_moderation_labels_status( + request_id: request_id, client: client, request_options: request_options + ) + end + + # @param uuid [String] + # @param params [Hash] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def uc_clamav_virus_scan(uuid:, params: {}, request_options: {}) + Uploadcare::Resources::AddonExecution.uc_clamav_virus_scan( + uuid: uuid, params: params, client: client, request_options: request_options + ) + end + + # @param request_id [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def uc_clamav_virus_scan_status(request_id:, request_options: {}) + Uploadcare::Resources::AddonExecution.uc_clamav_virus_scan_status( + request_id: request_id, client: client, request_options: request_options + ) + end + + # @param uuid [String] + # @param params [Hash] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def remove_bg(uuid:, params: {}, request_options: {}) + Uploadcare::Resources::AddonExecution.remove_bg( + uuid: uuid, params: params, client: client, request_options: request_options + ) + end + + # @param request_id [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::AddonExecution] + def remove_bg_status(request_id:, request_options: {}) + Uploadcare::Resources::AddonExecution.remove_bg_status( + request_id: request_id, client: client, request_options: request_options + ) + end +end diff --git a/lib/uploadcare/client/addons_client.rb b/lib/uploadcare/client/addons_client.rb deleted file mode 100644 index c60c7bef..00000000 --- a/lib/uploadcare/client/addons_client.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # API client for handling uploaded files - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons - class AddonsClient < RestClient - # Execute ClamAV virus checking Add-On for a given target. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/ucClamavVirusScanExecute - def uc_clamav_virus_scan(uuid, params = {}) - content = { target: uuid, params: params }.to_json - post(uri: '/addons/uc_clamav_virus_scan/execute/', content: content) - end - - # Check the status of an Add-On execution request that had been started using the Execute Add-On operation. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/ucClamavVirusScanExecutionStatus - def uc_clamav_virus_scan_status(uuid) - get(uri: "/addons/uc_clamav_virus_scan/execute/status/#{query_params(uuid)}") - end - - # Execute AWS Rekognition Add-On for a given target to detect labels in an image. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/awsRekognitionExecute - def ws_rekognition_detect_labels(uuid) - content = { target: uuid }.to_json - post(uri: '/addons/aws_rekognition_detect_labels/execute/', content: content) - end - - # Check the status of an Add-On execution request that had been started using the Execute Add-On operation. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/awsRekognitionExecutionStatus - def ws_rekognition_detect_labels_status(uuid) - get(uri: "/addons/aws_rekognition_detect_labels/execute/status/#{query_params(uuid)}") - end - - # Execute remove.bg background image removal Add-On for a given target. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/removeBgExecute - def remove_bg(uuid, params = {}) - content = { target: uuid, params: params }.to_json - post(uri: '/addons/remove_bg/execute/', content: content) - end - - # Check the status of an Add-On execution request that had been started using the Execute Add-On operation. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/removeBgExecutionStatus - def remove_bg_status(uuid) - get(uri: "/addons/remove_bg/execute/status/#{query_params(uuid)}") - end - - # Execute AWS Rekognition Moderation Add-On for a given target to detect labels in an image. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionDetectModerationLabelsExecute - def ws_rekognition_detect_moderation_labels(uuid) - content = { target: uuid }.to_json - post(uri: '/addons/aws_rekognition_detect_moderation_labels/execute/', content: content) - end - - # Check the status of an Add-On execution request that had been started using the Execute Add-On operation. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionDetectModerationLabelsExecutionStatus - def ws_rekognition_detect_moderation_labels_status(uuid) - get(uri: "/addons/aws_rekognition_detect_moderation_labels/execute/status/#{query_params(uuid)}") - end - - private - - def query_params(uuid) - "?#{URI.encode_www_form(request_id: uuid)}" - end - end - end -end diff --git a/lib/uploadcare/client/api.rb b/lib/uploadcare/client/api.rb new file mode 100644 index 00000000..44d1afed --- /dev/null +++ b/lib/uploadcare/client/api.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Access to the endpoint-parity REST and Upload API clients for one configuration. +class Uploadcare::Client::Api + attr_reader :config + + # @param config [Uploadcare::Configuration] + def initialize(config:) + @config = config + @memo_mutex = Mutex.new + end + + # @return [Uploadcare::Api::Rest] + def rest + memoized(:@rest) { Uploadcare::Api::Rest.new(config: config) } + end + + # @return [Uploadcare::Api::Upload] + def upload + memoized(:@upload) { Uploadcare::Api::Upload.new(config: config) } + end + + private + + def memoized(ivar) + cached = instance_variable_get(ivar) + return cached if cached + + @memo_mutex.synchronize do + instance_variable_get(ivar) || instance_variable_set(ivar, yield) + end + end +end diff --git a/lib/uploadcare/client/conversion/base_conversion_client.rb b/lib/uploadcare/client/conversion/base_conversion_client.rb deleted file mode 100644 index 8946bb33..00000000 --- a/lib/uploadcare/client/conversion/base_conversion_client.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -require_relative '../rest_client' -require 'exception/conversion_error' - -module Uploadcare - module Client - module Conversion - # This is a base client for conversion operations - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion - class BaseConversionClient < RestClient - API_VERSION_HEADER_VALUE = 'application/vnd.uploadcare-v0.7+json' - - def headers - { - 'Content-Type': 'application/json', - Accept: API_VERSION_HEADER_VALUE, - 'User-Agent': Uploadcare::Param::UserAgent.call - } - end - - private - - def send_convert_request(arr, options, url_builder_class) - body = build_body_for_many(arr, options, url_builder_class) - post(uri: convert_uri, content: body) - end - - def success(response) - body = response.body.to_s - extract_result(body) - end - - def extract_result(response_body) - return if response_body.empty? - - parsed_body = JSON.parse(response_body, symbolize_names: true) - errors = parsed_body[:error] || parsed_body[:problems] - return Dry::Monads::Result::Failure.call(errors) unless errors.nil? || errors.empty? - - Dry::Monads::Result::Success.call(parsed_body) - end - - # Prepares body for convert_many method - def build_body_for_many(arr, options, url_builder_class) - { - paths: arr.map do |params| - url_builder_class.call( - **build_paths_body(params) - ) - end, - store: options[:store], - save_in_group: options[:save_in_group] - }.compact.to_json - end - end - end - end -end diff --git a/lib/uploadcare/client/conversion/document_conversion_client.rb b/lib/uploadcare/client/conversion/document_conversion_client.rb deleted file mode 100644 index 07ff6d45..00000000 --- a/lib/uploadcare/client/conversion/document_conversion_client.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'client/conversion/base_conversion_client' -require 'param/conversion/document/processing_job_url_builder' - -module Uploadcare - module Client - module Conversion - # This is client for document conversion - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/documentConvert - class DocumentConversionClient < BaseConversionClient - def convert_many( - arr, - options = {}, - url_builder_class = Param::Conversion::Document::ProcessingJobUrlBuilder - ) - send_convert_request(arr, options, url_builder_class) - end - - def get_conversion_status(token) - get(uri: "/convert/document/status/#{token}/") - end - - def document_info(uuid) - get(uri: "/convert/document/#{uuid}/") - end - - private - - def convert_uri - '/convert/document/' - end - - def build_paths_body(params) - { - uuid: params[:uuid], - format: params[:format], - page: params[:page] - }.compact - end - end - end - end -end diff --git a/lib/uploadcare/client/conversion/video_conversion_client.rb b/lib/uploadcare/client/conversion/video_conversion_client.rb deleted file mode 100644 index cb11a367..00000000 --- a/lib/uploadcare/client/conversion/video_conversion_client.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'client/conversion/base_conversion_client' -require 'param/conversion/video/processing_job_url_builder' -require 'exception/conversion_error' - -module Uploadcare - module Client - module Conversion - # This is client for video conversion - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/videoConvert - class VideoConversionClient < BaseConversionClient - def convert_many( - params, - options = {}, - url_builder_class = Param::Conversion::Video::ProcessingJobUrlBuilder - ) - video_params = params.is_a?(Hash) ? [params] : params - send_convert_request(video_params, options, url_builder_class) - end - - def get_conversion_status(token) - get(uri: "/convert/video/status/#{token}/") - end - - private - - def convert_uri - '/convert/video/' - end - - def build_paths_body(params) - { - uuid: params[:uuid], - quality: params[:quality], - format: params[:format], - size: params[:size], - cut: params[:cut], - thumbs: params[:thumbs] - }.compact - end - end - end - end -end diff --git a/lib/uploadcare/client/conversions_accessor.rb b/lib/uploadcare/client/conversions_accessor.rb new file mode 100644 index 00000000..34f95e4d --- /dev/null +++ b/lib/uploadcare/client/conversions_accessor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Entry point for document and video conversion helpers on a client. +class Uploadcare::Client::ConversionsAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + @memo_mutex = Mutex.new + end + + # @return [Uploadcare::Client::DocumentConversionsAccessor] + def documents + memoized(:@documents) { Uploadcare::Client::DocumentConversionsAccessor.new(client: client) } + end + + # @return [Uploadcare::Client::VideoConversionsAccessor] + def videos + memoized(:@videos) { Uploadcare::Client::VideoConversionsAccessor.new(client: client) } + end + + private + + def memoized(ivar) + cached = instance_variable_get(ivar) + return cached if cached + + @memo_mutex.synchronize do + instance_variable_get(ivar) || instance_variable_set(ivar, yield) + end + end +end diff --git a/lib/uploadcare/client/document_conversions_accessor.rb b/lib/uploadcare/client/document_conversions_accessor.rb new file mode 100644 index 00000000..736bd066 --- /dev/null +++ b/lib/uploadcare/client/document_conversions_accessor.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# High-level document conversion helpers scoped to a client instance. +class Uploadcare::Client::DocumentConversionsAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param uuid [String] + # @param format [String, Symbol] + # @param options [Hash] + # @param request_options [Hash] + # @return [Hash] + def convert(uuid:, format:, options: {}, request_options: {}) + Uploadcare::Resources::DocumentConversion.convert_document( + params: { uuid: uuid, format: format }, options: options, client: client, + request_options: request_options + ) + end + + # @param token [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::DocumentConversion] + def status(token:, request_options: {}) + Uploadcare::Resources::DocumentConversion.status( + token: token, client: client, request_options: request_options + ) + end + + # @param uuid [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::DocumentConversion] + def info(uuid:, request_options: {}) + Uploadcare::Resources::DocumentConversion.info_for( + uuid: uuid, client: client, request_options: request_options + ) + end +end diff --git a/lib/uploadcare/client/file_client.rb b/lib/uploadcare/client/file_client.rb deleted file mode 100644 index 69f14bf0..00000000 --- a/lib/uploadcare/client/file_client.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # API client for handling single files - # @see https://uploadcare.com/docs/api_reference/rest/accessing_files/ - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File - class FileClient < RestClient - # Gets list of files without pagination fields - def index - response = get(uri: '/files/') - response.fmap { |i| i[:results] } - end - - # Acquire file info - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/fileInfo - def info(uuid, params = {}) - get(uri: "/files/#{uuid}/", params: params) - end - alias file info - - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/createLocalCopy - def local_copy(options = {}) - body = options.compact.to_json - post(uri: '/files/local_copy/', content: body) - end - - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/createRemoteCopy - def remote_copy(options = {}) - body = options.compact.to_json - post(uri: '/files/remote_copy/', content: body) - end - - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/deleteFileStorage - def delete(uuid) - request(method: 'DELETE', uri: "/files/#{uuid}/storage/") - end - - # Store a single file, preventing it from being deleted in 2 weeks - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/storeFile - def store(uuid) - put(uri: "/files/#{uuid}/storage/") - end - end - end -end diff --git a/lib/uploadcare/client/file_list_client.rb b/lib/uploadcare/client/file_list_client.rb deleted file mode 100644 index 9e22f9bd..00000000 --- a/lib/uploadcare/client/file_list_client.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # API client for handling file lists - class FileListClient < RestClient - # Returns a pagination json of files stored in project - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/filesList - # - # valid options: - # removed: [true|false] - # stored: [true|false] - # limit: (1..1000) - # ordering: ["datetime_uploaded"|"-datetime_uploaded"] - # from: number of files skipped - def file_list(options = {}) - query = options.empty? ? '' : "?#{URI.encode_www_form(options)}" - get(uri: "/files/#{query}") - end - - # Make a set of files "stored". This will prevent them from being deleted automatically - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/filesStoring - # uuids: Array - def batch_store(uuids) - body = uuids.to_json - put(uri: '/files/storage/', content: body) - end - - alias request_delete delete - - # Delete several files by list of uids - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/filesDelete - # uuids: Array - def batch_delete(uuids) - body = uuids.to_json - request_delete(uri: '/files/storage/', content: body) - end - - alias store_files batch_store - alias delete_files batch_delete - alias list file_list - end - end -end diff --git a/lib/uploadcare/client/file_metadata_accessor.rb b/lib/uploadcare/client/file_metadata_accessor.rb new file mode 100644 index 00000000..d040f33b --- /dev/null +++ b/lib/uploadcare/client/file_metadata_accessor.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# File metadata operations scoped to a client instance. +class Uploadcare::Client::FileMetadataAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param uuid [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::FileMetadata] + def index(uuid:, request_options: {}) + Uploadcare::Resources::FileMetadata.index(uuid: uuid, client: client, request_options: request_options) + end + + # @param uuid [String] + # @param key [String] + # @param request_options [Hash] + # @return [String, nil] + def show(uuid:, key:, request_options: {}) + Uploadcare::Resources::FileMetadata.show(uuid: uuid, key: key, client: client, + request_options: request_options) + end + + # @param uuid [String] + # @param key [String] + # @param value [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::FileMetadata] + def update(uuid:, key:, value:, request_options: {}) + Uploadcare::Resources::FileMetadata.update(uuid: uuid, key: key, value: value, client: client, + request_options: request_options) + end + + # @param uuid [String] + # @param key [String] + # @param request_options [Hash] + # @return [nil] + def delete(uuid:, key:, request_options: {}) + Uploadcare::Resources::FileMetadata.delete(uuid: uuid, key: key, client: client, + request_options: request_options) + end +end diff --git a/lib/uploadcare/client/file_metadata_client.rb b/lib/uploadcare/client/file_metadata_client.rb deleted file mode 100644 index f445d61b..00000000 --- a/lib/uploadcare/client/file_metadata_client.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # API client for handling single metadata_files - # @see https://uploadcare.com/docs/file-metadata/ - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata - class FileMetadataClient < RestClient - # Get file's metadata keys and values - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/fileMetadata - def index(uuid) - get(uri: "/files/#{uuid}/metadata/") - end - - # Get the value of a single metadata key. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/fileMetadataKey - def show(uuid, key) - get(uri: "/files/#{uuid}/metadata/#{key}/") - end - - # Update the value of a single metadata key. If the key does not exist, it will be created. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/updateFileMetadataKey - def update(uuid, key, value) - put(uri: "/files/#{uuid}/metadata/#{key}/", content: value.to_json) - end - - # Delete a file's metadata key. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/deleteFileMetadataKey - def delete(uuid, key) - request(method: 'DELETE', uri: "/files/#{uuid}/metadata/#{key}/") - end - end - end -end diff --git a/lib/uploadcare/client/files_accessor.rb b/lib/uploadcare/client/files_accessor.rb new file mode 100644 index 00000000..9a82b08a --- /dev/null +++ b/lib/uploadcare/client/files_accessor.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +# High-level file operations scoped to a client instance. +class Uploadcare::Client::FilesAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param uuid [String] + # @param params [Hash] + # @param request_options [Hash] + # @return [Uploadcare::Resources::File] + def find(uuid:, params: {}, request_options: {}) + Uploadcare::Resources::File.find( + uuid: uuid, params: params, client: client, request_options: request_options + ) + end + + # @param request_options [Hash] + # @param options [Hash] + # @return [Uploadcare::Collections::Paginated] + def list(request_options: {}, **options) + Uploadcare::Resources::File.list( + options: options, client: client, request_options: request_options + ) + end + + # @param source [IO, Array, String] + # @param request_options [Hash] + # @param options [Hash] + # @yield [Hash] + # @return [Uploadcare::Resources::File, Array, Hash] + def upload(source, request_options: {}, **options, &block) + client.uploads.upload(source, request_options: request_options, **options, &block) + end + + # @param url [String] + # @param request_options [Hash] + # @param options [Hash] + # @return [Uploadcare::Resources::File, Hash] + def upload_from_url(url, request_options: {}, **options) + client.uploads.upload_from_url(url: url, request_options: request_options, **options) + end + + # @param uuids [Array] + # @param request_options [Hash] + # @return [Uploadcare::BatchResult] + def batch_store(uuids:, request_options: {}) + Uploadcare::Resources::File.batch_store(uuids: uuids, client: client, request_options: request_options) + end + + # @param uuids [Array] + # @param request_options [Hash] + # @return [Uploadcare::BatchResult] + def batch_delete(uuids:, request_options: {}) + Uploadcare::Resources::File.batch_delete(uuids: uuids, client: client, request_options: request_options) + end + + # @param source [String] + # @param options [Hash] + # @param request_options [Hash] + # @return [Uploadcare::Resources::File] + def copy_to_local(source:, options: {}, request_options: {}) + Uploadcare::Resources::File.local_copy( + source: source, options: options, client: client, request_options: request_options + ) + end + + # @param source [String] + # @param target [String] + # @param options [Hash] + # @param request_options [Hash] + # @return [Hash] + def copy_to_remote(source:, target:, options: {}, request_options: {}) + Uploadcare::Resources::File.remote_copy( + source: source, target: target, options: options, client: client, request_options: request_options + ) + end +end diff --git a/lib/uploadcare/client/group_client.rb b/lib/uploadcare/client/group_client.rb deleted file mode 100644 index edf2218f..00000000 --- a/lib/uploadcare/client/group_client.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require_relative 'upload_client' - -module Uploadcare - module Client - # Groups serve a purpose of better organizing files in your Uploadcare projects. - # You can create one from a set of files by using their UUIDs. - # @see https://uploadcare.com/docs/api_reference/upload/groups/ - class GroupClient < UploadClient - # Create files group from a set of files by using their UUIDs. - # @see https://uploadcare.com/api-refs/upload-api/#operation/createFilesGroup - def create(file_list, options = {}) - body_hash = group_body_hash(file_list, options) - body = HTTP::FormData::Multipart.new(body_hash) - post(path: 'group/', - headers: { 'Content-Type': body.content_type }, - body: body) - end - - # Get group info - # @see https://uploadcare.com/api-refs/upload-api/#operation/filesGroupInfo - def info(group_id) - get(path: 'group/info/', params: { pub_key: Uploadcare.config.public_key, group_id: group_id }) - end - - private - - def file_params(file_ids) - ids = (0...file_ids.size).map { |i| "files[#{i}]" } - ids.zip(file_ids).to_h - end - - def group_body_hash(file_list, options = {}) - { pub_key: Uploadcare.config.public_key }.merge(file_params(parse_file_list(file_list))).merge(options) - end - - # API accepts only list of ids, but some users may want to upload list of files - # @return [Array] of [String] - def parse_file_list(file_list) - file_list.map { |file| file.methods.include?(:uuid) ? file.uuid : file } - end - end - end -end diff --git a/lib/uploadcare/client/groups_accessor.rb b/lib/uploadcare/client/groups_accessor.rb new file mode 100644 index 00000000..0984364f --- /dev/null +++ b/lib/uploadcare/client/groups_accessor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# High-level group operations scoped to a client instance. +class Uploadcare::Client::GroupsAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param uuids [Array] + # @param request_options [Hash] + # @param options [Hash] + # @return [Uploadcare::Resources::Group] + def create(uuids:, request_options: {}, **options) + Uploadcare::Resources::Group.create( + uuids: uuids, client: client, request_options: request_options, **options + ) + end + + # @param group_id [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::Group] + def find(group_id:, request_options: {}) + Uploadcare::Resources::Group.find(group_id: group_id, client: client, request_options: request_options) + end + + # @param request_options [Hash] + # @param params [Hash] + # @return [Uploadcare::Collections::Paginated] + def list(request_options: {}, **params) + Uploadcare::Resources::Group.list(params: params, client: client, request_options: request_options) + end +end diff --git a/lib/uploadcare/client/multipart_upload/chunks_client.rb b/lib/uploadcare/client/multipart_upload/chunks_client.rb deleted file mode 100644 index 452e80ba..00000000 --- a/lib/uploadcare/client/multipart_upload/chunks_client.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'parallel' -require 'dry/monads' -require 'api_struct' - -module Uploadcare - module Client - module MultipartUpload - # This class splits file into chunks of set chunk_size - # and uploads them into cloud storage. - # Used for multipart uploads - # @see https://uploadcare.com/api-refs/upload-api/#tag/Upload/paths/https:~1~1uploadcare.s3-accelerate.amazonaws.com~1%3C%3Cpresigned-url%3E/put - class ChunksClient < ApiStruct::Client - CHUNK_SIZE = 5_242_880 - - # In multiple threads, split file into chunks and upload those chunks into respective Amazon links - # @param object [File] - # @param links [Array] of strings; by default list of Amazon storage urls - def self.upload_chunks(object, links) - Parallel.each(0...links.count, in_threads: Uploadcare.config.upload_threads) do |link_id| - offset = link_id * CHUNK_SIZE - chunk = File.read(object, CHUNK_SIZE, offset) - new.upload_chunk(chunk, links[link_id]) - next unless block_given? - - yield( - chunk_size: CHUNK_SIZE, - object: object, - offset: offset, - link_id: link_id, - links: links, - links_count: links.count - ) - end - end - - def api_root - '' - end - - def headers - {} - end - - def upload_chunk(chunk, link) - put(path: link, body: chunk, headers: { 'Content-Type': 'application/octet-stream' }) - end - - private - - def default_params - {} - end - end - end - end -end diff --git a/lib/uploadcare/client/multipart_upload_client.rb b/lib/uploadcare/client/multipart_upload_client.rb deleted file mode 100644 index f5792c7e..00000000 --- a/lib/uploadcare/client/multipart_upload_client.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require 'client/multipart_upload/chunks_client' -require_relative 'upload_client' - -module Uploadcare - module Client - # Client for multipart uploads - # - # @see https://uploadcare.com/api-refs/upload-api/#tag/Upload - class MultipartUploaderClient < UploadClient - include MultipartUpload - - # Upload a big file by splitting it into parts and sending those parts into assigned buckets - # object should be File - def upload(object, options = {}, &block) - response = upload_start(object, options) - return response unless response.success[:parts] && response.success[:uuid] - - links = response.success[:parts] - uuid = response.success[:uuid] - ChunksClient.upload_chunks(object, links, &block) - upload_complete(uuid) - end - - # Asks Uploadcare server to create a number of storage bin for uploads - def upload_start(object, options = {}) - body = HTTP::FormData::Multipart.new( - Param::Upload::UploadParamsGenerator.call(options).merge(form_data_for(object)) - ) - post(path: 'multipart/start/', - headers: { 'Content-Type': body.content_type }, - body: body) - end - - # When every chunk is uploaded, ask Uploadcare server to finish the upload - def upload_complete(uuid) - body = HTTP::FormData::Multipart.new( - { - UPLOADCARE_PUB_KEY: Uploadcare.config.public_key, - uuid: uuid - } - ) - post(path: 'multipart/complete/', body: body, headers: { 'Content-Type': body.content_type }) - end - - private - - def form_data_for(file) - form_data_file = super - { - filename: form_data_file.filename, - size: form_data_file.size, - content_type: form_data_file.content_type - } - end - - alias api_struct_post post - def post(**args) - handle_throttling { api_struct_post(**args) } - end - end - end -end diff --git a/lib/uploadcare/client/project_accessor.rb b/lib/uploadcare/client/project_accessor.rb new file mode 100644 index 00000000..8bb8bbfd --- /dev/null +++ b/lib/uploadcare/client/project_accessor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Project endpoint wrapper scoped to a client instance. +class Uploadcare::Client::ProjectAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param request_options [Hash] + # @return [Uploadcare::Resources::Project] + def current(request_options: {}) + Uploadcare::Resources::Project.current(client: client, request_options: request_options) + end +end diff --git a/lib/uploadcare/client/project_client.rb b/lib/uploadcare/client/project_client.rb deleted file mode 100644 index 757c18ae..00000000 --- a/lib/uploadcare/client/project_client.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # API client for getting project info - # @see https://uploadcare.com/docs/api_reference/rest/handling_projects/ - class ProjectClient < RestClient - # get information about current project - # current project is determined by public and secret key combination - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Project - def show - get(uri: '/project/') - end - - alias project show - end - end -end diff --git a/lib/uploadcare/client/rest_client.rb b/lib/uploadcare/client/rest_client.rb deleted file mode 100644 index a7ae8035..00000000 --- a/lib/uploadcare/client/rest_client.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -require 'dry/monads' -require 'api_struct' -require 'uploadcare/concern/error_handler' -require 'uploadcare/concern/throttle_handler' -require 'param/authentication_header' - -module Uploadcare - module Client - # @abstract - # General client for signed REST requests - class RestClient < ApiStruct::Client - include Uploadcare::Concerns::ErrorHandler - include Uploadcare::Concerns::ThrottleHandler - include Exception - - alias api_struct_delete delete - alias api_struct_get get - alias api_struct_post post - alias api_struct_put put - - # Send request with authentication header - # - # Handle throttling as well - def request(uri:, method: 'GET', **options) - request_headers = Param::AuthenticationHeader.call(method: method.upcase, uri: uri, - content_type: headers[:'Content-Type'], **options) - handle_throttling do - send("api_struct_#{method.downcase}", - path: remove_trailing_slash(uri), - headers: request_headers, - body: options[:content], - params: options[:params]) - end - end - - def get(options = {}) - request(method: 'GET', **options) - end - - def post(options = {}) - request(method: 'POST', **options) - end - - def put(options = {}) - request(method: 'PUT', **options) - end - - def delete(options = {}) - request(method: 'DELETE', **options) - end - - def api_root - Uploadcare.config.rest_api_root - end - - def headers - { - 'Content-Type': 'application/json', - Accept: 'application/vnd.uploadcare-v0.7+json', - 'User-Agent': Uploadcare::Param::UserAgent.call - } - end - - private - - def remove_trailing_slash(str) - str.gsub(%r{^/}, '') - end - - def default_params - {} - end - end - end -end diff --git a/lib/uploadcare/client/rest_group_client.rb b/lib/uploadcare/client/rest_group_client.rb deleted file mode 100644 index c407a0f2..00000000 --- a/lib/uploadcare/client/rest_group_client.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group/paths/~1groups~1%3Cuuid%3E~1storage~1/put - class RestGroupClient < RestClient - # store all files in a group - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/storeFile - def store(uuid) - files = info(uuid).success[:files].compact - client = ::Uploadcare::Client::FileClient.new - files.each_slice(Uploadcare.config.file_chunk_size) do |file_chunk| - file_chunk.each do |file| - client.store(file[:uuid]) - end - end - - Dry::Monads::Result::Success.call(nil) - end - - # Get a file group by its ID. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/groupInfo - def info(uuid) - get(uri: "/groups/#{uuid}/") - end - - # return paginated list of groups - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/groupsList - def list(options = {}) - query = options.empty? ? '' : "?#{URI.encode_www_form(options)}" - get(uri: "/groups/#{query}") - end - - # Delete a file group by its ID. - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/deleteGroup - def delete(uuid) - request(method: 'DELETE', uri: "/groups/#{uuid}/") - end - end - end -end diff --git a/lib/uploadcare/client/upload_client.rb b/lib/uploadcare/client/upload_client.rb deleted file mode 100644 index b294ed01..00000000 --- a/lib/uploadcare/client/upload_client.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'dry/monads' -require 'api_struct' -require 'param/user_agent' -require 'uploadcare/concern/error_handler' -require 'uploadcare/concern/throttle_handler' -require 'mimemagic' - -module Uploadcare - module Client - # @abstract - # - # Headers and helper methods for clients working with upload API - # @see https://uploadcare.com/docs/api_reference/upload/ - class UploadClient < ApiStruct::Client - include Concerns::ErrorHandler - include Concerns::ThrottleHandler - include Exception - - def api_root - Uploadcare.config.upload_api_root - end - - def headers - { - 'User-Agent': Uploadcare::Param::UserAgent.call - } - end - - private - - def form_data_for(file) - filename = file.original_filename if file.respond_to?(:original_filename) - mime_type = MimeMagic.by_magic(file)&.type - mime_type = file.content_type if mime_type.nil? && file.respond_to?(:content_type) - options = { filename: filename, content_type: mime_type }.compact - HTTP::FormData::File.new(file, options) - end - - def default_params - {} - end - end - end -end diff --git a/lib/uploadcare/client/uploader_client.rb b/lib/uploadcare/client/uploader_client.rb deleted file mode 100644 index b0424c33..00000000 --- a/lib/uploadcare/client/uploader_client.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -require_relative 'upload_client' -require 'retries' -require 'param/upload/upload_params_generator' -require 'param/upload/signature_generator' - -module Uploadcare - module Client - # This is client for general uploads - # - # @see https://uploadcare.com/api-refs/upload-api/#tag/Upload - class UploaderClient < UploadClient - # @see https://uploadcare.com/api-refs/upload-api/#operation/baseUpload - - def upload_many(arr, options = {}) - body = upload_many_body(arr, options) - post(path: 'base/', - headers: { 'Content-Type': body.content_type }, - body: body) - end - - # syntactic sugar for upload_many - # There is actual upload method for one file, but it is redundant - - def upload(file, options = {}) - upload_many([file], options) - end - - # Upload files from url - # @see https://uploadcare.com/api-refs/upload-api/#operation/fromURLUpload - # options: - # - check_URL_duplicates - # - filename - # - save_URL_duplicates - # - async - returns upload token instead of upload data - # - metadata - file metadata, hash - def upload_from_url(url, options = {}) - body = upload_from_url_body(url, options) - token_response = post(path: 'from_url/', headers: { 'Content-Type': body.content_type }, body: body) - return token_response if options[:async] - - uploaded_response = poll_upload_response(token_response.success[:token]) - return uploaded_response if uploaded_response.success[:status] == 'error' - - Dry::Monads::Result::Success.call(files: [uploaded_response.success]) - end - - # Check upload status - # - # @see https://uploadcare.com/api-refs/upload-api/#operation/fromURLUploadStatus - def get_upload_from_url_status(token) - query_params = { token: token } - get(path: 'from_url/status/', params: query_params) - end - - # Get information about an uploaded file - # Secret key not needed - # - # https://uploadcare.com/api-refs/upload-api/#tag/Upload/operation/fileUploadInfo - def file_info(uuid) - query_params = { - file_id: uuid, - pub_key: Uploadcare.config.public_key - } - get(path: 'info/', params: query_params) - end - - private - - alias api_struct_post post - def post(args = {}) - handle_throttling { api_struct_post(**args) } - end - - def poll_upload_response(token) - with_retries(max_tries: Uploadcare.config.max_request_tries, - base_sleep_seconds: Uploadcare.config.base_request_sleep, - max_sleep_seconds: Uploadcare.config.max_request_sleep, - rescue: RetryError) do - response = get_upload_from_url_status(token) - handle_polling_response(response) - end - end - - def handle_polling_response(response) - case response.success[:status] - when 'error' - raise RequestError, response.success[:error] - when 'progress', 'waiting', 'unknown' - raise RetryError, response.success[:error] || 'Upload is taking longer than expected. Try increasing the max_request_tries config if you know your file uploads will take more time.' # rubocop:disable Layout/LineLength - end - - response - end - - # Prepares body for upload_many method - def upload_many_body(arr, options = {}) - files_formdata = arr.to_h do |file| - [HTTP::FormData::File.new(file).filename, - form_data_for(file)] - end - HTTP::FormData::Multipart.new( - Param::Upload::UploadParamsGenerator.call(options).merge(files_formdata) - ) - end - - # Prepare upload_from_url initial request body - def upload_from_url_body(url, options = {}) - opts = { - 'pub_key' => Uploadcare.config.public_key, - 'source_url' => url, - 'store' => store_value(options[:store]) - } - opts.merge!(Param::Upload::SignatureGenerator.call) if Uploadcare.config.sign_uploads - HTTP::FormData::Multipart.new(options.merge(opts)) - end - - def store_value(store) - case store - when true, '1', 1 then '1' - when false, '0', 0 then '0' - else 'auto' - end - end - end - end -end diff --git a/lib/uploadcare/client/video_conversions_accessor.rb b/lib/uploadcare/client/video_conversions_accessor.rb new file mode 100644 index 00000000..a76f18d0 --- /dev/null +++ b/lib/uploadcare/client/video_conversions_accessor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# High-level video conversion helpers scoped to a client instance. +class Uploadcare::Client::VideoConversionsAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param uuid [String] + # @param format [String, Symbol] + # @param quality [String, Symbol] + # @param options [Hash] + # @param request_options [Hash] + # @return [Uploadcare::Resources::VideoConversion] + def convert(uuid:, format:, quality:, options: {}, request_options: {}) + Uploadcare::Resources::VideoConversion.convert( + params: { uuid: uuid, format: format, quality: quality }, options: options, client: client, + request_options: request_options + ) + end + + # @param token [String] + # @param request_options [Hash] + # @return [Uploadcare::Resources::VideoConversion] + def status(token:, request_options: {}) + Uploadcare::Resources::VideoConversion.status( + token: token, client: client, request_options: request_options + ) + end +end diff --git a/lib/uploadcare/client/webhook_client.rb b/lib/uploadcare/client/webhook_client.rb deleted file mode 100644 index e9f420f5..00000000 --- a/lib/uploadcare/client/webhook_client.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require_relative 'rest_client' - -module Uploadcare - module Client - # client for webhook management - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook - class WebhookClient < RestClient - # Create webhook - # @see https://uploadcare.com/docs/api_reference/rest/webhooks/#subscribe - def create(options = {}) - body = { - target_url: options[:target_url], - event: options[:event] || 'file.uploaded', - is_active: options[:is_active].nil? || options[:is_active] - }.merge( - { signing_secret: options[:signing_secret] }.compact - ).to_json - post(uri: '/webhooks/', content: body) - end - - # Returns array (not paginated list) of webhooks - # @see https://uploadcare.com/docs/api_reference/rest/webhooks/#get-list - def list - get(uri: '/webhooks/') - end - - # Permanently deletes subscription - # @see https://uploadcare.com/docs/api_reference/rest/webhooks/#unsubscribe - def delete(target_url) - body = { target_url: target_url }.to_json - request(method: 'DELETE', uri: '/webhooks/unsubscribe/', content: body) - end - - # Updates webhook - # @see https://uploadcare.com/docs/api_reference/rest/webhooks/#subscribe-update - def update(id, options = {}) - body = options.to_json - put(uri: "/webhooks/#{id}/", content: body) - end - - alias create_webhook create - alias list_webhooks list - alias delete_webhook delete - alias update_webhook update - end - end -end diff --git a/lib/uploadcare/client/webhooks_accessor.rb b/lib/uploadcare/client/webhooks_accessor.rb new file mode 100644 index 00000000..ebe2b69d --- /dev/null +++ b/lib/uploadcare/client/webhooks_accessor.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# High-level webhook operations scoped to a client instance. +class Uploadcare::Client::WebhooksAccessor + attr_reader :client + + # @param client [Uploadcare::Client] + def initialize(client:) + @client = client + end + + # @param request_options [Hash] + # @return [Array] + def list(request_options: {}) + Uploadcare::Resources::Webhook.list(client: client, request_options: request_options) + end + + # @param target_url [String] + # @param request_options [Hash] + # @param options [Hash] + # @return [Uploadcare::Resources::Webhook] + def create(target_url:, request_options: {}, **options) + Uploadcare::Resources::Webhook.create( + target_url: target_url, client: client, request_options: request_options, **options + ) + end + + # @param id [String] + # @param request_options [Hash] + # @param options [Hash] + # @return [Uploadcare::Resources::Webhook] + def update(id:, request_options: {}, **options) + Uploadcare::Resources::Webhook.update(id: id, client: client, request_options: request_options, **options) + end + + # @param target_url [String] + # @param request_options [Hash] + # @return [nil] + def delete(target_url:, request_options: {}) + Uploadcare::Resources::Webhook.delete(target_url: target_url, client: client, request_options: request_options) + end +end diff --git a/lib/uploadcare/cname_generator.rb b/lib/uploadcare/cname_generator.rb index 86bd17a0..7594a1f5 100644 --- a/lib/uploadcare/cname_generator.rb +++ b/lib/uploadcare/cname_generator.rb @@ -2,36 +2,57 @@ # lib/uploadcare/client/cname_generator.rb require 'digest' +require 'monitor' +require 'uri' -module Uploadcare - # CNAME generator for Uploadcare CDN - # see https://uploadcare.com/docs/delivery/cdn - class CnameGenerator - CNAME_PREFIX_LEN = 10 - - class << self - def cdn_base_postfix - @cdn_base_postfix ||= begin - uri = URI.parse(Uploadcare.config.cdn_base_postfix) - uri.host = "#{custom_cname}.#{uri.host}" +# CNAME generator for Uploadcare CDN. +# @see https://uploadcare.com/docs/delivery/cdn +class Uploadcare::CnameGenerator + # CNAME prefix length. + CNAME_PREFIX_LEN = 10 + + class << self + # Build CDN base URL with a generated CNAME prefix. + # + # @param config [Uploadcare::Configuration] + # @return [String] + def cdn_base_postfix(config: Uploadcare.configuration) + key = [config.cdn_base_postfix, config.public_key] + cached = @cdn_base_postfix_cache&.[](key) + return cached if cached + + cache_mutex.synchronize do + @cdn_base_postfix_cache ||= {} + @cdn_base_postfix_cache[key] ||= begin + uri = URI.parse(config.cdn_base_postfix) + uri.host = "#{generate_cname(public_key: config.public_key)}.#{uri.host}" uri.to_s rescue URI::InvalidURIError => e raise Uploadcare::Exception::ConfigurationError, "Invalid cdn_base_postfix URL: #{e.message}" end end + end - def generate_cname - custom_cname - end + # Generate a CNAME prefix for the current public key. + # + # @param public_key [String] + # @return [String] + def generate_cname(public_key: Uploadcare.configuration.public_key) + custom_cname(public_key) + end + + private - private + # Generate CNAME prefix + def custom_cname(public_key = Uploadcare.configuration.public_key) + raise Uploadcare::Exception::ConfigurationError, "Invalid public_key: #{public_key}" if public_key.nil? - # Generate CNAME prefix - def custom_cname - @custom_cname ||= begin - public_key = Uploadcare.config.public_key - raise Uploadcare::Exception::ConfigurationError, "Invalid public_key: #{public_key}" if public_key.nil? + cached = @custom_cname_cache&.[](public_key) + return cached if cached + cache_mutex.synchronize do + @custom_cname_cache ||= {} + @custom_cname_cache[public_key] ||= begin sha256_hex = Digest::SHA256.hexdigest(public_key) sha256_hex = sha256_hex.to_i(16) sha256_base36 = sha256_hex.to_s(36) @@ -39,5 +60,9 @@ def custom_cname end end end + + def cache_mutex + @cache_mutex ||= Monitor.new + end end end diff --git a/lib/uploadcare/collections/batch_result.rb b/lib/uploadcare/collections/batch_result.rb new file mode 100644 index 00000000..59245ec2 --- /dev/null +++ b/lib/uploadcare/collections/batch_result.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# Result object for batch file operations (store/delete). +# +# Wraps the response from batch operations and provides access to: +# - Successfully processed files +# - Files that encountered problems +# - Overall operation status +# +# @example +# result = client.files.batch_store(uuids: ["uuid1", "uuid2"]) +# result.result.each { |file| puts file.uuid } +# result.problems.each { |uuid, error| puts "#{uuid}: #{error}" } +class Uploadcare::Collections::BatchResult + # @return [Integer, nil] HTTP status code of the operation + attr_reader :status + + # @return [Array] Successfully processed File objects + attr_reader :result + + # @return [Hash] Hash mapping UUIDs to error messages + attr_reader :problems + + # Initialize a new BatchResult. + # + # @param status [Integer, nil] HTTP status code + # @param result [Array] Array of file data hashes from the API + # @param problems [Hash] Hash of UUIDs to error messages + # @param client [Uploadcare::Client] Client for creating File objects + def initialize(status:, result:, problems:, client:) + @status = status + @result = result ? result.map { |file_data| Uploadcare::Resources::File.new(file_data, client) } : [] + @problems = problems || {} + end +end diff --git a/lib/uploadcare/collections/paginated.rb b/lib/uploadcare/collections/paginated.rb new file mode 100644 index 00000000..fa83fb2c --- /dev/null +++ b/lib/uploadcare/collections/paginated.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require 'uri' + +# Paginated collection for API list responses. +# +# Wraps paginated API responses and provides methods for navigating between pages. +# Implements Enumerable for easy iteration over resources. +# +# @example Iterating over resources +# files = client.files.list +# files.each { |file| puts file.uuid } +# +# @example Navigating pages +# files = client.files.list +# while files +# files.each { |file| process(file) } +# files = files.next_page +# end +# +# @example Fetching all resources +# all_files = client.files.list.all +class Uploadcare::Collections::Paginated + include Enumerable + + # @return [Array] Array of resource objects in the current page + attr_reader :resources + + # @return [String, nil] URL for the next page, or nil if on last page + attr_reader :next_page_url + + # @return [String, nil] URL for the previous page, or nil if on first page + attr_reader :previous_page_url + + # @return [Integer] Number of items per page + attr_reader :per_page + + # @return [Integer] Total number of items across all pages + attr_reader :total + + # @return [Object] API endpoint client for fetching additional pages + attr_reader :api_client + + # @return [Class] Resource class for instantiating items from raw data + attr_reader :resource_class + + # @return [Uploadcare::Client, nil] Client for resource instantiation + attr_reader :client + + # @return [Hash] Request options used when fetching pages + attr_reader :request_options + + # Initialize a new Paginated collection. + # + # @param params [Hash] Collection parameters + # @option params [Array] :resources Array of resource objects + # @option params [String, nil] :next_page URL for next page + # @option params [String, nil] :previous_page URL for previous page + # @option params [Integer] :per_page Items per page + # @option params [Integer] :total Total item count + # @option params [Object] :api_client API client for fetching pages + # @option params [Class] :resource_class Class for instantiating resources + # @option params [Uploadcare::Client, nil] :client Client for resources + # @option params [Hash] :request_options Request options for subsequent page fetches + def initialize(params = {}) + @resources = params[:resources] || [] + @next_page_url = params[:next_page] + @previous_page_url = params[:previous_page] + @per_page = params[:per_page] + @total = params[:total] + @api_client = params[:api_client] + @resource_class = params[:resource_class] + @client = params[:client] + @request_options = params[:request_options] || {} + end + + # Iterate over resources in the current page. + # + # @yield [resource] Block to execute for each resource + # @yieldparam resource [Object] A resource object + def each(&) + @resources.each(&) + end + + # Fetch the next page of resources. + # + # @return [Uploadcare::Collections::Paginated, nil] Next page, or nil if on last page + def next_page + fetch_page(@next_page_url) + end + + # Fetch the previous page of resources. + # + # @return [Uploadcare::Collections::Paginated, nil] Previous page, or nil if on first page + def previous_page + fetch_page(@previous_page_url) + end + + # Fetch all resources across all pages. + # + # @param limit [Integer, nil] Maximum number of resources to return + # @return [Array] All resources up to the requested limit + def all(limit: nil) + return [] if limit && limit <= 0 + + collection = self + items = [] + remaining = limit + + while collection + page_items = collection.resources || [] + if remaining + page_slice = page_items.first(remaining) + items.concat(page_slice) + remaining -= page_slice.length + break if remaining <= 0 + else + items.concat(page_items) + end + + collection = collection.next_page + end + + items + end + + private + + def fetch_page(page_url) + return nil unless page_url + + params = extract_params_from_url(page_url) + response = fetch_response(params) + build_paginated_collection(response) + end + + def extract_params_from_url(page_url) + uri = URI.parse(page_url) + URI.decode_www_form(uri.query.to_s).to_h + end + + def fetch_response(params) + Uploadcare::Result.unwrap(api_client.list(params: params, request_options: request_options)) + end + + def build_paginated_collection(response) + new_resources = build_resources(response['results']) + + self.class.new( + resources: new_resources, + next_page: response['next'], + previous_page: response['previous'], + per_page: response['per_page'], + total: response['total'], + api_client: api_client, + resource_class: resource_class, + client: client, + request_options: request_options + ) + end + + def build_resources(results) + results.map { |data| resource_class.new(data, client) } + end +end diff --git a/lib/uploadcare/concern/error_handler.rb b/lib/uploadcare/concern/error_handler.rb deleted file mode 100644 index 54236b60..00000000 --- a/lib/uploadcare/concern/error_handler.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Concerns - # Wrapper for responses - # raises errors instead of returning monads - module ErrorHandler - include Exception - - # Extension of ApiStruct's failure method - # - # Raises errors instead of returning falsey objects - # @see https://github.com/rubygarage/api_struct/blob/master/lib/api_struct/client.rb#L55 - def failure(response) - catch_upload_errors(response) - parsed_response = JSON.parse(response.body.to_s) - raise RequestError, parsed_response['detail'] || parsed_response.map { |k, v| "#{k}: #{v}" }.join('; ') - rescue JSON::ParserError - raise RequestError, response.body.to_s - end - - # Extension of ApiStruct's wrap method - # - # Catches throttling errors and Upload API errors - # - # @see https://github.com/rubygarage/api_struct/blob/master/lib/api_struct/client.rb#L45 - def wrap(response) - raise_throttling_error(response) if response.status == 429 - return failure(response) if response.status >= 300 - - catch_upload_errors(response) - success(response) - end - - private - - # Raise ThrottleError. Also, tells in error when server will be ready for next request - def raise_throttling_error(response) - retry_after = (response.headers['Retry-After'].to_i + 1) || 11 - raise ThrottleError.new(retry_after), "Response throttled, retry #{retry_after} seconds later" - end - - # Upload API returns its errors with code 200, and stores its actual code and details within response message - # This methods detects that and raises apropriate error - def catch_upload_errors(response) - return unless response.code == 200 - - parsed_response = JSON.parse(response.body.to_s) - error = parsed_response['error'] if parsed_response.is_a?(Hash) - raise RequestError, error if error - end - end - end -end diff --git a/lib/uploadcare/concern/throttle_handler.rb b/lib/uploadcare/concern/throttle_handler.rb deleted file mode 100644 index 454cb89e..00000000 --- a/lib/uploadcare/concern/throttle_handler.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Concerns - # This module lets clients send request multiple times if request is throttled - module ThrottleHandler - # call given block. If ThrottleError is returned, it will wait and attempt again 4 more times - # @yield executable block (HTTP request that may be throttled) - def handle_throttling - (Uploadcare.config.max_throttle_attempts - 1).times do - # rubocop:disable Style/RedundantBegin - begin - return yield - rescue(Exception::ThrottleError) => e - wait_time = e.timeout - sleep(wait_time) - next - end - # rubocop:enable Style/RedundantBegin - end - yield - end - end - end -end diff --git a/lib/uploadcare/concern/upload_error_handler.rb b/lib/uploadcare/concern/upload_error_handler.rb deleted file mode 100644 index 2dc9ed2f..00000000 --- a/lib/uploadcare/concern/upload_error_handler.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Concerns - # Wrapper for responses - # raises errors instead of returning monads - module UploadErrorHandler - include Exception - - # Extension of ApiStruct's failure method - # - # Raises errors instead of returning falsey objects - # @see https://github.com/rubygarage/api_struct/blob/master/lib/api_struct/client.rb#L55 - def failure(response) - catch_throttling_error(response) - parsed_response = JSON.parse(response.body.to_s) - raise RequestError, parsed_response['detail'] - rescue JSON::ParserError - raise RequestError, response.status - end - - private - - def catch_throttling_error(response) - return unless response.code == 429 - - retry_after = (response.headers['Retry-After'].to_i + 1) || 11 - raise ThrottleError.new(retry_after), "Response throttled, retry #{retry_after} seconds later" - end - end - end -end diff --git a/lib/uploadcare/configuration.rb b/lib/uploadcare/configuration.rb new file mode 100644 index 00000000..79251cb3 --- /dev/null +++ b/lib/uploadcare/configuration.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'logger' + +# Configuration container for client defaults and per-client overrides. +class Uploadcare::Configuration + attr_accessor :public_key, :secret_key, :auth_type, :multipart_size_threshold, :rest_api_root, + :upload_api_root, :max_request_tries, :base_request_sleep, :max_request_sleep, :sign_uploads, + :upload_signature_lifetime, :max_throttle_attempts, :upload_threads, :framework_data, + :file_chunk_size, :logger, :use_subdomains, :cdn_base_postfix, :default_cdn_base, + :multipart_chunk_size, :upload_timeout, :max_upload_retries + + # Default configuration values used as the template for new instances. + DEFAULTS = { + public_key: nil, + secret_key: nil, + auth_type: 'Uploadcare', + multipart_size_threshold: 100 * 1024 * 1024, + rest_api_root: 'https://api.uploadcare.com', + upload_api_root: 'https://upload.uploadcare.com', + max_request_tries: 100, + base_request_sleep: 1, # seconds + max_request_sleep: 60.0, # seconds + sign_uploads: false, + upload_signature_lifetime: 30 * 60, # seconds + max_throttle_attempts: 5, + upload_threads: 2, # used for multiupload only ATM + framework_data: '', + file_chunk_size: 100, + logger: nil, + use_subdomains: false, + cdn_base_postfix: 'https://ucarecd.net/', + default_cdn_base: 'https://ucarecdn.com/', + multipart_chunk_size: 5 * 1024 * 1024, # 5MB chunks for multipart upload + upload_timeout: 60, # seconds + max_upload_retries: 3 # retry failed uploads 3 times + }.freeze + + # @param options [Hash] Configuration overrides + def initialize(**options) + values = DEFAULTS.merge(options) + values[:public_key] = ENV.fetch('UPLOADCARE_PUBLIC_KEY', '') unless options.key?(:public_key) + values[:secret_key] = ENV.fetch('UPLOADCARE_SECRET_KEY', '') unless options.key?(:secret_key) + + values.each do |attribute, value| + send("#{attribute}=", value) + end + @logger ||= Logger.new($stdout) + end + + # Build the deterministic subdomain prefix for the configured public key. + # + # @return [String] + def custom_cname + Uploadcare::CnameGenerator.generate_cname(public_key: public_key) + end + + # Resolve the CDN base URL for this configuration. + # + # @return [String] + def cdn_base + return Uploadcare::CnameGenerator.cdn_base_postfix(config: self) if use_subdomains + + default_cdn_base + end + + # Clone this configuration with overrides. + # + # @param options [Hash] + # @return [Uploadcare::Configuration] + def with(**options) + self.class.new(**to_h, **options) + end + + # Convert this configuration to a serializable hash. + # + # @return [Hash] + def to_h + DEFAULTS.keys.to_h { |attribute| [attribute, public_send(attribute)] } + end +end diff --git a/lib/uploadcare/entity/addons.rb b/lib/uploadcare/entity/addons.rb deleted file mode 100644 index dc74938b..00000000 --- a/lib/uploadcare/entity/addons.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # This serializer is responsible for addons handling - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons - class Addons < Entity - client_service AddonsClient - - attr_entity :request_id, :status, :result - end - end -end diff --git a/lib/uploadcare/entity/conversion/base_converter.rb b/lib/uploadcare/entity/conversion/base_converter.rb deleted file mode 100644 index 7a016f86..00000000 --- a/lib/uploadcare/entity/conversion/base_converter.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - module Conversion - # This serializer lets a user convert uploaded documents - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/documentConvert - class BaseConverter < Entity - class << self - # Converts files - # - # @param params [Array] of hashes with params or [Hash] - # @option options [Boolean] :store whether to store file on servers. - def convert(params, options = {}) - files_params = params.is_a?(Hash) ? [params] : params - conversion_client.new.convert_many(files_params, options) - end - - # Returns a status of a conversion job - # - # @param token [Integer, String] token obtained from a server in convert method - def status(token) - conversion_client.new.get_conversion_status(token) - end - - # Returns the document format and possible conversion formats. - # - # @param uuid [String] UUID of the document - def info(uuid) - conversion_client.new.document_info(uuid) - end - - private - - def conversion_client - clients[:base] - end - end - end - end - end - include Conversion -end diff --git a/lib/uploadcare/entity/conversion/document_converter.rb b/lib/uploadcare/entity/conversion/document_converter.rb deleted file mode 100644 index 2e63f851..00000000 --- a/lib/uploadcare/entity/conversion/document_converter.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require_relative 'base_converter' - -module Uploadcare - module Entity - module Conversion - # This serializer lets a user convert uploaded documents - # @see https://uploadcare.com/api-refs/rest-api/v0.5.0/#operation/documentConvert - class DocumentConverter < BaseConverter - client_service Client::Conversion::DocumentConversionClient - end - end - end -end diff --git a/lib/uploadcare/entity/conversion/video_converter.rb b/lib/uploadcare/entity/conversion/video_converter.rb deleted file mode 100644 index c9c0c6a7..00000000 --- a/lib/uploadcare/entity/conversion/video_converter.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require_relative 'base_converter' - -module Uploadcare - module Entity - module Conversion - # This serializer lets a user convert uploaded videos, and usually returns an array of results - # @see https://uploadcare.com/api-refs/rest-api/v0.5.0/#operation/videoConvert - class VideoConverter < BaseConverter - client_service Client::Conversion::VideoConversionClient - end - end - end -end diff --git a/lib/uploadcare/entity/decorator/paginator.rb b/lib/uploadcare/entity/decorator/paginator.rb deleted file mode 100644 index e26f674a..00000000 --- a/lib/uploadcare/entity/decorator/paginator.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # @abstract - module Decorator - # provides pagination methods for things in Uploadcare that paginate, - # namely [FileList] and [Group] - # - # Requirements: - # - Should be Entity with Client - # - Associated Client should have `list` method that returns objects with pagination - # - Response should have :next, :previous, :total, :per_page params and :results fields - module Paginator - @entity ||= Hashie::Mash.new - - # meta data of a pagination object - def meta - Hashie::Mash.new(next: @entity[:next], previous: @entity[:previous], - total: @entity[:total], per_page: @entity[:per_page]) - end - - # Returns new instance of current object on next page - def next_page - url = @entity[:next] - return unless url - - query = URI.decode_www_form(URI(url).query).to_h - query = query.to_h { |k, v| [k.to_sym, v] } - self.class.list(**query) - end - - # Returns new instance of current object on previous page - def previous_page - url = @entity[:previous] - return unless url - - query = URI.decode_www_form(URI(url).query).to_h - query = query.to_h { |k, v| [k.to_sym, v] } - self.class.list(**query) - end - - # Attempts to load the entire list after offset into results of current object - # - # It's possible to avoid loading objects on previous pages by offsetting them first - def load - return self if @entity[:next].nil? || @entity[:results].length == @entity[:total] - - np = self - until np.next.nil? - np = np.next_page - @entity[:results].concat(np.results.map(&:to_h)) - end - @entity[:next] = nil - @entity[:per_page] = @entity[:total] - self - end - - # iterate through pages, starting with current one - # - # @yield [Block] - def each(&block) - current_page = self - while current_page - current_page.results.each(&block) - current_page = current_page.next_page - end - end - - # Load and return all objects in list - # - # @return [Array] - def all - load[:results] - end - end - end - end -end diff --git a/lib/uploadcare/entity/entity.rb b/lib/uploadcare/entity/entity.rb deleted file mode 100644 index a957a6a0..00000000 --- a/lib/uploadcare/entity/entity.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -Gem.find_files('client/**/*.rb').each { |path| require path } - -module Uploadcare - # Entities represent objects existing in Uploadcare cloud - # - # Typically, Entities inherit class methods from {Client} instance methods - # @see Client - module Entity - # @abstract - class Entity < ApiStruct::Entity - include Client - end - end - - include Entity -end diff --git a/lib/uploadcare/entity/file.rb b/lib/uploadcare/entity/file.rb deleted file mode 100644 index 6b28ee25..00000000 --- a/lib/uploadcare/entity/file.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # This serializer returns a single file - # - # @see https://uploadcare.com/docs/api_reference/rest/handling_projects/ - class File < Entity - RESPONSE_PARAMS = %i[ - datetime_removed datetime_stored datetime_uploaded is_image is_ready mime_type original_file_url cdn_url - original_filename size url uuid variations content_info metadata appdata source - ].freeze - - client_service FileClient - - attr_entity(*RESPONSE_PARAMS) - - def datetime_stored - Uploadcare.config.logger&.warn 'datetime_stored property has been deprecated, and will be removed without a replacement in future.' # rubocop:disable Layout/LineLength - @entity.datetime_stored - end - - # gets file's uuid - even if it's only initialized with url - # @returns [String] - def uuid - return @entity.uuid if @entity.uuid - - uuid = @entity.url.gsub('https://ucarecdn.com/', '') - uuid.gsub(%r{/.*}, '') - end - - # loads file metadata, if it's initialized with url or uuid - def load - initialize(File.info(uuid).entity) - end - - # The method to convert a document file to another file - # gets (conversion) params [Hash], options (store: Boolean) [Hash], converter [Class] - # @returns [File] - def convert_document(params = {}, options = {}, converter = Conversion::DocumentConverter) - convert_file(params, converter, options) - end - - # The method to convert a video file to another file - # gets (conversion) params [Hash], options (store: Boolean) [Hash], converter [Class] - # @returns [File] - def convert_video(params = {}, options = {}, converter = Conversion::VideoConverter) - convert_file(params, converter, options) - end - - # Copies file to current project - # - # source can be UID or full CDN link - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/createLocalCopy - def self.local_copy(source, args = {}) - response = FileClient.new.local_copy(source: source, **args).success[:result] - File.new(response) - end - - # copy file to different project - # - # source can be UID or full CDN link - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/createRemoteCopy - def self.remote_copy(source, target, args = {}) - FileClient.new.remote_copy(source: source, target: target, **args).success[:result] - end - - # Instance version of {internal_copy} - def local_copy(args = {}) - File.local_copy(uuid, **args) - end - - # Instance version of {external_copy} - def remote_copy(target, args = {}) - File.remote_copy(uuid, target, **args) - end - - # Store a single file, preventing it from being deleted in 2 weeks - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/storeFile - def store - File.store(uuid) - end - - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#operation/deleteFileStorage - def delete - File.delete(uuid) - end - - # Returns file's CDN URL - def cdn_url - "#{Uploadcare.config.cdn_base.call}#{uuid}/" - end - - private - - def convert_file(params, converter, options = {}) - raise Uploadcare::Exception::ConversionError, 'The first argument must be a Hash' unless params.is_a?(Hash) - - params_with_symbolized_keys = params.to_h { |k, v| [k.to_sym, v] } - params_with_symbolized_keys[:uuid] = uuid - result = converter.convert(params_with_symbolized_keys, options) - result.success? ? File.info(result.value![:result].first[:uuid]) : result - end - end - end -end diff --git a/lib/uploadcare/entity/file_list.rb b/lib/uploadcare/entity/file_list.rb deleted file mode 100644 index 1ac9fb8c..00000000 --- a/lib/uploadcare/entity/file_list.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'uploadcare/entity/file' -require 'uploadcare/entity/decorator/paginator' -require 'dry/monads' -require 'api_struct' - -module Uploadcare - module Entity - # This serializer returns lists of files - # - # This is a paginated list, so all pagination methods apply - # @see Uploadcare::Entity::Decorator::Paginator - class FileList < ApiStruct::Entity - include Uploadcare::Entity::Decorator::Paginator - - client_service Client::FileListClient - - attr_entity :next, :previous, :total, :per_page - - has_entities :results, as: Uploadcare::Entity::File - has_entities :result, as: Uploadcare::Entity::File - - # alias for result/results, depending on which API this FileList was initialized from - # @return [Array] of [Uploadcare::Entity::File] - def files - results - rescue ApiStruct::EntityError - result - end - end - end -end diff --git a/lib/uploadcare/entity/file_metadata.rb b/lib/uploadcare/entity/file_metadata.rb deleted file mode 100644 index 44284ce4..00000000 --- a/lib/uploadcare/entity/file_metadata.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # This serializer is responsible for file metadata handling - # - # @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata - class FileMetadata < Entity - client_service FileMetadataClient - - class << self - def index(uuid) - ::Uploadcare::Client::FileMetadataClient.new.index(uuid).success - end - - def show(uuid, key) - ::Uploadcare::Client::FileMetadataClient.new.show(uuid, key).success - end - - def update(uuid, key, value) - ::Uploadcare::Client::FileMetadataClient.new.update(uuid, key, value).success - end - - def delete(uuid, key) - ::Uploadcare::Client::FileMetadataClient.new.delete(uuid, key).success || '200 OK' - end - end - end - end -end diff --git a/lib/uploadcare/entity/group.rb b/lib/uploadcare/entity/group.rb deleted file mode 100644 index 58b82b0e..00000000 --- a/lib/uploadcare/entity/group.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require 'uploadcare/entity/file' - -module Uploadcare - module Entity - # Groups serve a purpose of better organizing files in your Uploadcare projects. - # - # You can create one from a set of files by using their UUIDs. - # - # @see https://uploadcare.com/docs/api_reference/upload/groups/ - class Group < Entity - client_service RestGroupClient, prefix: 'rest', only: %i[store info delete] - client_service GroupClient - - attr_entity :id, :datetime_created, :datetime_stored, :files_count, :cdn_url, :url, :file_cdn_urls - has_entities :files, as: Uploadcare::Entity::File - - # Remove these lines and bump api_struct version when this PR is accepted: - # @see https://github.com/rubygarage/api_struct/pull/15 - def self.store(uuid) - rest_store(uuid).success || '200 OK' - end - - # Get a file group by its ID. - def self.group_info(uuid) - rest_info(uuid) - end - - def self.delete(uuid) - rest_delete(uuid).success || '200 OK' - end - - # gets groups's id - even if it's only initialized with cdn_url - # @return [String] - def id - return @entity.id if @entity.id - - id = @entity.cdn_url.gsub('https://ucarecdn.com/', '') - id.gsub(%r{/.*}, '') - end - - # loads group metadata, if it's initialized with url or id - def load - initialize(Group.info(id).entity) - end - - # Returns group's CDN URL - def cdn_url - "#{Uploadcare.config.cdn_base.call}#{id}/" - end - - # Returns CDN URLs of all files from group without API requesting - def file_cdn_urls - file_cdn_urls = [] - (0...files.count).each do |file_index| - file_cdn_url = "#{cdn_url}nth/#{file_index}/" - file_cdn_urls << file_cdn_url - end - file_cdn_urls - end - end - end -end diff --git a/lib/uploadcare/entity/group_list.rb b/lib/uploadcare/entity/group_list.rb deleted file mode 100644 index 27b3a849..00000000 --- a/lib/uploadcare/entity/group_list.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'uploadcare/entity/group' -require 'uploadcare/entity/decorator/paginator' - -module Uploadcare - module Entity - # List of groups - # - # @see https://uploadcare.com/docs/api_reference/upload/groups/ - # - # This is a paginated list, so all pagination methods apply - # @see Uploadcare::Entity::Decorator::Paginator - class GroupList < Entity - include Uploadcare::Entity::Decorator::Paginator - - client_service RestGroupClient, only: :list - - attr_entity :next, :previous, :total, :per_page, :results - has_entities :results, as: Group - - alias groups results - end - end -end diff --git a/lib/uploadcare/entity/project.rb b/lib/uploadcare/entity/project.rb deleted file mode 100644 index 596303a2..00000000 --- a/lib/uploadcare/entity/project.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # This serializer returns info about a project and its data - # @see https://uploadcare.com/docs/api_reference/rest/handling_projects/ - class Project < Entity - client_service ProjectClient - - attr_entity :collaborators, :pub_key, :name, :autostore_enabled - end - end -end diff --git a/lib/uploadcare/entity/uploader.rb b/lib/uploadcare/entity/uploader.rb deleted file mode 100644 index 444f5ed9..00000000 --- a/lib/uploadcare/entity/uploader.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # This serializer lets user upload files by various means, and usually returns an array of files - # @see https://uploadcare.com/api-refs/upload-api/#tag/Upload - class Uploader < Entity - client_service UploaderClient - client_service MultipartUploaderClient, only: :upload, prefix: :multipart - - attr_entity :files - has_entities :files, as: Uploadcare::Entity::File - - # Upload file or group of files from array, File, or url - # - # @param object [Array], [String] or [File] - # @param [Hash] options options for upload - # @option options [Boolean] :store whether to store file on servers. - def self.upload(object, options = {}) - if big_file?(object) - multipart_upload(object, options) - elsif file?(object) - upload_file(object, options) - elsif object.is_a?(Array) - upload_files(object, options) - elsif object.is_a?(String) - upload_from_url(object, options) - else - raise ArgumentError, "Expected input to be a file/Array/URL, given: `#{object}`" - end - end - - # upload single file - def self.upload_file(file, options = {}) - response = UploaderClient.new.upload_many([file], options) - uuid = response.success.values.first - if Uploadcare.config.secret_key.nil? - Uploadcare::Entity::File.new(file_info(uuid).success) - else - # we can get more info about the uploaded file - Uploadcare::Entity::File.info(uuid) - end - end - - # upload multiple files - def self.upload_files(arr, options = {}) - response = UploaderClient.new.upload_many(arr, options) - response.success.map { |pair| Uploadcare::Entity::File.new(uuid: pair[1], original_filename: pair[0]) } - end - - # upload file of size above 10mb (involves multipart upload) - def self.multipart_upload(file, options = {}, &block) - response = MultipartUploaderClient.new.upload(file, options, &block) - Uploadcare::Entity::File.new(response.success) - end - - # upload files from url - # @param url [String] - def self.upload_from_url(url, options = {}) - response = UploaderClient.new.upload_from_url(url, options) - return response.success[:token] unless response.success[:files] - - response.success[:files].map { |file_data| Uploadcare::Entity::File.new(file_data) } - end - - # gets a status of upload from url - # @param url [String] - def self.get_upload_from_url_status(token) - UploaderClient.new.get_upload_from_url_status(token) - end - - # Get information about an uploaded file (without the secret key) - # @param uuid [String] - def self.file_info(uuid) - UploaderClient.new.file_info(uuid) - end - - class << self - private - - # check if object is a file - def file?(object) - object.respond_to?(:path) && ::File.exist?(object.path) - end - - # check if object needs to be uploaded using multipart upload - def big_file?(object) - file?(object) && object.size >= Uploadcare.config.multipart_size_threshold - end - end - end - end -end diff --git a/lib/uploadcare/entity/webhook.rb b/lib/uploadcare/entity/webhook.rb deleted file mode 100644 index 5e53a6ac..00000000 --- a/lib/uploadcare/entity/webhook.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Entity - # This serializer is responsible for webhook handling - # - # @see https://uploadcare.com/docs/api_reference/rest/webhooks/ - class Webhook < Entity - client_service WebhookClient - - attr_entity :id, :created, :updated, :event, :target_url, :project, :is_active - end - end -end diff --git a/lib/uploadcare/exception/auth_error.rb b/lib/uploadcare/exception/auth_error.rb index 29d38572..d133348c 100644 --- a/lib/uploadcare/exception/auth_error.rb +++ b/lib/uploadcare/exception/auth_error.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true -module Uploadcare - module Exception - # Invalid Auth configuration error - class AuthError < StandardError; end - end -end +# Invalid Auth configuration error +class Uploadcare::Exception::AuthError < StandardError; end diff --git a/lib/uploadcare/exception/configuration_error.rb b/lib/uploadcare/exception/configuration_error.rb index b7a0afb0..1d49a328 100644 --- a/lib/uploadcare/exception/configuration_error.rb +++ b/lib/uploadcare/exception/configuration_error.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true -module Uploadcare - module Exception - # Standard error for invalid API configuration responses - class ConfigurationError < StandardError; end - end -end +# Standard error for invalid API configuration responses +class Uploadcare::Exception::ConfigurationError < StandardError; end diff --git a/lib/uploadcare/exception/conversion_error.rb b/lib/uploadcare/exception/conversion_error.rb index a4aca95a..d05a5556 100644 --- a/lib/uploadcare/exception/conversion_error.rb +++ b/lib/uploadcare/exception/conversion_error.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true -module Uploadcare - module Exception - # Standard error for invalid API conversion responses - class ConversionError < StandardError; end - end -end +# Standard error for invalid API conversion responses +class Uploadcare::Exception::ConversionError < StandardError; end diff --git a/lib/uploadcare/exception/invalid_request_error.rb b/lib/uploadcare/exception/invalid_request_error.rb new file mode 100644 index 00000000..f1822310 --- /dev/null +++ b/lib/uploadcare/exception/invalid_request_error.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Specific error for invalid requests (400 Bad Request) +class Uploadcare::Exception::InvalidRequestError < Uploadcare::Exception::RequestError; end diff --git a/lib/uploadcare/exception/multipart_upload_error.rb b/lib/uploadcare/exception/multipart_upload_error.rb new file mode 100644 index 00000000..0c166dcb --- /dev/null +++ b/lib/uploadcare/exception/multipart_upload_error.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Raised when multipart upload fails +class Uploadcare::Exception::MultipartUploadError < Uploadcare::Exception::UploadError; end diff --git a/lib/uploadcare/exception/not_found_error.rb b/lib/uploadcare/exception/not_found_error.rb new file mode 100644 index 00000000..02a649a1 --- /dev/null +++ b/lib/uploadcare/exception/not_found_error.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Specific error for not found resources (404 Not Found) +class Uploadcare::Exception::NotFoundError < Uploadcare::Exception::RequestError; end diff --git a/lib/uploadcare/exception/request_error.rb b/lib/uploadcare/exception/request_error.rb index ca23bfc3..1b44e157 100644 --- a/lib/uploadcare/exception/request_error.rb +++ b/lib/uploadcare/exception/request_error.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true -module Uploadcare - module Exception - # Standard error for invalid API responses - class RequestError < StandardError; end - end -end +# Standard error for invalid API responses +class Uploadcare::Exception::RequestError < StandardError; end diff --git a/lib/uploadcare/exception/retry_error.rb b/lib/uploadcare/exception/retry_error.rb index ac05439b..20f8182f 100644 --- a/lib/uploadcare/exception/retry_error.rb +++ b/lib/uploadcare/exception/retry_error.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true -module Uploadcare - module Exception - # Standard error to raise when needing to retry a request - class RetryError < StandardError; end - end -end +# Standard error to raise when needing to retry a request +class Uploadcare::Exception::RetryError < StandardError; end diff --git a/lib/uploadcare/exception/throttle_error.rb b/lib/uploadcare/exception/throttle_error.rb index 9eaa06ac..057aeab8 100644 --- a/lib/uploadcare/exception/throttle_error.rb +++ b/lib/uploadcare/exception/throttle_error.rb @@ -1,16 +1,12 @@ # frozen_string_literal: true -module Uploadcare - module Exception - # Exception for throttled requests - class ThrottleError < StandardError - attr_reader :timeout +# Exception for throttled requests +class Uploadcare::Exception::ThrottleError < StandardError + attr_reader :timeout - # @param timeout [Float] Amount of seconds the request have been throttled for - def initialize(timeout = 10.0) - super - @timeout = timeout - end - end + # @param timeout [Float] Amount of seconds the request have been throttled for + def initialize(message = nil, timeout: 10.0) + super(message) + @timeout = timeout end end diff --git a/lib/uploadcare/exception/unknown_status_error.rb b/lib/uploadcare/exception/unknown_status_error.rb new file mode 100644 index 00000000..3104e566 --- /dev/null +++ b/lib/uploadcare/exception/unknown_status_error.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Raised when upload status is unknown +class Uploadcare::Exception::UnknownStatusError < Uploadcare::Exception::UploadError; end diff --git a/lib/uploadcare/exception/upload_error.rb b/lib/uploadcare/exception/upload_error.rb new file mode 100644 index 00000000..f9c2d217 --- /dev/null +++ b/lib/uploadcare/exception/upload_error.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# General upload error +class Uploadcare::Exception::UploadError < StandardError; end diff --git a/lib/uploadcare/exception/upload_timeout_error.rb b/lib/uploadcare/exception/upload_timeout_error.rb new file mode 100644 index 00000000..03d9a535 --- /dev/null +++ b/lib/uploadcare/exception/upload_timeout_error.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Raised when upload times out +class Uploadcare::Exception::UploadTimeoutError < Uploadcare::Exception::UploadError; end diff --git a/lib/uploadcare/internal/authenticator.rb b/lib/uploadcare/internal/authenticator.rb new file mode 100644 index 00000000..ec61be8f --- /dev/null +++ b/lib/uploadcare/internal/authenticator.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'openssl' +require 'time' + +# Handles authentication for Uploadcare REST API requests. +# +# Supports two authentication modes: +# - Simple authentication: Basic auth with public_key:secret_key +# - Secure authentication: signature-based authentication +# +# @example Using the authenticator +# authenticator = Uploadcare::Internal::Authenticator.new(config: config) +# headers = authenticator.headers('GET', '/files/', '') +# +# @see https://uploadcare.com/docs/api_reference/rest/requests_auth/ +class Uploadcare::Internal::Authenticator + BODY_DIGEST_NAME = 'MD5' + SIGNATURE_DIGEST_NAME = 'SHA1' + + # @return [Hash] Default headers included in all requests + attr_reader :default_headers + + # Initialize a new Authenticator. + # + # @param config [Uploadcare::Configuration] Configuration object with API credentials + def initialize(config:) + @config = config + @default_headers = { + 'Accept' => 'application/vnd.uploadcare-v0.7+json', + 'User-Agent' => Uploadcare::Internal::UserAgent.call(config: config) + } + end + + # Generate authentication headers for an API request. + # + # @param http_method [String] HTTP method (GET, POST, PUT, DELETE) + # @param uri [String] Request URI path + # @param body [String] Request body content (default: '') + # @param content_type [String] Content-Type header value (default: 'application/json') + # @return [Hash] Headers hash including authentication + # @raise [Uploadcare::Exception::AuthError] if credentials are blank when using secure auth + def headers(http_method, uri, body = '', content_type = nil) + resolved_content_type = content_type || 'application/json' + raise Uploadcare::Exception::AuthError, 'Secret Key is blank.' if @config.secret_key.to_s.empty? + + validate_public_key + + return simple_auth_headers(resolved_content_type) if @config.auth_type == 'Uploadcare.Simple' + + secure_auth_headers(http_method, uri, body, resolved_content_type) + end + + private + + def simple_auth_headers(content_type) + @default_headers.merge( + 'Content-Type' => content_type, + 'Authorization' => "#{@config.auth_type} #{@config.public_key}:#{@config.secret_key}" + ) + end + + def validate_public_key + return unless @config.public_key.nil? || @config.public_key.empty? + + raise Uploadcare::Exception::AuthError, 'Public Key is blank.' + end + + def secure_auth_headers(http_method, uri, body, content_type) + date = Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT') + signature = generate_signature(http_method, uri, body, content_type, date) + auth_headers = { 'Authorization' => "Uploadcare #{@config.public_key}:#{signature}", 'Date' => date } + @default_headers.merge('Content-Type' => content_type).merge(auth_headers) + end + + def generate_signature(http_method, uri, body, content_type, date) + normalized_uri = uri.start_with?('/') ? uri : "/#{uri}" + + sign_string = [ + http_method.upcase, + body_digest(body), + content_type, + date, + normalized_uri + ].join("\n") + + OpenSSL::HMAC.hexdigest( + signature_digest, + @config.secret_key, + sign_string + ) + end + + def body_digest(body) + OpenSSL::Digest.new(BODY_DIGEST_NAME).hexdigest(body) + end + + def signature_digest + OpenSSL::Digest.new(SIGNATURE_DIGEST_NAME) + end +end diff --git a/lib/uploadcare/internal/error_handler.rb b/lib/uploadcare/internal/error_handler.rb new file mode 100644 index 00000000..cd63166b --- /dev/null +++ b/lib/uploadcare/internal/error_handler.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'json' +require 'time' + +# Handles API errors and converts them to appropriate exceptions. +# +# This module is included in API base classes to provide consistent error handling +# across all API requests. It parses error responses and raises typed exceptions. +# +# @see Uploadcare::Api::Rest +# @see Uploadcare::Api::Upload +module Uploadcare::Internal::ErrorHandler + # Handle a failed API request and raise an appropriate exception. + # + # Parses the error response and raises a typed exception based on the HTTP status code. + # Also handles Upload API errors which return status 200 with error details in the body. + # + # @param error [Faraday::Error] The error from the HTTP client + # @raise [Uploadcare::Exception::InvalidRequestError] for 400 Bad Request + # @raise [Uploadcare::Exception::NotFoundError] for 404 Not Found + # @raise [Uploadcare::Exception::ThrottleError] for 429 Too Many Requests + # @raise [Uploadcare::Exception::RequestError] for other error statuses + def handle_error(error) + response = error.response + return raise Uploadcare::Exception::RequestError, error.message if response.nil? + + catch_upload_errors(response) + + error_message = extract_error_message(response) + raise_status_error(response, error_message) + end + + private + + # Extract error message from response body. + # + # @param response [Hash] Response hash with :body key + # @return [String] Extracted error message + def extract_error_message(response) + parsed = JSON.parse(response[:body].to_s) + return parsed['detail'] if parsed.is_a?(Hash) && parsed['detail'] + return parsed.map { |k, v| "#{k}: #{v}" }.join('; ') if parsed.is_a?(Hash) + + parsed.to_s + rescue JSON::ParserError + response[:body].to_s + end + + # Raise appropriate error based on HTTP status code. + # + # @param response [Hash] Response hash with :status key + # @param message [String] Error message + # @raise [Uploadcare::Exception::InvalidRequestError] for 400 + # @raise [Uploadcare::Exception::NotFoundError] for 404 + # @raise [Uploadcare::Exception::ThrottleError] for 429 + # @raise [Uploadcare::Exception::RequestError] for other statuses + def raise_status_error(response, message) + status = response.is_a?(Hash) ? response[:status] : response + raise Uploadcare::Exception::InvalidRequestError, message if status == 400 + raise Uploadcare::Exception::NotFoundError, message if status == 404 + return raise_throttle_error(response, message) if status == 429 + + raise Uploadcare::Exception::RequestError, message + end + + # Upload API returns its errors with code 200, and stores its actual code and details + # within the response message. This method detects that and raises an appropriate error. + # + # @param response [Hash] Response hash + def catch_upload_errors(response) + return unless response[:status] == 200 + + parsed_response = JSON.parse(response[:body].to_s) + error = parsed_response['error'] if parsed_response.is_a?(Hash) + raise Uploadcare::Exception::RequestError, error if error + rescue JSON::ParserError + nil + end + + # Raise a throttle error with retry-after timeout. + # + # @param response [Hash] Response hash + # @param message [String] Error message + # @raise [Uploadcare::Exception::ThrottleError] + def raise_throttle_error(response, message) + headers = response.is_a?(Hash) ? response[:headers] : nil + retry_after = headers && (headers['retry-after'] || headers['Retry-After']) + timeout = + if retry_after.to_s.match?(/\A\d+(\.\d+)?\z/) + retry_after.to_f + else + begin + [Time.httpdate(retry_after.to_s) - Time.now, 0].max + rescue ArgumentError + 0 + end + end + timeout = 10.0 if timeout <= 0 + raise Uploadcare::Exception::ThrottleError.new(message, timeout: timeout) + end +end diff --git a/lib/uploadcare/internal/signature_generator.rb b/lib/uploadcare/internal/signature_generator.rb new file mode 100644 index 00000000..f3f9b765 --- /dev/null +++ b/lib/uploadcare/internal/signature_generator.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'openssl' + +# Generates HMAC-SHA256 signatures for signed uploads. +# +# Used when `config.sign_uploads` is enabled to generate authentication +# signatures for Upload API requests. +# +# @example +# Uploadcare::Internal::SignatureGenerator.call(config: config) +# # => { signature: "abc123...", expire: 1234567890 } +class Uploadcare::Internal::SignatureGenerator + # Generate signature params for signed uploads. + # + # @param config [Uploadcare::Configuration] Configuration with secret key and lifetime + # @return [Hash] Hash with :signature and :expire keys + # @raise [ArgumentError] if secret_key is empty or lifetime is invalid + def self.call(config: Uploadcare.configuration) + secret_key = config.secret_key.to_s + lifetime = config.upload_signature_lifetime + raise ArgumentError, 'secret_key is required for upload signature' if secret_key.empty? + unless lifetime.is_a?(Integer) && lifetime.positive? + raise ArgumentError, 'upload_signature_lifetime must be a positive Integer' + end + + expires_at = Time.now.to_i + lifetime + signature = OpenSSL::HMAC.hexdigest('sha256', secret_key, expires_at.to_s) + { signature: signature, expire: expires_at } + end +end diff --git a/lib/uploadcare/internal/throttle_handler.rb b/lib/uploadcare/internal/throttle_handler.rb new file mode 100644 index 00000000..6c9eb8c9 --- /dev/null +++ b/lib/uploadcare/internal/throttle_handler.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# Handles API rate limiting (throttling) with automatic retry. +# +# This module is included in API base classes to provide automatic retry logic +# when the API returns a throttle error (HTTP 429). It respects the retry-after +# header (via ThrottleError#timeout) and implements exponential backoff. +# +# @see https://uploadcare.com/docs/api_reference/rest/rate_limiting/ +module Uploadcare::Internal::ThrottleHandler + # Execute a block with automatic retry on throttle errors. + # + # Wraps an HTTP request and automatically retries if a ThrottleError is raised. + # Sleep duration between retries is determined by the error's timeout value + # with exponential backoff. + # + # @param max_attempts [Integer, nil] Maximum retry attempts (defaults to config value) + # @yield Block containing the HTTP request to execute + # @return [Object] The result of the block execution + # @raise [Uploadcare::Exception::ThrottleError] if max retry attempts exceeded + def handle_throttling(max_attempts: nil) + attempts = max_attempts + if attempts.nil? + attempts = respond_to?(:config) ? config.max_throttle_attempts : Uploadcare.configuration.max_throttle_attempts + end + attempts = attempts.to_i + raise ArgumentError, 'max_attempts must be at least 1' if attempts < 1 + + (attempts - 1).times do |index| + return yield + rescue Uploadcare::Exception::ThrottleError => e + sleep(e.timeout * (2**index)) + end + yield + end +end diff --git a/lib/uploadcare/internal/upload_io.rb b/lib/uploadcare/internal/upload_io.rb new file mode 100644 index 00000000..d7003efb --- /dev/null +++ b/lib/uploadcare/internal/upload_io.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'tempfile' + +# Wraps upload inputs so multipart and direct uploads can treat files and streams uniformly. +class Uploadcare::Internal::UploadIo + # Fallback filename used when the source object does not expose one. + DEFAULT_FILENAME = 'upload.bin' + + attr_reader :io, :original_filename + + # Wrap a readable source into a unified upload object. + # + # @param source [IO] + # @param filename [String, nil] + # @return [Uploadcare::Internal::UploadIo] + def self.wrap(source, filename: nil) + raise ArgumentError, 'file must be a readable IO object' unless source.respond_to?(:read) + + if path_backed?(source) + new(source, original_filename: filename || extract_filename(source), cleanup: false) + else + wrap_stream(source, filename: filename || extract_filename(source)) + end + end + + # @param source [IO] + # @return [Boolean] + def self.path_backed?(source) + source.respond_to?(:path) && + source.path && + ::File.file?(source.path) && + ::File.readable?(source.path) + end + + # @param source [IO] + # @return [String] + def self.extract_filename(source) + if source.respond_to?(:original_filename) && source.original_filename && !source.original_filename.empty? + source.original_filename + elsif path_backed?(source) + ::File.basename(source.path) + else + DEFAULT_FILENAME + end + end + + # Materialize a non-path-backed stream into a tempfile. + # + # @param source [IO] + # @param filename [String] + # @return [Uploadcare::Internal::UploadIo] + def self.wrap_stream(source, filename:) + extension = ::File.extname(filename.to_s) + tempfile = Tempfile.new(['uploadcare-upload', extension.empty? ? '.bin' : extension]) + tempfile.binmode + + source.rewind if source.respond_to?(:rewind) + IO.copy_stream(source, tempfile) + tempfile.rewind + source.rewind if source.respond_to?(:rewind) + + new(tempfile, original_filename: filename, cleanup: true) + end + + # @param io [IO] + # @param original_filename [String] + # @param cleanup [Boolean] + def initialize(io, original_filename:, cleanup:) + @io = io + @original_filename = original_filename + @cleanup = cleanup + end + + # @return [String] + def path + io.path + end + + # @return [Integer] + def size + return io.size if io.respond_to?(:size) + + ::File.size(path) + end + + # @return [String, nil] + def read(*args) + io.read(*args) + end + + # @return [Integer] + def seek(*args) + io.seek(*args) + end + + # @return [void] + def rewind + io.rewind + end + + # Close and unlink the wrapped tempfile when cleanup is enabled. + # + # @return [void] + def close! + return unless @cleanup + + io.close! + end +end diff --git a/lib/uploadcare/internal/upload_params_generator.rb b/lib/uploadcare/internal/upload_params_generator.rb new file mode 100644 index 00000000..520f7b46 --- /dev/null +++ b/lib/uploadcare/internal/upload_params_generator.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +# Generates upload parameters for Upload API requests. +# +# Builds the parameter hash needed for file uploads, including public key, +# store preferences, metadata, and optional signature params. +class Uploadcare::Internal::UploadParamsGenerator + class << self + # Build upload parameters. + # + # @param options [Hash] Upload options (:store, :metadata, :signature, :expire) + # @param config [Uploadcare::Configuration] Configuration with public key and signing settings + # @return [Hash] Upload parameters hash + def call(options: {}, config: Uploadcare.configuration) + params = { + 'UPLOADCARE_PUB_KEY' => config.public_key + } + + store = store_value(options[:store]) + params['UPLOADCARE_STORE'] = store unless store.nil? + + params.merge!(metadata(options: options)) + params.merge!(signature_params(options: options, config: config)) + + params.compact + end + + private + + # Convert store option to API format. + # + # @param store [Boolean, String, Integer, nil] Store option value + # @return [String, nil] Formatted store value ('0', '1', or nil) + def store_value(store) + return nil if store.nil? + + case store + when true, '1', 1 then '1' + when false, '0', 0 then '0' + else store.to_s + end + end + + # Generate metadata parameters from options hash. + # + # @param options [Hash] Options containing :metadata key + # @return [Hash] Metadata params formatted as "metadata[key]" => "value" + def metadata(options:) + return {} if options[:metadata].nil? + raise ArgumentError, 'metadata must be a hash' unless options[:metadata].is_a?(Hash) + + options[:metadata].each_with_object({}) do |(k, v), res| + res["metadata[#{k}]"] = v.to_s + end + end + + # Generate signature parameters for signed uploads. + # + # @param options [Hash] Options with optional :signature and :expire keys + # @param config [Uploadcare::Configuration] Configuration with signing settings + # @return [Hash] Signature parameters + def signature_params(options:, config:) + return explicit_signature_params(options) if options.key?(:signature) + return {} unless config.sign_uploads + + signature_data = Uploadcare::Internal::SignatureGenerator.call(config: config) + return { 'signature' => signature_data } unless signature_data.is_a?(Hash) + + params = {} + params['signature'] = signature_data[:signature] || signature_data['signature'] + params['expire'] = signature_data[:expire] || signature_data['expire'] + params.compact + end + + # Extract explicit signature params from options. + # + # @param options [Hash] Options with :signature and optional :expire + # @return [Hash] Signature parameters + def explicit_signature_params(options) + params = {} + params['signature'] = options[:signature] + params['expire'] = options[:expire] if options.key?(:expire) + params.compact + end + end +end diff --git a/lib/uploadcare/internal/user_agent.rb b/lib/uploadcare/internal/user_agent.rb new file mode 100644 index 00000000..cb9b1014 --- /dev/null +++ b/lib/uploadcare/internal/user_agent.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# User-Agent string builder for Uploadcare API requests. +# +# Generates a standardized User-Agent header that identifies the Ruby gem version, +# public key, Ruby version, and optional framework information. +# +# @example +# Uploadcare::Internal::UserAgent.call(config: config) +# # => "UploadcareRuby/5.0.0/demopublickey (Ruby/3.3.0)" +class Uploadcare::Internal::UserAgent + # Build a User-Agent string. + # + # @param config [Uploadcare::Configuration] Configuration with public key and framework data + # @return [String] Formatted User-Agent string + def self.call(config: Uploadcare.configuration) + framework_data = config.framework_data.to_s + framework_suffix = framework_data.empty? ? '' : "; #{framework_data}" + public_key = config.public_key + "UploadcareRuby/#{Uploadcare::VERSION}/#{public_key} (Ruby/#{RUBY_VERSION}#{framework_suffix})" + end +end diff --git a/lib/uploadcare/operations/multipart_upload.rb b/lib/uploadcare/operations/multipart_upload.rb new file mode 100644 index 00000000..e8373303 --- /dev/null +++ b/lib/uploadcare/operations/multipart_upload.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require 'mime/types' + +# Handles the complete multipart upload workflow. +# +# Multipart upload is used for files larger than the multipart threshold (default: 100MB). +# The process: +# 1. Start upload → get UUID and presigned URLs +# 2. Upload file parts to presigned URLs (optionally in parallel) +# 3. Complete upload → finalize and get file info +# +# @example +# mp = Uploadcare::Operations::MultipartUpload.new(upload_client: upload, config: config) +# result = mp.upload(file: large_file, store: true, threads: 4) +class Uploadcare::Operations::MultipartUpload + CHUNK_SIZE = 5_242_880 # 5MB default chunk size + + # @return [Uploadcare::Api::Upload] Upload API client + attr_reader :upload_client + + # @return [Uploadcare::Configuration] Configuration + attr_reader :config + + # @param upload_client [Uploadcare::Api::Upload] Upload API client + # @param config [Uploadcare::Configuration] Configuration + def initialize(upload_client:, config:) + @upload_client = upload_client + @config = config + end + + # Execute the full multipart upload flow. + # + # @param file [File, IO] File to upload + # @param options [Hash] Upload options (:store, :metadata, :threads, :part_size) + # @param request_options [Hash] Request options + # @yield [Hash] Progress callback with :uploaded, :total, :part, :total_parts + # @return [Uploadcare::Result] Result containing { 'uuid' => '...' } + def upload(file:, request_options: {}, **options, &block) + Uploadcare::Result.capture do + prepared_file = Uploadcare::Internal::UploadIo.wrap(file) + file_size = prepared_file.size + filename = prepared_file.original_filename + content_type = MIME::Types.type_for(prepared_file.path).first&.content_type || 'application/octet-stream' + part_size, threads = normalize_upload_options(options) + + start_response = Uploadcare::Result.unwrap( + upload_client.files.multipart_start( + filename: filename, + size: file_size, + content_type: content_type, + request_options: request_options, + **options + ) + ) + + uuid = start_response['uuid'] + presigned_urls = start_response['parts'] + + if threads > 1 + upload_parts_parallel(prepared_file, presigned_urls, part_size, threads, &block) + else + upload_parts_sequential(prepared_file, presigned_urls, part_size, &block) + end + + Uploadcare::Result.unwrap( + upload_client.files.multipart_complete(uuid: uuid, request_options: request_options) + ) + + { 'uuid' => uuid } + ensure + prepared_file&.close! + end + end + + private + + def normalize_upload_options(options) + part_size = Integer(options.fetch(:part_size, config.multipart_chunk_size || CHUNK_SIZE)) + max_threads = Integer(config.upload_threads || 1) + threads = Integer(options.fetch(:threads, max_threads)) + + raise ArgumentError, 'part_size must be > 0' if part_size <= 0 + raise ArgumentError, 'upload_threads must be >= 1' if max_threads < 1 + raise ArgumentError, 'threads must be >= 1' if threads < 1 + raise ArgumentError, "threads must be <= #{max_threads}" if threads > max_threads + + [part_size, threads] + end + + def upload_parts_sequential(file, presigned_urls, part_size, &block) + total_size = file.respond_to?(:size) ? file.size : ::File.size(file.path) + uploaded = 0 + + presigned_urls.each_with_index do |presigned_url, index| + file.seek(index * part_size) + part_data = file.read(part_size) + break if part_data.nil? || part_data.empty? + + upload_part(presigned_url, part_data) + uploaded += part_data.bytesize + + block&.call(uploaded: uploaded, total: total_size, part: index + 1, total_parts: presigned_urls.length) + end + end + + def upload_parts_parallel(file, presigned_urls, part_size, threads, &) + total_size = file.respond_to?(:size) ? file.size : ::File.size(file.path) + uploaded = { value: 0 } + mutex = Mutex.new + queue = Queue.new + errors = [] + cancel = { value: false } + file_path = file.path + total_parts = presigned_urls.length + + presigned_urls.each_with_index { |url, index| queue << [url, index] } + threads.times { queue << nil } + + worker_context = { + queue: queue, + file_path: file_path, + part_size: part_size, + total_size: total_size, + total_parts: total_parts, + mutex: mutex, + uploaded: uploaded, + errors: errors, + cancel: cancel + } + + workers = threads.times.map do + Thread.new do + run_parallel_worker(worker_context, &) + end + end + + workers.each(&:join) + raise errors.first if errors.any? + end + + def run_parallel_worker(context, &) + ::File.open(context[:file_path], 'rb') do |worker_file| + process_parallel_jobs(worker_file, context, &) + rescue StandardError => e + record_parallel_error(context, e) + end + end + + def process_parallel_jobs(worker_file, context, &) + loop do + job = context[:queue].pop + break unless process_parallel_job?(worker_file, context, job, &) + end + end + + def process_parallel_job?(worker_file, context, job) + return false if job.nil? || context[:cancel][:value] + + presigned_url, index = job + offset = index * context[:part_size] + return false if offset >= context[:total_size] + + worker_file.seek(offset) + part_data = worker_file.read(context[:part_size]) + return false if part_data.nil? || part_data.empty? + + upload_part(presigned_url, part_data) + update_parallel_progress(context, index, part_data.bytesize) { |progress| yield(progress) if block_given? } + true + end + + def update_parallel_progress(context, index, bytesize) + context[:mutex].synchronize do + context[:uploaded][:value] += bytesize + progress = { + uploaded: context[:uploaded][:value], + total: context[:total_size], + part: index + 1, + total_parts: context[:total_parts] + } + yield(progress) + end + end + + def record_parallel_error(context, error) + context[:mutex].synchronize do + context[:cancel][:value] = true + context[:errors] << error + end + end + + def upload_part(presigned_url, part_data) + upload_client.upload_part_to_url( + presigned_url, + part_data, + max_retries: configured_max_upload_retries, + timeout: configured_upload_timeout + ) + end + + def configured_max_upload_retries + value = config.max_upload_retries + value.nil? ? 3 : Integer(value) + end + + def configured_upload_timeout + value = config.upload_timeout + return nil if value.nil? + + Integer(value) + end +end diff --git a/lib/uploadcare/operations/upload_router.rb b/lib/uploadcare/operations/upload_router.rb new file mode 100644 index 00000000..0fc78396 --- /dev/null +++ b/lib/uploadcare/operations/upload_router.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +# Routes upload requests to the appropriate Upload API endpoint. +# +# Handles the decision logic for choosing between: +# - Direct upload (small files < multipart threshold) +# - Multipart upload (large files >= multipart threshold) +# - URL upload (string URLs) +# - Batch upload (arrays of files) +# +# @example +# router = Uploadcare::Operations::UploadRouter.new(client: client) +# file = router.upload(File.open("image.jpg")) +# file = router.upload("https://example.com/image.jpg") +# files = router.upload([file1, file2]) +class Uploadcare::Operations::UploadRouter + # @return [Uploadcare::Client] Client instance + attr_reader :client + + # @param client [Uploadcare::Client] Client instance + def initialize(client:) + @client = client + end + + # Upload a file, URL, or array of files. + # + # Automatically routes to the appropriate upload method based on the source type: + # - File/IO objects >= multipart threshold → multipart upload + # - File/IO objects < multipart threshold → direct upload + # - Arrays → batch direct upload + # - Strings → URL upload + # + # @param source [File, IO, String, Array] Upload source + # @param options [Hash] Upload options (:store, :metadata, etc.) + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File, Array, Hash] + # @raise [ArgumentError] if source type is not recognized + def upload(source, request_options: {}, **options, &block) + if big_file?(source) + multipart_upload(file: source, request_options: request_options, **options, &block) + elsif file?(source) + upload_file(file: source, request_options: request_options, **options) + elsif source.is_a?(Array) + upload_files(files: source, request_options: request_options, **options) + elsif source.is_a?(String) + upload_from_url(url: source, request_options: request_options, **options) + else + raise ArgumentError, "Expected input to be a File/Array/URL, given: `#{source}`" + end + end + + # Upload a single file directly. + # + # @param file [File, IO] File to upload + # @param options [Hash] Upload options + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File] + def upload_file(file:, request_options: {}, **options) + response = Uploadcare::Result.unwrap( + client.api.upload.files.direct_many(files: [file], request_options: request_options, **options) + ) + file_name, uuid = response.first + Uploadcare::Resources::File.new({ uuid: uuid, original_filename: decode_uploaded_filename(file_name) }, client) + end + + # Upload multiple files directly. + # + # @param files [Array] Files to upload + # @param options [Hash] Upload options + # @param request_options [Hash] Request options + # @return [Array] + def upload_files(files:, request_options: {}, **options) + response = Uploadcare::Result.unwrap( + client.api.upload.files.direct_many(files: files, request_options: request_options, **options) + ) + response.map do |file_name, uuid| + Uploadcare::Resources::File.new({ uuid: uuid, original_filename: decode_uploaded_filename(file_name) }, client) + end + end + + # Upload a file from URL. + # + # @param url [String] Source URL + # @param options [Hash] Upload options (:async, :store, :metadata) + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File, Hash] File resource (sync) or token hash (async) + def upload_from_url(url:, request_options: {}, **options) + response = Uploadcare::Result.unwrap( + client.api.upload.files.from_url(source_url: url, request_options: request_options, **options) + ) + return response if options[:async] + + Uploadcare::Resources::File.new(response, client) + end + + # Upload a large file using multipart upload. + # + # @param file [File, IO] Large file to upload + # @param options [Hash] Upload options + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File] + def multipart_upload(file:, request_options: {}, **options, &block) + response = Uploadcare::Result.unwrap( + Uploadcare::Operations::MultipartUpload.new( + upload_client: client.api.upload, + config: client.config + ).upload(file: file, request_options: request_options, **options, &block) + ) + return response unless response.is_a?(Hash) && response['uuid'] + + Uploadcare::Resources::File.new(response, client) + end + + # Get upload-from-URL status. + # + # @param token [String] Upload token + # @param request_options [Hash] Request options + # @return [Hash] Status response + def upload_from_url_status(token:, request_options: {}) + Uploadcare::Result.unwrap( + client.api.upload.files.from_url_status(token: token, request_options: request_options) + ) + end + + # Get file info from Upload API (without secret key). + # + # @param file_id [String] File UUID + # @param request_options [Hash] Request options + # @return [Hash] File information + def file_info(file_id:, request_options: {}) + Uploadcare::Result.unwrap( + client.api.upload.files.info(file_id: file_id, request_options: request_options) + ) + end + + private + + def file?(object) + !object.is_a?(String) && object.respond_to?(:read) + end + + def big_file?(object) + return false unless file?(object) + + upload_size(object) >= client.config.multipart_size_threshold + rescue StandardError + false + end + + def upload_size(object) + return object.size if object.respond_to?(:size) + return ::File.size(object.path) if object.respond_to?(:path) && object.path && ::File.exist?(object.path) + + 0 + end + + def decode_uploaded_filename(file_name) + encoded_prefix = /\A__uploadcare_form_\d+(?:_\d+)?__(.+)\z/ + match = encoded_prefix.match(file_name.to_s) + match ? match[1] : file_name + end +end diff --git a/lib/uploadcare/param/authentication_header.rb b/lib/uploadcare/param/authentication_header.rb deleted file mode 100644 index 4c878094..00000000 --- a/lib/uploadcare/param/authentication_header.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require 'digest/md5' -require 'param/secure_auth_header' -require 'param/simple_auth_header' - -module Uploadcare - module Param - # This object returns headers needed for authentication - # This authentication method is more secure, but more tedious - class AuthenticationHeader - # @see https://uploadcare.com/docs/api_reference/rest/requests_auth/#auth-uploadcare - def self.call(options = {}) - validate_auth_config - case Uploadcare.config.auth_type - when 'Uploadcare' - SecureAuthHeader.call(options) - when 'Uploadcare.Simple' - SimpleAuthHeader.call - else - raise ArgumentError, "Unknown auth_scheme: '#{Uploadcare.config.auth_type}'" - end - end - - def self.validate_auth_config - if empty_config_for?(Uploadcare.config.public_key) - raise Uploadcare::Exception::AuthError, - 'Public Key is blank.' - end - return unless empty_config_for?(Uploadcare.config.secret_key) - - raise Uploadcare::Exception::AuthError, - 'Secret Key is blank.' - end - - def self.empty_config_for?(value) - value.nil? || value.empty? - end - end - end -end diff --git a/lib/uploadcare/param/conversion/document/processing_job_url_builder.rb b/lib/uploadcare/param/conversion/document/processing_job_url_builder.rb deleted file mode 100644 index 923080a4..00000000 --- a/lib/uploadcare/param/conversion/document/processing_job_url_builder.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Param - module Conversion - module Document - class ProcessingJobUrlBuilder - class << self - def call(uuid:, format: nil, page: nil) - [ - uuid_part(uuid), - format_part(format), - page_part(page) - ].compact.join('-') - end - - private - - def uuid_part(uuid) - "#{uuid}/document/" - end - - def format_part(format) - return if format.nil? - - "/format/#{format}/" - end - - def page_part(page) - return if page.nil? - - "/page/#{page}/" - end - end - end - end - end - end -end diff --git a/lib/uploadcare/param/conversion/video/processing_job_url_builder.rb b/lib/uploadcare/param/conversion/video/processing_job_url_builder.rb deleted file mode 100644 index 0c6e61f9..00000000 --- a/lib/uploadcare/param/conversion/video/processing_job_url_builder.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Param - module Conversion - module Video - class ProcessingJobUrlBuilder - class << self - # rubocop:disable Metrics/ParameterLists - def call(uuid:, size: {}, quality: nil, format: nil, cut: {}, thumbs: {}) - [ - uuid_part(uuid), - size_part(size), - quality_part(quality), - format_part(format), - cut_part(cut), - thumbs_part(thumbs) - ].compact.join('-') - end - # rubocop:enable Metrics/ParameterLists - - private - - def uuid_part(uuid) - "#{uuid}/video/" - end - - def size_part(size) - return if size.empty? - - dimensions = "#{size[:width]}x#{size[:height]}" if size[:width] || size[:height] - resize_mode = size[:resize_mode].to_s - "/size/#{dimensions}/#{resize_mode}/".squeeze('/') - end - - def quality_part(quality) - return if quality.nil? - - "/quality/#{quality}/" - end - - def format_part(format) - return if format.nil? - - "/format/#{format}/" - end - - def cut_part(cut) - return if cut.empty? - - "/cut/#{cut[:start_time]}/#{cut[:length]}/" - end - - def thumbs_part(thumbs) - return if thumbs.empty? - - "/thumbs~#{thumbs[:N]}/#{thumbs[:number]}/".squeeze('/') - end - end - end - end - end - end -end diff --git a/lib/uploadcare/param/param.rb b/lib/uploadcare/param/param.rb deleted file mode 100644 index defe5ae0..00000000 --- a/lib/uploadcare/param/param.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - # @abstract - # This module is responsible for everything related to generation of request params - - # such as authentication headers, signatures and serialized uploads - module Param - end - include Param -end diff --git a/lib/uploadcare/param/secure_auth_header.rb b/lib/uploadcare/param/secure_auth_header.rb deleted file mode 100644 index fb5db9da..00000000 --- a/lib/uploadcare/param/secure_auth_header.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require 'digest/md5' -require 'addressable/uri' - -module Uploadcare - module Param - # This object returns headers needed for authentication - # This authentication method is more secure, but more tedious - class SecureAuthHeader - class << self - # @see https://uploadcare.com/docs/api_reference/rest/requests_auth/#auth-uploadcare - def call(options = {}) - @method = options[:method] - @body = options[:content] || '' - @content_type = options[:content_type] - @uri = make_uri(options) - - @date_for_header = timestamp - { - Date: @date_for_header, - Authorization: "Uploadcare #{Uploadcare.config.public_key}:#{signature}" - } - end - - def signature - content_md5 = Digest::MD5.hexdigest(@body) - sign_string = [@method, content_md5, @content_type, @date_for_header, @uri].join("\n") - digest = OpenSSL::Digest.new('sha1') - OpenSSL::HMAC.hexdigest(digest, Uploadcare.config.secret_key, sign_string) - end - - def timestamp - Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT') - end - - private - - def make_uri(options) - if options[:params] && !options[:params].empty? - uri = Addressable::URI.parse options[:uri] - uri.query_values = uri.query_values(Array).to_a.concat(options[:params].to_a) - uri.to_s - else - options[:uri] - end - end - end - end - end -end diff --git a/lib/uploadcare/param/simple_auth_header.rb b/lib/uploadcare/param/simple_auth_header.rb deleted file mode 100644 index f72bad2b..00000000 --- a/lib/uploadcare/param/simple_auth_header.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - module Param - # This object returns simple header for authentication - # Simple header is relatively unsafe, but can be useful for debug and development - class SimpleAuthHeader - # @see https://uploadcare.com/docs/api_reference/rest/requests_auth/#auth-simple - def self.call - { Authorization: "Uploadcare.Simple #{Uploadcare.config.public_key}:#{Uploadcare.config.secret_key}" } - end - end - end -end diff --git a/lib/uploadcare/param/upload/signature_generator.rb b/lib/uploadcare/param/upload/signature_generator.rb deleted file mode 100644 index 23ff3fc5..00000000 --- a/lib/uploadcare/param/upload/signature_generator.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'digest' - -module Uploadcare - module Param - module Upload - # This class generates signatures for protected uploads - class SignatureGenerator - # @see https://uploadcare.com/docs/api_reference/upload/signed_uploads/ - # @return [Hash] signature and its expiration time - def self.call - expires_at = Time.now.to_i + Uploadcare.config.upload_signature_lifetime - to_sign = Uploadcare.config.secret_key + expires_at.to_s - signature = Digest::MD5.hexdigest(to_sign) - { - signature: signature, - expire: expires_at - } - end - end - end - end -end diff --git a/lib/uploadcare/param/upload/upload_params_generator.rb b/lib/uploadcare/param/upload/upload_params_generator.rb deleted file mode 100644 index 01a0128f..00000000 --- a/lib/uploadcare/param/upload/upload_params_generator.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require 'digest' - -module Uploadcare - module Param - module Upload - # This class generates body params for uploads - class UploadParamsGenerator - # @see https://uploadcare.com/docs/api_reference/upload/request_based/ - class << self - def call(options = {}) - { - 'UPLOADCARE_PUB_KEY' => Uploadcare.config.public_key, - 'UPLOADCARE_STORE' => store_value(options[:store]), - 'signature' => (Upload::SignatureGenerator.call if Uploadcare.config.sign_uploads) - }.merge(metadata(options)).compact - end - - private - - def store_value(store) - case store - when true, '1', 1 then '1' - when false, '0', 0 then '0' - else 'auto' - end - end - - def metadata(options = {}) - return {} if options[:metadata].nil? - - options[:metadata].each_with_object({}) do |(k, v), res| - res.merge!("metadata[#{k}]" => v) - end - end - end - end - end - end -end diff --git a/lib/uploadcare/param/user_agent.rb b/lib/uploadcare/param/user_agent.rb deleted file mode 100644 index 564c6066..00000000 --- a/lib/uploadcare/param/user_agent.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'uploadcare' - -module Uploadcare - module Param - # This header is added to track libraries using Uploadcare API - class UserAgent - # Generate header from Gem's config - # - # @example Uploadcare::Param::UserAgent.call - # UploadcareRuby/3.0.0-dev/Pubkey_(Ruby/2.6.3;UploadcareRuby) - def self.call - framework_data = Uploadcare.config.framework_data || '' - framework_data_string = "; #{Uploadcare.config.framework_data}" unless framework_data.empty? - public_key = Uploadcare.config.public_key - "UploadcareRuby/#{VERSION}/#{public_key} (Ruby/#{RUBY_VERSION}#{framework_data_string})" - end - end - end -end diff --git a/lib/uploadcare/param/webhook_signature_verifier.rb b/lib/uploadcare/param/webhook_signature_verifier.rb deleted file mode 100644 index 3ebdaf1d..00000000 --- a/lib/uploadcare/param/webhook_signature_verifier.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'digest/md5' - -module Uploadcare - module Param - # This object verifies a signature received along with webhook headers - class WebhookSignatureVerifier - # @see https://uploadcare.com/docs/security/secure-webhooks/ - def self.valid?(options = {}) - webhook_body_json = options[:webhook_body] - signing_secret = options[:signing_secret] || ENV.fetch('UC_SIGNING_SECRET', nil) - x_uc_signature_header = options[:x_uc_signature_header] - - digest = OpenSSL::Digest.new('sha256') - - calculated_signature = "v1=#{OpenSSL::HMAC.hexdigest(digest, signing_secret, webhook_body_json)}" - - calculated_signature == x_uc_signature_header - end - end - end -end diff --git a/lib/uploadcare/resources/addon_execution.rb b/lib/uploadcare/resources/addon_execution.rb new file mode 100644 index 00000000..d0b355d0 --- /dev/null +++ b/lib/uploadcare/resources/addon_execution.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +# Add-on execution resource. +# +# Provides a unified interface for executing and checking status of +# AWS Rekognition, ClamAV, and Remove.bg add-ons. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons +class Uploadcare::Resources::AddonExecution < Uploadcare::Resources::BaseResource + attr_accessor :request_id, :status, :result + + class << self + # Execute AWS Rekognition label detection. + def aws_rekognition_detect_labels(uuid:, client: nil, config: Uploadcare.configuration, request_options: {}) + execute_addon(:aws_rekognition_detect_labels, client: client, config: config, + request_options: request_options, uuid: uuid) + end + + # Check AWS Rekognition label detection status. + def aws_rekognition_detect_labels_status(request_id:, client: nil, config: Uploadcare.configuration, + request_options: {}) + check_addon_status(:aws_rekognition_detect_labels_status, client: client, config: config, + request_options: request_options, + request_id: request_id) + end + + # Execute AWS Rekognition moderation label detection. + def aws_rekognition_detect_moderation_labels(uuid:, client: nil, config: Uploadcare.configuration, + request_options: {}) + execute_addon(:aws_rekognition_detect_moderation_labels, client: client, config: config, + request_options: request_options, uuid: uuid) + end + + # Check AWS Rekognition moderation label detection status. + def aws_rekognition_detect_moderation_labels_status(request_id:, client: nil, + config: Uploadcare.configuration, request_options: {}) + check_addon_status(:aws_rekognition_detect_moderation_labels_status, client: client, config: config, + request_options: request_options, + request_id: request_id) + end + + # Execute ClamAV virus scan. + def uc_clamav_virus_scan(uuid:, params: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.addons.uc_clamav_virus_scan( + uuid: uuid, params: params, request_options: request_options + ) + ) + new(response, resolved_client) + end + + # Check ClamAV virus scan status. + def uc_clamav_virus_scan_status(request_id:, client: nil, config: Uploadcare.configuration, + request_options: {}) + check_addon_status(:uc_clamav_virus_scan_status, client: client, config: config, + request_options: request_options, request_id: request_id) + end + + # Execute Remove.bg background removal. + def remove_bg(uuid:, params: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.addons.remove_bg( + uuid: uuid, params: params, request_options: request_options + ) + ) + new(response, resolved_client) + end + + # Check Remove.bg execution status. + def remove_bg_status(request_id:, client: nil, config: Uploadcare.configuration, request_options: {}) + check_addon_status(:remove_bg_status, client: client, config: config, + request_options: request_options, request_id: request_id) + end + + private + + def execute_addon(method_name, client:, config:, request_options:, uuid:) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.addons.public_send(method_name, uuid: uuid, request_options: request_options) + ) + new(response, resolved_client) + end + + def check_addon_status(method_name, client:, config:, request_options:, request_id:) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.addons.public_send( + method_name, request_id: request_id, request_options: request_options + ) + ) + new(response, resolved_client) + end + end +end diff --git a/lib/uploadcare/resources/base_resource.rb b/lib/uploadcare/resources/base_resource.rb new file mode 100644 index 00000000..2dadd765 --- /dev/null +++ b/lib/uploadcare/resources/base_resource.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# Base class for all Uploadcare resource objects. +# +# Resources represent domain objects returned by the Uploadcare API. +# They hold attributes and a reference to the client that created them, +# enabling instance methods to make further API calls. +class Uploadcare::Resources::BaseResource + # @return [Uploadcare::Client] Client that created this resource + attr_reader :client + + # @return [Uploadcare::Configuration] Configuration from the client + attr_reader :config + + # Initialize a new resource with attributes and client context. + # + # @param attributes [Hash] API response attributes + # @param client_or_config [Uploadcare::Client, Uploadcare::Configuration, nil] + def initialize(attributes = {}, client_or_config = nil) + @client = self.class.resolve_client(client_or_config) + @config = @client.config + assign_attributes(attributes) + end + + protected + + # Assign hash attributes to instance variables via setter methods. + # + # @param attributes [Hash] Key-value pairs to assign + def assign_attributes(attributes) + attributes.each do |key, value| + setter = "#{key}=" + public_send(setter, value) if respond_to?(setter) + end + end + + class << self + # Resolve a client from various input types. + # + # @param client_or_config [Uploadcare::Client, Uploadcare::Configuration, nil] + # @param client [Uploadcare::Client, nil] Explicit client + # @param config [Uploadcare::Configuration] Configuration fallback + # @return [Uploadcare::Client] + def resolve_client(client_or_config = nil, client: nil, config: nil) + return client if client + + case client_or_config + when Uploadcare::Client + client_or_config + when Uploadcare::Configuration + Uploadcare.client(config: client_or_config) + when nil + return Uploadcare.client(config: config) if config + + raise ArgumentError, 'client or config is required' + else + raise ArgumentError, 'client or config is required' + end + end + end +end diff --git a/lib/uploadcare/resources/document_conversion.rb b/lib/uploadcare/resources/document_conversion.rb new file mode 100644 index 00000000..cb0134f4 --- /dev/null +++ b/lib/uploadcare/resources/document_conversion.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +# Document conversion resource. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Conversion +class Uploadcare::Resources::DocumentConversion < Uploadcare::Resources::BaseResource + attr_accessor :uuid, :error, :format, :converted_groups, :status, :result + + # Get document format info and possible conversions. + # + # @param request_options [Hash] Request options + # @return [self] + def info(request_options: {}) + raise ArgumentError, 'uuid is required' if uuid.to_s.empty? + + response = Uploadcare::Result.unwrap( + client.api.rest.document_conversions.info(uuid: uuid, request_options: request_options) + ) + assign_attributes(response) + self + end + + # Convert a document to a specified format (class method). + # + # @param params [Hash] Conversion parameters (:uuid, :format) + # @param options [Hash] Optional parameters (:store, :save_in_group) + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Hash] Conversion response + def self.convert_document(params:, options: {}, client: nil, config: Uploadcare.configuration, + request_options: {}) + resolved_client = resolve_client(client: client, config: config) + paths = Array(params[:uuid]).map do |uuid| + "#{uuid}/document/-/format/#{params[:format]}/" + end + + Uploadcare::Result.unwrap( + resolved_client.api.rest.document_conversions.convert( + paths: paths, options: options, request_options: request_options + ) + ) + end + + # Fetch document conversion status for a job token. + # + # @param token [String] + # @param client [Uploadcare::Client, nil] + # @param config [Uploadcare::Configuration] + # @param request_options [Hash] + # @return [Uploadcare::Resources::DocumentConversion] + def self.status(token:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + new({}, resolved_client).fetch_status(token: token, request_options: request_options) + end + + # Fetch document conversion capabilities for a file. + # + # @param uuid [String] + # @param client [Uploadcare::Client, nil] + # @param config [Uploadcare::Configuration] + # @param request_options [Hash] + # @return [Uploadcare::Resources::DocumentConversion] + def self.info_for(uuid:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + new({ 'uuid' => uuid }, resolved_client).info(request_options: request_options) + end + + # Get conversion job status. + # + # @param token [String] Job token + # @param request_options [Hash] Request options + # @return [self] + def fetch_status(token:, request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.document_conversions.status(token: token, request_options: request_options) + ) + assign_attributes(response) + self + end +end diff --git a/lib/uploadcare/resources/file.rb b/lib/uploadcare/resources/file.rb new file mode 100644 index 00000000..7b575c81 --- /dev/null +++ b/lib/uploadcare/resources/file.rb @@ -0,0 +1,366 @@ +# frozen_string_literal: true + +# File resource representing an uploaded file in Uploadcare. +# +# Provides both class methods (find, list, upload, batch operations, copy) +# and instance methods (store, delete, reload, convert) for working with files. +# +# @example Finding a file +# client = Uploadcare.client +# file = client.files.find(uuid: "file-uuid") +# file.original_filename # => "photo.jpg" +# +# @example Uploading +# client = Uploadcare.client +# file = client.files.upload(::File.open("photo.jpg"), store: true) +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File +class Uploadcare::Resources::File < Uploadcare::Resources::BaseResource + # API fields assigned onto file resources. + ATTRIBUTES = %i[ + datetime_removed datetime_stored datetime_uploaded is_image is_ready mime_type original_file_url + original_filename size url uuid variations content_info metadata appdata source + ].freeze + + attr_writer :uuid + attr_accessor :datetime_removed, :datetime_stored, :datetime_uploaded, :is_image, :is_ready, :mime_type, + :original_file_url, :original_filename, :size, :url, :variations, :content_info, + :metadata, :appdata, :source + + # --- Class methods --- + + # Find a file by UUID. + # + # @param uuid [String] File UUID + # @param params [Hash] Optional parameters (e.g., include: "appdata") + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File] + def self.find(uuid:, params: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.files.info(uuid: uuid, params: params, request_options: request_options) + ) + new(response, resolved_client) + end + + class << self + alias retrieve find + alias info find + end + + # List files with optional filtering and pagination. + # + # @param options [Hash] Query parameters (limit, ordering, etc.) + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Collections::Paginated] + def self.list(options: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.files.list(params: options, request_options: request_options) + ) + + files = response['results'].map { |data| new(data, resolved_client) } + + Uploadcare::Collections::Paginated.new( + resources: files, + next_page: response['next'], + previous_page: response['previous'], + per_page: response['per_page'], + total: response['total'], + api_client: resolved_client.api.rest.files, + resource_class: self, + client: resolved_client, + request_options: request_options + ) + end + + # Upload a single file. + # + # @param file [File, IO] File to upload + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param options [Hash] Upload options + # @return [Uploadcare::Resources::File] + def self.upload(file, client: nil, config: Uploadcare.configuration, request_options: {}, **options) + resolved_client = resolve_client(client: client, config: config) + resolved_client.uploads.upload_file(file: file, request_options: request_options, **options) + end + + # Upload multiple files. + # + # @param files [Array] Files to upload + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param options [Hash] Upload options + # @return [Array] + def self.upload_many(files, client: nil, config: Uploadcare.configuration, request_options: {}, **options) + resolved_client = resolve_client(client: client, config: config) + resolved_client.uploads.upload_files(files: files, request_options: request_options, **options) + end + + # Upload a file from URL. + # + # @param url [String] Source URL + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param options [Hash] Upload options + # @return [Uploadcare::Resources::File] + def self.upload_url(url, client: nil, config: Uploadcare.configuration, request_options: {}, **options) + resolved_client = resolve_client(client: client, config: config) + resolved_client.uploads.upload_from_url(url: url, request_options: request_options, **options) + end + + class << self + alias upload_from_url upload_url + end + + # Batch store files. + # + # @param uuids [Array] File UUIDs to store + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Collections::BatchResult] + def self.batch_store(uuids:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.files.batch_store(uuids: uuids, request_options: request_options) + ) + normalized = response.transform_keys(&:to_s) + + Uploadcare::Collections::BatchResult.new( + status: normalized['status'], + result: normalized['result'], + problems: normalized['problems'] || {}, + client: resolved_client + ) + end + + # Batch delete files. + # + # @param uuids [Array] File UUIDs to delete + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Collections::BatchResult] + def self.batch_delete(uuids:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.files.batch_delete(uuids: uuids, request_options: request_options) + ) + normalized = response.transform_keys(&:to_s) + + Uploadcare::Collections::BatchResult.new( + status: normalized['status'], + result: normalized['result'], + problems: normalized['problems'] || {}, + client: resolved_client + ) + end + + # Copy a file to local storage (class method). + # + # @param source [String] CDN URL or UUID + # @param options [Hash] Optional parameters + # @return [Uploadcare::Resources::File] + def self.local_copy(source:, options: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.files.local_copy(source: source, options: options, request_options: request_options) + ) + new(response['result'], resolved_client) + end + + class << self + alias copy_to_local local_copy + end + + # Copy a file to remote storage (class method). + # + # @param source [String] CDN URL or UUID + # @param target [String] Custom storage name + # @param options [Hash] Optional parameters + # @return [String] URL of the copied file + def self.remote_copy(source:, target:, options: {}, client: nil, config: Uploadcare.configuration, + request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.files.remote_copy( + source: source, target: target, options: options, request_options: request_options + ) + ) + response['result'] + end + + class << self + alias copy_to_remote remote_copy + end + + # --- Instance methods --- + + # Store this file, making it permanently available. + # + # @param request_options [Hash] Request options + # @return [self] + def store(request_options: {}) + response = Uploadcare::Result.unwrap(client.api.rest.files.store(uuid: uuid, request_options: request_options)) + assign_attributes(response) + self + end + + # Delete this file. + # + # @param request_options [Hash] Request options + # @return [self] + def delete(request_options: {}) + response = Uploadcare::Result.unwrap(client.api.rest.files.delete(uuid: uuid, request_options: request_options)) + assign_attributes(response) + self + end + + # Reload file information from the API. + # + # @param params [Hash] Optional parameters + # @param request_options [Hash] Request options + # @return [self] + def reload(params: {}, request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.files.info(uuid: uuid, params: params, request_options: request_options) + ) + assign_attributes(response) + self + end + alias load reload + + # Copy this file to local storage. + # + # @param options [Hash] Optional parameters + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File] The copied file + def copy_to_local(options: {}, request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.files.local_copy(source: uuid, options: options, request_options: request_options) + ) + self.class.new(response['result'], client) + end + alias local_copy copy_to_local + + # Copy this file to remote storage. + # + # @param target [String] Custom storage name + # @param options [Hash] Optional parameters + # @param request_options [Hash] Request options + # @return [String] URL of the copied file + def copy_to_remote(target:, options: {}, request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.files.remote_copy( + source: uuid, target: target, options: options, request_options: request_options + ) + ) + response['result'] + end + alias remote_copy copy_to_remote + + # Convert this file to a document format. + # + # @param params [Hash] Conversion parameters (:format, etc.) + # @param options [Hash] Optional parameters (:store, etc.) + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File] The converted file + def convert_to_document(params: {}, options: {}, request_options: {}) + convert_file(params, Uploadcare::Resources::DocumentConversion, options, request_options: request_options) + end + + # Convert this file to a video format. + # + # @param params [Hash] Conversion parameters (:format, :quality, etc.) + # @param options [Hash] Optional parameters (:store, etc.) + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::File] The converted file + def convert_to_video(params: {}, options: {}, request_options: {}) + convert_file(params, Uploadcare::Resources::VideoConversion, options, request_options: request_options) + end + + # Returns the file UUID, extracting from URL if needed. + # + # @return [String, nil] + def uuid + return @uuid if @uuid + + source = + if @url + @url + elsif uploadcare_cdn_url?(@original_file_url) + @original_file_url + end + return @uuid unless source + + @uuid = source[/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/] + end + + # Returns the CDN URL for this file. + # + # @return [String] + def cdn_url + return @url if uploadcare_cdn_url?(@url) + return @original_file_url if uploadcare_cdn_url?(@original_file_url) + return nil unless uuid + + "#{config.cdn_base}#{uuid}/" + end + + private + + def uploadcare_cdn_url?(value) + value.to_s.start_with?(config.cdn_base.to_s) + end + + def convert_file(params, converter, options = {}, request_options: {}) + raise ArgumentError, 'The first argument must be a Hash' unless params.is_a?(Hash) + + params_with_symbolized_keys = params.transform_keys(&:to_sym) + params_with_symbolized_keys[:uuid] = uuid + + result = if converter.respond_to?(:convert_document) + converter.convert_document( + params: params_with_symbolized_keys, options: options, config: config, + request_options: request_options + ) + elsif converter.respond_to?(:convert) + converter.convert( + params: params_with_symbolized_keys, options: options, config: config, + request_options: request_options + ) + else + raise Uploadcare::Exception::ConversionError, + "Converter #{converter.name} must implement .convert_document or .convert" + end + + process_convert_result(result, request_options: request_options) + end + + def process_convert_result(result, request_options: {}) + if result.is_a?(Hash) && result['result']&.first + return process_hash_result(result, request_options: request_options) + end + + if result.respond_to?(:result) && result.result.is_a?(Array) && result.result.first.is_a?(Hash) + return process_hash_result({ 'result' => result.result }, request_options: request_options) + end + + result + end + + def process_hash_result(result, request_options: {}) + result_data = result['result'].first + if result_data['uuid'] + return self.class.find(uuid: result_data['uuid'], client: client, request_options: request_options) + end + + result + end +end diff --git a/lib/uploadcare/resources/file_metadata.rb b/lib/uploadcare/resources/file_metadata.rb new file mode 100644 index 00000000..15fef6a8 --- /dev/null +++ b/lib/uploadcare/resources/file_metadata.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +# File metadata resource for managing key-value metadata on files. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata +class Uploadcare::Resources::FileMetadata < Uploadcare::Resources::BaseResource + attr_accessor :uuid + + def initialize(attributes = {}, client_or_config = nil) + super + @metadata = {} + end + + # --- Instance methods --- + + # Retrieve all metadata for the file. + # + # @param uuid [String, nil] File UUID (defaults to instance UUID) + # @param request_options [Hash] Request options + # @return [self] + def index(uuid: nil, request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.file_metadata.index(uuid: uuid || @uuid, request_options: request_options) + ) + @metadata = response.is_a?(Hash) ? response.transform_keys(&:to_s) : {} + self + end + + # Access a metadata value by key. + # + # @param key [String, Symbol] Metadata key + # @return [String, nil] Metadata value + def [](key) + @metadata[key.to_s] + end + + # Set a metadata value by key (local only, call #update to persist). + # + # @param key [String, Symbol] Metadata key + # @param value [String] Metadata value + def []=(key, value) + @metadata[key.to_s] = value + end + + # Return all metadata as a hash. + # + # @return [Hash] + def to_h + @metadata.dup + end + + # Update a metadata key's value on the server. + # + # @param key [String] Metadata key + # @param value [String] Metadata value + # @param uuid [String, nil] File UUID (defaults to instance UUID) + # @param request_options [Hash] Request options + # @return [String] The updated value + def update(key:, value:, uuid: nil, request_options: {}) + target_uuid = uuid || @uuid + result = Uploadcare::Result.unwrap( + client.api.rest.file_metadata.update( + uuid: target_uuid, key: key, value: value, request_options: request_options + ) + ) + @metadata[key.to_s] = result if target_uuid == @uuid + result + end + + # Retrieve a single metadata key's value. + # + # @param key [String] Metadata key + # @param uuid [String, nil] File UUID (defaults to instance UUID) + # @param request_options [Hash] Request options + # @return [String] Metadata value + def show(key:, uuid: nil, request_options: {}) + target_uuid = uuid || @uuid + result = Uploadcare::Result.unwrap( + client.api.rest.file_metadata.show(uuid: target_uuid, key: key, request_options: request_options) + ) + @metadata[key.to_s] = result if target_uuid == @uuid + result + end + + # Delete a metadata key. + # + # @param key [String] Metadata key + # @param uuid [String, nil] File UUID (defaults to instance UUID) + # @param request_options [Hash] Request options + # @return [nil] + def delete(key:, uuid: nil, request_options: {}) + target_uuid = uuid || @uuid + result = Uploadcare::Result.unwrap( + client.api.rest.file_metadata.delete(uuid: target_uuid, key: key, request_options: request_options) + ) + @metadata.delete(key.to_s) if target_uuid == @uuid + result + end + + # --- Class methods --- + + # Get all metadata for a file. + def self.index(uuid:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + Uploadcare::Result.unwrap( + resolved_client.api.rest.file_metadata.index(uuid: uuid, request_options: request_options) + ) + end + + # Get a single metadata key's value. + def self.show(uuid:, key:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + Uploadcare::Result.unwrap( + resolved_client.api.rest.file_metadata.show(uuid: uuid, key: key, request_options: request_options) + ) + end + + # Update a metadata key's value. + def self.update(uuid:, key:, value:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + Uploadcare::Result.unwrap( + resolved_client.api.rest.file_metadata.update( + uuid: uuid, key: key, value: value, request_options: request_options + ) + ) + end + + # Delete a metadata key. + def self.delete(uuid:, key:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + Uploadcare::Result.unwrap( + resolved_client.api.rest.file_metadata.delete(uuid: uuid, key: key, request_options: request_options) + ) + end +end diff --git a/lib/uploadcare/resources/group.rb b/lib/uploadcare/resources/group.rb new file mode 100644 index 00000000..455a83f1 --- /dev/null +++ b/lib/uploadcare/resources/group.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require 'uri' + +# Group resource representing a collection of files in Uploadcare. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group +class Uploadcare::Resources::Group < Uploadcare::Resources::BaseResource + # API fields assigned onto group resources. + ATTRIBUTES = %i[ + id datetime_removed datetime_stored datetime_uploaded is_image is_ready mime_type original_file_url cdn_url + original_filename size url uuid variations content_info metadata appdata source datetime_created files_count + files + ].freeze + + attr_writer :id, :cdn_url + attr_accessor :datetime_removed, :datetime_stored, :datetime_uploaded, :is_image, :is_ready, :mime_type, + :original_file_url, :original_filename, :size, :url, :uuid, :variations, + :content_info, :metadata, :appdata, :source, :datetime_created, :files_count, :files + + # --- Class methods --- + + # List groups with optional filtering and pagination. + # + # @param params [Hash] Query parameters + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Collections::Paginated] + def self.list(params: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.groups.list(params: params, request_options: request_options) + ) + groups = response['results'].map { |data| new(data, resolved_client) } + + Uploadcare::Collections::Paginated.new( + resources: groups, + next_page: response['next'], + previous_page: response['previous'], + per_page: response['per_page'], + total: response['total'], + api_client: resolved_client.api.rest.groups, + resource_class: self, + client: resolved_client, + request_options: request_options + ) + end + + # Find a group by ID. + # + # @param group_id [String] Group UUID (formatted as UUID~size) + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::Group] + def self.find(group_id:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.groups.info(uuid: group_id, request_options: request_options) + ) + new(response, resolved_client) + end + + class << self + alias retrieve find + alias info find + end + + # Create a group from file UUIDs. + # + # @param uuids [Array] File UUIDs + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param options [Hash] Additional options + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::Group] + def self.create(uuids:, client: nil, config: Uploadcare.configuration, request_options: {}, **options) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.upload.groups.create( + files: uuids, request_options: request_options, **options + ) + ) + new(response, resolved_client) + end + + # --- Instance methods --- + + # Reload group information from the API. + # + # @param request_options [Hash] Request options + # @return [self] + def reload(request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.groups.info(uuid: id, request_options: request_options) + ) + assign_attributes(response) + self + end + alias load reload + + # Delete this group. + # + # @param request_options [Hash] Request options + # @return [nil] + def delete(request_options: {}) + Uploadcare::Result.unwrap( + client.api.rest.groups.delete(uuid: id, request_options: request_options) + ) + end + + # Returns group ID, extracting from CDN URL if needed. + # + # @return [String, nil] + def id + return @id if @id + return @uuid if defined?(@uuid) && !@uuid.to_s.empty? + return unless @cdn_url + + uri = URI.parse(@cdn_url) + @id = uri.path.split('/').reject(&:empty?).first + end + + # Returns the CDN URL for this group. + # + # @return [String] + def cdn_url + return @cdn_url if @cdn_url && !@cdn_url.empty? + + "#{config.cdn_base}#{id}/" + end + + # Returns CDN URLs for all files in the group. + # + # @return [Array] + def file_cdn_urls + return [] if files_count.nil? + + files_count.times.map { |i| "#{cdn_url}nth/#{i}/" } + end +end diff --git a/lib/uploadcare/resources/project.rb b/lib/uploadcare/resources/project.rb new file mode 100644 index 00000000..f3b0ca0a --- /dev/null +++ b/lib/uploadcare/resources/project.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Project resource representing the current Uploadcare project. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Project +class Uploadcare::Resources::Project < Uploadcare::Resources::BaseResource + attr_accessor :name, :pub_key, :autostore_enabled, :collaborators + + # Get current project information. + # + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::Project] + def self.current(client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.project.show(request_options: request_options) + ) + new(response, resolved_client) + end + + class << self + alias show current + end +end diff --git a/lib/uploadcare/resources/video_conversion.rb b/lib/uploadcare/resources/video_conversion.rb new file mode 100644 index 00000000..71e10a4c --- /dev/null +++ b/lib/uploadcare/resources/video_conversion.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# Video conversion resource. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Video +class Uploadcare::Resources::VideoConversion < Uploadcare::Resources::BaseResource + attr_accessor :problems, :status, :error, :result + + # Convert a video to a specified format (class method). + # + # @param params [Hash] Conversion parameters (:uuid, :format, :quality) + # @param options [Hash] Optional parameters (:store) + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::VideoConversion] + def self.convert(params:, options: {}, client: nil, config: Uploadcare.configuration, request_options: {}) + raise ArgumentError, 'params must include :uuid' unless params[:uuid] + raise ArgumentError, 'params must include :format' unless params[:format] + raise ArgumentError, 'params must include :quality' unless params[:quality] + + paths = Array(params[:uuid]).map do |uuid| + "#{uuid}/video/-/format/#{params[:format]}/-/quality/#{params[:quality]}/" + end + + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.video_conversions.convert( + paths: paths, options: options, request_options: request_options + ) + ) + new(response, resolved_client) + end + + # Fetch video conversion status for a job token. + # + # @param token [String] + # @param client [Uploadcare::Client, nil] + # @param config [Uploadcare::Configuration] + # @param request_options [Hash] + # @return [Uploadcare::Resources::VideoConversion] + def self.status(token:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + new({}, resolved_client).fetch_status(token: token, request_options: request_options) + end + + # Refresh this resource from the conversion status endpoint. + # + # @param token [String] + # @param request_options [Hash] + # @return [self] + def fetch_status(token:, request_options: {}) + response = Uploadcare::Result.unwrap( + client.api.rest.video_conversions.status(token: token, request_options: request_options) + ) + assign_attributes(response) + self + end +end diff --git a/lib/uploadcare/resources/webhook.rb b/lib/uploadcare/resources/webhook.rb new file mode 100644 index 00000000..f4ff873a --- /dev/null +++ b/lib/uploadcare/resources/webhook.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# Webhook resource for managing Uploadcare webhooks. +# +# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook +class Uploadcare::Resources::Webhook < Uploadcare::Resources::BaseResource + attr_accessor :id, :project, :created, :updated, :event, :target_url, :is_active, :signing_secret, :version + + # List all project webhooks. + # + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Array] + def self.list(client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.webhooks.list(request_options: request_options) + ) + response.map { |data| new(data, resolved_client) } + end + + # Create a new webhook. + # + # @param target_url [String] Webhook target URL + # @param options [Hash] Webhook options + # @option options [String] :event Event type (default: "file.uploaded") + # @option options [Boolean] :is_active Active flag (default: true) + # @option options [String] :signing_secret Signing secret + # @option options [String] :version Webhook payload version + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::Webhook] + def self.create(target_url:, client: nil, config: Uploadcare.configuration, request_options: {}, **options) + resolved_client = resolve_client(client: client, config: config) + event = options.fetch(:event, 'file.uploaded') + is_active = options.key?(:is_active) ? options[:is_active] : true + payload = { target_url: target_url, event: event, is_active: is_active } + payload[:signing_secret] = options[:signing_secret] if options[:signing_secret] + payload[:version] = options[:version] if options[:version] + + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.webhooks.create(options: payload, request_options: request_options) + ) + new(response, resolved_client) + end + + # Update a webhook. + # + # @param id [Integer] Webhook ID + # @param options [Hash] Webhook options to update + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [Uploadcare::Resources::Webhook] + def self.update(id:, client: nil, config: Uploadcare.configuration, request_options: {}, **options) + resolved_client = resolve_client(client: client, config: config) + payload = options.slice(:target_url, :event, :signing_secret, :version) + payload[:is_active] = options[:is_active] if options.key?(:is_active) + + response = Uploadcare::Result.unwrap( + resolved_client.api.rest.webhooks.update(id: id, options: payload, request_options: request_options) + ) + new(response, resolved_client) + end + + # Delete a webhook by target URL. + # + # @param target_url [String] Target URL of the webhook to delete + # @param client [Uploadcare::Client, nil] Client instance + # @param config [Uploadcare::Configuration] Configuration fallback + # @param request_options [Hash] Request options + # @return [nil] + def self.delete(target_url:, client: nil, config: Uploadcare.configuration, request_options: {}) + resolved_client = resolve_client(client: client, config: config) + Uploadcare::Result.unwrap( + resolved_client.api.rest.webhooks.delete(target_url: target_url, request_options: request_options) + ) + end + + class << self + alias unsubscribe delete + end +end diff --git a/lib/uploadcare/result.rb b/lib/uploadcare/result.rb new file mode 100644 index 00000000..69ed1a06 --- /dev/null +++ b/lib/uploadcare/result.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# Result wrapper for success/error handling. +class Uploadcare::Result + attr_reader :value, :error + + def initialize(value: nil, error: nil) + @value = value + @error = error + end + + # Build a success result. + # + # @param value [Object] + # @return [Uploadcare::Result] + def self.success(value) + new(value: value) + end + + # Build a failure result. + # + # @param error [Object] + # @return [Uploadcare::Result] + def self.failure(error) + new(error: error) + end + + # Capture exceptions and wrap in Result. + # + # @return [Uploadcare::Result] + def self.capture + success(yield) + rescue StandardError => e + failure(e) + end + + # Unwrap a Result or return the value as-is. + # + # @param value [Object] + # @return [Object] + def self.unwrap(value) + value.is_a?(Uploadcare::Result) ? value.value! : value + end + + def success? + @error.nil? + end + + def failure? + !success? + end + + # @return [Object] success value + def success + @value + end + + # @return [Object] error value + def failure + @error + end + + # Return the success value or raise the wrapped error. + # + # @return [Object] + # @raise [Exception, RuntimeError] + def value! + if failure? + error = @error + raise error if error.is_a?(Exception) + raise error if error.is_a?(String) + + raise error.to_s + end + + @value + end + + # @return [String, nil] error message + def error_message + return nil if @error.nil? + + @error.respond_to?(:message) ? @error.message : @error.to_s + end +end diff --git a/lib/uploadcare/ruby/version.rb b/lib/uploadcare/ruby/version.rb deleted file mode 100644 index b57ce1ff..00000000 --- a/lib/uploadcare/ruby/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Uploadcare - VERSION = '4.5.0' -end diff --git a/lib/uploadcare/signed_url_generators/akamai_generator.rb b/lib/uploadcare/signed_url_generators/akamai_generator.rb index 870b0f40..f8869f8b 100644 --- a/lib/uploadcare/signed_url_generators/akamai_generator.rb +++ b/lib/uploadcare/signed_url_generators/akamai_generator.rb @@ -1,68 +1,67 @@ # frozen_string_literal: true +require 'openssl' require_relative 'base_generator' -module Uploadcare - module SignedUrlGenerators - class AkamaiGenerator < Uploadcare::SignedUrlGenerators::BaseGenerator - UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}' - TEMPLATE = 'https://{cdn_host}/{uuid}/?token=exp={expiration}{delimiter}acl={acl}{delimiter}hmac={token}' +# Akamai signed URL generator. +class Uploadcare::SignedUrlGenerators::AkamaiGenerator < Uploadcare::SignedUrlGenerators::BaseGenerator + # UUID validation regex. + UUID_REGEX = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\z/i + # Akamai token template. + TEMPLATE = 'https://{cdn_host}/{uuid}/?token=exp={expiration}{delimiter}acl={acl}{delimiter}hmac={token}' - def generate_url(uuid, acl = uuid, wildcard: false) - raise ArgumentError, 'Must contain valid UUID' unless valid?(uuid) + def generate_url(uuid, acl = uuid, wildcard: false) + raise ArgumentError, 'Must contain valid UUID' unless valid?(uuid) - formatted_acl = build_acl(uuid, acl, wildcard: wildcard) - expire = build_expire - signature = build_signature(expire, formatted_acl) + formatted_acl = build_acl(uuid, acl, wildcard: wildcard) + expire = build_expire + signature = build_signature(expire, formatted_acl) - TEMPLATE.gsub('{delimiter}', delimiter) - .sub('{cdn_host}', sanitized_string(cdn_host)) - .sub('{uuid}', sanitized_string(uuid)) - .sub('{acl}', formatted_acl) - .sub('{expiration}', expire) - .sub('{token}', signature) - end + TEMPLATE.gsub('{delimiter}', delimiter) + .sub('{cdn_host}', sanitized_string(cdn_host)) + .sub('{uuid}', sanitized_string(uuid)) + .sub('{acl}', formatted_acl) + .sub('{expiration}', expire) + .sub('{token}', signature) + end - private + private - def valid?(uuid) - uuid.match(UUID_REGEX) - end + def valid?(uuid) + raise ArgumentError, 'Must contain valid UUID' unless uuid.is_a?(String) - def delimiter - '~' - end + UUID_REGEX.match?(uuid) + end - def build_acl(uuid, acl, wildcard: false) - if wildcard - "/#{sanitized_delimiter_path(uuid)}/*" - else - "/#{sanitized_delimiter_path(acl)}/" - end - end + def delimiter + '~' + end + + def build_acl(uuid, acl, wildcard: false) + if wildcard + "/#{sanitized_delimiter_path(uuid)}/*" + else + "/#{sanitized_delimiter_path(acl)}/" + end + end - # Delimiter sanitization referenced from: https://github.com/uploadcare/pyuploadcare/blob/main/pyuploadcare/secure_url.py#L74 - def sanitized_delimiter_path(path) - sanitized_string(path).gsub('~') { |escape_char| "%#{escape_char.ord.to_s(16).downcase}" } - end + def sanitized_delimiter_path(path) + sanitized_string(path).gsub('~') { |escape_char| "%#{escape_char.ord.to_s(16).downcase}" } + end - def build_expire - (Time.now.to_i + ttl).to_s - end + def build_expire + (Time.now.to_i + ttl).to_s + end - def build_signature(expire, acl) - signature = ["exp=#{expire}", "acl=#{acl}"].join(delimiter) - secret_key_bin = Array(secret_key.gsub(/\s/, '')).pack('H*') - OpenSSL::HMAC.hexdigest(algorithm, secret_key_bin, signature) - end + def build_signature(expire, acl) + signature = ["exp=#{expire}", "acl=#{acl}"].join(delimiter) + secret_key_bin = Array(secret_key.delete(" \t\r\n")).pack('H*') + OpenSSL::HMAC.hexdigest(algorithm, secret_key_bin, signature) + end - # rubocop:disable Style/SlicingWithRange - def sanitized_string(string) - string = string[1..-1] if string[0] == '/' - string = string[0...-1] if string[-1] == '/' - string.strip - end - # rubocop:enable Style/SlicingWithRange - end + def sanitized_string(string) + string = string[1..] if string[0] == '/' + string = string[0...-1] if string[-1] == '/' + string.strip end end diff --git a/lib/uploadcare/signed_url_generators/base_generator.rb b/lib/uploadcare/signed_url_generators/base_generator.rb index df5e5d84..048da0f0 100644 --- a/lib/uploadcare/signed_url_generators/base_generator.rb +++ b/lib/uploadcare/signed_url_generators/base_generator.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true -module Uploadcare - module SignedUrlGenerators - class BaseGenerator - attr_accessor :cdn_host, :ttl, :algorithm - attr_reader :secret_key +# Base class for signed URL generators. +class Uploadcare::SignedUrlGenerators::BaseGenerator + attr_accessor :cdn_host, :ttl, :algorithm + attr_reader :secret_key - def initialize(cdn_host:, secret_key:, ttl: 300, algorithm: 'sha256') - @ttl = ttl - @algorithm = algorithm - @cdn_host = cdn_host - @secret_key = secret_key - end + def initialize(cdn_host:, secret_key:, ttl: 300, algorithm: 'sha256') + @ttl = ttl + @algorithm = algorithm + @cdn_host = cdn_host + @secret_key = secret_key + end - def generate_url - raise NotImplementedError, "#{__method__} method not present" - end - end + # Generate a signed URL. + # + # @return [String] + def generate_url + raise NotImplementedError, "#{__method__} method not present" end end diff --git a/lib/uploadcare/version.rb b/lib/uploadcare/version.rb new file mode 100644 index 00000000..058834c4 --- /dev/null +++ b/lib/uploadcare/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Root namespace for the Uploadcare Ruby SDK. +module Uploadcare + # Gem version. + VERSION = '5.0.0' +end diff --git a/lib/uploadcare/webhook_signature_verifier.rb b/lib/uploadcare/webhook_signature_verifier.rb new file mode 100644 index 00000000..1a66711a --- /dev/null +++ b/lib/uploadcare/webhook_signature_verifier.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'openssl' + +# This object verifies a signature received along with webhook headers +class Uploadcare::WebhookSignatureVerifier + # @see https://uploadcare.com/docs/security/secure-webhooks/ + def self.valid?(webhook_body: nil, signing_secret: nil, x_uc_signature_header: nil) + webhook_body_json = webhook_body + signing_secret ||= ENV.fetch('UC_SIGNING_SECRET', nil) + + return false unless valid_parameters?(signing_secret, x_uc_signature_header, webhook_body_json) + + calculated_signature = calculate_signature(signing_secret, webhook_body_json) + + # Use constant-time comparison to prevent timing attacks + secure_compare?(calculated_signature, x_uc_signature_header) + end + + # Check if all required parameters are present and non-empty + # @param signing_secret [String] signing secret + # @param signature_header [String] signature from header + # @param body [String] webhook body + # @return [Boolean] true if all parameters are valid + def self.valid_parameters?(signing_secret, signature_header, body) + return false if signing_secret.nil? || signing_secret.to_s.empty? + return false if signature_header.nil? || signature_header.to_s.empty? + return false if body.nil? || body.to_s.empty? + + true + end + + # Calculate HMAC signature for webhook body + # @param secret [String] signing secret + # @param body [String] webhook body JSON + # @return [String] calculated signature + def self.calculate_signature(secret, body) + digest = OpenSSL::Digest.new('sha256') + "v1=#{OpenSSL::HMAC.hexdigest(digest, secret, body)}" + end + + # Constant-time string comparison to prevent timing attacks + # @param first [String] first string + # @param second [String] second string + # @return [Boolean] true if strings are equal + def self.secure_compare?(first, second) + return false if first.nil? || second.nil? + return false unless first.bytesize == second.bytesize + + OpenSSL.fixed_length_secure_compare(first, second) + rescue NoMethodError + result = 0 + index = 0 + while index < first.bytesize + result |= first.getbyte(index) ^ second.getbyte(index) + index += 1 + end + result.zero? + end +end diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..5a061357 --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +ruby = "4.0.1" diff --git a/spec/TESTING.md b/spec/TESTING.md new file mode 100644 index 00000000..ef4d0bfe --- /dev/null +++ b/spec/TESTING.md @@ -0,0 +1,156 @@ +# Testing Guide + +This document describes the testing approaches used in the uploadcare-ruby gem and when to use each. + +## Overview + +The test suite uses two complementary approaches for mocking HTTP requests: + +1. **WebMock** — For unit tests with explicit request/response stubs +2. **VCR** — For integration tests that record and replay real API interactions + +## When to Use Each Approach + +### Use WebMock (`stub_request`) for: + +- **Unit tests** — Testing individual methods in isolation +- **Client specs** — Testing HTTP client behavior with controlled responses +- **Error handling** — Testing specific error scenarios (400, 404, 500, etc.) +- **Edge cases** — Testing unusual response formats or error conditions +- **Fast, deterministic tests** — When you need predictable, instant responses + +```ruby +# Example: spec/uploadcare/api/rest/files_spec.rb +describe '#store' do + before do + stub_request(:put, "#{rest_api_root}/files/#{uuid}/storage/") + .to_return( + status: 200, + body: response_body.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it { is_expected.to include('uuid' => uuid) } +end +``` + +**Advantages:** +- Explicit control over request matching and responses +- No external dependencies or cassette files +- Easy to test error scenarios +- Tests run quickly + +### Use VCR (`VCR.use_cassette`) for: + +- **Integration tests** — Testing complete workflows end-to-end +- **Upload operations** — Testing actual file upload flows +- **Complex multi-step operations** — When multiple API calls are involved +- **Verifying real API behavior** — When you need to ensure compatibility with the actual API + +```ruby +# Example: spec/uploadcare/integration_spec.rb +it 'performs the full lifecycle' do + VCR.use_cassette('file_lifecycle') do + file = client.files.find(uuid: uuid) + expect(file).to be_kind_of(Uploadcare::File) + end +end +``` + +**Advantages:** +- Records real API responses for accurate testing +- Captures complex multi-request flows automatically +- Ensures tests match actual API behavior +- Good for regression testing + +### Use RSpec Mocks (`allow_any_instance_of`) for: + +- **Resource specs** — Testing resource classes that delegate to clients +- **Behavior verification** — When you care about method calls, not HTTP details +- **Isolation** — When testing higher-level abstractions + +```ruby +# Example: spec/uploadcare/resources/file_spec.rb +before do + allow(client.api.rest.files).to receive(:info) + .with(uuid: uuid, request_options: {}) + .and_return(Uploadcare::Result.success(response_body)) +end +``` + +## Directory Structure + +``` +spec/ +├── fixtures/ +│ └── vcr_cassettes/ # VCR recorded cassettes +│ └── Upload_API_Integration/ # Integration test cassettes +├── integration/ +│ └── upload_spec.rb # End-to-end integration tests (uses VCR) +├── support/ +│ └── vcr.rb # VCR configuration +└── uploadcare/ + ├── api/ # API endpoint specs (use WebMock) + ├── client_spec.rb # Client/accessor specs + ├── operations/ # Workflow helpers + └── resources/ # Resource specs (use RSpec mocks) +``` + +## VCR Configuration + +VCR is configured in `spec/support/vcr.rb`: + +```ruby +VCR.configure do |config| + config.cassette_library_dir = 'spec/fixtures/vcr_cassettes' + config.hook_into :webmock + config.filter_sensitive_data('') { Uploadcare.configuration.public_key } + config.filter_sensitive_data('') { Uploadcare.configuration.secret_key } + config.configure_rspec_metadata! +end +``` + +### Recording New Cassettes + +To record a new cassette: + +1. Ensure you have valid API credentials in `.env` or environment variables +2. Delete the existing cassette file (if updating) +3. Run the spec — VCR will record the real API interaction +4. Commit the new cassette file + +### Using VCR Metadata + +For integration specs, you can use the `:vcr` metadata tag: + +```ruby +it 'uploads and retrieves file', :vcr do + # VCR automatically creates cassette from spec description +end +``` + +## Best Practices + +1. **Prefer WebMock for unit tests** — Faster, more explicit, easier to maintain +2. **Use VCR for integration tests** — Captures real API behavior +3. **Keep cassettes minimal** — Only record what's needed for the test +4. **Filter sensitive data** — Never commit real API keys in cassettes +5. **Update cassettes periodically** — API responses may change over time +6. **Name cassettes descriptively** — Use names that indicate what's being tested + +## Running Tests + +```bash +# Run all tests +mise exec -- bundle exec rspec + +# Run only unit tests (fast) +mise exec -- bundle exec rspec spec/uploadcare/ + +# Run integration tests +mise exec -- bundle exec rspec spec/integration/ + +# Run with verbose output +mise exec -- bundle exec rspec --format documentation +``` diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Base_Upload_Store_Retrieve/uploads_stores_and_retrieves_file_information.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Base_Upload_Store_Retrieve/uploads_stores_and_retrieves_file_information.yml new file mode 100644 index 00000000..34b2bbe9 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Base_Upload_Store_Retrieve/uploads_stores_and_retrieves_file_information.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWFhNzJiZThjYTM4MzQ2ZDc1YzE5ODQwNTBmZGUyNWQxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1hYTcyYmU4Y2EzODM0NmQ3NWMxOTg0MDUwZmRlMjVkMQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtYWE3MmJlOGNhMzgzNDZkNzVjMTk4NDA1MGZkZTI1ZDENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtYWE3MmJlOGNhMzgzNDZkNzVjMTk4NDA1MGZkZTI1ZDEtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-aa72be8ca38346d75c1984050fde25d1 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:36 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 5d5bf508-6761-464b-9c38-6edf3e40b577 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"90016e97-6cde-4d81-b3c8-3b624a161384"}' + recorded_at: Tue, 25 Nov 2025 08:44:36 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=90016e97-6cde-4d81-b3c8-3b624a161384&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:37 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - e66d4076-00a5-46d6-b364-ae3cd3518254 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"90016e97-6cde-4d81-b3c8-3b624a161384","file_id":"90016e97-6cde-4d81-b3c8-3b624a161384","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Tue, 25 Nov 2025 08:44:37 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Batch_Upload_Verify_All/uploads_multiple_files_and_verifies_all.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Batch_Upload_Verify_All/uploads_multiple_files_and_verifies_all.yml new file mode 100644 index 00000000..5327c94e --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Batch_Upload_Verify_All/uploads_multiple_files_and_verifies_all.yml @@ -0,0 +1,181 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LThmNmEzYzdmMGJjZjg2ODVmYjRmMTdkYjk5YzI3ODkyDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC04ZjZhM2M3ZjBiY2Y4Njg1ZmI0ZjE3ZGI5OWMyNzg5Mg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtOGY2YTNjN2YwYmNmODY4NWZiNGYxN2RiOTljMjc4OTINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtOGY2YTNjN2YwYmNmODY4NWZiNGYxN2RiOTljMjc4OTINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iODZraXR0ZW4uanBlZyI7IGZpbGVuYW1lPSI4NmtpdHRlbi5qcGVnIg0KQ29udGVudC1MZW5ndGg6IDEyOTANCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5DQoNCv/Y/+AAEEpGSUYAAQEBAGAAYAAA//4AO0NSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDY1Cv/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/AABEIADIAMgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANDRtM0/RzMRKbkykEmfB24z04461rDULWMfKIx9ABXlxurlSd12WBHQmk86Z+XuHAH91jWvtUtkRyM9WfVEiUFm2hhke9Qtr0C4zMv51zk9zBJYW0EyNKXhQ7RkEjHXPY+9JbaDoc4V83Z9mlH5dKlVu6NJUmtmdlbXaXlosyMGUkjIpN2XXHoapaeIbSD7JawiO3jJK4Ykkk81aX/XIf8AZP8ASlKXNqiUnHRlgdBRTgvAoqBnhADFjh8ZHUmpAJcY4b2FdTF4dZ3CKhY9uOtXz4QMQDXEkSeq5y35AVThbcE7kpVX0nTrxsjdbrESf9nI/pVTTGlu7siS0+zwLwjBs5Hrmt6a5s5tNSxiXakACoO/H9ayLkm5SKKJim0jIGRnB9qzaSeponfQ2dMuJmeRLmDyXViF5zuXsa1lb51Psf6VQG4x20jDBJKkY9qtb9rofY/0q0vdM27s0h90fSio1kG0fSikBj3Ukem2nmRriSTgH0Fc3c6hK+90ctjGK1fEt15cojUj5RtA7AVzccgClAQWPZj2pTld3NIRsrEiT73DqSGY4Yf1q9bFo5lDetZhUwsSgYjuPSnRaiY50SQbj2PtWV+5bj2O1eQeRAOwbP6Uye4SNFd2CqO5OKgmkA0+2lByGfj8qoau+60tx6zL/Ot18Jg9zV/tm1HBm6f7J/woqyJTgUVXKScp4lJ+2Sc+tYcHLtnng0UVzyOmJLbuwljAY4K+tS6uAIYmAwc9aKKnoPqb0RJ8PWPJ/wBZ/Q02/wD+Pe2/67L/ADooroj8BhL4jZyfWiiitCD/2Q0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LThmNmEzYzdmMGJjZjg2ODVmYjRmMTdkYjk5YzI3ODkyLS0NCg== + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-8f6a3c7f0bcf8685fb4f17db99c27892 + Content-Length: + - '3375' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:44 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - b2b37e5e-9b7f-4f7b-9a38-d0904f72b123 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"76051c1f-afeb-490a-b6dc-6ea00a7c4ed0","86kitten.jpeg":"d7dfe186-614a-4dcc-9f5b-cca3206fef0f"}' + recorded_at: Tue, 25 Nov 2025 08:44:44 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=76051c1f-afeb-490a-b6dc-6ea00a7c4ed0&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:45 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 487e1b5d-8dec-4d8e-8051-50ed8cb35430 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"76051c1f-afeb-490a-b6dc-6ea00a7c4ed0","file_id":"76051c1f-afeb-490a-b6dc-6ea00a7c4ed0","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Tue, 25 Nov 2025 08:45:03 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=d7dfe186-614a-4dcc-9f5b-cca3206fef0f&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:46 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 912196fc-68f7-4c5f-8a0a-56484c7c431f + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"d7dfe186-614a-4dcc-9f5b-cca3206fef0f","file_id":"d7dfe186-614a-4dcc-9f5b-cca3206fef0f","original_filename":"86kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"86kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Tue, 25 Nov 2025 08:44:46 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Group_Creation_Info_Verify/creates_group_and_retrieves_information.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Group_Creation_Info_Verify/creates_group_and_retrieves_information.yml new file mode 100644 index 00000000..0f0282e6 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/Group_Creation_Info_Verify/creates_group_and_retrieves_information.yml @@ -0,0 +1,239 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTJiMzY0NTRhYWJjNjgzZDA5MGMwMDEyZWYzYmEyMjNiDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0yYjM2NDU0YWFiYzY4M2QwOTBjMDAxMmVmM2JhMjIzYg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMmIzNjQ1NGFhYmM2ODNkMDkwYzAwMTJlZjNiYTIyM2INCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMmIzNjQ1NGFhYmM2ODNkMDkwYzAwMTJlZjNiYTIyM2ItLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-2b36454aabc683d090c0012ef3ba223b + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:41 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, X-UC-User-Agent, DNT + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - dc892ff0-5b3a-4113-9378-e3901d5dd371 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"dcfa202c-cb63-42eb-bff2-6fcc9140c58c"}' + recorded_at: Tue, 25 Nov 2025 08:44:41 GMT +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTgzNWQyNTc2MmI1Y2ZmZTJkN2QyYTQ5ODlhODM1ZDdlDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC04MzVkMjU3NjJiNWNmZmUyZDdkMmE0OTg5YTgzNWQ3ZQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtODM1ZDI1NzYyYjVjZmZlMmQ3ZDJhNDk4OWE4MzVkN2UNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtODM1ZDI1NzYyYjVjZmZlMmQ3ZDJhNDk4OWE4MzVkN2UtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-835d25762b5cffe2d7d2a4989a835d7e + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:42 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - cab95b85-30fd-46e0-945b-0f02c944db32 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"31398594-0cc0-4a0e-806e-a89826ef872d"}' + recorded_at: Tue, 25 Nov 2025 08:44:42 GMT +- request: + method: post + uri: https://upload.uploadcare.com/group/ + body: + encoding: UTF-8 + string: files%5B0%5D=dcfa202c-cb63-42eb-bff2-6fcc9140c58c&files%5B1%5D=31398594-0cc0-4a0e-806e-a89826ef872d&pub_key= + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:43 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 07de0171-75bc-477c-b338-fbf5e99840ca + body: + encoding: ASCII-8BIT + string: '{"id":"89e55aa6-3c9f-40a6-a5ba-280c819964a5~2","datetime_created":"2025-11-25T08:44:42.993560Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/89e55aa6-3c9f-40a6-a5ba-280c819964a5~2/","url":"https://api.uploadcare.com/groups/89e55aa6-3c9f-40a6-a5ba-280c819964a5~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"dcfa202c-cb63-42eb-bff2-6fcc9140c58c","file_id":"dcfa202c-cb63-42eb-bff2-6fcc9140c58c","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""},{"size":1290,"total":1290,"done":1290,"uuid":"31398594-0cc0-4a0e-806e-a89826ef872d","file_id":"31398594-0cc0-4a0e-806e-a89826ef872d","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""}]}' + recorded_at: Tue, 25 Nov 2025 08:44:43 GMT +- request: + method: get + uri: https://upload.uploadcare.com/group/info/?group_id=89e55aa6-3c9f-40a6-a5ba-280c819964a5~2&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:43 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 32c72b17-657e-4c7f-9a29-a5a2943023b0 + body: + encoding: ASCII-8BIT + string: '{"id":"89e55aa6-3c9f-40a6-a5ba-280c819964a5~2","datetime_created":"2025-11-25T08:44:42.993560Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/89e55aa6-3c9f-40a6-a5ba-280c819964a5~2/","url":"https://api.uploadcare.com/groups/89e55aa6-3c9f-40a6-a5ba-280c819964a5~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"dcfa202c-cb63-42eb-bff2-6fcc9140c58c","file_id":"dcfa202c-cb63-42eb-bff2-6fcc9140c58c","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""},{"size":1290,"total":1290,"done":1290,"uuid":"31398594-0cc0-4a0e-806e-a89826ef872d","file_id":"31398594-0cc0-4a0e-806e-a89826ef872d","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""}]}' + recorded_at: Tue, 25 Nov 2025 08:44:43 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/URL_Upload_Poll_Complete/handles_async_URL_upload_with_status_checking.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/URL_Upload_Poll_Complete/handles_async_URL_upload_with_status_checking.yml new file mode 100644 index 00000000..044811ec --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/URL_Upload_Poll_Complete/handles_async_URL_upload_with_status_checking.yml @@ -0,0 +1,119 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/from_url/ + body: + encoding: UTF-8 + string: pub_key=&source_url=https%3A%2F%2Fraw.githubusercontent.com%2Fuploadcare%2Fuploadcare-ruby%2Fmain%2Fspec%2Ffixtures%2Fkitten.jpeg + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:27 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - OPTIONS, GET, POST + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 174f15ad-7241-4d96-9e2e-af2d75f50c6b + body: + encoding: ASCII-8BIT + string: '{"type":"token","token":""}' + recorded_at: Tue, 25 Nov 2025 08:50:27 GMT +- request: + method: get + uri: https://upload.uploadcare.com/from_url/status/?token= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:27 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 67b71e95-bc2f-4f72-9d81-fcac008a9777 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"ef4a65a7-cf94-4f40-81e2-98bc73af7317","file_id":"ef4a65a7-cf94-4f40-81e2-98bc73af7317","original_filename":"kitten.jpeg","is_image":true,"is_stored":false,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"status":"success"}' + recorded_at: Tue, 25 Nov 2025 08:50:27 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/URL_Upload_Poll_Complete/uploads_from_URL_and_polls_until_complete.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/URL_Upload_Poll_Complete/uploads_from_URL_and_polls_until_complete.yml new file mode 100644 index 00000000..bcd8cfa0 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/URL_Upload_Poll_Complete/uploads_from_URL_and_polls_until_complete.yml @@ -0,0 +1,178 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/from_url/ + body: + encoding: UTF-8 + string: pub_key=&source_url=https%3A%2F%2Fraw.githubusercontent.com%2Fuploadcare%2Fuploadcare-ruby%2Fmain%2Fspec%2Ffixtures%2Fkitten.jpeg&store=1 + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:25 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - OPTIONS, GET, POST + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 4b4a4247-6d7c-4533-9716-dae23f8ef5c2 + body: + encoding: ASCII-8BIT + string: '{"type":"token","token":""}' + recorded_at: Tue, 25 Nov 2025 08:50:25 GMT +- request: + method: get + uri: https://upload.uploadcare.com/from_url/status/?token= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:25 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - db3aca9c-ea01-4b1a-b850-e47b078f2ef5 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"94601461-1c80-4004-8884-a41530f5df36","file_id":"94601461-1c80-4004-8884-a41530f5df36","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"status":"success"}' + recorded_at: Tue, 25 Nov 2025 08:50:25 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=94601461-1c80-4004-8884-a41530f5df36&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:26 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 9af3df2b-e38a-4f57-8e59-6815150ccce2 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"94601461-1c80-4004-8884-a41530f5df36","file_id":"94601461-1c80-4004-8884-a41530f5df36","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Tue, 25 Nov 2025 08:50:26 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_batch_uploading/uploads_multiple_files_and_verifies_all.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_batch_uploading/uploads_multiple_files_and_verifies_all.yml new file mode 100644 index 00000000..e82552f2 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_batch_uploading/uploads_multiple_files_and_verifies_all.yml @@ -0,0 +1,175 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTI4MTQ1ZDIxOTRkNWI3NmY1NjMyMWVmYWQwN2RjYmQ5DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0yODE0NWQyMTk0ZDViNzZmNTYzMjFlZmFkMDdkY2JkOQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMjgxNDVkMjE5NGQ1Yjc2ZjU2MzIxZWZhZDA3ZGNiZDkNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMjgxNDVkMjE5NGQ1Yjc2ZjU2MzIxZWZhZDA3ZGNiZDkNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iOTZraXR0ZW4uanBlZyI7IGZpbGVuYW1lPSI5NmtpdHRlbi5qcGVnIg0KQ29udGVudC1MZW5ndGg6IDEyOTANCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5DQoNCv/Y/+AAEEpGSUYAAQEBAGAAYAAA//4AO0NSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDY1Cv/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/AABEIADIAMgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANDRtM0/RzMRKbkykEmfB24z04461rDULWMfKIx9ABXlxurlSd12WBHQmk86Z+XuHAH91jWvtUtkRyM9WfVEiUFm2hhke9Qtr0C4zMv51zk9zBJYW0EyNKXhQ7RkEjHXPY+9JbaDoc4V83Z9mlH5dKlVu6NJUmtmdlbXaXlosyMGUkjIpN2XXHoapaeIbSD7JawiO3jJK4Ykkk81aX/XIf8AZP8ASlKXNqiUnHRlgdBRTgvAoqBnhADFjh8ZHUmpAJcY4b2FdTF4dZ3CKhY9uOtXz4QMQDXEkSeq5y35AVThbcE7kpVX0nTrxsjdbrESf9nI/pVTTGlu7siS0+zwLwjBs5Hrmt6a5s5tNSxiXakACoO/H9ayLkm5SKKJim0jIGRnB9qzaSeponfQ2dMuJmeRLmDyXViF5zuXsa1lb51Psf6VQG4x20jDBJKkY9qtb9rofY/0q0vdM27s0h90fSio1kG0fSikBj3Ukem2nmRriSTgH0Fc3c6hK+90ctjGK1fEt15cojUj5RtA7AVzccgClAQWPZj2pTld3NIRsrEiT73DqSGY4Yf1q9bFo5lDetZhUwsSgYjuPSnRaiY50SQbj2PtWV+5bj2O1eQeRAOwbP6Uye4SNFd2CqO5OKgmkA0+2lByGfj8qoau+60tx6zL/Ot18Jg9zV/tm1HBm6f7J/woqyJTgUVXKScp4lJ+2Sc+tYcHLtnng0UVzyOmJLbuwljAY4K+tS6uAIYmAwc9aKKnoPqb0RJ8PWPJ/wBZ/Q02/wD+Pe2/67L/ADooroj8BhL4jZyfWiiitCD/2Q0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTI4MTQ1ZDIxOTRkNWI3NmY1NjMyMWVmYWQwN2RjYmQ5LS0NCg== + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-28145d2194d5b76f56321efad07dcbd9 + Content-Length: + - '3375' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:48 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 5ecd8af1-763f-4430-b752-e69fecb7f98c + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"6a7216e7-0f33-4ea1-8859-fe5f6544ae14","96kitten.jpeg":"a9a104fb-647f-4a79-8074-4771d6d83819"}' + recorded_at: Sun, 08 Feb 2026 06:32:48 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=6a7216e7-0f33-4ea1-8859-fe5f6544ae14&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:49 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - da1c8814-0283-4ed2-b7ff-62012b50c875 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"6a7216e7-0f33-4ea1-8859-fe5f6544ae14","file_id":"6a7216e7-0f33-4ea1-8859-fe5f6544ae14","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Sun, 08 Feb 2026 06:32:49 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=a9a104fb-647f-4a79-8074-4771d6d83819&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:50 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - cb01c2ac-2bca-4066-8ed5-f1bc1cb13016 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"a9a104fb-647f-4a79-8074-4771d6d83819","file_id":"a9a104fb-647f-4a79-8074-4771d6d83819","original_filename":"96kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"96kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Sun, 08 Feb 2026 06:32:50 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_creating_and_verifying_groups/creates_group_and_retrieves_information.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_creating_and_verifying_groups/creates_group_and_retrieves_information.yml new file mode 100644 index 00000000..0314a0e9 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_creating_and_verifying_groups/creates_group_and_retrieves_information.yml @@ -0,0 +1,231 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTc4MWM2ZjI3NjUzMWM2NTJkYWFmMGFkNDNhZWE0OGRjDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC03ODFjNmYyNzY1MzFjNjUyZGFhZjBhZDQzYWVhNDhkYw0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNzgxYzZmMjc2NTMxYzY1MmRhYWYwYWQ0M2FlYTQ4ZGMNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNzgxYzZmMjc2NTMxYzY1MmRhYWYwYWQ0M2FlYTQ4ZGMtLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-781c6f276531c652daaf0ad43aea48dc + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:43 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 4bdc3f3f-bd37-4c66-b4fd-9bce85680d91 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"fc49ce63-4f51-449b-889f-573575bdff9c"}' + recorded_at: Sun, 08 Feb 2026 06:32:43 GMT +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWJmODU1MGU1ZTMyNjJjNzE2ZjgzMzkzZGYzZmI0ODQwDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1iZjg1NTBlNWUzMjYyYzcxNmY4MzM5M2RmM2ZiNDg0MA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtYmY4NTUwZTVlMzI2MmM3MTZmODMzOTNkZjNmYjQ4NDANCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtYmY4NTUwZTVlMzI2MmM3MTZmODMzOTNkZjNmYjQ4NDAtLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-bf8550e5e3262c716f83393df3fb4840 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:44 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - fc77389f-3ac1-46f2-978b-2e76154307d9 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"8154a31b-daa1-4695-afc0-edc6cf7d7731"}' + recorded_at: Sun, 08 Feb 2026 06:32:44 GMT +- request: + method: post + uri: https://upload.uploadcare.com/group/ + body: + encoding: UTF-8 + string: files%5B0%5D=fc49ce63-4f51-449b-889f-573575bdff9c&files%5B1%5D=8154a31b-daa1-4695-afc0-edc6cf7d7731&pub_key= + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:45 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - bad11a1b-76e5-4b5a-9413-d41400c5bc69 + body: + encoding: ASCII-8BIT + string: '{"id":"3b21f286-ce6e-4623-8128-155c504c110c~2","datetime_created":"2026-02-08T06:32:45.535046Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/3b21f286-ce6e-4623-8128-155c504c110c~2/","url":"https://api.uploadcare.com/groups/3b21f286-ce6e-4623-8128-155c504c110c~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"fc49ce63-4f51-449b-889f-573575bdff9c","file_id":"fc49ce63-4f51-449b-889f-573575bdff9c","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""},{"size":1290,"total":1290,"done":1290,"uuid":"8154a31b-daa1-4695-afc0-edc6cf7d7731","file_id":"8154a31b-daa1-4695-afc0-edc6cf7d7731","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""}]}' + recorded_at: Sun, 08 Feb 2026 06:32:45 GMT +- request: + method: get + uri: https://upload.uploadcare.com/group/info/?group_id=3b21f286-ce6e-4623-8128-155c504c110c~2&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:46 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - faaf1caf-6c21-4eb1-91ee-4412fab85ab2 + body: + encoding: ASCII-8BIT + string: '{"id":"3b21f286-ce6e-4623-8128-155c504c110c~2","datetime_created":"2026-02-08T06:32:45.535046Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/3b21f286-ce6e-4623-8128-155c504c110c~2/","url":"https://api.uploadcare.com/groups/3b21f286-ce6e-4623-8128-155c504c110c~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"fc49ce63-4f51-449b-889f-573575bdff9c","file_id":"fc49ce63-4f51-449b-889f-573575bdff9c","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""},{"size":1290,"total":1290,"done":1290,"uuid":"8154a31b-daa1-4695-afc0-edc6cf7d7731","file_id":"8154a31b-daa1-4695-afc0-edc6cf7d7731","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"default_effects":""}]}' + recorded_at: Sun, 08 Feb 2026 06:32:46 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_from_URL/handles_async_URL_upload_with_status_checking.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_from_URL/handles_async_URL_upload_with_status_checking.yml new file mode 100644 index 00000000..a82c0fc2 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_from_URL/handles_async_URL_upload_with_status_checking.yml @@ -0,0 +1,115 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/from_url/ + body: + encoding: UTF-8 + string: pub_key=&source_url=https%3A%2F%2Fraw.githubusercontent.com%2Fuploadcare%2Fuploadcare-ruby%2Fmain%2Fspec%2Ffixtures%2Fkitten.jpeg + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:41 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - OPTIONS, GET, POST + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 37ef7bc4-2cfb-4392-b6d7-cb6ead6537ec + body: + encoding: ASCII-8BIT + string: '{"type":"token","token":""}' + recorded_at: Sun, 08 Feb 2026 06:32:41 GMT +- request: + method: get + uri: https://upload.uploadcare.com/from_url/status/?token= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:42 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 4e27c6f6-d3a7-45d6-b6c7-938128958aa9 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"0cad2be4-55b3-4ceb-87be-15993c25c6bb","file_id":"0cad2be4-55b3-4ceb-87be-15993c25c6bb","original_filename":"kitten.jpeg","is_image":true,"is_stored":false,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"status":"success"}' + recorded_at: Sun, 08 Feb 2026 06:32:42 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_from_URL/uploads_from_URL_and_polls_until_complete.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_from_URL/uploads_from_URL_and_polls_until_complete.yml new file mode 100644 index 00000000..b8a4222c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_from_URL/uploads_from_URL_and_polls_until_complete.yml @@ -0,0 +1,172 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/from_url/ + body: + encoding: UTF-8 + string: pub_key=&source_url=https%3A%2F%2Fraw.githubusercontent.com%2Fuploadcare%2Fuploadcare-ruby%2Fmain%2Fspec%2Ffixtures%2Fkitten.jpeg&store=1 + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:38 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - OPTIONS, GET, POST + Access-Control-Allow-Headers: + - X-UC-User-Agent, DNT, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 5ae35cc7-f44d-4bed-b64f-535b38cdf1be + body: + encoding: ASCII-8BIT + string: '{"type":"token","token":""}' + recorded_at: Sun, 08 Feb 2026 06:32:38 GMT +- request: + method: get + uri: https://upload.uploadcare.com/from_url/status/?token= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:39 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - be5aa7e1-d71e-41e0-b2ca-d9e007ae528b + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"77419655-885e-4ad0-ba72-f672a6fab7d4","file_id":"77419655-885e-4ad0-ba72-f672a6fab7d4","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"status":"success"}' + recorded_at: Sun, 08 Feb 2026 06:32:39 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=77419655-885e-4ad0-ba72-f672a6fab7d4&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:40 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - e6f491c4-ebf9-4ce8-8bbb-429620a76dc0 + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"77419655-885e-4ad0-ba72-f672a6fab7d4","file_id":"77419655-885e-4ad0-ba72-f672a6fab7d4","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Sun, 08 Feb 2026 06:32:40 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_storing_and_retrieving/uploads_stores_and_retrieves_file_information.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_storing_and_retrieving/uploads_stores_and_retrieves_file_information.yml new file mode 100644 index 00000000..88e6256a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Complete_Upload_Workflow/when_uploading_storing_and_retrieving/uploads_stores_and_retrieves_file_information.yml @@ -0,0 +1,118 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTVmYTk5OWFhYTA5ZTFmNmM2Y2U1MzU2OTFjNDM4ZWMyDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC01ZmE5OTlhYWEwOWUxZjZjNmNlNTM1NjkxYzQzOGVjMg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNWZhOTk5YWFhMDllMWY2YzZjZTUzNTY5MWM0MzhlYzINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNWZhOTk5YWFhMDllMWY2YzZjZTUzNTY5MWM0MzhlYzItLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-5fa999aaa09e1f6c6ce535691c438ec2 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:36 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - f179ee7a-68ca-4bfc-bc26-3e16edb21937 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"c3b06624-d2e0-43b9-9c5e-b51f39c8b9e5"}' + recorded_at: Sun, 08 Feb 2026 06:32:36 GMT +- request: + method: get + uri: https://upload.uploadcare.com/info/?file_id=c3b06624-d2e0-43b9-9c5e-b51f39c8b9e5&pub_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - UploadcareRuby/5.0.0/ (Ruby/4.0.1) + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:37 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 2b19e33f-6d2f-4a15-9471-58b170483cab + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"c3b06624-d2e0-43b9-9c5e-b51f39c8b9e5","file_id":"c3b06624-d2e0-43b9-9c5e-b51f39c8b9e5","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Sun, 08 Feb 2026 06:32:37 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Basic_file_uploads/handles_basic_file_uploads.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Basic_file_uploads/handles_basic_file_uploads.yml new file mode 100644 index 00000000..4d2a7a95 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Basic_file_uploads/handles_basic_file_uploads.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTJhYWY4ZWU3MTFjOWNhNTVmNWQwMTI5ZjBhYmNlODYwDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0yYWFmOGVlNzExYzljYTU1ZjVkMDEyOWYwYWJjZTg2MA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMmFhZjhlZTcxMWM5Y2E1NWY1ZDAxMjlmMGFiY2U4NjANCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMmFhZjhlZTcxMWM5Y2E1NWY1ZDAxMjlmMGFiY2U4NjAtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-2aaf8ee711c9ca55f5d0129f0abce860 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 19 Jan 2026 06:45:16 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - d19aaf67-0d98-42c4-befc-fa5400b41c5c + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"1d2c1e97-411a-42a1-9adc-eb74ee4350f5"}' + recorded_at: Mon, 19 Jan 2026 06:45:16 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Concurrent_uploads/handles_multiple_simultaneous_uploads.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Concurrent_uploads/handles_multiple_simultaneous_uploads.yml new file mode 100644 index 00000000..89d5cfc4 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Concurrent_uploads/handles_multiple_simultaneous_uploads.yml @@ -0,0 +1,183 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTE1NGQxYmUzMWYxNmUwOWQ3NjdmYjliMTgwNWYyMGFmDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0xNTRkMWJlMzFmMTZlMDlkNzY3ZmI5YjE4MDVmMjBhZg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMTU0ZDFiZTMxZjE2ZTA5ZDc2N2ZiOWIxODA1ZjIwYWYNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMTU0ZDFiZTMxZjE2ZTA5ZDc2N2ZiOWIxODA1ZjIwYWYtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-154d1be31f16e09d767fb9b1805f20af + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:49 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - cab5be4e-3e64-4b76-af22-8d68c7a0ee19 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"2acb91ea-76a5-45cf-8f2f-3a6ead6ec663"}' + recorded_at: Tue, 25 Nov 2025 08:44:49 GMT +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTQwNmJmOTYyNDYwZWFiYjRmNWJmNDI2N2M5YmI5OTQ5DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC00MDZiZjk2MjQ2MGVhYmI0ZjViZjQyNjdjOWJiOTk0OQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNDA2YmY5NjI0NjBlYWJiNGY1YmY0MjY3YzliYjk5NDkNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNDA2YmY5NjI0NjBlYWJiNGY1YmY0MjY3YzliYjk5NDktLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-406bf962460eabb4f5bf4267c9bb9949 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:49 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - ea8ae6a1-47d8-4899-b3a7-fae22805522e + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"3779068f-4bff-4a95-b74c-bd7fb1d65efc"}' + recorded_at: Tue, 25 Nov 2025 08:44:49 GMT +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTU3NGViZTMwNGIyNDVlNDNmOTI0ZTVkZDMyMmEyZDAxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC01NzRlYmUzMDRiMjQ1ZTQzZjkyNGU1ZGQzMjJhMmQwMQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNTc0ZWJlMzA0YjI0NWU0M2Y5MjRlNWRkMzIyYTJkMDENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNTc0ZWJlMzA0YjI0NWU0M2Y5MjRlNWRkMzIyYTJkMDEtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-574ebe304b245e43f924e5dd322a2d01 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:49 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 69191c27-d10b-4347-b267-d6bfff1017b0 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"dcd5bcba-9db9-4fd0-9188-d1a80a32d5dc"}' + recorded_at: Tue, 25 Nov 2025 08:44:49 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Files_with_special_characters/handles_filenames_with_special_characters.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Files_with_special_characters/handles_filenames_with_special_characters.yml new file mode 100644 index 00000000..e3aa7e0b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Files_with_special_characters/handles_filenames_with_special_characters.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWVmZjNlOGFlYjgzNDI3MmE2N2Q5YzAyYjBlMmJjODNhDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1lZmYzZThhZWI4MzQyNzJhNjdkOWMwMmIwZTJiYzgzYQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZWZmM2U4YWViODM0MjcyYTY3ZDljMDJiMGUyYmM4M2ENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZWZmM2U4YWViODM0MjcyYTY3ZDljMDJiMGUyYmM4M2EtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-eff3e8aeb834272a67d9c02b0e2bc83a + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:48:05 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 4588e9ca-9001-495f-93be-1408fac756c8 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"edd4284a-c293-4d78-8c32-b1d49bad5be5"}' + recorded_at: Tue, 25 Nov 2025 08:48:24 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Metadata/preserves_metadata_through_upload.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Metadata/preserves_metadata_through_upload.yml new file mode 100644 index 00000000..5a4290a1 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Metadata/preserves_metadata_through_upload.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWUwZWQzZjM4N2YwYzc3OGE5NzZmZGE1ZTAwODQ2ODlmDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1lMGVkM2YzODdmMGM3NzhhOTc2ZmRhNWUwMDg0Njg5Zg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZTBlZDNmMzg3ZjBjNzc4YTk3NmZkYTVlMDA4NDY4OWYNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibWV0YWRhdGFbY2F0ZWdvcnldIg0KDQp0ZXN0DQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZTBlZDNmMzg3ZjBjNzc4YTk3NmZkYTVlMDA4NDY4OWYNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibWV0YWRhdGFbdXNlcl9pZF0iDQoNCjEyMzQ1DQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZTBlZDNmMzg3ZjBjNzc4YTk3NmZkYTVlMDA4NDY4OWYNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibWV0YWRhdGFbdGltZXN0YW1wXSINCg0KMTc2NDA2MDI4Nw0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWUwZWQzZjM4N2YwYzc3OGE5NzZmZGE1ZTAwODQ2ODlmDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImtpdHRlbi5qcGVnIjsgZmlsZW5hbWU9ImtpdHRlbi5qcGVnIg0KQ29udGVudC1MZW5ndGg6IDEyOTANCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5DQoNCv/Y/+AAEEpGSUYAAQEBAGAAYAAA//4AO0NSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDY1Cv/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/AABEIADIAMgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANDRtM0/RzMRKbkykEmfB24z04461rDULWMfKIx9ABXlxurlSd12WBHQmk86Z+XuHAH91jWvtUtkRyM9WfVEiUFm2hhke9Qtr0C4zMv51zk9zBJYW0EyNKXhQ7RkEjHXPY+9JbaDoc4V83Z9mlH5dKlVu6NJUmtmdlbXaXlosyMGUkjIpN2XXHoapaeIbSD7JawiO3jJK4Ykkk81aX/XIf8AZP8ASlKXNqiUnHRlgdBRTgvAoqBnhADFjh8ZHUmpAJcY4b2FdTF4dZ3CKhY9uOtXz4QMQDXEkSeq5y35AVThbcE7kpVX0nTrxsjdbrESf9nI/pVTTGlu7siS0+zwLwjBs5Hrmt6a5s5tNSxiXakACoO/H9ayLkm5SKKJim0jIGRnB9qzaSeponfQ2dMuJmeRLmDyXViF5zuXsa1lb51Psf6VQG4x20jDBJKkY9qtb9rofY/0q0vdM27s0h90fSio1kG0fSikBj3Ukem2nmRriSTgH0Fc3c6hK+90ctjGK1fEt15cojUj5RtA7AVzccgClAQWPZj2pTld3NIRsrEiT73DqSGY4Yf1q9bFo5lDetZhUwsSgYjuPSnRaiY50SQbj2PtWV+5bj2O1eQeRAOwbP6Uye4SNFd2CqO5OKgmkA0+2lByGfj8qoau+60tx6zL/Ot18Jg9zV/tm1HBm6f7J/woqyJTgUVXKScp4lJ+2Sc+tYcHLtnng0UVzyOmJLbuwljAY4K+tS6uAIYmAwc9aKKnoPqb0RJ8PWPJ/wBZ/Q02/wD+Pe2/67L/ADooroj8BhL4jZyfWiiitCD/2Q0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWUwZWQzZjM4N2YwYzc3OGE5NzZmZGE1ZTAwODQ2ODlmLS0NCg== + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-e0ed3f387f0c778a976fda5e0084689f + Content-Length: + - '2256' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:48 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 29568a32-65a5-40f9-ae51-44b46d4e90be + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"8f985039-7e92-4766-9534-e0aa9f086fdb"}' + recorded_at: Tue, 25 Nov 2025 08:44:48 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Very_small_files/handles_small_fixture_files.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Very_small_files/handles_small_fixture_files.yml new file mode 100644 index 00000000..dd3315c5 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/Very_small_files/handles_small_fixture_files.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTQ1NWUwMDRiMDViNDA1NWMwMzZiZTY4MDEzMTQxMzBiDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC00NTVlMDA0YjA1YjQwNTVjMDM2YmU2ODAxMzE0MTMwYg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNDU1ZTAwNGIwNWI0MDU1YzAzNmJlNjgwMTMxNDEzMGINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNDU1ZTAwNGIwNWI0MDU1YzAzNmJlNjgwMTMxNDEzMGItLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-455e004b05b4055c036be6801314130b + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 19 Jan 2026 06:45:15 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, X-UC-User-Agent, DNT + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - e764e739-44a1-4f24-84fa-4cb60f665b4d + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"e6e8c1b0-c65f-45ee-bb12-f0b3db95f2cd"}' + recorded_at: Mon, 19 Jan 2026 06:45:15 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_metadata_is_provided/preserves_metadata_through_upload.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_metadata_is_provided/preserves_metadata_through_upload.yml new file mode 100644 index 00000000..b8d73d75 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_metadata_is_provided/preserves_metadata_through_upload.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWRlYzFlMTNjODgxZDU2OWVmYjg1MjVkYjA2ZTZmOTYxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1kZWMxZTEzYzg4MWQ1NjllZmI4NTI1ZGIwNmU2Zjk2MQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZGVjMWUxM2M4ODFkNTY5ZWZiODUyNWRiMDZlNmY5NjENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibWV0YWRhdGFbY2F0ZWdvcnldIg0KDQp0ZXN0DQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZGVjMWUxM2M4ODFkNTY5ZWZiODUyNWRiMDZlNmY5NjENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibWV0YWRhdGFbdXNlcl9pZF0iDQoNCjEyMzQ1DQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZGVjMWUxM2M4ODFkNTY5ZWZiODUyNWRiMDZlNmY5NjENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibWV0YWRhdGFbdGltZXN0YW1wXSINCg0KMTc3MDUzMjM3Mg0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWRlYzFlMTNjODgxZDU2OWVmYjg1MjVkYjA2ZTZmOTYxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImtpdHRlbi5qcGVnIjsgZmlsZW5hbWU9ImtpdHRlbi5qcGVnIg0KQ29udGVudC1MZW5ndGg6IDEyOTANCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5DQoNCv/Y/+AAEEpGSUYAAQEBAGAAYAAA//4AO0NSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDY1Cv/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/AABEIADIAMgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANDRtM0/RzMRKbkykEmfB24z04461rDULWMfKIx9ABXlxurlSd12WBHQmk86Z+XuHAH91jWvtUtkRyM9WfVEiUFm2hhke9Qtr0C4zMv51zk9zBJYW0EyNKXhQ7RkEjHXPY+9JbaDoc4V83Z9mlH5dKlVu6NJUmtmdlbXaXlosyMGUkjIpN2XXHoapaeIbSD7JawiO3jJK4Ykkk81aX/XIf8AZP8ASlKXNqiUnHRlgdBRTgvAoqBnhADFjh8ZHUmpAJcY4b2FdTF4dZ3CKhY9uOtXz4QMQDXEkSeq5y35AVThbcE7kpVX0nTrxsjdbrESf9nI/pVTTGlu7siS0+zwLwjBs5Hrmt6a5s5tNSxiXakACoO/H9ayLkm5SKKJim0jIGRnB9qzaSeponfQ2dMuJmeRLmDyXViF5zuXsa1lb51Psf6VQG4x20jDBJKkY9qtb9rofY/0q0vdM27s0h90fSio1kG0fSikBj3Ukem2nmRriSTgH0Fc3c6hK+90ctjGK1fEt15cojUj5RtA7AVzccgClAQWPZj2pTld3NIRsrEiT73DqSGY4Yf1q9bFo5lDetZhUwsSgYjuPSnRaiY50SQbj2PtWV+5bj2O1eQeRAOwbP6Uye4SNFd2CqO5OKgmkA0+2lByGfj8qoau+60tx6zL/Ot18Jg9zV/tm1HBm6f7J/woqyJTgUVXKScp4lJ+2Sc+tYcHLtnng0UVzyOmJLbuwljAY4K+tS6uAIYmAwc9aKKnoPqb0RJ8PWPJ/wBZ/Q02/wD+Pe2/67L/ADooroj8BhL4jZyfWiiitCD/2Q0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWRlYzFlMTNjODgxZDU2OWVmYjg1MjVkYjA2ZTZmOTYxLS0NCg== + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-dec1e13c881d569efb8525db06e6f961 + Content-Length: + - '2256' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:53 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - c2bb5912-8221-4911-8de5-880648a8fbd5 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"ff80532d-4fd2-4002-be84-7beeb04100b0"}' + recorded_at: Sun, 08 Feb 2026 06:32:53 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_basic_files/handles_basic_file_uploads.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_basic_files/handles_basic_file_uploads.yml new file mode 100644 index 00000000..f8fd9063 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_basic_files/handles_basic_file_uploads.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWZhMzAwYWVmZTk2NzYzODlkOTczYTlmYmRkZjExMmM1DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1mYTMwMGFlZmU5Njc2Mzg5ZDk3M2E5ZmJkZGYxMTJjNQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZmEzMDBhZWZlOTY3NjM4OWQ5NzNhOWZiZGRmMTEyYzUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZmEzMDBhZWZlOTY3NjM4OWQ5NzNhOWZiZGRmMTEyYzUtLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-fa300aefe9676389d973a9fbddf112c5 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:52 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - d8e1aa03-5db8-4451-b2a2-dd9d49069534 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"b362f733-8c33-4b83-b70a-19a3343d4cdb"}' + recorded_at: Sun, 08 Feb 2026 06:32:52 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_concurrently/handles_multiple_simultaneous_uploads.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_concurrently/handles_multiple_simultaneous_uploads.yml new file mode 100644 index 00000000..be1ccfcb --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_concurrently/handles_multiple_simultaneous_uploads.yml @@ -0,0 +1,177 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTA4Y2UwMmQ3NmYzOWZjYjc2YWYzOWI1OThlZjU3OWU1DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0wOGNlMDJkNzZmMzlmY2I3NmFmMzliNTk4ZWY1NzllNQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMDhjZTAyZDc2ZjM5ZmNiNzZhZjM5YjU5OGVmNTc5ZTUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMDhjZTAyZDc2ZjM5ZmNiNzZhZjM5YjU5OGVmNTc5ZTUtLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-08ce02d76f39fcb76af39b598ef579e5 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:54 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - b82ab44f-e4a7-46e9-8bbe-72ba83209ba5 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"47d5f3c6-d60e-4c5f-b2cc-5383fd378ac5"}' + recorded_at: Sun, 08 Feb 2026 06:32:55 GMT +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWRjNDMwYWE0ZjBkODVhODk1Y2QyMTRkYWJiOTRiMmZhDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1kYzQzMGFhNGYwZDg1YTg5NWNkMjE0ZGFiYjk0YjJmYQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZGM0MzBhYTRmMGQ4NWE4OTVjZDIxNGRhYmI5NGIyZmENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZGM0MzBhYTRmMGQ4NWE4OTVjZDIxNGRhYmI5NGIyZmEtLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-dc430aa4f0d85a895cd214dabb94b2fa + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:54 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - e4697442-f8c2-4785-a10b-6183a9e99c94 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"72de9da1-3930-4a1d-b02d-ceca55a0aaff"}' + recorded_at: Sun, 08 Feb 2026 06:32:55 GMT +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTc3YzFhN2Y2ZDNlNzY5NGYzNTU5ZTc4ZmQyNTI2YTc3DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC03N2MxYTdmNmQzZTc2OTRmMzU1OWU3OGZkMjUyNmE3Nw0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNzdjMWE3ZjZkM2U3Njk0ZjM1NTllNzhmZDI1MjZhNzcNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtNzdjMWE3ZjZkM2U3Njk0ZjM1NTllNzhmZDI1MjZhNzctLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-77c1a7f6d3e7694f3559e78fd2526a77 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:54 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-UC-User-Agent, DNT, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - cf751b13-6baa-4a46-977c-d28ea94ecc08 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"389351a0-5df4-4c76-a8c4-4832abdabeac"}' + recorded_at: Sun, 08 Feb 2026 06:32:55 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_very_small_files/handles_small_fixture_files.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_very_small_files/handles_small_fixture_files.yml new file mode 100644 index 00000000..9187683f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Edge_Cases/when_uploading_very_small_files/handles_small_fixture_files.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTExYjQwZjYwZmFmNWIyYzczOTQ2ZjNiMGY5Nzc2MjUyDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0xMWI0MGY2MGZhZjViMmM3Mzk0NmYzYjBmOTc3NjI1Mg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMTFiNDBmNjBmYWY1YjJjNzM5NDZmM2IwZjk3NzYyNTINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMTFiNDBmNjBmYWY1YjJjNzM5NDZmM2IwZjk3NzYyNTItLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-11b40f60faf5b2c73946f3b0f9776252 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:51 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 3dbb952b-4321-47bf-84a4-f9537b69e8be + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"ba898e3f-f9d0-46a1-bb6b-44428e1958a3"}' + recorded_at: Sun, 08 Feb 2026 06:32:51 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/Parallel_multipart_upload/parallel_upload_is_faster_than_sequential.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/Parallel_multipart_upload/parallel_upload_is_faster_than_sequential.yml new file mode 100644 index 00000000..7bb2f4e4 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/Parallel_multipart_upload/parallel_upload_is_faster_than_sequential.yml @@ -0,0 +1,59 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/multipart/start/ + body: + encoding: UTF-8 + string: UPLOADCARE_PUB_KEY=&UPLOADCARE_STORE=1&content_type=image%2Fjpeg&filename=big.jpeg&part_size=5242880&size=10487050 + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 400 + message: Bad Request + headers: + Date: + - Tue, 25 Nov 2025 08:44:50 GMT + Content-Type: + - text/plain; charset=utf-8 + Content-Length: + - '32' + Connection: + - keep-alive + Server: + - nginx + Vary: + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 043a003e-f0c5-46fd-9d83-42bc0ebed71a + body: + encoding: UTF-8 + string: File size exceeds project limit. + recorded_at: Tue, 25 Nov 2025 08:44:50 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/Upload_speed/uploads_files_in_reasonable_time.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/Upload_speed/uploads_files_in_reasonable_time.yml new file mode 100644 index 00000000..29f72388 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/Upload_speed/uploads_files_in_reasonable_time.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTM3NGUyODI4ZWEwZjE2MWFhMTc1MTFhY2ZmYjhhYmMyDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0zNzRlMjgyOGVhMGYxNjFhYTE3NTExYWNmZmI4YWJjMg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMzc0ZTI4MjhlYTBmMTYxYWExNzUxMWFjZmZiOGFiYzINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMzc0ZTI4MjhlYTBmMTYxYWExNzUxMWFjZmZiOGFiYzItLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-374e2828ea0f161aa17511acffb8abc2 + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:44:50 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - f331fb49-ce87-4b1d-8c73-d2725b996ca0 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"ed124a8c-b770-49c7-a097-35491e34d460"}' + recorded_at: Tue, 25 Nov 2025 08:44:50 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/when_measuring_upload_speed/uploads_files_in_reasonable_time.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/when_measuring_upload_speed/uploads_files_in_reasonable_time.yml new file mode 100644 index 00000000..9fd26aef --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Performance/when_measuring_upload_speed/uploads_files_in_reasonable_time.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTI5MzM1YjlmOGJlNjU5M2QzZTNhMDZmZGRhZGI1YzVhDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC0yOTMzNWI5ZjhiZTY1OTNkM2UzYTA2ZmRkYWRiNWM1YQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMjkzMzViOWY4YmU2NTkzZDNlM2EwNmZkZGFkYjVjNWENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtMjkzMzViOWY4YmU2NTkzZDNlM2EwNmZkZGFkYjVjNWEtLQ0K + headers: + User-Agent: + - Faraday v2.14.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-29335b9f8be6593d3e3a06fddadb5c5a + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 08 Feb 2026 06:32:56 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-PINGOTHER, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - 489dcf61-ce83-4149-8963-552d567d4b80 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"620f1107-bdab-4b1e-9f8a-20a5f941c867"}' + recorded_at: Sun, 08 Feb 2026 06:32:56 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_URLs.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_URLs.yml new file mode 100644 index 00000000..feb3802c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_URLs.yml @@ -0,0 +1,119 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/from_url/ + body: + encoding: UTF-8 + string: pub_key=&source_url=https%3A%2F%2Fraw.githubusercontent.com%2Fuploadcare%2Fuploadcare-ruby%2Fmain%2Fspec%2Ffixtures%2Fkitten.jpeg&store=1 + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:30 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - OPTIONS, GET, POST + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 7b88870b-2158-4f23-b0f9-3371266368a9 + body: + encoding: ASCII-8BIT + string: '{"type":"token","token":""}' + recorded_at: Tue, 25 Nov 2025 08:50:30 GMT +- request: + method: get + uri: https://upload.uploadcare.com/from_url/status/?token= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:30 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, DNT, X-UC-User-Agent + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - d2334c71-4a67-43ea-b851-ea63c73a756f + body: + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"b62b02b3-ab94-497a-9fc3-4aa835749b8c","file_id":"b62b02b3-ab94-497a-9fc3-4aa835749b8c","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{},"status":"success"}' + recorded_at: Tue, 25 Nov 2025 08:50:49 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_arrays.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_arrays.yml new file mode 100644 index 00000000..6360fe2d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_arrays.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LThkOWZlNGI2YjM0YmE5M2NkZmE4YTA1MjJlMmM5YWQ2DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC04ZDlmZTRiNmIzNGJhOTNjZGZhOGEwNTIyZTJjOWFkNg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtOGQ5ZmU0YjZiMzRiYTkzY2RmYThhMDUyMmUyYzlhZDYNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtOGQ5ZmU0YjZiMzRiYTkzY2RmYThhMDUyMmUyYzlhZDYNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iNDVraXR0ZW4uanBlZyI7IGZpbGVuYW1lPSI0NWtpdHRlbi5qcGVnIg0KQ29udGVudC1MZW5ndGg6IDEyOTANCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5DQoNCv/Y/+AAEEpGSUYAAQEBAGAAYAAA//4AO0NSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDY1Cv/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/AABEIADIAMgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANDRtM0/RzMRKbkykEmfB24z04461rDULWMfKIx9ABXlxurlSd12WBHQmk86Z+XuHAH91jWvtUtkRyM9WfVEiUFm2hhke9Qtr0C4zMv51zk9zBJYW0EyNKXhQ7RkEjHXPY+9JbaDoc4V83Z9mlH5dKlVu6NJUmtmdlbXaXlosyMGUkjIpN2XXHoapaeIbSD7JawiO3jJK4Ykkk81aX/XIf8AZP8ASlKXNqiUnHRlgdBRTgvAoqBnhADFjh8ZHUmpAJcY4b2FdTF4dZ3CKhY9uOtXz4QMQDXEkSeq5y35AVThbcE7kpVX0nTrxsjdbrESf9nI/pVTTGlu7siS0+zwLwjBs5Hrmt6a5s5tNSxiXakACoO/H9ayLkm5SKKJim0jIGRnB9qzaSeponfQ2dMuJmeRLmDyXViF5zuXsa1lb51Psf6VQG4x20jDBJKkY9qtb9rofY/0q0vdM27s0h90fSio1kG0fSikBj3Ukem2nmRriSTgH0Fc3c6hK+90ctjGK1fEt15cojUj5RtA7AVzccgClAQWPZj2pTld3NIRsrEiT73DqSGY4Yf1q9bFo5lDetZhUwsSgYjuPSnRaiY50SQbj2PtWV+5bj2O1eQeRAOwbP6Uye4SNFd2CqO5OKgmkA0+2lByGfj8qoau+60tx6zL/Ot18Jg9zV/tm1HBm6f7J/woqyJTgUVXKScp4lJ+2Sc+tYcHLtnng0UVzyOmJLbuwljAY4K+tS6uAIYmAwc9aKKnoPqb0RJ8PWPJ/wBZ/Q02/wD+Pe2/67L/ADooroj8BhL4jZyfWiiitCD/2Q0KLS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LThkOWZlNGI2YjM0YmE5M2NkZmE4YTA1MjJlMmM5YWQ2LS0NCg== + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-8d9fe4b6b34ba93cdfa8a0522e2c9ad6 + Content-Length: + - '3375' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:31 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - DNT, X-UC-User-Agent, X-PINGOTHER + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 837125e8-9e45-488e-976b-52a8229eeca5 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"bc078e3a-c74f-4194-be2e-af55712aea80","45kitten.jpeg":"12aa25c7-c6bd-4b38-8f2a-21005c6fa090"}' + recorded_at: Tue, 25 Nov 2025 08:50:31 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_small_files.yml b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_small_files.yml new file mode 100644 index 00000000..bf146dd9 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Upload_API_Integration/Smart_Upload_Detection/detects_and_uses_correct_upload_method_for_small_files.yml @@ -0,0 +1,117 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWFlMzExYjU0ODZkOTZmMmExZDgzNmIyNzkwZWRjOGRhDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC1hZTMxMWI1NDg2ZDk2ZjJhMWQ4MzZiMjc5MGVkYzhkYQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJVUExPQURDQVJFX1NUT1JFIg0KDQoxDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtYWUzMTFiNTQ4NmQ5NmYyYTFkODM2YjI3OTBlZGM4ZGENCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtYWUzMTFiNTQ4NmQ5NmYyYTFkODM2YjI3OTBlZGM4ZGEtLQ0K + headers: + User-Agent: + - Faraday v2.14.0 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-ae311b5486d96f2a1d836b2790edc8da + Content-Length: + - '1853' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:28 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, X-UC-User-Agent, DNT + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' + X-Uploadcare-Request-Id: + - 76466d36-a537-4342-8671-1bd0bb0936a5 + body: + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"edaed8d8-54aa-4b6b-8d8c-ae3e7fa738e4"}' + recorded_at: Tue, 25 Nov 2025 08:50:28 GMT +- request: + method: get + uri: https://api.uploadcare.com/files/edaed8d8-54aa-4b6b-8d8c-ae3e7fa738e4/ + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - Uploadcare.Simple + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Nov 2025 08:50:29 GMT + Content-Type: + - application/vnd.uploadcare-v0.7+json + Content-Length: + - '716' + Connection: + - keep-alive + Server: + - nginx + Warning: + - '199 Miscellaneous warning: You are using the demo project' + Access-Control-Allow-Origin: + - https://uploadcare.com + Vary: + - Accept + Allow: + - DELETE, GET, HEAD, OPTIONS + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - d3befa21-559e-4f81-accc-b263cfc53e89 + X-Frame-Options: + - SAMEORIGIN + X-Robots-Tag: + - noindex, nofollow, nosnippet, noarchive + body: + encoding: UTF-8 + string: '{"datetime_removed":null,"datetime_stored":"2025-11-25T08:50:28.921252Z","datetime_uploaded":"2025-11-25T08:50:28.767896Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/edaed8d8-54aa-4b6b-8d8c-ae3e7fa738e4/kitten.jpeg","original_filename":"kitten.jpeg","size":1290,"url":"https://api.uploadcare.com/files/edaed8d8-54aa-4b6b-8d8c-ae3e7fa738e4/","uuid":"edaed8d8-54aa-4b6b-8d8c-ae3e7fa738e4","variations":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}' + recorded_at: Tue, 25 Nov 2025 08:50:29 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/amazon_upload.yml b/spec/fixtures/vcr_cassettes/amazon_upload.yml deleted file mode 100644 index f78ffbc3..00000000 --- a/spec/fixtures/vcr_cassettes/amazon_upload.yml +++ /dev/null @@ -1,215 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/start/ - body: - encoding: UTF-8 - string: "-----------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_STORE\"\r\n\r\n0\r\n-----------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81\r\nContent-Disposition: - form-data; name=\"filename\"\r\n\r\nbig.jpg\r\n-----------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81\r\nContent-Disposition: - form-data; name=\"size\"\r\n\r\n14679474\r\n-----------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81\r\nContent-Disposition: - form-data; name=\"content_type\"\r\n\r\napplication/octet-stream\r\n-----------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------e0ca7b64be1639327bb2555bc8fbc89595b15c5b81 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 23 Jan 2020 13:59:08 GMT - Content-Type: - - application/json - Content-Length: - - '1596' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"parts":["https://uploadcare.s3-accelerate.amazonaws.com/4fbf73de-2822-40a0-979b-08a2ed5cbcf1/big.jpg?partNumber=1&uploadId=.AZffdlib1V_wMMya6XvEYhygDFNHo.cM_MXctI03FJP6PWZREiN2pdNOJ3AiF_8VxMzXKoxBH044wydNRQG7kSzrOPkNSEco4QdNeRrfUiJ.muNfk_2_etJzPqaeh83&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200123%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200123T135908Z&X-Amz-Signature=68a155be23acf14485851c5c4ac9b8b600b54c869d0d3067b1005f4d85b16808","https://uploadcare.s3-accelerate.amazonaws.com/4fbf73de-2822-40a0-979b-08a2ed5cbcf1/big.jpg?partNumber=2&uploadId=.AZffdlib1V_wMMya6XvEYhygDFNHo.cM_MXctI03FJP6PWZREiN2pdNOJ3AiF_8VxMzXKoxBH044wydNRQG7kSzrOPkNSEco4QdNeRrfUiJ.muNfk_2_etJzPqaeh83&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200123%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200123T135908Z&X-Amz-Signature=e56a68596eab4dd8d2b0c1ecf7417e64dd6ead8d89c98ea0b0356acb375e97d6","https://uploadcare.s3-accelerate.amazonaws.com/4fbf73de-2822-40a0-979b-08a2ed5cbcf1/big.jpg?partNumber=3&uploadId=.AZffdlib1V_wMMya6XvEYhygDFNHo.cM_MXctI03FJP6PWZREiN2pdNOJ3AiF_8VxMzXKoxBH044wydNRQG7kSzrOPkNSEco4QdNeRrfUiJ.muNfk_2_etJzPqaeh83&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200123%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200123T135908Z&X-Amz-Signature=a22f7b4687be9c93e92f40b3489eaa73eeaaf211443ab5b88e7241369cff5d90"],"uuid":"4fbf73de-2822-40a0-979b-08a2ed5cbcf1"}' - http_version: - recorded_at: Thu, 23 Jan 2020 13:59:08 GMT -- request: - method: put - uri: https://uploadcare.s3-accelerate.amazonaws.com/4fbf73de-2822-40a0-979b-08a2ed5cbcf1/big.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA/20200123/us-east-1/s3/aws4_request&X-Amz-Date=20200123T135908Z&X-Amz-Expires=86400&X-Amz-Signature=68a155be23acf14485851c5c4ac9b8b600b54c869d0d3067b1005f4d85b16808&X-Amz-SignedHeaders=host&partNumber=1&uploadId=.AZffdlib1V_wMMya6XvEYhygDFNHo.cM_MXctI03FJP6PWZREiN2pdNOJ3AiF_8VxMzXKoxBH044wydNRQG7kSzrOPkNSEco4QdNeRrfUiJ.muNfk_2_etJzPqaeh83 - body: - encoding: UTF-8 - string: Big string (5) MB - headers: - Accept: - - application/json - Content-Type: - - application/octet-stream - Connection: - - close - Host: - - uploadcare.s3-accelerate.amazonaws.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Content-Length: - - '0' - Connection: - - close - X-Amz-Id-2: - - mtO3Rdj2uCvt0eS4++DmQG2+nVrIt2PIEgYxThPVjcEB5u9xv2FTYlLeYBfyLXTtDPH/BaFCjRc= - X-Amz-Request-Id: - - A101A0124A5605B2 - Date: - - Thu, 23 Jan 2020 14:00:22 GMT - Etag: - - '"be18919d68022a42053129186a3c7da1"' - X-Amz-Server-Side-Encryption: - - AES256 - Server: - - AmazonS3 - X-Cache: - - Miss from cloudfront - Via: - - 1.1 f8fe53d5464b299529d281799da8de30.cloudfront.net (CloudFront) - X-Amz-Cf-Pop: - - FRA2-C2 - X-Amz-Cf-Id: - - hVzH5qKJGF7xpZHxqHS-761I1L1nKu_MipKjCQvFsTzmxNwp88F4Hg== - body: - encoding: ASCII-8BIT - string: '' - http_version: - recorded_at: Thu, 23 Jan 2020 14:00:21 GMT -- request: - method: put - uri: https://uploadcare.s3-accelerate.amazonaws.com/4fbf73de-2822-40a0-979b-08a2ed5cbcf1/big.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA/20200123/us-east-1/s3/aws4_request&X-Amz-Date=20200123T135908Z&X-Amz-Expires=86400&X-Amz-Signature=e56a68596eab4dd8d2b0c1ecf7417e64dd6ead8d89c98ea0b0356acb375e97d6&X-Amz-SignedHeaders=host&partNumber=2&uploadId=.AZffdlib1V_wMMya6XvEYhygDFNHo.cM_MXctI03FJP6PWZREiN2pdNOJ3AiF_8VxMzXKoxBH044wydNRQG7kSzrOPkNSEco4QdNeRrfUiJ.muNfk_2_etJzPqaeh83 - body: - encoding: UTF-8 - string: Big string (5) MB - headers: - Accept: - - application/json - Content-Type: - - application/octet-stream - Connection: - - close - Host: - - uploadcare.s3-accelerate.amazonaws.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Content-Length: - - '0' - Connection: - - close - X-Amz-Id-2: - - qUEkwjFVMieKZfkuXSUDGhAZ5z6bS2xZbIxqJ01oYA1XUeqGVufKnCQu6MQnm1289Cm756DDojs= - X-Amz-Request-Id: - - B221CF338DD172F1 - Date: - - Thu, 23 Jan 2020 14:00:27 GMT - Etag: - - '"f4ff1342ee44e1c27405fb7c88a18140"' - X-Amz-Server-Side-Encryption: - - AES256 - Server: - - AmazonS3 - X-Cache: - - Miss from cloudfront - Via: - - 1.1 a32f966fc5896281eb3de44fd8f57d40.cloudfront.net (CloudFront) - X-Amz-Cf-Pop: - - FRA2-C2 - X-Amz-Cf-Id: - - xGyl1LvtY9U4tXPyOc9BGQBSGenrMlORxc3qaBLd5g99_IGgjDi6og== - body: - encoding: ASCII-8BIT - string: '' - http_version: - recorded_at: Thu, 23 Jan 2020 14:00:26 GMT -- request: - method: put - uri: https://uploadcare.s3-accelerate.amazonaws.com/4fbf73de-2822-40a0-979b-08a2ed5cbcf1/big.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA/20200123/us-east-1/s3/aws4_request&X-Amz-Date=20200123T135908Z&X-Amz-Expires=86400&X-Amz-Signature=a22f7b4687be9c93e92f40b3489eaa73eeaaf211443ab5b88e7241369cff5d90&X-Amz-SignedHeaders=host&partNumber=3&uploadId=.AZffdlib1V_wMMya6XvEYhygDFNHo.cM_MXctI03FJP6PWZREiN2pdNOJ3AiF_8VxMzXKoxBH044wydNRQG7kSzrOPkNSEco4QdNeRrfUiJ.muNfk_2_etJzPqaeh83 - body: - encoding: UTF-8 - string: Big string (3) MB - headers: - Accept: - - application/json - Content-Type: - - application/octet-stream - Connection: - - close - Host: - - uploadcare.s3-accelerate.amazonaws.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Content-Length: - - '0' - Connection: - - close - X-Amz-Id-2: - - xIU2AfD9DV4Q7id62/nBqmZ2TLNNdPSrHZoDrFxl3ycVhvELFvLdDKEx6/RJfanP/sIaQCT2TqY= - X-Amz-Request-Id: - - 8420D4E645708444 - Date: - - Thu, 23 Jan 2020 14:00:28 GMT - Etag: - - '"b37ea7944af3a36e79a9c4e803b0afc0"' - X-Amz-Server-Side-Encryption: - - AES256 - Server: - - AmazonS3 - X-Cache: - - Miss from cloudfront - Via: - - 1.1 0e75d8f2d484ce463fc04f5c422aa179.cloudfront.net (CloudFront) - X-Amz-Cf-Pop: - - FRA2-C1 - X-Amz-Cf-Id: - - GAlLa3srxi-yEa_whUKygyN_VuFF-eBgVsC3WkQYQKHLlnXYhLWOfA== - body: - encoding: ASCII-8BIT - string: '' - http_version: - recorded_at: Thu, 23 Jan 2020 14:00:29 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_convert_many.yml b/spec/fixtures/vcr_cassettes/document_convert_convert_many.yml deleted file mode 100644 index 11808f56..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_convert_many.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/document/ - body: - encoding: UTF-8 - string: '{"paths":["a4b9db2f-1591-4f4c-8f68-94018924525d/document/-/format/png/-/page/1/"],"store":"0"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/2.7.2) - Date: - - Mon, 19 Jul 2021 12:38:41 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:ed63280f553fde33e885615b4d22f1d384ddcd56 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 19 Jul 2021 12:38:42 GMT - Content-Type: - - application/json - Content-Length: - - '188' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.6' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"result": [{"original_source": "a4b9db2f-1591-4f4c-8f68-94018924525d/document/-/format/png/-/page/1/", - "token": 21120391, "uuid": "cd7a51d4-9776-4749-b749-c9fc691891f1"}], "problems": - {}}' - recorded_at: Mon, 19 Jul 2021 12:38:42 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_convert_many_with_error.yml b/spec/fixtures/vcr_cassettes/document_convert_convert_many_with_error.yml deleted file mode 100644 index 6e65108a..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_convert_many_with_error.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/document/ - body: - encoding: UTF-8 - string: '{"paths":["86c54d9a-3453-4b12-8dcc-49883ae8f084/document/-/format/jpg/-/page/1/"],"store":"0"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/2.7.2) - Date: - - Fri, 23 Jul 2021 07:50:42 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:b325415d285d095cb48dffd38a281fb31b8de912 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Jul 2021 07:50:43 GMT - Content-Type: - - application/json - Content-Length: - - '185' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.6' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"result": [], "problems": {"86c54d9a-3453-4b12-8dcc-49883ae8f084/document/-/format/jpg/-/page/1/": - "the target_format is not a supported ''to'' format for this source file. - txt -> jpg"}}' - recorded_at: Fri, 23 Jul 2021 07:50:43 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_convert_multipage_group.yml b/spec/fixtures/vcr_cassettes/document_convert_convert_multipage_group.yml deleted file mode 100644 index 07ff7ec8..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_convert_multipage_group.yml +++ /dev/null @@ -1,118 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/document/ - body: - encoding: UTF-8 - string: '{"paths":["23d29586-713e-4152-b400-05fb54730453/document/-/format/jpg/"],"store":"0","save_in_group":"1"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.2/ (Ruby/3.3.0) - Date: - - Thu, 20 Jun 2024 12:14:07 GMT - Authorization: - - Uploadcare : - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 20 Jun 2024 12:14:09 GMT - Content-Type: - - application/json - Content-Length: - - '179' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - d5ba6783-f33d-48b7-b567-f9eda216a17d - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"result": [{"original_source": "23d29586-713e-4152-b400-05fb54730453/document/-/format/jpg/", - "token": 44428559, "uuid": "d586b32a-9b5b-4209-94ad-b79761f5b57f"}], "problems": - {}}' - recorded_at: Thu, 20 Jun 2024 12:14:09 GMT -- request: - method: get - uri: https://api.uploadcare.com/convert/document/23d29586-713e-4152-b400-05fb54730453/ - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.2/ (Ruby/3.3.0) - Date: - - Thu, 20 Jun 2024 12:14:09 GMT - Authorization: - - Uploadcare : - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 20 Jun 2024 12:14:10 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '778' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4b26200c-ef3d-48f9-8073-f6d9a4fe237e - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"error":null,"format":{"name":"pdf","conversion_formats":[{"name":"bmp"},{"name":"csv"},{"name":"doc"},{"name":"docx"},{"name":"dwg"},{"name":"dxf"},{"name":"enhanced.jpg"},{"name":"epub"},{"name":"fb2"},{"name":"gif"},{"name":"html"},{"name":"html5"},{"name":"html5-1page"},{"name":"htmlz"},{"name":"jpg"},{"name":"lit"},{"name":"lrf"},{"name":"md"},{"name":"mobi"},{"name":"mp3"},{"name":"pcx"},{"name":"pdb"},{"name":"png"},{"name":"ppt"},{"name":"pptx"},{"name":"prc"},{"name":"ps"},{"name":"rb"},{"name":"rtf"},{"name":"snb"},{"name":"svg"},{"name":"tcr"},{"name":"thumbnail"},{"name":"tiff"},{"name":"txt"},{"name":"txtz"},{"name":"xls"},{"name":"xlsx"}],"converted_groups":{"png":"88d59cae-c88d-459d-b99b-a87a6a2519d0~2","jpg":"69fe5a35-e5e5-449d-9287-e8453f76aab3~2"}}}' - recorded_at: Thu, 20 Jun 2024 12:14:10 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_convert_multipage_zip.yml b/spec/fixtures/vcr_cassettes/document_convert_convert_multipage_zip.yml deleted file mode 100644 index 59f4309f..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_convert_multipage_zip.yml +++ /dev/null @@ -1,118 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/document/ - body: - encoding: UTF-8 - string: '{"paths":["d95309eb-50bd-4594-bd7a-950011578480/document/-/format/jpg/"],"store":"1","save_in_group":"0"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.2/ (Ruby/3.3.0) - Date: - - Thu, 20 Jun 2024 12:01:04 GMT - Authorization: - - Uploadcare : - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 20 Jun 2024 12:01:06 GMT - Content-Type: - - application/json - Content-Length: - - '179' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - dcc60a86-38cb-4836-a037-8279b60e97ab - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"result": [{"original_source": "d95309eb-50bd-4594-bd7a-950011578480/document/-/format/jpg/", - "token": 44428259, "uuid": "ddf298c3-6113-465b-8677-1f566cced4f1"}], "problems": - {}}' - recorded_at: Thu, 20 Jun 2024 12:01:06 GMT -- request: - method: get - uri: https://api.uploadcare.com/convert/document/d95309eb-50bd-4594-bd7a-950011578480/ - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.2/ (Ruby/3.3.0) - Date: - - Thu, 20 Jun 2024 12:01:06 GMT - Authorization: - - Uploadcare : - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 20 Jun 2024 12:01:07 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '663' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 6caab90a-9fc0-4194-a092-480e8ce65ff9 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"error":null,"format":{"name":"pdf","conversion_formats":[{"name":"bmp"},{"name":"csv"},{"name":"doc"},{"name":"docx"},{"name":"dwg"},{"name":"dxf"},{"name":"enhanced.jpg"},{"name":"epub"},{"name":"fb2"},{"name":"gif"},{"name":"html"},{"name":"html5"},{"name":"html5-1page"},{"name":"htmlz"},{"name":"jpg"},{"name":"lit"},{"name":"lrf"},{"name":"md"},{"name":"mobi"},{"name":"mp3"},{"name":"pcx"},{"name":"pdb"},{"name":"png"},{"name":"ppt"},{"name":"pptx"},{"name":"prc"},{"name":"ps"},{"name":"rb"},{"name":"rtf"},{"name":"snb"},{"name":"svg"},{"name":"tcr"},{"name":"thumbnail"},{"name":"tiff"},{"name":"txt"},{"name":"txtz"},{"name":"xls"},{"name":"xlsx"}]}}' - recorded_at: Thu, 20 Jun 2024 12:01:07 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_file_info.yml b/spec/fixtures/vcr_cassettes/document_convert_file_info.yml deleted file mode 100644 index 89da313b..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_file_info.yml +++ /dev/null @@ -1,57 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/cd7a51d4-9776-4749-b749-c9fc691891f1/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/3.0.2) - Date: - - Mon, 06 Sep 2021 05:03:37 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:371eb52c3597fa0985ccde444c5c8f549fe83c8f - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 06 Sep 2021 05:03:38 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '573' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":"2021-07-20T14:04:23.565506Z","datetime_stored":null,"datetime_uploaded":"2021-07-19T12:38:42.387616Z","image_info":{"width":2480,"height":3508,"format":"PNG","color_mode":"LA","geo_location":null,"orientation":null,"dpi":null,"datetime_original":null,"sequence":false},"is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":null,"original_filename":"sample-0.png","size":57932,"url":"https://api.uploadcare.com/files/cd7a51d4-9776-4749-b749-c9fc691891f1/","uuid":"cd7a51d4-9776-4749-b749-c9fc691891f1","source":null}' - recorded_at: Mon, 06 Sep 2021 05:03:38 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_get_status.yml b/spec/fixtures/vcr_cassettes/document_convert_get_status.yml deleted file mode 100644 index 3a266b87..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_get_status.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/convert/document/status/21120333/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/2.7.2) - Date: - - Mon, 19 Jul 2021 13:01:44 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:027b3a6d53b028ab0e7d5f3997c043e760fc2fd9 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 19 Jul 2021 13:01:45 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '91' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.6' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"status":"finished","error":null,"result":{"uuid":"eeaa86d1-0b46-4002-9933-59a48d1d8466"}}' - recorded_at: Mon, 19 Jul 2021 13:01:45 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_info.yml b/spec/fixtures/vcr_cassettes/document_convert_info.yml deleted file mode 100644 index b57fbb70..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_info.yml +++ /dev/null @@ -1,60 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/convert/document/cd7a51d4-9776-4749-b749-c9fc691891f1/ - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.1/demopublickey (Ruby/3.1.3) - Date: - - Tue, 30 Apr 2024 12:30:58 GMT - Authorization: - - Uploadcare demopublickey:219d67acad8d30c4b33e2eb4b14359bb1d82ebd8 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: ok - headers: - Date: - - Tue, 30 Apr 2024 12:31:00 GMT - Content-Type: - - text/html; charset=utf-8 - Content-Length: - - '62' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: You are using the demo project' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - '0797972e-39d6-4636-8385-8666c9d3c98f' - X-Frame-Options: - - SAMEORIGIN - body: - encoding: UTF-8 - string: '{"error":null, "format":{"name":"jpg", "conversion_formats":[{"name":"avif"}, {"name":"bmp"}, {"name":"gif"}, {"name":"ico"}, {"name":"pcx"}, {"name":"pdf"}, {"name":"png"}, {"name":"ps"}, {"name":"svg"}, {"name":"tga"}, {"name":"thumbnail"}, {"name":"tiff"}, {"name":"wbmp"}, {"name":"webp"}]}}' - recorded_at: Tue, 30 Apr 2024 12:31:00 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/document_convert_to_multipage.yml b/spec/fixtures/vcr_cassettes/document_convert_to_multipage.yml deleted file mode 100644 index dbe2ad36..00000000 --- a/spec/fixtures/vcr_cassettes/document_convert_to_multipage.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/document/ - body: - encoding: UTF-8 - string: '{"paths":["23d29586-713e-4152-b400-05fb54730453/document/-/format/png/"],"store":"0","save_in_group":"1"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.2/ (Ruby/3.3.0) - Date: - - Thu, 20 Jun 2024 11:27:39 GMT - Authorization: - - Uploadcare : - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 20 Jun 2024 11:27:41 GMT - Content-Type: - - application/json - Content-Length: - - '179' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 3bfdd243-a700-4a53-93c5-f3bbbbdf6efa - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"result": [{"original_source": "23d29586-713e-4152-b400-05fb54730453/document/-/format/png/", - "token": 44427572, "uuid": "44c011c3-de82-4c65-81ac-2738c741bec5"}], "problems": - {}}' - recorded_at: Thu, 20 Jun 2024 11:27:41 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/file_info.yml b/spec/fixtures/vcr_cassettes/file_info.yml deleted file mode 100644 index bef28b9e..00000000 --- a/spec/fixtures/vcr_cassettes/file_info.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 17 Jan 2020 13:53:30 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '629' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2020-01-16T15:03:15.315064Z","datetime_uploaded":"2020-01-16T15:03:14.676902Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/8f64f313-e6b1-4731-96c0-6751f1e7a50a/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/","uuid":"8f64f313-e6b1-4731-96c0-6751f1e7a50a","source":null}' - http_version: - recorded_at: Fri, 17 Jan 2020 13:53:30 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_create.yml b/spec/fixtures/vcr_cassettes/file_metadata_create.yml deleted file mode 100644 index dc404ff5..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_create.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: put - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/metadata/new_key/ - body: - encoding: UTF-8 - string: '"some value"' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Tue, 20 Sep 2022 07:34:01 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:eeb30505be2fa6333ff5949384e418657712fb87 - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 201 - message: Created - headers: - Date: - - Tue, 20 Sep 2022 07:34:02 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '12' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 58133b5c-fa76-42f6-8f37-1f5aa04b266a - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '"some value"' - recorded_at: Tue, 20 Sep 2022 07:34:02 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_delete.yml b/spec/fixtures/vcr_cassettes/file_metadata_delete.yml deleted file mode 100644 index 6ec9dd74..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_delete.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/metadata/subsystem/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Mon, 19 Sep 2022 15:15:17 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:52579bb5464e74d605e97676f1d202aeef92d68c - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 204 - message: No Content - headers: - Date: - - Mon, 19 Sep 2022 15:15:18 GMT - Content-Length: - - '0' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 94485c3a-0f3b-413f-b25f-40e58db30507 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '' - recorded_at: Mon, 19 Sep 2022 15:15:18 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_index.yml b/spec/fixtures/vcr_cassettes/file_metadata_index.yml deleted file mode 100644 index bb2ea939..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_index.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/metadata/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Wed, 21 Sep 2022 08:06:08 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:e5c9d1d868195dede3460284f0e3d9443f176b39 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 21 Sep 2022 08:06:08 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '48' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c3888e4e-8bbc-4dac-bf63-c9f71d395242 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"subsystem":"test"}' - recorded_at: Wed, 21 Sep 2022 08:06:08 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_index_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/file_metadata_index_nonexistent_uuid.yml deleted file mode 100644 index 0341a844..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_index_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/nonexistent/metadata/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Tue, 20 Sep 2022 07:21:06 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:4772c55593cb5a7c1b0ba315be09945e403a7359 - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 404 - message: Not Found - headers: - Date: - - Tue, 20 Sep 2022 07:21:06 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 2a75e9c3-2090-4e94-b168-96d3c8218f4e - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - recorded_at: Tue, 20 Sep 2022 07:21:06 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_show.yml b/spec/fixtures/vcr_cassettes/file_metadata_show.yml deleted file mode 100644 index dd406594..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_show.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/metadata/subsystem/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Mon, 19 Sep 2022 12:45:52 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:b808934d4a43d45ef76e91605df78e3124c9159c - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 19 Sep 2022 12:45:52 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '6' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - cf290e3d-5fa5-4f68-b727-8e7cd1865c7f - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '"test"' - recorded_at: Mon, 19 Sep 2022 12:45:52 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_show_nonexistent_key.yml b/spec/fixtures/vcr_cassettes/file_metadata_show_nonexistent_key.yml deleted file mode 100644 index d350a55a..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_show_nonexistent_key.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/metadata/nonexistent/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Tue, 20 Sep 2022 07:27:42 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:dcec8d99dc4ff0352cbd05b1a402e7c4aad3a098 - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 404 - message: Not Found - headers: - Date: - - Tue, 20 Sep 2022 07:27:43 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 51586837-7487-4afa-b035-360b80839b5f - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - recorded_at: Tue, 20 Sep 2022 07:27:43 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/file_metadata_update.yml b/spec/fixtures/vcr_cassettes/file_metadata_update.yml deleted file mode 100644 index 20cfefe6..00000000 --- a/spec/fixtures/vcr_cassettes/file_metadata_update.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: put - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/metadata/subsystem/ - body: - encoding: UTF-8 - string: '"new test value"' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Mon, 19 Sep 2022 15:45:08 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:e0ad05dc4c50ac90678cbb7bc7630bffdf08e998 - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 201 - message: Created - headers: - Date: - - Mon, 19 Sep 2022 15:45:09 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '16' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - d9cd8d55-42d6-4346-8fee-d10ddc609433 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '"new test value"' - recorded_at: Mon, 19 Sep 2022 15:45:09 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/group_delete_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/group_delete_nonexistent_uuid.yml deleted file mode 100644 index c0099be9..00000000 --- a/spec/fixtures/vcr_cassettes/group_delete_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/groups/nonexistent/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Wed, 21 Sep 2022 16:01:25 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:0c165166270de214288d2cd0f0e1038686ce0f00 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 404 - message: Not Found - headers: - Date: - - Wed, 21 Sep 2022 16:01:25 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a220520f-5af4-4a53-a311-416197a267d6 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - recorded_at: Wed, 21 Sep 2022 16:01:25 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/http_fail.yml b/spec/fixtures/vcr_cassettes/http_fail.yml deleted file mode 100644 index 59bb0721..00000000 --- a/spec/fixtures/vcr_cassettes/http_fail.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: http://api.uploadcare.com/files/172f8ce7-fa49-4677-b58c-1fbdfbab5c22/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 17 Feb 2020 13:19:05 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '614' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Warning: - - '199 Miscellaneous warning: You should use secure requests to Uploadcare' - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-02-17T09:43:28.211822Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":50,"width":50,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/172f8ce7-fa49-4677-b58c-1fbdfbab5c22/kitten.jpeg","original_filename":"kitten.jpeg","size":1290,"url":"https://api.uploadcare.com/files/172f8ce7-fa49-4677-b58c-1fbdfbab5c22/","uuid":"172f8ce7-fa49-4677-b58c-1fbdfbab5c22","source":null}' - http_version: - recorded_at: Mon, 17 Feb 2020 13:19:05 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/project.yml b/spec/fixtures/vcr_cassettes/project.yml deleted file mode 100644 index bc496f8d..00000000 --- a/spec/fixtures/vcr_cassettes/project.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/project/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Tue, 21 Jan 2020 08:57:26 GMT - Authorization: - - Uploadcare foo:bar - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 21 Jan 2020 08:57:26 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '99' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"collaborators":[],"name":"New project","pub_key":"foo","autostore_enabled":true}' - http_version: - recorded_at: Tue, 21 Jan 2020 08:57:27 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/remove_bg.yml b/spec/fixtures/vcr_cassettes/remove_bg.yml deleted file mode 100644 index 3770ec98..00000000 --- a/spec/fixtures/vcr_cassettes/remove_bg.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/addons/remove_bg/execute/ - body: - encoding: UTF-8 - string: '{"target":"ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768","params":{"crop":true,"type_level":"2"}}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:29:23 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:1e8f4ed9741bff27646ee6d4e056ba22c7e8fc67 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Sep 2022 11:29:24 GMT - Content-Type: - - application/json - Content-Length: - - '54' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c3446e41-9eb0-4301-aeb4-356d0fdcf9af - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"request_id": "c3446e41-9eb0-4301-aeb4-356d0fdcf9af"}' - recorded_at: Fri, 23 Sep 2022 11:29:24 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/remove_bg_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/remove_bg_nonexistent_uuid.yml deleted file mode 100644 index 16bbf84c..00000000 --- a/spec/fixtures/vcr_cassettes/remove_bg_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/?request_id=nonexistent - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 12:06:25 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:d64c5bd12625554a9f441d3693f96f1165274c95 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Fri, 23 Sep 2022 12:06:26 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '40' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 12577f1f-44f4-4933-b36e-44a5c18e0719 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Fri, 23 Sep 2022 12:06:26 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/remove_bg_status.yml b/spec/fixtures/vcr_cassettes/remove_bg_status.yml deleted file mode 100644 index 16b54c76..00000000 --- a/spec/fixtures/vcr_cassettes/remove_bg_status.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/remove_bg/execute/status/?request_id=c3446e41-9eb0-4301-aeb4-356d0fdcf9af - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:33:32 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:99b4750f31c2ec474040d6a798c11d2d86916c69 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Sep 2022 11:33:33 GMT - Content-Type: - - application/json - Content-Length: - - '81' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 1bf5582b-0cf2-49d8-bfbe-fe2951085a03 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"status": "done", "result": {"file_id": "bc37b996-916d-4ed7-b230-fa71a4290cb3"}}' - recorded_at: Fri, 23 Sep 2022 11:33:33 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/remove_bg_status_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/remove_bg_status_nonexistent_uuid.yml deleted file mode 100644 index f03b42ef..00000000 --- a/spec/fixtures/vcr_cassettes/remove_bg_status_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/?request_id=nonexistent - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 12:08:12 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:0a286192b03c35b02023a32e102c8c7870a00797 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Fri, 23 Sep 2022 12:08:12 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '40' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a19b41c3-5715-4d03-b607-4640127d90ce - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Fri, 23 Sep 2022 12:08:12 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_batch_delete.yml b/spec/fixtures/vcr_cassettes/rest_file_batch_delete.yml deleted file mode 100644 index 53f0e78a..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_batch_delete.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/files/storage/ - body: - encoding: UTF-8 - string: '["935ff093-a5cf-48c5-81cf-208511bac6e6","63be5a6e-9b6b-454b-8aec-9136d5f83d0c"]' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 20 Jan 2020 15:53:31 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1158' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, OPTIONS, PUT - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"status":"ok","problems":{},"result":[{"datetime_removed":"2020-01-20T09:20:11.930717Z","datetime_stored":null,"datetime_uploaded":"2020-01-20T09:16:37.250441Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":null,"original_filename":"delete.png","size":3118,"url":"https://api.uploadcare.com/files/935ff093-a5cf-48c5-81cf-208511bac6e6/","uuid":"935ff093-a5cf-48c5-81cf-208511bac6e6","source":null},{"datetime_removed":"2020-01-20T09:20:11.930717Z","datetime_stored":null,"datetime_uploaded":"2020-01-20T09:16:45.539533Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":null,"original_filename":"delete.png","size":3118,"url":"https://api.uploadcare.com/files/63be5a6e-9b6b-454b-8aec-9136d5f83d0c/","uuid":"63be5a6e-9b6b-454b-8aec-9136d5f83d0c","source":null}]}' - http_version: - recorded_at: Mon, 20 Jan 2020 15:53:31 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_batch_delete_fail.yml b/spec/fixtures/vcr_cassettes/rest_file_batch_delete_fail.yml deleted file mode 100644 index f5063b42..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_batch_delete_fail.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/files/storage/ - body: - encoding: UTF-8 - string: '["nonexistent","other_nonexistent"]' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 20 Jan 2020 15:53:32 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '94' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, OPTIONS, PUT - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"status":"ok","problems":{"other_nonexistent":"Invalid","nonexistent":"Invalid"},"result":[]}' - http_version: - recorded_at: Mon, 20 Jan 2020 15:53:32 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_batch_store.yml b/spec/fixtures/vcr_cassettes/rest_file_batch_store.yml deleted file mode 100644 index ca7c4114..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_batch_store.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: put - uri: https://api.uploadcare.com/files/storage/ - body: - encoding: UTF-8 - string: '["e9a9f291-cc52-4388-bf65-9feec1c75ff9","c724feac-86f7-447c-b2d6-b0ced220173d"]' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Mon, 20 Jan 2020 15:53:28 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:b8b7e2039b585b86ca4109181523bb40b3c86c95 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 20 Jan 2020 15:53:29 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1291' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, OPTIONS, PUT - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"status":"ok","problems":{},"result":[{"datetime_removed":null,"datetime_stored":"2020-01-16T15:02:30.430281Z","datetime_uploaded":"2020-01-16T15:02:30.308894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/c724feac-86f7-447c-b2d6-b0ced220173d/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/c724feac-86f7-447c-b2d6-b0ced220173d/","uuid":"c724feac-86f7-447c-b2d6-b0ced220173d","source":null},{"datetime_removed":null,"datetime_stored":"2020-01-20T15:53:29.507216Z","datetime_uploaded":"2020-01-17T14:46:41.919939Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e9a9f291-cc52-4388-bf65-9feec1c75ff9/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/e9a9f291-cc52-4388-bf65-9feec1c75ff9/","uuid":"e9a9f291-cc52-4388-bf65-9feec1c75ff9","source":null}]}' - http_version: - recorded_at: Mon, 20 Jan 2020 15:53:29 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_batch_store_fail.yml b/spec/fixtures/vcr_cassettes/rest_file_batch_store_fail.yml deleted file mode 100644 index adc9e1fd..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_batch_store_fail.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: put - uri: https://api.uploadcare.com/files/storage/ - body: - encoding: UTF-8 - string: '["nonexistent","other_nonexistent"]' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Mon, 20 Jan 2020 15:53:29 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:2ba1ca30ae1b3a52145c5479832ba0701932e2ad - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 20 Jan 2020 15:53:30 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '94' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, OPTIONS, PUT - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"status":"ok","problems":{"other_nonexistent":"Invalid","nonexistent":"Invalid"},"result":[]}' - http_version: - recorded_at: Mon, 20 Jan 2020 15:53:30 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_copy_dont_strip_operations.yml b/spec/fixtures/vcr_cassettes/rest_file_copy_dont_strip_operations.yml deleted file mode 100644 index 79250e4f..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_copy_dont_strip_operations.yml +++ /dev/null @@ -1,107 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/files/ - body: - encoding: UTF-8 - string: '{"source":"https://ucarecdn.com/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/-/resize/10x10/"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 201 - message: Created - headers: - Date: - - Fri, 06 Mar 2020 14:05:05 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '531' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Location: - - https://api.uploadcare.com/files/ea6e7b04-862e-4956-a571-684178f85a43/ - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"type":"file","result":{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-03-06T14:05:05.421906Z","image_info":null,"is_image":null,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/ea6e7b04-862e-4956-a571-684178f85a43/121.jpeg","original_filename":"121.jpeg","size":0,"url":"https://api.uploadcare.com/files/ea6e7b04-862e-4956-a571-684178f85a43/","uuid":"ea6e7b04-862e-4956-a571-684178f85a43","source":"/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/-/resize/10x10/"}}' - http_version: - recorded_at: Fri, 06 Mar 2020 14:05:05 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/ea6e7b04-862e-4956-a571-684178f85a43/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 06 Mar 2020 14:05:06 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '590' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-03-06T14:05:05.421906Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":10,"width":10,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/ea6e7b04-862e-4956-a571-684178f85a43/121.jpeg","original_filename":"121.jpeg","size":379,"url":"https://api.uploadcare.com/files/ea6e7b04-862e-4956-a571-684178f85a43/","uuid":"ea6e7b04-862e-4956-a571-684178f85a43","source":null}' - http_version: - recorded_at: Fri, 06 Mar 2020 14:05:06 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_copy_strip_operations.yml b/spec/fixtures/vcr_cassettes/rest_file_copy_strip_operations.yml deleted file mode 100644 index 10370e8a..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_copy_strip_operations.yml +++ /dev/null @@ -1,158 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 06 Mar 2020 14:05:25 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '621' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2020-02-12T12:16:47.326688Z","datetime_uploaded":"2020-02-12T12:16:47.262363Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":121,"width":100,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/121.jpeg","original_filename":"121.jpeg","size":2744,"url":"https://api.uploadcare.com/files/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/","uuid":"35b7fcd7-9bca-40e1-99b1-2adcc21c405d","source":null}' - http_version: - recorded_at: Fri, 06 Mar 2020 14:05:25 GMT -- request: - method: post - uri: https://api.uploadcare.com/files/ - body: - encoding: UTF-8 - string: '{"source":"https://ucarecdn.com/35b7fcd7-9bca-40e1-99b1-2adcc21c405d","strip_operations":true}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 201 - message: Created - headers: - Date: - - Fri, 06 Mar 2020 14:05:26 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '516' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Location: - - https://api.uploadcare.com/files/c276cf0c-7388-48bc-94cd-ef6962f39cb7/ - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"type":"file","result":{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-03-06T14:05:26.080709Z","image_info":null,"is_image":null,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/c276cf0c-7388-48bc-94cd-ef6962f39cb7/121.jpeg","original_filename":"121.jpeg","size":0,"url":"https://api.uploadcare.com/files/c276cf0c-7388-48bc-94cd-ef6962f39cb7/","uuid":"c276cf0c-7388-48bc-94cd-ef6962f39cb7","source":"/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/"}}' - http_version: - recorded_at: Fri, 06 Mar 2020 14:05:26 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/c276cf0c-7388-48bc-94cd-ef6962f39cb7/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 06 Mar 2020 14:05:27 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '596' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-03-06T14:05:26.080709Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":121,"width":100,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c276cf0c-7388-48bc-94cd-ef6962f39cb7/121.jpeg","original_filename":"121.jpeg","size":2744,"url":"https://api.uploadcare.com/files/c276cf0c-7388-48bc-94cd-ef6962f39cb7/","uuid":"c276cf0c-7388-48bc-94cd-ef6962f39cb7","source":null}' - http_version: - recorded_at: Fri, 06 Mar 2020 14:05:27 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_delete.yml b/spec/fixtures/vcr_cassettes/rest_file_delete.yml deleted file mode 100644 index 22f36d9b..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_delete.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/files/158e7c82-8246-4017-9f17-0798e18c91b0/storage/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Thu, 29 Sep 2022 18:32:33 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:f423d985670ff8eb7e728626f497f5f086cf276c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 29 Sep 2022 18:32:34 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '587' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, POST, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 2858bc95-ea6c-4f7c-9111-38a9d024d6d6 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":"2022-09-29T18:32:34.294207Z","datetime_stored":null,"datetime_uploaded":"2021-10-21T13:02:26.853869Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":null,"original_filename":"demo7.jpeg","size":21554,"url":"https://api.uploadcare.com/files/158e7c82-8246-4017-9f17-0798e18c91b0/","uuid":"158e7c82-8246-4017-9f17-0798e18c91b0","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}' - recorded_at: Thu, 29 Sep 2022 18:32:34 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_delete_nonexistent.yml b/spec/fixtures/vcr_cassettes/rest_file_delete_nonexistent.yml deleted file mode 100644 index 63d110df..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_delete_nonexistent.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/files/nonexistent/storage/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Thu, 29 Sep 2022 18:36:27 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:b66c4857b84326a62c4fd90d7e8a9e018cb13147 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 404 - message: Not Found - headers: - Date: - - Thu, 29 Sep 2022 18:36:27 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, POST, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - acfeca30-bb13-477d-99cd-a8d690bd23a7 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - recorded_at: Thu, 29 Sep 2022 18:36:27 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_external_copy.yml b/spec/fixtures/vcr_cassettes/rest_file_external_copy.yml deleted file mode 100644 index 7ba3ad21..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_external_copy.yml +++ /dev/null @@ -1,113 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/5632fc94-9dff-499f-a373-f69ea6f67ff8/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.7.6) - Date: - - Wed, 30 Nov 2022 13:27:59 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:2c041b3e704e775c5bdff1b3aa0a450b615a1efe - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 30 Nov 2022 13:28:00 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '712' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 190304d9-a180-4d67-b996-d33880855f32 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2022-11-29T20:11:58.097631Z","datetime_uploaded":"2022-11-29T20:11:57.935579Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/5632fc94-9dff-499f-a373-f69ea6f67ff8/image.png","original_filename":"image.png","size":180833,"url":"https://api.uploadcare.com/files/5632fc94-9dff-499f-a373-f69ea6f67ff8/","uuid":"5632fc94-9dff-499f-a373-f69ea6f67ff8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":2560,"format":"PNG","height":1440,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}' - recorded_at: Wed, 30 Nov 2022 13:28:00 GMT -- request: - method: post - uri: https://api.uploadcare.com/files/remote_copy/ - body: - encoding: UTF-8 - string: '{"source":"5632fc94-9dff-499f-a373-f69ea6f67ff8","target":"16d8625b4c5c4a372a8f"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.7.6) - Date: - - Wed, 30 Nov 2022 13:28:00 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:741cd9ff9b382eea440ff6af4120a4cfff29f554 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Wed, 30 Nov 2022 13:28:01 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '55' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 824644fb-28d5-4539-b25c-6a29d1fbfbfd - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Project has no storage with provided name."}' - recorded_at: Wed, 30 Nov 2022 13:28:01 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_info.yml b/spec/fixtures/vcr_cassettes/rest_file_info.yml deleted file mode 100644 index 352b3032..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_info.yml +++ /dev/null @@ -1,118 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Mon, 19 Sep 2022 17:27:31 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:0abb21ae52a6a9da15bc1a84e6a9ff16aea1af2e - Accept: - - application/vnd.uploadcare-v0.7+json - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 19 Sep 2022 17:27:32 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '733' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 38c98fa3-76e0-421c-918a-3492c79eb1ba - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2022-09-19T12:29:59.461061Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/kitten.jpeg","original_filename":"kitten.jpeg","size":1290,"url":"https://api.uploadcare.com/files/2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6/","uuid":"2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6","variations":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{"subsystem":"new - test value"}}' - recorded_at: Mon, 19 Sep 2022 17:27:32 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/640fe4b7-7352-42ca-8d87-0e4387957157/?include=appdata - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.3.6/5d5bb5639e3f2df33674 (Ruby/3.3.0) - Date: - - Sat, 24 Feb 2024 18:59:02 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:0cd5a6181fa53bc946d83db616e619fa75672634 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sat, 24 Feb 2024 18:59:03 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '2292' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c9e3fc1d-c435-4212-a3ef-0c142f4d2054 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2024-02-16T14:44:29.637342Z","datetime_uploaded":"2024-02-16T14:44:29.395043Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/640fe4b7-7352-42ca-8d87-0e4387957157/samplefile.jpeg","original_filename":"sample-file.jpeg","size":3518420,"url":"https://api.uploadcare.com/files/640fe4b7-7352-42ca-8d87-0e4387957157/","uuid":"640fe4b7-7352-42ca-8d87-0e4387957157","variations":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[72,72],"width":3011,"format":"JPEG","height":4516,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":"2023-03-10T16:23:15"}},"metadata":{},"appdata":{"aws_rekognition_detect_labels":{"data":{"Labels":[{"Name":"Accessories","Parents":[],"Instances":[],"Confidence":99.99993133544922},{"Name":"Strap","Parents":[{"Name":"Accessories"}],"Instances":[],"Confidence":99.99993133544922},{"Name":"Path","Parents":[],"Instances":[],"Confidence":99.9738998413086},{"Name":"Road","Parents":[],"Instances":[],"Confidence":99.55068969726562},{"Name":"Street","Parents":[{"Name":"City"},{"Name":"Road"},{"Name":"Urban"}],"Instances":[],"Confidence":97.583984375},{"Name":"Dog","Parents":[{"Name":"Animal"},{"Name":"Canine"},{"Name":"Mammal"},{"Name":"Pet"}],"Instances":[{"Confidence":97.21764373779297,"BoundingBox":{"Top":0.22813057899475098,"Left":0.20908746123313904,"Width":0.603455662727356,"Height":0.5941042304039001}}],"Confidence":97.41680908203125},{"Name":"Husky","Parents":[{"Name":"Animal"},{"Name":"Canine"},{"Name":"Dog"},{"Name":"Mammal"},{"Name":"Pet"}],"Instances":[],"Confidence":97.41680908203125},{"Name":"Leash","Parents":[],"Instances":[],"Confidence":95.52120971679688},{"Name":"Tarmac","Parents":[{"Name":"Road"}],"Instances":[],"Confidence":93.37832641601562},{"Name":"Walkway","Parents":[{"Name":"Path"}],"Instances":[],"Confidence":90.72362518310547}],"LabelModelVersion":"3.0"},"datetime_created":"2024-02-24T14:00:23.545995Z","datetime_updated":"2024-02-24T14:07:10.598091Z","version":"2016-06-27"},"uc_clamav_virus_scan":{"data":{"infected":false},"datetime_created":"2024-02-16T14:44:29.676188Z","datetime_updated":"2024-02-16T14:44:29.676207Z","version":"1.0.1"}}}' - recorded_at: Sat, 24 Feb 2024 18:59:03 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_info_fail.yml b/spec/fixtures/vcr_cassettes/rest_file_info_fail.yml deleted file mode 100644 index aab932eb..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_info_fail.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/nonexistent/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Mon, 20 Jan 2020 15:53:26 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:108d521ae36a6a6661f700b81070d49d2e83c7ab - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 404 - message: Not Found - headers: - Date: - - Mon, 20 Jan 2020 15:53:27 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - http_version: - recorded_at: Mon, 20 Jan 2020 15:53:27 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_info_no_connection.yml b/spec/fixtures/vcr_cassettes/rest_file_info_no_connection.yml deleted file mode 100644 index db70349a..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_info_no_connection.yml +++ /dev/null @@ -1,58 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/nonexistent/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - google.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 301 - message: Moved Permanently - headers: - Location: - - https://www.google.com/files/nonexistent/ - Content-Type: - - text/html; charset=UTF-8 - X-Content-Type-Options: - - nosniff - Date: - - Tue, 04 Feb 2020 14:13:49 GMT - Expires: - - Wed, 05 Feb 2020 14:13:49 GMT - Cache-Control: - - public, max-age=86400 - Server: - - sffe - Content-Length: - - '238' - X-Xss-Protection: - - '0' - Alt-Svc: - - quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; - ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; - ma=2592000 - Connection: - - close - body: - encoding: UTF-8 - string: "\n301 - Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n" - http_version: - recorded_at: Tue, 04 Feb 2020 14:13:49 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_internal_copy.yml b/spec/fixtures/vcr_cassettes/rest_file_internal_copy.yml deleted file mode 100644 index 23442e00..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_internal_copy.yml +++ /dev/null @@ -1,117 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/5632fc94-9dff-499f-a373-f69ea6f67ff8/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.7.6) - Date: - - Wed, 30 Nov 2022 13:00:33 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:8beab0f0ec5e6fac77682e6cd51c3f65fea6fab1 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 30 Nov 2022 13:00:34 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '712' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4b0db1e6-b596-4da4-b885-da81bbafecd2 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2022-11-29T20:11:58.097631Z","datetime_uploaded":"2022-11-29T20:11:57.935579Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/5632fc94-9dff-499f-a373-f69ea6f67ff8/image.png","original_filename":"image.png","size":180833,"url":"https://api.uploadcare.com/files/5632fc94-9dff-499f-a373-f69ea6f67ff8/","uuid":"5632fc94-9dff-499f-a373-f69ea6f67ff8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":2560,"format":"PNG","height":1440,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}' - recorded_at: Wed, 30 Nov 2022 13:00:34 GMT -- request: - method: post - uri: https://api.uploadcare.com/files/local_copy/ - body: - encoding: UTF-8 - string: '{"source":"5632fc94-9dff-499f-a373-f69ea6f67ff8"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.7.6) - Date: - - Wed, 30 Nov 2022 13:00:34 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:1adaff74d07cc49672c404f885295c646a0a509b - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 201 - message: Created - headers: - Date: - - Wed, 30 Nov 2022 13:00:34 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '502' - Connection: - - close - Server: - - nginx - Location: - - https://api.uploadcare.com/files/d359955d-665c-4556-b305-6301290ccdec/ - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - ba55b3db-db3f-4589-b830-8f4b221b6338 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"type":"file","result":{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2022-11-30T13:00:34.811719Z","is_image":null,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/d359955d-665c-4556-b305-6301290ccdec/image.png","original_filename":"image.png","size":0,"url":"https://api.uploadcare.com/files/d359955d-665c-4556-b305-6301290ccdec/","uuid":"d359955d-665c-4556-b305-6301290ccdec","variations":null,"content_info":null,"metadata":{}}}' - recorded_at: Wed, 30 Nov 2022 13:00:34 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list.yml b/spec/fixtures/vcr_cassettes/rest_file_list.yml deleted file mode 100644 index d292f81d..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Mon, 20 Jan 2020 16:10:54 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:eb6362514cbf1297a5f81e02f493902f9108637b - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 20 Jan 2020 16:10:55 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '4988' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":null,"previous":null,"total":8,"per_page":100,"results":[{"datetime_removed":null,"datetime_stored":"2020-01-16T14:48:46.897379Z","datetime_uploaded":"2020-01-16T14:48:46.776894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/bf3aba03-2213-46d0-83f0-4a240002f3ec/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/bf3aba03-2213-46d0-83f0-4a240002f3ec/","uuid":"bf3aba03-2213-46d0-83f0-4a240002f3ec","source":null},{"datetime_removed":null,"datetime_stored":"2020-01-16T15:02:30.430281Z","datetime_uploaded":"2020-01-16T15:02:30.308894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/c724feac-86f7-447c-b2d6-b0ced220173d/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/c724feac-86f7-447c-b2d6-b0ced220173d/","uuid":"c724feac-86f7-447c-b2d6-b0ced220173d","source":null},{"datetime_removed":null,"datetime_stored":"2020-01-16T15:03:15.315064Z","datetime_uploaded":"2020-01-16T15:03:14.676902Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/8f64f313-e6b1-4731-96c0-6751f1e7a50a/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/","uuid":"8f64f313-e6b1-4731-96c0-6751f1e7a50a","source":null},{"datetime_removed":null,"datetime_stored":"2020-01-20T15:53:29.507216Z","datetime_uploaded":"2020-01-17T14:46:41.919939Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e9a9f291-cc52-4388-bf65-9feec1c75ff9/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/e9a9f291-cc52-4388-bf65-9feec1c75ff9/","uuid":"e9a9f291-cc52-4388-bf65-9feec1c75ff9","source":null},{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-01-20T08:24:50.939615Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/f336251f-4848-4d1a-b1de-05d7dc3321f6/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/f336251f-4848-4d1a-b1de-05d7dc3321f6/","uuid":"f336251f-4848-4d1a-b1de-05d7dc3321f6","source":null},{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-01-20T11:06:32.006238Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/1ac5707b-2935-4ce7-b645-2a7131dd105b/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/1ac5707b-2935-4ce7-b645-2a7131dd105b/","uuid":"1ac5707b-2935-4ce7-b645-2a7131dd105b","source":null},{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-01-20T11:39:55.613278Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/0368ca60-0b10-4db6-a953-3d49a562d3b9/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/0368ca60-0b10-4db6-a953-3d49a562d3b9/","uuid":"0368ca60-0b10-4db6-a953-3d49a562d3b9","source":null},{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-01-20T15:45:40.772211Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/68cf87d7-2986-4584-bc0c-49606fa2968e/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/68cf87d7-2986-4584-bc0c-49606fa2968e/","uuid":"68cf87d7-2986-4584-bc0c-49606fa2968e","source":null}]}' - http_version: - recorded_at: Mon, 20 Jan 2020 16:10:55 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list_each.yml b/spec/fixtures/vcr_cassettes/rest_file_list_each.yml deleted file mode 100644 index 1cd85519..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list_each.yml +++ /dev/null @@ -1,207 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.0.1-dev/ecd779dc169645ce3c91 (Ruby/2.6.3) - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 13 Mar 2020 15:44:01 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1396' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?from=2020-02-12T12%3A16%3A47.262363%2B00%3A00&limit=2&offset=0","previous":null,"total":7,"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2020-02-05T14:21:31.618409Z","datetime_uploaded":"2020-02-05T14:21:31.471606Z","image_info":{"color_mode":"RGB","orientation":null,"format":"WEBP","sequence":false,"height":253,"width":480,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/webp","original_file_url":"https://ucarecdn.com/e7f25edf-511d-4668-8258-0192a73b9f2d/i.webp","original_filename":"i.webp","size":13380,"url":"https://api.uploadcare.com/files/e7f25edf-511d-4668-8258-0192a73b9f2d/","uuid":"e7f25edf-511d-4668-8258-0192a73b9f2d","source":null},{"datetime_removed":null,"datetime_stored":"2020-02-12T12:16:47.326688Z","datetime_uploaded":"2020-02-12T12:16:47.262363Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":121,"width":100,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/121.jpeg","original_filename":"121.jpeg","size":2744,"url":"https://api.uploadcare.com/files/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/","uuid":"35b7fcd7-9bca-40e1-99b1-2adcc21c405d","source":null}]}' - http_version: - recorded_at: Fri, 13 Mar 2020 15:44:01 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2020-02-12T12:16:47.262363%2B00:00&limit=2&offset=0 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.0.1-dev/ecd779dc169645ce3c91 (Ruby/2.6.3) - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 13 Mar 2020 15:44:02 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1504' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&from=2020-03-13T15%3A34%3A02.562415%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&to=2020-02-14T13%3A30%3A08.785811%2B00%3A00&offset=0","total":7,"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2020-02-14T13:30:08.959836Z","datetime_uploaded":"2020-02-14T13:30:08.785811Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":139,"width":200,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/730c5fb9-bbe7-402c-9c26-789e92a5a353/139","original_filename":"139","size":4394,"url":"https://api.uploadcare.com/files/730c5fb9-bbe7-402c-9c26-789e92a5a353/","uuid":"730c5fb9-bbe7-402c-9c26-789e92a5a353","source":null},{"datetime_removed":null,"datetime_stored":"2020-03-13T15:34:02.760978Z","datetime_uploaded":"2020-03-13T15:34:02.562415Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":1875,"width":3000,"geo_location":null,"datetime_original":null,"dpi":[300,300]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/295ae0c8-cd7e-4b3a-bab9-ed3b25c57c69/big.jpeg","original_filename":"big.jpeg","size":348322,"url":"https://api.uploadcare.com/files/295ae0c8-cd7e-4b3a-bab9-ed3b25c57c69/","uuid":"295ae0c8-cd7e-4b3a-bab9-ed3b25c57c69","source":null}]}' - http_version: - recorded_at: Fri, 13 Mar 2020 15:44:02 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2020-03-13T15:34:02.562415%2B00:00&limit=2&offset=0 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.0.1-dev/ecd779dc169645ce3c91 (Ruby/2.6.3) - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 13 Mar 2020 15:44:03 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1474' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?from=2020-03-13T15%3A34%3A39.850626%2B00%3A00&limit=2&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&to=2020-03-13T15%3A34%3A29.865431%2B00%3A00&offset=0","total":7,"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2020-03-13T15:34:29.967976Z","datetime_uploaded":"2020-03-13T15:34:29.865431Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":225,"width":225,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/3a23697f-f407-409f-985b-7dcaffc54a9d/225","original_filename":"225","size":8596,"url":"https://api.uploadcare.com/files/3a23697f-f407-409f-985b-7dcaffc54a9d/","uuid":"3a23697f-f407-409f-985b-7dcaffc54a9d","source":null},{"datetime_removed":null,"datetime_stored":"2020-03-13T15:34:39.918912Z","datetime_uploaded":"2020-03-13T15:34:39.850626Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":227,"width":225,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/21c30070-e9e6-408d-a403-0a34c884790d/227","original_filename":"227","size":8484,"url":"https://api.uploadcare.com/files/21c30070-e9e6-408d-a403-0a34c884790d/","uuid":"21c30070-e9e6-408d-a403-0a34c884790d","source":null}]}' - http_version: - recorded_at: Fri, 13 Mar 2020 15:44:03 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2020-03-13T15:34:39.850626%2B00:00&limit=2&offset=0 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.0.1-dev/ecd779dc169645ce3c91 (Ruby/2.6.3) - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 13 Mar 2020 15:44:04 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '768' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":null,"previous":"https://api.uploadcare.com/files/?limit=2&to=2020-03-13T15%3A34%3A51.981093%2B00%3A00&offset=0","total":7,"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2020-03-13T15:34:52.114967Z","datetime_uploaded":"2020-03-13T15:34:51.981093Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":225,"width":221,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/fb4fd1cb-b961-44ac-9159-5c90f736d139/225","original_filename":"225","size":6852,"url":"https://api.uploadcare.com/files/fb4fd1cb-b961-44ac-9159-5c90f736d139/","uuid":"fb4fd1cb-b961-44ac-9159-5c90f736d139","source":null}]}' - http_version: - recorded_at: Fri, 13 Mar 2020 15:44:04 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list_limited.yml b/spec/fixtures/vcr_cassettes/rest_file_list_limited.yml deleted file mode 100644 index 3f925fcf..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list_limited.yml +++ /dev/null @@ -1,109 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Thu, 30 Jan 2020 12:35:45 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:632d213b6995079665c30e7d72ca8afacbd3d57e - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 30 Jan 2020 12:35:46 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1401' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?from=2020-01-16T15%3A02%3A30.308894%2B00%3A00&limit=2&offset=0","previous":null,"total":16,"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2020-01-16T14:48:46.897379Z","datetime_uploaded":"2020-01-16T14:48:46.776894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/bf3aba03-2213-46d0-83f0-4a240002f3ec/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/bf3aba03-2213-46d0-83f0-4a240002f3ec/","uuid":"bf3aba03-2213-46d0-83f0-4a240002f3ec","source":null},{"datetime_removed":null,"datetime_stored":"2020-01-16T15:02:30.430281Z","datetime_uploaded":"2020-01-16T15:02:30.308894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/c724feac-86f7-447c-b2d6-b0ced220173d/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/c724feac-86f7-447c-b2d6-b0ced220173d/","uuid":"c724feac-86f7-447c-b2d6-b0ced220173d","source":null}]}' - http_version: - recorded_at: Thu, 30 Jan 2020 12:35:46 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Thu, 30 Jan 2020 12:35:46 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:2f8fd213d8dd862cf9eae984bd7c56869ea8321d - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 30 Jan 2020 12:35:47 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1401' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?from=2020-01-16T15%3A02%3A30.308894%2B00%3A00&limit=2&offset=0","previous":null,"total":16,"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2020-01-16T14:48:46.897379Z","datetime_uploaded":"2020-01-16T14:48:46.776894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/bf3aba03-2213-46d0-83f0-4a240002f3ec/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/bf3aba03-2213-46d0-83f0-4a240002f3ec/","uuid":"bf3aba03-2213-46d0-83f0-4a240002f3ec","source":null},{"datetime_removed":null,"datetime_stored":"2020-01-16T15:02:30.430281Z","datetime_uploaded":"2020-01-16T15:02:30.308894Z","image_info":{"color_mode":"RGBA","orientation":null,"format":"PNG","sequence":false,"height":300,"width":300,"geo_location":null,"datetime_original":null,"dpi":[72,72]},"is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/c724feac-86f7-447c-b2d6-b0ced220173d/Test.png","original_filename":"Test.png","size":3118,"url":"https://api.uploadcare.com/files/c724feac-86f7-447c-b2d6-b0ced220173d/","uuid":"c724feac-86f7-447c-b2d6-b0ced220173d","source":null}]}' - http_version: - recorded_at: Thu, 30 Jan 2020 12:35:47 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list_load.yml b/spec/fixtures/vcr_cassettes/rest_file_list_load.yml deleted file mode 100644 index 57c0b78d..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list_load.yml +++ /dev/null @@ -1,3777 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:39 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:0268580ca8ee6a4a1a53d7064ff5506fe96f4779 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:40 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1656' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4fa6271b-6774-474f-a4a0-c809695f1b2b - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-28T15%3A53%3A36.082137%2B00%3A00&offset=0","previous":null,"total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-10-01T08:31:32.834879Z","datetime_uploaded":"2022-10-01T08:31:32.730675Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/image.png","original_filename":"image.png","size":86745,"url":"https://api.uploadcare.com/files/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/","uuid":"8f0a2a28-3ed7-481e-b415-ee3cce982aaa","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":536,"format":"PNG","height":354,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-28T15:53:36.188117Z","datetime_uploaded":"2022-09-28T15:53:36.082137Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/4654a098-ddef-4157-98f2-9c902b699bd8/image.png","original_filename":"image.png","size":4659,"url":"https://api.uploadcare.com/files/4654a098-ddef-4157-98f2-9c902b699bd8/","uuid":"4654a098-ddef-4157-98f2-9c902b699bd8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":274,"format":"PNG","height":367,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:40 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2022-09-28T15:53:36.082137%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:40 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:047221a58eda3349362de2ebdb9cf2a93f0cdd9c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:40 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1777' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4394d09f-08d5-4aff-a548-6616f9cd16ca - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-23T11%3A29%3A25.213425%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2022-09-28T15%3A21%3A26.699767%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-09-28T15:21:26.793937Z","datetime_uploaded":"2022-09-28T15:21:26.699767Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/461bfed9-179f-4e4b-aab2-fbaf132e4fb5/image.png","original_filename":"image.png","size":61645,"url":"https://api.uploadcare.com/files/461bfed9-179f-4e4b-aab2-fbaf132e4fb5/","uuid":"461bfed9-179f-4e4b-aab2-fbaf132e4fb5","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":300,"format":"PNG","height":105,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-23T11:29:25.358080Z","datetime_uploaded":"2022-09-23T11:29:25.213425Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/bc37b996-916d-4ed7-b230-fa71a4290cb3/image.png","original_filename":"image.png","size":62167,"url":"https://api.uploadcare.com/files/bc37b996-916d-4ed7-b230-fa71a4290cb3/","uuid":"bc37b996-916d-4ed7-b230-fa71a4290cb3","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":300,"format":"PNG","height":105,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:40 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2022-09-23T11:29:25.213425%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:40 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:1af599408b6d4ae4d96b16f6426f9eb67b98a29a - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:41 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1782' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4911be71-1cfc-453d-ab47-17fb2d3a2828 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T15%3A43%3A55.172407%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-11-18T06%3A18%3A47.829374%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-11-18T06:19:02.497541Z","datetime_uploaded":"2021-11-18T06:18:47.829374Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768/download.jpeg","original_filename":"download.jpeg","size":10603,"url":"https://api.uploadcare.com/files/ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768/","uuid":"ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768","variations":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":null,"width":300,"format":"JPEG","height":168,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{"test-key":"test-value"}},{"datetime_removed":null,"datetime_stored":"2021-10-26T15:44:03.649368Z","datetime_uploaded":"2021-10-26T15:43:55.172407Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e9a35a83-9a9f-428f-a5f2-72b6bb905fd5/demo2.jpeg","original_filename":"demo2.jpeg","size":33822,"url":"https://api.uploadcare.com/files/e9a35a83-9a9f-428f-a5f2-72b6bb905fd5/","uuid":"e9a35a83-9a9f-428f-a5f2-72b6bb905fd5","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{"variation":"no - limitation"}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:41 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T15:43:55.172407%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:41 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:3d65e40b5def29aab4b023cf5a5e98f2c7725f83 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:41 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1695' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - def37223-9374-43d7-bb30-2971141ca2ff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T15%3A43%3A48.998087%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T15%3A43%3A55.035178%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T15:44:03.700808Z","datetime_uploaded":"2021-10-26T15:43:55.035178Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/a270e0a8-43ce-49a6-8cac-17823439d5b7/demo3.jpeg","original_filename":"demo3.jpeg","size":37872,"url":"https://api.uploadcare.com/files/a270e0a8-43ce-49a6-8cac-17823439d5b7/","uuid":"a270e0a8-43ce-49a6-8cac-17823439d5b7","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{"test-value":"000"}},{"datetime_removed":null,"datetime_stored":"2021-10-26T15:44:02.974493Z","datetime_uploaded":"2021-10-26T15:43:48.998087Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/f757ea10-8b1a-4361-9a7c-56bfa5d45176/demo.jpeg","original_filename":"demo.jpeg","size":49395,"url":"https://api.uploadcare.com/files/f757ea10-8b1a-4361-9a7c-56bfa5d45176/","uuid":"f757ea10-8b1a-4361-9a7c-56bfa5d45176","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{"sub":"lalala"}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:41 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T15:43:48.998087%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:41 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:60d368a614eff3b05988f7c6c293df01a4fb1b64 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:42 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 1ae3b6e4-2cb4-4585-bb4d-be32fa87c285 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T13%3A35%3A20.580197%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T13%3A35%3A20.724745%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T13:35:24.455335Z","datetime_uploaded":"2021-10-26T13:35:20.724745Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/664586f2-2345-401d-8e03-77f2c65c88f6/demo5.jpeg","original_filename":"demo5.jpeg","size":26324,"url":"https://api.uploadcare.com/files/664586f2-2345-401d-8e03-77f2c65c88f6/","uuid":"664586f2-2345-401d-8e03-77f2c65c88f6","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T13:35:24.544576Z","datetime_uploaded":"2021-10-26T13:35:20.580197Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c06f6d8e-e585-48a6-9eeb-150d0ab08756/demo6.jpeg","original_filename":"demo6.jpeg","size":24301,"url":"https://api.uploadcare.com/files/c06f6d8e-e585-48a6-9eeb-150d0ab08756/","uuid":"c06f6d8e-e585-48a6-9eeb-150d0ab08756","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:42 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T13:35:20.580197%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:42 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:8d1b2533835b4c7f46dac88ffe796a7eda80e96b - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:42 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 9aa8dfc5-454e-477b-907a-9e1972e67eae - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T13%3A33%3A42.626230%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T13%3A35%3A11.884215%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T13:35:23.559329Z","datetime_uploaded":"2021-10-26T13:35:11.884215Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/0822500d-0620-4db5-aab3-b31015269555/demo4.jpeg","original_filename":"demo4.jpeg","size":18382,"url":"https://api.uploadcare.com/files/0822500d-0620-4db5-aab3-b31015269555/","uuid":"0822500d-0620-4db5-aab3-b31015269555","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T13:33:46.796975Z","datetime_uploaded":"2021-10-26T13:33:42.626230Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/b1fbbf83-4f34-4d10-b4e0-7dc8d231ef0d/demo3.jpeg","original_filename":"demo3.jpeg","size":37872,"url":"https://api.uploadcare.com/files/b1fbbf83-4f34-4d10-b4e0-7dc8d231ef0d/","uuid":"b1fbbf83-4f34-4d10-b4e0-7dc8d231ef0d","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:42 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T13:33:42.626230%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:42 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:7c73e6db615d85f4809651f09f7b5d09d18512cd - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:43 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1663' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - e8b11b5e-042e-4613-9625-18851662942f - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T13%3A33%3A37.029320%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T13%3A33%3A42.411153%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T13:33:46.723692Z","datetime_uploaded":"2021-10-26T13:33:42.411153Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/97f6d4dc-3dd7-4216-9100-172d1447f72a/demo2.jpeg","original_filename":"demo2.jpeg","size":33822,"url":"https://api.uploadcare.com/files/97f6d4dc-3dd7-4216-9100-172d1447f72a/","uuid":"97f6d4dc-3dd7-4216-9100-172d1447f72a","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T13:33:46.056670Z","datetime_uploaded":"2021-10-26T13:33:37.029320Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c7b1a5fb-949d-4a9a-a6cd-44bf663d5b60/demo.jpeg","original_filename":"demo.jpeg","size":49395,"url":"https://api.uploadcare.com/files/c7b1a5fb-949d-4a9a-a6cd-44bf663d5b60/","uuid":"c7b1a5fb-949d-4a9a-a6cd-44bf663d5b60","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:43 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T13:33:37.029320%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:43 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:7c758eac05f444694cc1aa5ec22315ce2b1f07a7 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:44 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1787' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 8ad22773-bb72-4e02-8f00-c416d0bf9a0f - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A59%3A51.866096%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A59%3A53.052236%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:56.756099Z","datetime_uploaded":"2021-10-26T09:59:53.052236Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/a4d83a99-68ed-4cf0-8eed-94cb5a558c92/110988kirpichnaya_kladkamaterialkirpichstenadoroga1920x1080.jpg","original_filename":"110988-kirpichnaya_kladka-material-kirpich-stena-doroga-1920x1080.jpg","size":1257642,"url":"https://api.uploadcare.com/files/a4d83a99-68ed-4cf0-8eed-94cb5a558c92/","uuid":"a4d83a99-68ed-4cf0-8eed-94cb5a558c92","variations":null,"content_info":{"image":{"dpi":[96,96],"width":1920,"format":"JPEG","height":1080,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:56.675910Z","datetime_uploaded":"2021-10-26T09:59:51.866096Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c94fe7b2-626b-4da0-883a-3683e6d67477/2143_r.jpg","original_filename":"2143_r.jpg","size":455590,"url":"https://api.uploadcare.com/files/c94fe7b2-626b-4da0-883a-3683e6d67477/","uuid":"c94fe7b2-626b-4da0-883a-3683e6d67477","variations":null,"content_info":{"image":{"dpi":[96,96],"width":3840,"format":"JPEG","height":2400,"sequence":false,"color_mode":"RGB","orientation":3,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:43 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:59:51.866096%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:43 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:cf0244cfdb312a7181cd8d5b33795fdcf3a07266 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:44 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1663' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 43e8d376-53f9-4328-a62f-b0c7c341cfe5 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A59%3A51.835160%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A59%3A51.848651%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:56.620115Z","datetime_uploaded":"2021-10-26T09:59:51.848651Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c4684f6d-3717-4910-95b1-784465c9b8b0/2143.jpg","original_filename":"2143.jpg","size":455590,"url":"https://api.uploadcare.com/files/c4684f6d-3717-4910-95b1-784465c9b8b0/","uuid":"c4684f6d-3717-4910-95b1-784465c9b8b0","variations":null,"content_info":{"image":{"dpi":[96,96],"width":3840,"format":"JPEG","height":2400,"sequence":false,"color_mode":"RGB","orientation":3,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:56.552643Z","datetime_uploaded":"2021-10-26T09:59:51.835160Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/9e015138-5273-4499-8ddd-6979d64772ef/2140.jpg","original_filename":"2140.jpg","size":537204,"url":"https://api.uploadcare.com/files/9e015138-5273-4499-8ddd-6979d64772ef/","uuid":"9e015138-5273-4499-8ddd-6979d64772ef","variations":null,"content_info":{"image":{"dpi":null,"width":3840,"format":"JPEG","height":2400,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:44 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:59:51.835160%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:44 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:b2efdfbbb11d362278aedff778dd513bf8fda56d - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:45 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1668' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 0fc9b204-38de-46b2-b7af-893ad4405fdc - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A59%3A50.934311%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A59%3A50.937288%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:56.484154Z","datetime_uploaded":"2021-10-26T09:59:50.937288Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/772612e7-c645-4af9-84e8-de6b044913cb/2121.jpg","original_filename":"2121.jpg","size":106528,"url":"https://api.uploadcare.com/files/772612e7-c645-4af9-84e8-de6b044913cb/","uuid":"772612e7-c645-4af9-84e8-de6b044913cb","variations":null,"content_info":{"image":{"dpi":null,"width":1600,"format":"JPEG","height":900,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:56.804273Z","datetime_uploaded":"2021-10-26T09:59:50.934311Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/47de06db-6d0a-4215-b8c1-b90ea4dbb8fd/15271289.jpg","original_filename":"15271289.jpg","size":45503,"url":"https://api.uploadcare.com/files/47de06db-6d0a-4215-b8c1-b90ea4dbb8fd/","uuid":"47de06db-6d0a-4215-b8c1-b90ea4dbb8fd","variations":null,"content_info":{"image":{"dpi":null,"width":1200,"format":"JPEG","height":865,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:45 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:59:50.934311%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:45 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:aa1256d740ff9473502e964043d80feb0efee22c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:45 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1774' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 77a6cc5c-9aa6-415a-b8fe-397bb7453d33 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A59%3A29.415794%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A59%3A43.454795%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:55.797137Z","datetime_uploaded":"2021-10-26T09:59:43.454795Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/b5ce344c-fd8b-4eab-95b5-d93a942401ef/belyykirpichstenaizkirpicha.jpg","original_filename":"belyy-kirpich-stena-iz-kirpicha.jpg","size":1033896,"url":"https://api.uploadcare.com/files/b5ce344c-fd8b-4eab-95b5-d93a942401ef/","uuid":"b5ce344c-fd8b-4eab-95b5-d93a942401ef","variations":null,"content_info":{"image":{"dpi":[96,96],"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:59:31.056939Z","datetime_uploaded":"2021-10-26T09:59:29.415794Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c31eef4c-fd73-4977-a4e9-549f5ccdb971/DarkGreyWallpaper021920x1200.jpg","original_filename":"Dark-Grey-Wallpaper-02-1920x1200.jpg","size":317483,"url":"https://api.uploadcare.com/files/c31eef4c-fd73-4977-a4e9-549f5ccdb971/","uuid":"c31eef4c-fd73-4977-a4e9-549f5ccdb971","variations":null,"content_info":{"image":{"dpi":[300,300],"width":1920,"format":"JPEG","height":1200,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:45 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:59:29.415794%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:45 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:e12aa63b549cdef09adce1e0e13b4194f725395c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:46 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1765' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - da15aef1-99b0-4eb9-8cc2-8ba4226699b0 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A51%3A29.683705%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A51%3A29.709922%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:52:04.622268Z","datetime_uploaded":"2021-10-26T09:51:29.709922Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/8cf61392-c34c-481c-baab-d40fd1fcecd8/beach_29wallpaper1920x1080.jpg","original_filename":"beach_29-wallpaper-1920x1080.jpg","size":642315,"url":"https://api.uploadcare.com/files/8cf61392-c34c-481c-baab-d40fd1fcecd8/","uuid":"8cf61392-c34c-481c-baab-d40fd1fcecd8","variations":null,"content_info":{"image":{"dpi":null,"width":1920,"format":"JPEG","height":1080,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:52:04.496254Z","datetime_uploaded":"2021-10-26T09:51:29.683705Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/a465b6f9-f083-47c6-ae23-eaa172520226/amirrabieelHSZxLa29DUunsplash.jpg","original_filename":"amir-rabiee-lHSZxLa29DU-unsplash.jpg","size":721781,"url":"https://api.uploadcare.com/files/a465b6f9-f083-47c6-ae23-eaa172520226/","uuid":"a465b6f9-f083-47c6-ae23-eaa172520226","variations":null,"content_info":{"image":{"dpi":[72,72],"width":2400,"format":"JPEG","height":1573,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:46 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:51:29.683705%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:46 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:7074a3e840709f1493c28c5397ec47f84fa50e2c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:46 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1801' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a7b4c574-f46f-4c38-8ac3-513250691e84 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A49%3A17.944115%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A51%3A16.955402%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:52:03.817134Z","datetime_uploaded":"2021-10-26T09:51:16.955402Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/97306afb-3547-45b3-992c-1e5bfd918206/iconfinder_logo_brand_brands_logos_Google_Authenticator_2993690.png","original_filename":"iconfinder_logo_brand_brands_logos_Google_Authenticator_2993690.png","size":16046,"url":"https://api.uploadcare.com/files/97306afb-3547-45b3-992c-1e5bfd918206/","uuid":"97306afb-3547-45b3-992c-1e5bfd918206","variations":null,"content_info":{"image":{"dpi":[72,72],"width":256,"format":"PNG","height":256,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:49:26.111024Z","datetime_uploaded":"2021-10-26T09:49:17.944115Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/aee7d38a-ff4e-48af-96e8-33e726e4bd61/demo9.jpg","original_filename":"demo9.jpg","size":152252,"url":"https://api.uploadcare.com/files/aee7d38a-ff4e-48af-96e8-33e726e4bd61/","uuid":"aee7d38a-ff4e-48af-96e8-33e726e4bd61","variations":null,"content_info":{"image":{"dpi":[300,300],"width":2233,"format":"JPEG","height":1475,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:46 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:49:17.944115%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:46 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:83647afa9de1a8bc4976d545d219d74cc97c70b2 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:47 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1721' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 2d24663d-c24b-4ece-846f-3d84ba748643 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A48%3A28.805169%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A48%3A28.826325%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:48:38.574649Z","datetime_uploaded":"2021-10-26T09:48:28.826325Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/474ce188-8163-4518-a329-b1d4e27474ad/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13769,"url":"https://api.uploadcare.com/files/474ce188-8163-4518-a329-b1d4e27474ad/","uuid":"474ce188-8163-4518-a329-b1d4e27474ad","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:48:38.546468Z","datetime_uploaded":"2021-10-26T09:48:28.805169Z","is_image":false,"is_ready":true,"mime_type":"video/mp4","original_file_url":"https://ucarecdn.com/847db367-f7e2-409a-8b1b-e1c116b22f23/video.mp4","original_filename":"video.mp4","size":10424,"url":"https://api.uploadcare.com/files/847db367-f7e2-409a-8b1b-e1c116b22f23/","uuid":"847db367-f7e2-409a-8b1b-e1c116b22f23","variations":null,"content_info":{"video":{"audio":[{"codec":"aac","bitrate":3,"channels":2,"sample_rate":44100}],"video":[{"codec":"h264","width":1280,"height":720,"bitrate":67,"frame_rate":29}],"format":"mpeg4","bitrate":70,"duration":1114}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:47 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:48:28.805169%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:47 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:31d578a89192a5fb352f58a2c3a02488f4eebfa6 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:47 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1501' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - aadc3277-c715-4f5b-8fee-dfcff17bfa9d - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-26T09%3A19%3A43.137243%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A29%3A27.171168%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:31:24.406103Z","datetime_uploaded":"2021-10-26T09:29:27.171168Z","is_image":false,"is_ready":true,"mime_type":"application/zip","original_file_url":"https://ucarecdn.com/6c8b0930-6353-482d-a89f-61d008e2b4e2/iconfinder_logo_brand_brands_logos_google_2993685png.zip","original_filename":"iconfinder_logo_brand_brands_logos_google_2993685.png.zip","size":13745,"url":"https://api.uploadcare.com/files/6c8b0930-6353-482d-a89f-61d008e2b4e2/","uuid":"6c8b0930-6353-482d-a89f-61d008e2b4e2","variations":null,"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-26T09:23:28.457693Z","datetime_uploaded":"2021-10-26T09:19:43.137243Z","is_image":null,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/5223010b-2104-46fc-8489-5cbe8cca3c95/amirrabieelHSZxLa29DUunsplash.jpg","original_filename":"amir-rabiee-lHSZxLa29DU-unsplash.jpg","size":0,"url":"https://api.uploadcare.com/files/5223010b-2104-46fc-8489-5cbe8cca3c95/","uuid":"5223010b-2104-46fc-8489-5cbe8cca3c95","variations":null,"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:47 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-26T09:19:43.137243%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:47 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:0dc9e7ec41a071b0d2638ed2dc632095afca6db3 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:48 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1746' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - af3ea9cf-7971-4e7f-a185-68755c9ed18c - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-25T13%3A19%3A37.745634%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-26T09%3A16%3A25.248259%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-26T09:16:48.851475Z","datetime_uploaded":"2021-10-26T09:16:25.248259Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e93dcf91-5b40-47a7-9127-b69431dcfbad/DSCN0010.jpg","original_filename":"DSCN0010.jpg","size":161713,"url":"https://api.uploadcare.com/files/e93dcf91-5b40-47a7-9127-b69431dcfbad/","uuid":"e93dcf91-5b40-47a7-9127-b69431dcfbad","variations":null,"content_info":{"image":{"dpi":[300,300],"width":640,"format":"JPEG","height":480,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":{"latitude":43.46744833333334,"longitude":11.885126666663888},"datetime_original":"2008-10-22T16:28:39"}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-25T13:19:37.810446Z","datetime_uploaded":"2021-10-25T13:19:37.745634Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/ce6a2413-e047-4949-9a79-8b01f220fff2/demo4.jpeg","original_filename":"demo4.jpeg","size":18382,"url":"https://api.uploadcare.com/files/ce6a2413-e047-4949-9a79-8b01f220fff2/","uuid":"ce6a2413-e047-4949-9a79-8b01f220fff2","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:48 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-25T13:19:37.745634%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:48 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:b07b6ee2a9878d3dad5d6a49e07cc5d010885f12 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:49 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - ad72a3da-8190-4c98-9981-7670819cfebc - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-25T13%3A18%3A57.177168%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-25T13%3A18%3A57.377055%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-25T13:18:57.445417Z","datetime_uploaded":"2021-10-25T13:18:57.377055Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/613f72be-eae0-47d2-aace-1fdfe44b8c4b/demo3.jpeg","original_filename":"demo3.jpeg","size":37872,"url":"https://api.uploadcare.com/files/613f72be-eae0-47d2-aace-1fdfe44b8c4b/","uuid":"613f72be-eae0-47d2-aace-1fdfe44b8c4b","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-25T13:18:57.246840Z","datetime_uploaded":"2021-10-25T13:18:57.177168Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/d0a258fb-9c39-4aa0-872b-914e171555f7/demo2.jpeg","original_filename":"demo2.jpeg","size":33822,"url":"https://api.uploadcare.com/files/d0a258fb-9c39-4aa0-872b-914e171555f7/","uuid":"d0a258fb-9c39-4aa0-872b-914e171555f7","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:49 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-25T13:18:57.177168%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:49 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:6965f3e5f9fff95403cd2ae2c4f5f81a8c23430a - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:49 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1724' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 77988cc4-3fb8-4045-bd36-2bdcddfd85d1 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T13%3A02%3A34.047351%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-25T13%3A18%3A49.832445%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-25T13:18:49.928651Z","datetime_uploaded":"2021-10-25T13:18:49.832445Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/10ed3945-2106-4987-8247-1bc5b74feabc/demo.jpeg","original_filename":"demo.jpeg","size":49395,"url":"https://api.uploadcare.com/files/10ed3945-2106-4987-8247-1bc5b74feabc/","uuid":"10ed3945-2106-4987-8247-1bc5b74feabc","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T13:02:34.105687Z","datetime_uploaded":"2021-10-21T13:02:34.047351Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/f9b0a213-9791-4ed7-8c54-3b370bbb852e/demo9.jpeg","original_filename":"demo9.jpeg","size":13795,"url":"https://api.uploadcare.com/files/f9b0a213-9791-4ed7-8c54-3b370bbb852e/","uuid":"f9b0a213-9791-4ed7-8c54-3b370bbb852e","variations":{"document/-/format/pdf/":"f8c2742f-d888-41b2-bae4-7c10ad1b10ae"},"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:49 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T13:02:34.047351%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:49 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:218ab710ed4e02381abda615e8b2d2526bf0f563 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:50 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1663' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 7d44215f-0fe4-4d46-81b5-473a5567f6f1 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T12%3A08%3A07.692034%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T13%3A02%3A33.886887%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T13:02:33.941716Z","datetime_uploaded":"2021-10-21T13:02:33.886887Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c6a04940-e5eb-4a2a-9455-7d3cd0070dec/demo8.jpeg","original_filename":"demo8.jpeg","size":16120,"url":"https://api.uploadcare.com/files/c6a04940-e5eb-4a2a-9455-7d3cd0070dec/","uuid":"c6a04940-e5eb-4a2a-9455-7d3cd0070dec","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T12:08:07.753635Z","datetime_uploaded":"2021-10-21T12:08:07.692034Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/b3259e89-688d-4225-a9ae-9a521fd5c96a/demo.jpeg","original_filename":"demo.jpeg","size":49395,"url":"https://api.uploadcare.com/files/b3259e89-688d-4225-a9ae-9a521fd5c96a/","uuid":"b3259e89-688d-4225-a9ae-9a521fd5c96a","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:50 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T12:08:07.692034%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:50 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:15c9c756804c44b7c171f505c5840be0e71b7411 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:50 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1726' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a04d9fcb-9d24-4d83-8472-d11af44bf133 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T12%3A01%3A19.129147%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T12%3A07%3A51.514225%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T12:07:51.582306Z","datetime_uploaded":"2021-10-21T12:07:51.514225Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/2dcf583f-8de7-4b53-ae13-54c21bae39c5/sample.jpg","original_filename":"sample.jpg","size":56780,"url":"https://api.uploadcare.com/files/2dcf583f-8de7-4b53-ae13-54c21bae39c5/","uuid":"2dcf583f-8de7-4b53-ae13-54c21bae39c5","variations":{"document/-/format/png/":"ba1ca860-a0ac-4bcd-877c-844bf25d4aa0"},"content_info":{"image":{"dpi":[72,72],"width":600,"format":"JPEG","height":320,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:19.185151Z","datetime_uploaded":"2021-10-21T12:01:19.129147Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/32b70177-26f9-4281-bfe4-8ba284130c77/demo8.jpeg","original_filename":"demo8.jpeg","size":16120,"url":"https://api.uploadcare.com/files/32b70177-26f9-4281-bfe4-8ba284130c77/","uuid":"32b70177-26f9-4281-bfe4-8ba284130c77","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:50 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T12:01:19.129147%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:50 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:3b4041ee803c17231373b0523907fcce848dd831 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:51 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - cdd469e9-39bb-4889-9ee8-8e547e74c606 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T12%3A01%3A06.510052%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T12%3A01%3A19.114966%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:19.165385Z","datetime_uploaded":"2021-10-21T12:01:19.114966Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/b1df1763-45a8-4c03-b44a-2034e8ef9d5a/demo9.jpeg","original_filename":"demo9.jpeg","size":13795,"url":"https://api.uploadcare.com/files/b1df1763-45a8-4c03-b44a-2034e8ef9d5a/","uuid":"b1df1763-45a8-4c03-b44a-2034e8ef9d5a","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:06.583143Z","datetime_uploaded":"2021-10-21T12:01:06.510052Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/9784fb94-c555-4869-b0bc-60dfae36076b/demo9.jpeg","original_filename":"demo9.jpeg","size":13795,"url":"https://api.uploadcare.com/files/9784fb94-c555-4869-b0bc-60dfae36076b/","uuid":"9784fb94-c555-4869-b0bc-60dfae36076b","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:51 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T12:01:06.510052%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:51 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:022b7188a1b5cb4074497f1f68f01a9ebfaa53ca - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:51 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - bb1fce23-ad52-4eed-8108-778434a414c0 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T12%3A00%3A58.295614%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T12%3A01%3A06.325164%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:06.553804Z","datetime_uploaded":"2021-10-21T12:01:06.325164Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/36dbb6e1-bdee-473f-8a06-78d1dd12c0d2/demo8.jpeg","original_filename":"demo8.jpeg","size":16120,"url":"https://api.uploadcare.com/files/36dbb6e1-bdee-473f-8a06-78d1dd12c0d2/","uuid":"36dbb6e1-bdee-473f-8a06-78d1dd12c0d2","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T12:00:58.354463Z","datetime_uploaded":"2021-10-21T12:00:58.295614Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/0f88e725-1d7e-4799-8194-325d3a60e8e1/demo7.jpeg","original_filename":"demo7.jpeg","size":21554,"url":"https://api.uploadcare.com/files/0f88e725-1d7e-4799-8194-325d3a60e8e1/","uuid":"0f88e725-1d7e-4799-8194-325d3a60e8e1","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:51 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T12:00:58.295614%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:51 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:f62cdbc48f90138a1345eb6824bf9b7fbc916cea - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:52 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - f77cf7b3-aa20-42c9-a897-1d45fa3dd7a5 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T11%3A59%3A58.450875%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T11%3A59%3A58.459076%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T11:59:58.520801Z","datetime_uploaded":"2021-10-21T11:59:58.459076Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/63947f94-568c-4719-ae9b-1a555240c925/demo6.jpeg","original_filename":"demo6.jpeg","size":24301,"url":"https://api.uploadcare.com/files/63947f94-568c-4719-ae9b-1a555240c925/","uuid":"63947f94-568c-4719-ae9b-1a555240c925","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T11:59:58.523448Z","datetime_uploaded":"2021-10-21T11:59:58.450875Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/ecb551ad-1c73-4809-87e5-5d8274484f75/demo5.jpeg","original_filename":"demo5.jpeg","size":26324,"url":"https://api.uploadcare.com/files/ecb551ad-1c73-4809-87e5-5d8274484f75/","uuid":"ecb551ad-1c73-4809-87e5-5d8274484f75","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:52 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T11:59:58.450875%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:52 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:01df9fa4af317e7507896e824b8f9bf854b5ac3e - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:53 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - b3bc1346-5024-44cf-b697-1343b9dfc6ea - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T11%3A58%3A58.433698%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T11%3A59%3A48.576920%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T11:59:48.660908Z","datetime_uploaded":"2021-10-21T11:59:48.576920Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/ac3076a6-cdf5-41f3-86f8-557d8947e42f/demo4.jpeg","original_filename":"demo4.jpeg","size":18382,"url":"https://api.uploadcare.com/files/ac3076a6-cdf5-41f3-86f8-557d8947e42f/","uuid":"ac3076a6-cdf5-41f3-86f8-557d8947e42f","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T11:58:58.505476Z","datetime_uploaded":"2021-10-21T11:58:58.433698Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/2fd5610e-18c9-4e0f-8a95-e9bb209e4358/demo2.jpeg","original_filename":"demo2.jpeg","size":33822,"url":"https://api.uploadcare.com/files/2fd5610e-18c9-4e0f-8a95-e9bb209e4358/","uuid":"2fd5610e-18c9-4e0f-8a95-e9bb209e4358","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:53 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T11:58:58.433698%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:53 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:f0de0edc9ae12f554741558cadbf279e41c98eb8 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:53 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1663' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 78291b1e-129f-4ac4-a35f-4e104129ba8f - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T11%3A58%3A47.825964%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T11%3A58%3A58.322059%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T11:58:58.384323Z","datetime_uploaded":"2021-10-21T11:58:58.322059Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/362b5f23-3108-4f2f-8bd8-968efb6bf505/demo3.jpeg","original_filename":"demo3.jpeg","size":37872,"url":"https://api.uploadcare.com/files/362b5f23-3108-4f2f-8bd8-968efb6bf505/","uuid":"362b5f23-3108-4f2f-8bd8-968efb6bf505","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T11:58:47.908318Z","datetime_uploaded":"2021-10-21T11:58:47.825964Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/f5ee3ea0-5f10-4ebc-b354-0978663d899e/demo.jpeg","original_filename":"demo.jpeg","size":49395,"url":"https://api.uploadcare.com/files/f5ee3ea0-5f10-4ebc-b354-0978663d899e/","uuid":"f5ee3ea0-5f10-4ebc-b354-0978663d899e","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:53 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T11:58:47.825964%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:53 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:09b0a9db2a011dc9102cf9ed2c749c0ab776dce2 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:54 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a4419993-5e45-4242-ac65-73552990f63d - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T10%3A36%3A58.930910%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T10%3A36%3A59.071128%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T10:36:59.163917Z","datetime_uploaded":"2021-10-21T10:36:59.071128Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/397af4ab-bb87-4828-8804-b2cf30edc7e8/demo4.jpeg","original_filename":"demo4.jpeg","size":33541,"url":"https://api.uploadcare.com/files/397af4ab-bb87-4828-8804-b2cf30edc7e8/","uuid":"397af4ab-bb87-4828-8804-b2cf30edc7e8","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T10:36:59.051502Z","datetime_uploaded":"2021-10-21T10:36:58.930910Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/2cd6a0de-5081-440c-88e0-b2423c39da51/demo2.jpeg","original_filename":"demo2.jpeg","size":30632,"url":"https://api.uploadcare.com/files/2cd6a0de-5081-440c-88e0-b2423c39da51/","uuid":"2cd6a0de-5081-440c-88e0-b2423c39da51","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:54 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T10:36:58.930910%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:54 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:dfacf6e742b27796109314371b51811b5e75fd87 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:54 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4aaef434-9632-4f63-9979-ac8a9acfe808 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T10%3A36%3A58.768648%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T10%3A36%3A58.907317%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T10:36:59.013481Z","datetime_uploaded":"2021-10-21T10:36:58.907317Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/b1ee38d6-0454-4872-b9c9-a50e7b90abc8/demo5.jpeg","original_filename":"demo5.jpeg","size":12648,"url":"https://api.uploadcare.com/files/b1ee38d6-0454-4872-b9c9-a50e7b90abc8/","uuid":"b1ee38d6-0454-4872-b9c9-a50e7b90abc8","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T10:36:58.953973Z","datetime_uploaded":"2021-10-21T10:36:58.768648Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/2dfa9649-7f76-4d6f-82cb-1d6cb99ac6ab/demo3.jpeg","original_filename":"demo3.jpeg","size":49499,"url":"https://api.uploadcare.com/files/2dfa9649-7f76-4d6f-82cb-1d6cb99ac6ab/","uuid":"2dfa9649-7f76-4d6f-82cb-1d6cb99ac6ab","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:54 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T10:36:58.768648%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:54 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:4ad37ef290dd111d8bc5c26b0c5ec9518f633034 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:55 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1663' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 3149963f-dccb-45d8-8452-3613508d1e93 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-21T10%3A26%3A51.166354%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T10%3A36%3A12.764574%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T10:36:12.916011Z","datetime_uploaded":"2021-10-21T10:36:12.764574Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/ba4ec17b-f0a0-4b15-996e-04d0fe2416a9/demo.jpeg","original_filename":"demo.jpeg","size":37445,"url":"https://api.uploadcare.com/files/ba4ec17b-f0a0-4b15-996e-04d0fe2416a9/","uuid":"ba4ec17b-f0a0-4b15-996e-04d0fe2416a9","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-21T10:26:51.992202Z","datetime_uploaded":"2021-10-21T10:26:51.166354Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/b99ed6e4-0f66-4f9a-a812-79586a891762/sample.jpg","original_filename":"sample.jpg","size":56780,"url":"https://api.uploadcare.com/files/b99ed6e4-0f66-4f9a-a812-79586a891762/","uuid":"b99ed6e4-0f66-4f9a-a812-79586a891762","variations":null,"content_info":{"image":{"dpi":[72,72],"width":600,"format":"JPEG","height":320,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:55 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-21T10:26:51.166354%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:55 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:585e15dd87f3a8fc63bf31677397f0bc6ce04c02 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:55 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 20e0d316-3806-4c00-8f29-b552eef432c8 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-20T08%3A15%3A18.913743%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-21T10%3A26%3A21.863439%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-21T10:26:21.926803Z","datetime_uploaded":"2021-10-21T10:26:21.863439Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/0883b9ef-ebe6-4949-a4f8-f381a2c178ed/sample.jpg","original_filename":"sample.jpg","size":56780,"url":"https://api.uploadcare.com/files/0883b9ef-ebe6-4949-a4f8-f381a2c178ed/","uuid":"0883b9ef-ebe6-4949-a4f8-f381a2c178ed","variations":null,"content_info":{"image":{"dpi":[72,72],"width":600,"format":"JPEG","height":320,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-20T08:15:18.984475Z","datetime_uploaded":"2021-10-20T08:15:18.913743Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/a69a30ef-a7c7-4eaf-9343-3447261fc9cd/sample.jpg","original_filename":"sample.jpg","size":56780,"url":"https://api.uploadcare.com/files/a69a30ef-a7c7-4eaf-9343-3447261fc9cd/","uuid":"a69a30ef-a7c7-4eaf-9343-3447261fc9cd","variations":null,"content_info":{"image":{"dpi":[72,72],"width":600,"format":"JPEG","height":320,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:55 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-20T08:15:18.913743%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:55 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:d72b4d05ca9ee5fbad1512c7364ed73d8f7e593e - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:56 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1674' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - aa69ecf8-e111-42a5-8882-9222387ddeae - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-14T14%3A39%3A58.681405%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-14T14%3A41%3A04.066458%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-14T14:41:04.196062Z","datetime_uploaded":"2021-10-14T14:41:04.066458Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/44f91ae9-edea-48db-b758-3eaca409ad2c/img.png","original_filename":"img.png","size":5500486,"url":"https://api.uploadcare.com/files/44f91ae9-edea-48db-b758-3eaca409ad2c/","uuid":"44f91ae9-edea-48db-b758-3eaca409ad2c","variations":null,"content_info":{"image":{"dpi":null,"width":1194,"format":"PNG","height":2048,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-14T14:39:58.880456Z","datetime_uploaded":"2021-10-14T14:39:58.681405Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/e7181ed0-2873-4c50-9b26-d1a8e98aa35c/img.png","original_filename":"img.png","size":5500486,"url":"https://api.uploadcare.com/files/e7181ed0-2873-4c50-9b26-d1a8e98aa35c/","uuid":"e7181ed0-2873-4c50-9b26-d1a8e98aa35c","variations":null,"content_info":{"image":{"dpi":null,"width":1194,"format":"PNG","height":2048,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:56 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-14T14:39:58.681405%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:56 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:cc06aea8deaaa20a918da8ddb3ba8b82835e45b4 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:56 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1671' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 0bd28f22-37c8-4851-a70f-7df8a8469b68 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-13T13%3A33%3A42.902720%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-14T14%3A39%3A26.037023%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-14T14:39:26.284181Z","datetime_uploaded":"2021-10-14T14:39:26.037023Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/eadacca0-310e-4257-9241-7ba7934ef07e/img.png","original_filename":"img.png","size":5500486,"url":"https://api.uploadcare.com/files/eadacca0-310e-4257-9241-7ba7934ef07e/","uuid":"eadacca0-310e-4257-9241-7ba7934ef07e","variations":null,"content_info":{"image":{"dpi":null,"width":1194,"format":"PNG","height":2048,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-13T13:33:52.594885Z","datetime_uploaded":"2021-10-13T13:33:42.902720Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/d28e6abe-e0fb-47a3-8b08-33f77d390fb8/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":6522,"url":"https://api.uploadcare.com/files/d28e6abe-e0fb-47a3-8b08-33f77d390fb8/","uuid":"d28e6abe-e0fb-47a3-8b08-33f77d390fb8","variations":null,"content_info":{"image":{"dpi":null,"width":640,"format":"JPEG","height":480,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:56 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-13T13:33:42.902720%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:56 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:6b4bb24e642d786cc2b85cd9a69a43ce750174e9 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:57 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1444' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a9316e99-d316-4859-914d-786524628192 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A50%3A20.745789%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-13T11%3A00%3A29.793110%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-13T11:01:00.103617Z","datetime_uploaded":"2021-10-13T11:00:29.793110Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/d3036ac4-a94c-48e7-be97-0b429f7f3071/atom.deb","original_filename":"atom.deb","size":111760768,"url":"https://api.uploadcare.com/files/d3036ac4-a94c-48e7-be97-0b429f7f3071/","uuid":"d3036ac4-a94c-48e7-be97-0b429f7f3071","variations":null,"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:50:28.374069Z","datetime_uploaded":"2021-10-01T08:50:20.745789Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/90a65f54-1ff7-4b35-8f5d-7fa359d4950f/thumbnail_1.pdf","original_filename":"thumbnail_1.pdf","size":26815,"url":"https://api.uploadcare.com/files/90a65f54-1ff7-4b35-8f5d-7fa359d4950f/","uuid":"90a65f54-1ff7-4b35-8f5d-7fa359d4950f","variations":{"document/-/format/jpg/":"5e1c0ee6-7246-482f-b85c-85c1cb9e116e"},"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:57 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:50:20.745789%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:57 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:17c94fb0856273c4e3fe087e46a85c4520b671be - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:58 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1687' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 93d456cb-cba6-4d89-bdc8-5e092fd5eb1e - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A41%3A02.823464%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A41%3A02.849531%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:41:14.716365Z","datetime_uploaded":"2021-10-01T08:41:02.849531Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/942e97b2-8efe-4699-bbac-70f07c37f88e/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":13772,"url":"https://api.uploadcare.com/files/942e97b2-8efe-4699-bbac-70f07c37f88e/","uuid":"942e97b2-8efe-4699-bbac-70f07c37f88e","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:41:14.657422Z","datetime_uploaded":"2021-10-01T08:41:02.823464Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/676437cd-feda-4609-a58c-9aec5f408e39/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13766,"url":"https://api.uploadcare.com/files/676437cd-feda-4609-a58c-9aec5f408e39/","uuid":"676437cd-feda-4609-a58c-9aec5f408e39","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:58 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:41:02.823464%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:58 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:a0856f4d3c3ca4a7d89eb86dd88e81ebf084506f - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:58 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '2658' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 8c7774b8-6a94-4922-bd68-15156ec4f2be - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A32%3A24.714047%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A41%3A02.796735%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:41:14.620406Z","datetime_uploaded":"2021-10-01T08:41:02.796735Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/bc607978-b348-4585-bcc1-bde10528457c/video.webm","original_filename":"video.webm","size":21085,"url":"https://api.uploadcare.com/files/bc607978-b348-4585-bcc1-bde10528457c/","uuid":"bc607978-b348-4585-bcc1-bde10528457c","variations":{"video/-/format/mp4/-/quality/lightest/":"59eddee3-4db0-42e6-8c80-bd48b375bcb1","video/-/format/mp4/-/quality/lightest/-/thumbs~1/0/":"f84b00b4-91ef-4104-8584-5785fbf528b8","video/-/format/mp4/-/quality/normal/-/size/640x480/preserve_ratio/-/cut/0/1/":"9a43dba8-82bb-42d8-9f34-69b932dfcd73","video/-/format/mp4/-/quality/normal/-/size/640x480/preserve_ratio/-/cut/0/1/-/thumbs~5/0/":"a1fa4a6b-5309-4726-b98a-2539a1796118","video/-/format/mp4/-/quality/normal/-/size/640x480/preserve_ratio/-/cut/0/1/-/thumbs~5/1/":"b87db75a-7705-45e5-81fb-2e8817a78557","video/-/format/mp4/-/quality/normal/-/size/640x480/preserve_ratio/-/cut/0/1/-/thumbs~5/2/":"f39494f7-9281-4c60-8a85-dfc068ff9ae0","video/-/format/mp4/-/quality/normal/-/size/640x480/preserve_ratio/-/cut/0/1/-/thumbs~5/3/":"45cb100b-ea2d-4feb-8ed9-f9fd312071fa","video/-/format/mp4/-/quality/normal/-/size/640x480/preserve_ratio/-/cut/0/1/-/thumbs~5/4/":"0090fbdf-fde8-43ad-be53-784cc319487e"},"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":10,"channels":2,"sample_rate":44100}],"video":[{"codec":"vp8","width":1280,"height":720,"bitrate":116,"frame_rate":29}],"format":"webm","bitrate":126,"duration":1020}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:32:24.971941Z","datetime_uploaded":"2021-10-01T08:32:24.714047Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/c294fd50-14ec-4597-affa-8210afe698e2/Octocat.png","original_filename":"Octocat.png","size":2947,"url":"https://api.uploadcare.com/files/c294fd50-14ec-4597-affa-8210afe698e2/","uuid":"c294fd50-14ec-4597-affa-8210afe698e2","variations":null,"content_info":{"image":{"dpi":null,"width":50,"format":"PNG","height":42,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:58 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:32:24.714047%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:58 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:e40cdbc9521183a5fe57425ddaed29f02a039c87 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:59 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1510' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - d4a4d207-f3a1-4f6e-ae88-3a568979398a - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A31%3A51.865474%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A31%3A57.511076%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:58.787045Z","datetime_uploaded":"2021-10-01T08:31:57.511076Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/6297f57c-5405-46c0-a648-10013b75c962/tmpxm3tblz9","original_filename":"tmpxm3tblz9","size":4,"url":"https://api.uploadcare.com/files/6297f57c-5405-46c0-a648-10013b75c962/","uuid":"6297f57c-5405-46c0-a648-10013b75c962","variations":null,"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:51.927853Z","datetime_uploaded":"2021-10-01T08:31:51.865474Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/4bb7c545-a9d4-48fd-a229-975eb231701f/meh.png","original_filename":"meh.png","size":32590,"url":"https://api.uploadcare.com/files/4bb7c545-a9d4-48fd-a229-975eb231701f/","uuid":"4bb7c545-a9d4-48fd-a229-975eb231701f","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:59 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:31:51.865474%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:59 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:19c242195eee3116b1c65e32ea9537e00d4dc2b9 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:59 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1667' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 3058ee8e-a892-479d-ae56-d727bb0300c2 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A31%3A45.532147%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A31%3A49.648787%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:49.712473Z","datetime_uploaded":"2021-10-01T08:31:49.648787Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/36e61fb4-5569-4fa0-ba3c-7368e2b82f9b/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/36e61fb4-5569-4fa0-ba3c-7368e2b82f9b/","uuid":"36e61fb4-5569-4fa0-ba3c-7368e2b82f9b","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:45.612262Z","datetime_uploaded":"2021-10-01T08:31:45.532147Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/d647221e-c292-4ed7-932e-f41f9532d23e/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/d647221e-c292-4ed7-932e-f41f9532d23e/","uuid":"d647221e-c292-4ed7-932e-f41f9532d23e","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:59 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:31:45.532147%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:59 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:c0a34121e98aa5530b251fa6261794b458f5bc30 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:00 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1667' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - efdf0d82-b4bc-48f9-a0d4-ce1129750fcb - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A31%3A41.986934%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A31%3A44.083875%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:44.155398Z","datetime_uploaded":"2021-10-01T08:31:44.083875Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/76421e57-6a0d-4774-b7ce-338afb712732/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/76421e57-6a0d-4774-b7ce-338afb712732/","uuid":"76421e57-6a0d-4774-b7ce-338afb712732","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:42.052216Z","datetime_uploaded":"2021-10-01T08:31:41.986934Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/e64c370c-d3e7-402c-8a0d-a7f665c91bb1/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/e64c370c-d3e7-402c-8a0d-a7f665c91bb1/","uuid":"e64c370c-d3e7-402c-8a0d-a7f665c91bb1","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:00 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:31:41.986934%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:00 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:cef91a7d6b60984f923b5571c325597d56d903d6 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:00 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1667' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - dc6f5dbf-eb06-463b-8951-e0b21ec26b49 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A31%3A38.189789%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A31%3A39.849882%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:39.912954Z","datetime_uploaded":"2021-10-01T08:31:39.849882Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/6e10c05d-c7e3-48a4-a94e-e45b980aa611/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/6e10c05d-c7e3-48a4-a94e-e45b980aa611/","uuid":"6e10c05d-c7e3-48a4-a94e-e45b980aa611","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:38.254833Z","datetime_uploaded":"2021-10-01T08:31:38.189789Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/93ef7c07-1258-4ee7-b88d-cd03201e8e9a/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/93ef7c07-1258-4ee7-b88d-cd03201e8e9a/","uuid":"93ef7c07-1258-4ee7-b88d-cd03201e8e9a","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:00 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:31:38.189789%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:00 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:91ff68ef4b54af107ce226ee1968e0c70192aba4 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:01 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1504' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - a83ba9bd-f2ba-40d5-800d-98dca81662c8 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A31%3A36.444164%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A31%3A37.375816%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:37.457056Z","datetime_uploaded":"2021-10-01T08:31:37.375816Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/d4f47a38-7e2b-44e9-8f92-b7ef1df98975/Octocat.png","original_filename":"Octocat.png","size":32590,"url":"https://api.uploadcare.com/files/d4f47a38-7e2b-44e9-8f92-b7ef1df98975/","uuid":"d4f47a38-7e2b-44e9-8f92-b7ef1df98975","variations":null,"content_info":{"image":{"dpi":null,"width":800,"format":"PNG","height":665,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:36.526410Z","datetime_uploaded":"2021-10-01T08:31:36.444164Z","is_image":false,"is_ready":true,"mime_type":"text/plain","original_file_url":"https://ucarecdn.com/74711d48-dacd-4c00-8cb5-cec072f4d31e/sample1.txt","original_filename":"sample1.txt","size":5,"url":"https://api.uploadcare.com/files/74711d48-dacd-4c00-8cb5-cec072f4d31e/","uuid":"74711d48-dacd-4c00-8cb5-cec072f4d31e","variations":null,"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:01 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:31:36.444164%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:01 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:ce7e9d9eec4ea1fd10a15ea7df21deccf99c313b - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:02 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1534' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 755a7f00-4534-45e7-8a37-cbc1e5fca464 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T08%3A05%3A08.484745%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A31%3A35.618904%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:31:35.679512Z","datetime_uploaded":"2021-10-01T08:31:35.618904Z","is_image":false,"is_ready":true,"mime_type":"text/plain","original_file_url":"https://ucarecdn.com/45377587-5cb3-4f64-8d73-38f856f1597f/sample1.txt","original_filename":"sample1.txt","size":5,"url":"https://api.uploadcare.com/files/45377587-5cb3-4f64-8d73-38f856f1597f/","uuid":"45377587-5cb3-4f64-8d73-38f856f1597f","variations":null,"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T08:05:20.257445Z","datetime_uploaded":"2021-10-01T08:05:08.484745Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/61d26c60-135b-44f9-9791-bbb7069109ac/thumbnail_1.png","original_filename":"thumbnail_1.png","size":112290,"url":"https://api.uploadcare.com/files/61d26c60-135b-44f9-9791-bbb7069109ac/","uuid":"61d26c60-135b-44f9-9791-bbb7069109ac","variations":null,"content_info":{"image":{"dpi":[300,300],"width":5333,"format":"PNG","height":3000,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:01 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T08:05:08.484745%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:02 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:b1588865f1f83aa4345d3d395da39cd2a1067ae2 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:02 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1699' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 456d5b0a-4463-43e8-8fa3-9bce1f6b689a - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-10-01T07%3A57%3A45.368217%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-10-01T08%3A04%3A05.994865%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-10-01T08:04:20.884147Z","datetime_uploaded":"2021-10-01T08:04:05.994865Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/5705d05d-09d5-48ae-b03b-fc44b33e01e7/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":371930,"url":"https://api.uploadcare.com/files/5705d05d-09d5-48ae-b03b-fc44b33e01e7/","uuid":"5705d05d-09d5-48ae-b03b-fc44b33e01e7","variations":null,"content_info":{"image":{"dpi":null,"width":5333,"format":"JPEG","height":3000,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-10-01T07:57:56.341075Z","datetime_uploaded":"2021-10-01T07:57:45.368217Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/5dddafa0-a742-4a51-ac40-ae491201ff97/thumbnail_1.pdf","original_filename":"thumbnail_1.pdf","size":26815,"url":"https://api.uploadcare.com/files/5dddafa0-a742-4a51-ac40-ae491201ff97/","uuid":"5dddafa0-a742-4a51-ac40-ae491201ff97","variations":{"document/-/format/jpg/-/page/1/":"5705d05d-09d5-48ae-b03b-fc44b33e01e7","document/-/format/png/-/page/1/":"61d26c60-135b-44f9-9791-bbb7069109ac"},"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:02 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-10-01T07:57:45.368217%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:02 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:28683e1d1ae1f9e657def0f268592c88ff4e90cd - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:03 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1748' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 7a5bd542-6e8f-417b-b79d-7cabd22154d3 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A33%3A32.584145%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A33%3A32.604072%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:34:18.210901Z","datetime_uploaded":"2021-09-30T15:33:32.604072Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/0e1cac48-1296-417f-9e7f-9bf13e330dcf/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":13772,"url":"https://api.uploadcare.com/files/0e1cac48-1296-417f-9e7f-9bf13e330dcf/","uuid":"0e1cac48-1296-417f-9e7f-9bf13e330dcf","variations":{"document/-/format/pdf/":"90a65f54-1ff7-4b35-8f5d-7fa359d4950f"},"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:34:18.146189Z","datetime_uploaded":"2021-09-30T15:33:32.584145Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/607acb70-8778-4d78-9ad9-060316a4c5b5/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13766,"url":"https://api.uploadcare.com/files/607acb70-8778-4d78-9ad9-060316a4c5b5/","uuid":"607acb70-8778-4d78-9ad9-060316a4c5b5","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:03 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:33:32.584145%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:03 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:5c04180a517a3d00989090edad4640d1dda8f2a4 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:03 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '2119' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 1953b615-2e9e-4b12-bdf2-80d84de679a6 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A30%3A33.565044%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A33%3A32.522449%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:34:18.132648Z","datetime_uploaded":"2021-09-30T15:33:32.522449Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/72967d89-18bd-4906-a675-762ae721ccb5/video.webm","original_filename":"video.webm","size":21085,"url":"https://api.uploadcare.com/files/72967d89-18bd-4906-a675-762ae721ccb5/","uuid":"72967d89-18bd-4906-a675-762ae721ccb5","variations":{"video/-/format/mp4/-/quality/normal/":"9a620cb1-e06f-4ad8-a386-a1cf22928542","video/-/format/mp4/-/quality/normal/-/thumbs~1/0/":"ee0cadb3-54bb-4701-891d-a1c1b9267808","video/-/format/mp4/-/quality/lighter/-/size/640x480/add_padding/":"db5c0182-4601-432e-84ed-e6fffff38b26","video/-/format/mp4/-/quality/lighter/-/size/640x480/add_padding/-/thumbs~1/0/":"d28e6abe-e0fb-47a3-8b08-33f77d390fb8"},"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":10,"channels":2,"sample_rate":44100}],"video":[{"codec":"vp8","width":1280,"height":720,"bitrate":116,"frame_rate":29}],"format":"webm","bitrate":126,"duration":1020}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:30:46.827881Z","datetime_uploaded":"2021-09-30T15:30:33.565044Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/81633db0-e474-4b1e-9b23-b905596409cd/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":13772,"url":"https://api.uploadcare.com/files/81633db0-e474-4b1e-9b23-b905596409cd/","uuid":"81633db0-e474-4b1e-9b23-b905596409cd","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:03 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:30:33.565044%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:03 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:8805673b18f7850dbb00eb22cfc417f6b5638696 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:04 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1894' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 02eece34-5374-45cd-a604-bd068ba90a74 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A30%3A33.501059%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A30%3A33.537242%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:30:46.774615Z","datetime_uploaded":"2021-09-30T15:30:33.537242Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/42b7614a-989a-407f-9e2f-201880a59092/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13766,"url":"https://api.uploadcare.com/files/42b7614a-989a-407f-9e2f-201880a59092/","uuid":"42b7614a-989a-407f-9e2f-201880a59092","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:30:46.784185Z","datetime_uploaded":"2021-09-30T15:30:33.501059Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/a96df059-1587-4918-afdc-54609011a0d9/video.webm","original_filename":"video.webm","size":21085,"url":"https://api.uploadcare.com/files/a96df059-1587-4918-afdc-54609011a0d9/","uuid":"a96df059-1587-4918-afdc-54609011a0d9","variations":{"video/-/format/mp4/-/quality/normal/":"847db367-f7e2-409a-8b1b-e1c116b22f23","video/-/format/mp4/-/quality/normal/-/thumbs~1/0/":"474ce188-8163-4518-a329-b1d4e27474ad"},"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":10,"channels":2,"sample_rate":44100}],"video":[{"codec":"vp8","width":1280,"height":720,"bitrate":116,"frame_rate":29}],"format":"webm","bitrate":126,"duration":1020}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:04 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:30:33.501059%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:04 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:97a1225d4402dfd7d17a618d0b8d610f46b0062a - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:04 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1687' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c01a54af-d157-4d98-aa16-6bcd6328e1b9 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A19%3A17.072516%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A19%3A17.090484%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:19:36.827040Z","datetime_uploaded":"2021-09-30T15:19:17.090484Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/88be874c-e5c8-4287-a28f-95260384ae3d/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":13772,"url":"https://api.uploadcare.com/files/88be874c-e5c8-4287-a28f-95260384ae3d/","uuid":"88be874c-e5c8-4287-a28f-95260384ae3d","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:19:36.749282Z","datetime_uploaded":"2021-09-30T15:19:17.072516Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/f6fa61d8-b967-4f0f-8aa4-a77780d83319/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13766,"url":"https://api.uploadcare.com/files/f6fa61d8-b967-4f0f-8aa4-a77780d83319/","uuid":"f6fa61d8-b967-4f0f-8aa4-a77780d83319","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:04 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:19:17.072516%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:04 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:d71d76ca646965730223e72967ec6b24f885509b - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:05 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1728' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 0a4fe359-3c68-405b-973d-69e39ade3481 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A16%3A01.063479%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A19%3A17.051994%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:19:36.761745Z","datetime_uploaded":"2021-09-30T15:19:17.051994Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/58fa9e53-9a50-479d-854e-c7dffb88df73/video.webm","original_filename":"video.webm","size":21085,"url":"https://api.uploadcare.com/files/58fa9e53-9a50-479d-854e-c7dffb88df73/","uuid":"58fa9e53-9a50-479d-854e-c7dffb88df73","variations":null,"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":10,"channels":2,"sample_rate":44100}],"video":[{"codec":"vp8","width":1280,"height":720,"bitrate":116,"frame_rate":29}],"format":"webm","bitrate":126,"duration":1020}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:16:12.178758Z","datetime_uploaded":"2021-09-30T15:16:01.063479Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/34629b47-a1bc-431b-988b-b2d01bdf55ba/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":13772,"url":"https://api.uploadcare.com/files/34629b47-a1bc-431b-988b-b2d01bdf55ba/","uuid":"34629b47-a1bc-431b-988b-b2d01bdf55ba","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:05 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:16:01.063479%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:05 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:9e23d5d537c011408f2c05eb1ac18fb32122e644 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:05 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1898' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 83e1f00f-ff24-4336-9d38-b632d703399b - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A16%3A01.010263%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A16%3A01.033228%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:16:12.164925Z","datetime_uploaded":"2021-09-30T15:16:01.033228Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/9a4fb121-8eb8-4069-85ee-2265d23e5c1b/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13766,"url":"https://api.uploadcare.com/files/9a4fb121-8eb8-4069-85ee-2265d23e5c1b/","uuid":"9a4fb121-8eb8-4069-85ee-2265d23e5c1b","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:16:12.156063Z","datetime_uploaded":"2021-09-30T15:16:01.010263Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/218574fd-40ad-4b63-be69-a03456b1532f/video.webm","original_filename":"video.webm","size":21085,"url":"https://api.uploadcare.com/files/218574fd-40ad-4b63-be69-a03456b1532f/","uuid":"218574fd-40ad-4b63-be69-a03456b1532f","variations":{"video/-/format/ogg/-/quality/lightest/":"54b0b43d-84b6-4b9a-abc5-84edc32223a9","video/-/format/ogg/-/quality/lightest/-/thumbs~1/0/":"3c592fb1-9945-4e2a-8a5e-98a65cbcb292"},"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":10,"channels":2,"sample_rate":44100}],"video":[{"codec":"vp8","width":1280,"height":720,"bitrate":116,"frame_rate":29}],"format":"webm","bitrate":126,"duration":1020}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:05 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:16:01.010263%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:05 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:65593b469357764bc315a1a56677f0f4fecc30e6 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:06 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1728' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - d5e8143c-e35b-4313-ac38-07d4f5b6af98 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-30T15%3A10%3A23.551172%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-30T15%3A10%3A23.579540%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-30T15:10:38.040687Z","datetime_uploaded":"2021-09-30T15:10:23.579540Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c8e24d55-a34f-4caa-afc5-30345635d026/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":13766,"url":"https://api.uploadcare.com/files/c8e24d55-a34f-4caa-afc5-30345635d026/","uuid":"c8e24d55-a34f-4caa-afc5-30345635d026","variations":null,"content_info":{"image":{"dpi":null,"width":1280,"format":"JPEG","height":720,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-30T15:10:37.995613Z","datetime_uploaded":"2021-09-30T15:10:23.551172Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/7b504148-e947-469f-bd7b-41ec0da93e52/video.webm","original_filename":"video.webm","size":21085,"url":"https://api.uploadcare.com/files/7b504148-e947-469f-bd7b-41ec0da93e52/","uuid":"7b504148-e947-469f-bd7b-41ec0da93e52","variations":null,"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":10,"channels":2,"sample_rate":44100}],"video":[{"codec":"vp8","width":1280,"height":720,"bitrate":116,"frame_rate":29}],"format":"webm","bitrate":126,"duration":1020}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:06 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-30T15:10:23.551172%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:06 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:8d451c4dc47693f571a74f744794ba6450b7d020 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:06 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1886' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - fc3f63b0-1157-4158-9c6e-9323c0ea069e - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-06T04%3A51%3A35.336394%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-06T04%3A51%3A53.064113%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-06T04:51:53.165836Z","datetime_uploaded":"2021-09-06T04:51:53.064113Z","is_image":false,"is_ready":true,"mime_type":"video/mp4","original_file_url":"https://ucarecdn.com/740e1b8c-1ad8-4324-b7ec-112c79d8eac2/1secVIDEO.mp4","original_filename":"1 - sec VIDEO.mp4","size":22108,"url":"https://api.uploadcare.com/files/740e1b8c-1ad8-4324-b7ec-112c79d8eac2/","uuid":"740e1b8c-1ad8-4324-b7ec-112c79d8eac2","variations":{"video/-/format/webm/-/quality/normal/":"bc607978-b348-4585-bcc1-bde10528457c","video/-/format/webm/-/quality/lightest/":"a581e784-84d1-4050-ac42-82b73dc3512f","video/-/format/webm/-/quality/normal/-/thumbs~1/0/":"c8e24d55-a34f-4caa-afc5-30345635d026","video/-/format/webm/-/quality/normal/-/thumbs~2/0/":"676437cd-feda-4609-a58c-9aec5f408e39","video/-/format/webm/-/quality/normal/-/thumbs~2/1/":"942e97b2-8efe-4699-bbac-70f07c37f88e","video/-/format/webm/-/quality/lightest/-/thumbs~1/0/":"14ceb46f-be5c-4962-8983-adc39928b5c0"},"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-06T04:51:35.459689Z","datetime_uploaded":"2021-09-06T04:51:35.336394Z","is_image":false,"is_ready":true,"mime_type":"video/mp4","original_file_url":"https://ucarecdn.com/1c79f573-73dc-4ae0-90f2-b16e28d358f7/1secVIDEO.mp4","original_filename":"1 - sec VIDEO.mp4","size":22108,"url":"https://api.uploadcare.com/files/1c79f573-73dc-4ae0-90f2-b16e28d358f7/","uuid":"1c79f573-73dc-4ae0-90f2-b16e28d358f7","variations":null,"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:06 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-06T04:51:35.336394%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:06 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:80089c2dd9ec62962093a243c986d60828026ebe - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:07 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1603' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 8545bd5f-46ab-49a5-a5e8-f192e8a31af6 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-03T03%3A32%3A51.536825%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-06T03%3A28%3A41.194584%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-06T03:29:01.561490Z","datetime_uploaded":"2021-09-06T03:28:41.194584Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/b9715d5c-e781-4d89-bd58-ed61bde780b5/134PoryadokvvodarossiyskikhLPvoborot0.png","original_filename":"134PoryadokvvodarossiyskikhLPvoborot-0.png","size":127914,"url":"https://api.uploadcare.com/files/b9715d5c-e781-4d89-bd58-ed61bde780b5/","uuid":"b9715d5c-e781-4d89-bd58-ed61bde780b5","variations":null,"content_info":{"image":{"dpi":[300,300],"width":2480,"format":"PNG","height":3508,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-03T03:33:30.446090Z","datetime_uploaded":"2021-09-03T03:32:51.536825Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/33237282-22e7-44f6-abe2-a18ca22c7826/atom.deb","original_filename":"atom.deb","size":111760768,"url":"https://api.uploadcare.com/files/33237282-22e7-44f6-abe2-a18ca22c7826/","uuid":"33237282-22e7-44f6-abe2-a18ca22c7826","variations":null,"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:07 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-03T03:32:51.536825%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:07 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:8d105c3ffa8830a23679b68b5c68604dd9826971 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:08 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1373' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - f848ed5d-4cee-42de-99de-4a41991ea89e - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-03T03%3A29%3A52.390434%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-03T03%3A31%3A16.623338%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-03T03:31:54.969421Z","datetime_uploaded":"2021-09-03T03:31:16.623338Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/e71aca37-207f-4caf-a878-d4a338cde9f4/atom.deb","original_filename":"atom.deb","size":111760768,"url":"https://api.uploadcare.com/files/e71aca37-207f-4caf-a878-d4a338cde9f4/","uuid":"e71aca37-207f-4caf-a878-d4a338cde9f4","variations":null,"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-03T03:30:40.014440Z","datetime_uploaded":"2021-09-03T03:29:52.390434Z","is_image":false,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/9a3edf53-f8b9-4019-b27f-8635c2404926/atom.deb","original_filename":"atom.deb","size":111760768,"url":"https://api.uploadcare.com/files/9a3edf53-f8b9-4019-b27f-8635c2404926/","uuid":"9a3edf53-f8b9-4019-b27f-8635c2404926","variations":null,"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:08 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-03T03:29:52.390434%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:08 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:01d6a5dc8472fb2d2fd4ee41842583f945f5fae1 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:08 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1665' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4d59ab2b-e35d-4491-9b0f-983af251d815 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-02T06%3A34%3A43.182262%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-02T07%3A53%3A31.674520%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-02T07:53:31.807816Z","datetime_uploaded":"2021-09-02T07:53:31.674520Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/8596a732-421e-42fe-8300-a3fd3788ca3a/444.png","original_filename":"444.png","size":578886,"url":"https://api.uploadcare.com/files/8596a732-421e-42fe-8300-a3fd3788ca3a/","uuid":"8596a732-421e-42fe-8300-a3fd3788ca3a","variations":null,"content_info":{"image":{"dpi":null,"width":1855,"format":"PNG","height":1056,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-02T06:34:43.310926Z","datetime_uploaded":"2021-09-02T06:34:43.182262Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/31d00bd4-799c-49dc-bdd0-ab789cd6bf22/194027.jpg","original_filename":"194027.jpg","size":2999798,"url":"https://api.uploadcare.com/files/31d00bd4-799c-49dc-bdd0-ab789cd6bf22/","uuid":"31d00bd4-799c-49dc-bdd0-ab789cd6bf22","variations":null,"content_info":{"image":{"dpi":null,"width":5169,"format":"JPEG","height":3423,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:08 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-02T06:34:43.182262%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:08 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:bf4b8b4ed570a0b99cf6cdd731b777520f5b1461 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:09 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1658' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 21af1038-c604-428b-9900-d462a08aa216 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-09-01T05%3A19%3A46.240576%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-09-02T06%3A34%3A30.013902%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-09-02T06:34:30.120996Z","datetime_uploaded":"2021-09-02T06:34:30.013902Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/780f43ee-8bca-4d42-bd53-091b7294b7a4/5568.png","original_filename":"5568.png","size":155021,"url":"https://api.uploadcare.com/files/780f43ee-8bca-4d42-bd53-091b7294b7a4/","uuid":"780f43ee-8bca-4d42-bd53-091b7294b7a4","variations":null,"content_info":{"image":{"dpi":null,"width":1855,"format":"PNG","height":1056,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-09-01T07:02:57.580685Z","datetime_uploaded":"2021-09-01T05:19:46.240576Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/a5c4b078-39ec-424d-8a51-1eb15c673e49/111.png","original_filename":"111.png","size":246018,"url":"https://api.uploadcare.com/files/a5c4b078-39ec-424d-8a51-1eb15c673e49/","uuid":"a5c4b078-39ec-424d-8a51-1eb15c673e49","variations":null,"content_info":{"image":{"dpi":null,"width":1484,"format":"PNG","height":872,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:09 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-09-01T05:19:46.240576%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:09 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:9322ce6cd0ddc816380233458570cefec7bdca2c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:09 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1971' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 8cedf846-9771-420d-9fbf-f1004834363d - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-23T08%3A17%3A36.611669%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-23T09%3A58%3A12.461343%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-23T09:58:12.536782Z","datetime_uploaded":"2021-08-23T09:58:12.461343Z","is_image":false,"is_ready":true,"mime_type":"video/mp4","original_file_url":"https://ucarecdn.com/2ff1e5ac-8ba8-4150-95c9-dd53d1d5e903/1secVIDEO.mp4","original_filename":"1 - sec VIDEO.mp4","size":22108,"url":"https://api.uploadcare.com/files/2ff1e5ac-8ba8-4150-95c9-dd53d1d5e903/","uuid":"2ff1e5ac-8ba8-4150-95c9-dd53d1d5e903","variations":{"video/-/format/ogg/-/quality/normal/":"8dd5015b-c2c8-4a01-bb67-ebdf25ae0d5e","video/-/format/ogg/-/quality/normal/-/thumbs~1/0/":"d65794ec-b644-46fc-957b-ba295c9aa612","video/-/format/ogg/-/quality/best/-/size/600x400/change_ratio/-/cut/0/end/":"7f8717db-eeab-49d6-8d44-580c55ea53f5","video/-/format/ogg/-/quality/best/-/size/600x400/change_ratio/-/cut/0/end/-/thumbs~1/0/":"2113e564-d88f-48d8-8e38-1ae207c1831f"},"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-23T08:17:36.726784Z","datetime_uploaded":"2021-08-23T08:17:36.611669Z","is_image":false,"is_ready":true,"mime_type":"application/pdf","original_file_url":"https://ucarecdn.com/56e7fb43-3d8c-4e29-b0ff-b06ba95250b2/134PoryadokvvodarossiyskikhLPvoborot.pdf","original_filename":"1.34.-Poryadok-vvoda-rossiyskikh-LP-v-oborot.pdf","size":190272,"url":"https://api.uploadcare.com/files/56e7fb43-3d8c-4e29-b0ff-b06ba95250b2/","uuid":"56e7fb43-3d8c-4e29-b0ff-b06ba95250b2","variations":{"document/-/format/png/":"aefdfb41-99af-42e0-a987-52bba1967b3b","document/-/format/png/-/page/1/":"5e23ce62-7163-44d8-a0c9-00fcb10c484a"},"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:09 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-23T08:17:36.611669%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:09 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:5ebefca78f48e42c14dd0795d807bcf2b01888f8 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:10 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1698' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 1e95c239-4932-473a-a046-a546888b30a0 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-18T12%3A39%3A12.456654%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-18T12%3A40%3A05.248786%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-18T12:40:24.784708Z","datetime_uploaded":"2021-08-18T12:40:05.248786Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/f76a2986-62d5-4a76-b08a-eb7e9df963e2/noroot","original_filename":"134PoryadokvvodarossiyskikhLPvoborot-0.png","size":127914,"url":"https://api.uploadcare.com/files/f76a2986-62d5-4a76-b08a-eb7e9df963e2/","uuid":"f76a2986-62d5-4a76-b08a-eb7e9df963e2","variations":null,"content_info":{"image":{"dpi":[300,300],"width":2480,"format":"PNG","height":3508,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-18T12:39:12.560662Z","datetime_uploaded":"2021-08-18T12:39:12.456654Z","is_image":false,"is_ready":true,"mime_type":"application/pdf","original_file_url":"https://ucarecdn.com/5afd45b7-2c72-4f84-bf8e-af517c05ffb4/134PoryadokvvodarossiyskikhLPvoborot.pdf","original_filename":"1.34.-Poryadok-vvoda-rossiyskikh-LP-v-oborot.pdf","size":190272,"url":"https://api.uploadcare.com/files/5afd45b7-2c72-4f84-bf8e-af517c05ffb4/","uuid":"5afd45b7-2c72-4f84-bf8e-af517c05ffb4","variations":{"document/-/format/png/-/page/1/":"f76a2986-62d5-4a76-b08a-eb7e9df963e2"},"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:10 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-18T12:39:12.456654%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:10 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:ad1bb1fbf4ce02ffa9e9711f15ba192ebf53dc64 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:10 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1696' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 5989e051-a799-4ad3-9705-e8a80b732bab - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T12%3A42%3A53.643827%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T12%3A42%3A57.880154%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.932261Z","datetime_uploaded":"2021-08-17T12:42:57.880154Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/d6cfb605-7e30-4b57-8f32-f9b9700ac779/dpsusuka4.jpg","original_filename":"dpsu-suka-4.jpg","size":89021,"url":"https://api.uploadcare.com/files/d6cfb605-7e30-4b57-8f32-f9b9700ac779/","uuid":"d6cfb605-7e30-4b57-8f32-f9b9700ac779","variations":null,"content_info":{"image":{"dpi":[96,96],"width":1080,"format":"JPEG","height":1080,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:53.696916Z","datetime_uploaded":"2021-08-17T12:42:53.643827Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/ce072829-8594-48cf-ae1c-881bb3e996e2/1317664704_Cancel.png","original_filename":"1317664704_Cancel.png","size":17092,"url":"https://api.uploadcare.com/files/ce072829-8594-48cf-ae1c-881bb3e996e2/","uuid":"ce072829-8594-48cf-ae1c-881bb3e996e2","variations":null,"content_info":{"image":{"dpi":null,"width":128,"format":"PNG","height":128,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:10 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T12:42:53.643827%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:10 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:9f305c8c5463c9fa6e80af22761f2950165987cb - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:11 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1691' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - dc095e02-1d49-4891-bd6c-d7f62af2c71d - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T12%3A41%3A23.513437%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T12%3A41%3A23.535672%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.411528Z","datetime_uploaded":"2021-08-17T12:41:23.535672Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e856f604-c99c-4d44-b888-19c0e4e97408/thumbnail_5.jpg","original_filename":"thumbnail_5.jpg","size":237280,"url":"https://api.uploadcare.com/files/e856f604-c99c-4d44-b888-19c0e4e97408/","uuid":"e856f604-c99c-4d44-b888-19c0e4e97408","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.270686Z","datetime_uploaded":"2021-08-17T12:41:23.513437Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c75cb1b9-d565-4cde-91b4-286a2b66dd49/thumbnail_4.jpg","original_filename":"thumbnail_4.jpg","size":229804,"url":"https://api.uploadcare.com/files/c75cb1b9-d565-4cde-91b4-286a2b66dd49/","uuid":"c75cb1b9-d565-4cde-91b4-286a2b66dd49","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:11 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T12:41:23.513437%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:11 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:617028940b9f12089aec5ed0d3c91eb095653a55 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:11 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1691' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 942d7fc4-d4b7-4544-8d7a-ee4cffd8e5ad - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T12%3A41%3A23.480722%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T12%3A41%3A23.497540%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.265510Z","datetime_uploaded":"2021-08-17T12:41:23.497540Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/4549185f-88c3-4a40-99e2-2c5196c97eba/thumbnail_3.jpg","original_filename":"thumbnail_3.jpg","size":229805,"url":"https://api.uploadcare.com/files/4549185f-88c3-4a40-99e2-2c5196c97eba/","uuid":"4549185f-88c3-4a40-99e2-2c5196c97eba","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.434370Z","datetime_uploaded":"2021-08-17T12:41:23.480722Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/de27a4ee-1181-4eda-9cb2-5bfb588217c1/thumbnail_2.jpg","original_filename":"thumbnail_2.jpg","size":241595,"url":"https://api.uploadcare.com/files/de27a4ee-1181-4eda-9cb2-5bfb588217c1/","uuid":"de27a4ee-1181-4eda-9cb2-5bfb588217c1","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:11 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T12:41:23.480722%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:11 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:9144541808e9b2f6ea515fb93f8a250bf850ca86 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:12 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1691' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 9a7534c1-703d-44c6-8166-8b4868d489de - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T12%3A41%3A23.437838%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T12%3A41%3A23.455744%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.362773Z","datetime_uploaded":"2021-08-17T12:41:23.455744Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/dfa92abe-2525-491d-bd6b-06654937b98d/thumbnail_1.jpg","original_filename":"thumbnail_1.jpg","size":262637,"url":"https://api.uploadcare.com/files/dfa92abe-2525-491d-bd6b-06654937b98d/","uuid":"dfa92abe-2525-491d-bd6b-06654937b98d","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.267077Z","datetime_uploaded":"2021-08-17T12:41:23.437838Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/85946c2a-6c8a-4998-b9e7-61d46cc63260/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":300632,"url":"https://api.uploadcare.com/files/85946c2a-6c8a-4998-b9e7-61d46cc63260/","uuid":"85946c2a-6c8a-4998-b9e7-61d46cc63260","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:12 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T12:41:23.437838%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:12 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:e1133578ac2e0a1fe908cf38786ec01f8131a5b4 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:13 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1570' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 104be39c-3d88-4bf1-9559-a825858ec6b4 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T12%3A39%3A56.028294%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T12%3A41%3A23.418342%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T12:42:57.640700Z","datetime_uploaded":"2021-08-17T12:41:23.418342Z","is_image":false,"is_ready":true,"mime_type":"audio/ogg","original_file_url":"https://ucarecdn.com/93af5994-ff10-4eec-beb0-a6923ad0b0cb/video.ogg","original_filename":"video.ogg","size":46753570,"url":"https://api.uploadcare.com/files/93af5994-ff10-4eec-beb0-a6923ad0b0cb/","uuid":"93af5994-ff10-4eec-beb0-a6923ad0b0cb","variations":null,"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":106,"channels":2,"sample_rate":48000}],"video":[{"codec":"theora","width":2560,"height":1440,"bitrate":31231,"frame_rate":30}],"format":"ogg","bitrate":31337,"duration":11933}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T12:39:56.471205Z","datetime_uploaded":"2021-08-17T12:39:56.028294Z","is_image":false,"is_ready":true,"mime_type":"application/zip","original_file_url":"https://ucarecdn.com/46361d33-9a54-4090-a25f-60170c001cdb/10MB.zip","original_filename":"10MB.zip","size":10485760,"url":"https://api.uploadcare.com/files/46361d33-9a54-4090-a25f-60170c001cdb/","uuid":"46361d33-9a54-4090-a25f-60170c001cdb","variations":null,"content_info":{},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:13 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T12:39:56.028294%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:13 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:e13431863e08cda9b3b755c4f503b6352d43f04c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:13 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1740' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - eaffbfd6-d398-44af-84c2-49ebcde92774 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T08%3A59%3A07.741178%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T08%3A59%3A07.771719%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T09:00:08.723519Z","datetime_uploaded":"2021-08-17T08:59:07.771719Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c22c6ced-fb35-48e8-b8bd-b68c0d1de301/thumbnail_0.jpg","original_filename":"thumbnail_0.jpg","size":241283,"url":"https://api.uploadcare.com/files/c22c6ced-fb35-48e8-b8bd-b68c0d1de301/","uuid":"c22c6ced-fb35-48e8-b8bd-b68c0d1de301","variations":null,"content_info":{"image":{"dpi":null,"width":2560,"format":"JPEG","height":1440,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T09:00:09.088313Z","datetime_uploaded":"2021-08-17T08:59:07.741178Z","is_image":false,"is_ready":true,"mime_type":"video/webm","original_file_url":"https://ucarecdn.com/68f06266-435d-4475-9b40-62032bb52f0f/video.webm","original_filename":"video.webm","size":71913294,"url":"https://api.uploadcare.com/files/68f06266-435d-4475-9b40-62032bb52f0f/","uuid":"68f06266-435d-4475-9b40-62032bb52f0f","variations":null,"content_info":{"video":{"audio":[{"codec":"vorbis","bitrate":105,"channels":2,"sample_rate":48000}],"video":[{"codec":"vp8","width":2560,"height":1440,"bitrate":48085,"frame_rate":30}],"format":"webm","bitrate":48190,"duration":11936}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:13 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T08:59:07.741178%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:13 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:14eb4890169ae3b8234ac7c7076fbdc311632604 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:14 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '2383' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 2f6e8cb9-4ca5-4096-a2c3-fca0d1d39818 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T08%3A19%3A29.635980%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T08%3A58%3A03.961419%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T08:58:21.681165Z","datetime_uploaded":"2021-08-17T08:58:03.961419Z","is_image":false,"is_ready":true,"mime_type":"video/mp4","original_file_url":"https://ucarecdn.com/e58ad58d-5fdc-4be0-b05c-8d72fc383142/NO20201108105124008142_1_1.mp4","original_filename":"NO20201108-105124-008142_1_1.mp4","size":18818227,"url":"https://api.uploadcare.com/files/e58ad58d-5fdc-4be0-b05c-8d72fc383142/","uuid":"e58ad58d-5fdc-4be0-b05c-8d72fc383142","variations":{"video/-/format/webm/-/quality/best/":"68f06266-435d-4475-9b40-62032bb52f0f","video/-/format/ogg/-/quality/normal/":"93af5994-ff10-4eec-beb0-a6923ad0b0cb","video/-/format/webm/-/quality/best/-/thumbs~1/0/":"c22c6ced-fb35-48e8-b8bd-b68c0d1de301","video/-/format/ogg/-/quality/normal/-/thumbs~6/0/":"85946c2a-6c8a-4998-b9e7-61d46cc63260","video/-/format/ogg/-/quality/normal/-/thumbs~6/1/":"dfa92abe-2525-491d-bd6b-06654937b98d","video/-/format/ogg/-/quality/normal/-/thumbs~6/2/":"de27a4ee-1181-4eda-9cb2-5bfb588217c1","video/-/format/ogg/-/quality/normal/-/thumbs~6/3/":"4549185f-88c3-4a40-99e2-2c5196c97eba","video/-/format/ogg/-/quality/normal/-/thumbs~6/4/":"c75cb1b9-d565-4cde-91b4-286a2b66dd49","video/-/format/ogg/-/quality/normal/-/thumbs~6/5/":"e856f604-c99c-4d44-b888-19c0e4e97408"},"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T08:21:38.306116Z","datetime_uploaded":"2021-08-17T08:19:29.635980Z","is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/61751a36-84b8-473d-b65e-c47d3ce94d30/noroot","original_filename":"web_AV190305_SRC20A_ug_UCRABGV_En_B0-4.jpg","size":734444,"url":"https://api.uploadcare.com/files/61751a36-84b8-473d-b65e-c47d3ce94d30/","uuid":"61751a36-84b8-473d-b65e-c47d3ce94d30","variations":null,"content_info":{"image":{"dpi":null,"width":1748,"format":"JPEG","height":2480,"sequence":false,"color_mode":"CMYK","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:14 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T08:19:29.635980%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:14 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:f48c3037fe0a13198244df2697996c4ada26836c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:14 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1704' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 9bcb7a0e-12be-449e-aed5-878e5a80a0c5 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T08%3A16%3A52.960415%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T08%3A18%3A47.663486%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T08:18:48.435955Z","datetime_uploaded":"2021-08-17T08:18:47.663486Z","is_image":false,"is_ready":true,"mime_type":"application/pdf","original_file_url":"https://ucarecdn.com/07ab9e0d-b944-42b3-9123-0aa1abe9a6e4/web_AV190305_SRC20A_ug_UCRABGV_En_B0.pdf","original_filename":"web_AV19-0305_SR-C20A_ug_UCRABGV_En_B0.pdf","size":2071429,"url":"https://api.uploadcare.com/files/07ab9e0d-b944-42b3-9123-0aa1abe9a6e4/","uuid":"07ab9e0d-b944-42b3-9123-0aa1abe9a6e4","variations":{"document/-/format/jpg/-/page/5/":"61751a36-84b8-473d-b65e-c47d3ce94d30"},"content_info":{},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T08:17:18.106976Z","datetime_uploaded":"2021-08-17T08:16:52.960415Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/c43fa5b5-62c8-479e-9cb3-ffc2edc60eee/jeremybishop2e3hgvDnCpMunsplash.jpg","original_filename":"jeremy-bishop-2e3hgvDnCpM-unsplash.jpg","size":1038646,"url":"https://api.uploadcare.com/files/c43fa5b5-62c8-479e-9cb3-ffc2edc60eee/","uuid":"c43fa5b5-62c8-479e-9cb3-ffc2edc60eee","variations":null,"content_info":{"image":{"dpi":[72,72],"width":3840,"format":"JPEG","height":2400,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:14 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T08:16:52.960415%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:14 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:7442a15b4f19ec4f3bfb2c10ffd1367fd6dab306 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:15 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1784' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 2d9b47dc-682e-474b-beb0-1567494d4968 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2021-08-17T08%3A15%3A43.023027%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T08%3A15%3A52.779165%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T08:15:52.927658Z","datetime_uploaded":"2021-08-17T08:15:52.779165Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e7cfce5a-ba38-4793-9224-5756fcd3939f/mikeyukhtenkoa2kD4b0KK4sunsplash.jpg","original_filename":"mike-yukhtenko-a2kD4b0KK4s-unsplash.jpg","size":1864950,"url":"https://api.uploadcare.com/files/e7cfce5a-ba38-4793-9224-5756fcd3939f/","uuid":"e7cfce5a-ba38-4793-9224-5756fcd3939f","variations":null,"content_info":{"image":{"dpi":[72,72],"width":3840,"format":"JPEG","height":2160,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2021-08-17T08:16:31.770009Z","datetime_uploaded":"2021-08-17T08:15:43.023027Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/cbad5ca3-0fce-4369-88ec-0a6dde322dc7/jeremybishop2e3hgvDnCpMunsplash.jpg","original_filename":"jeremy-bishop-2e3hgvDnCpM-unsplash.jpg","size":1038646,"url":"https://api.uploadcare.com/files/cbad5ca3-0fce-4369-88ec-0a6dde322dc7/","uuid":"cbad5ca3-0fce-4369-88ec-0a6dde322dc7","variations":null,"content_info":{"image":{"dpi":[72,72],"width":3840,"format":"JPEG","height":2400,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:15 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2021-08-17T08:15:43.023027%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:27:15 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:bb2e9ab161a6407476f06a8a50515be261a3a295 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:27:15 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '945' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - ef92ff27-3077-4f2b-8337-b4e9192f3c56 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":null,"previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2021-08-17T08%3A15%3A34.806209%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2021-08-17T08:15:34.926759Z","datetime_uploaded":"2021-08-17T08:15:34.806209Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/91274cdc-a834-4f37-b707-a224e2963783/ashimdsilvaWeYamle9fDMunsplash.jpg","original_filename":"ashim-d-silva-WeYamle9fDM-unsplash.jpg","size":1954070,"url":"https://api.uploadcare.com/files/91274cdc-a834-4f37-b707-a224e2963783/","uuid":"91274cdc-a834-4f37-b707-a224e2963783","variations":null,"content_info":{"image":{"dpi":[72,72],"width":3840,"format":"JPEG","height":2400,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:27:15 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list_pages.yml b/spec/fixtures/vcr_cassettes/rest_file_list_pages.yml deleted file mode 100644 index eee97c4d..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list_pages.yml +++ /dev/null @@ -1,119 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:36 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:083b67931eab9a75f634ca6d34010dacd768ffcd - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:37 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1656' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 8bf760cd-70c0-4503-9527-11e1f18f723c - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-28T15%3A53%3A36.082137%2B00%3A00&offset=0","previous":null,"total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-10-01T08:31:32.834879Z","datetime_uploaded":"2022-10-01T08:31:32.730675Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/image.png","original_filename":"image.png","size":86745,"url":"https://api.uploadcare.com/files/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/","uuid":"8f0a2a28-3ed7-481e-b415-ee3cce982aaa","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":536,"format":"PNG","height":354,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-28T15:53:36.188117Z","datetime_uploaded":"2022-09-28T15:53:36.082137Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/4654a098-ddef-4157-98f2-9c902b699bd8/image.png","original_filename":"image.png","size":4659,"url":"https://api.uploadcare.com/files/4654a098-ddef-4157-98f2-9c902b699bd8/","uuid":"4654a098-ddef-4157-98f2-9c902b699bd8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":274,"format":"PNG","height":367,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:37 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2022-09-28T15:53:36.082137%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:37 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:f7f0f397380af7d7db84ff4e5f37a8c251f4f46e - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:37 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1777' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 921cd85f-6725-4234-8b62-0845c6800d78 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-23T11%3A29%3A25.213425%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2022-09-28T15%3A21%3A26.699767%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-09-28T15:21:26.793937Z","datetime_uploaded":"2022-09-28T15:21:26.699767Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/461bfed9-179f-4e4b-aab2-fbaf132e4fb5/image.png","original_filename":"image.png","size":61645,"url":"https://api.uploadcare.com/files/461bfed9-179f-4e4b-aab2-fbaf132e4fb5/","uuid":"461bfed9-179f-4e4b-aab2-fbaf132e4fb5","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":300,"format":"PNG","height":105,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-23T11:29:25.358080Z","datetime_uploaded":"2022-09-23T11:29:25.213425Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/bc37b996-916d-4ed7-b230-fa71a4290cb3/image.png","original_filename":"image.png","size":62167,"url":"https://api.uploadcare.com/files/bc37b996-916d-4ed7-b230-fa71a4290cb3/","uuid":"bc37b996-916d-4ed7-b230-fa71a4290cb3","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":300,"format":"PNG","height":105,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:37 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list_params.yml b/spec/fixtures/vcr_cassettes/rest_file_list_params.yml deleted file mode 100644 index 4f73c274..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list_params.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:36 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:083b67931eab9a75f634ca6d34010dacd768ffcd - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:36 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1656' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 5a263cf0-5f8e-4e8d-9c7c-dbfa7955ad3a - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-28T15%3A53%3A36.082137%2B00%3A00&offset=0","previous":null,"total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-10-01T08:31:32.834879Z","datetime_uploaded":"2022-10-01T08:31:32.730675Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/image.png","original_filename":"image.png","size":86745,"url":"https://api.uploadcare.com/files/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/","uuid":"8f0a2a28-3ed7-481e-b415-ee3cce982aaa","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":536,"format":"PNG","height":354,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-28T15:53:36.188117Z","datetime_uploaded":"2022-09-28T15:53:36.082137Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/4654a098-ddef-4157-98f2-9c902b699bd8/image.png","original_filename":"image.png","size":4659,"url":"https://api.uploadcare.com/files/4654a098-ddef-4157-98f2-9c902b699bd8/","uuid":"4654a098-ddef-4157-98f2-9c902b699bd8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":274,"format":"PNG","height":367,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:36 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_list_previous_page.yml b/spec/fixtures/vcr_cassettes/rest_file_list_previous_page.yml deleted file mode 100644 index 1a1725d5..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_list_previous_page.yml +++ /dev/null @@ -1,177 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:37 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:4230a586b79c615ea88c4230588ad22900f1b62d - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:38 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1656' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 60b2c300-5e10-4811-99ff-0d9cf5bb90e4 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-28T15%3A53%3A36.082137%2B00%3A00&offset=0","previous":null,"total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-10-01T08:31:32.834879Z","datetime_uploaded":"2022-10-01T08:31:32.730675Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/image.png","original_filename":"image.png","size":86745,"url":"https://api.uploadcare.com/files/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/","uuid":"8f0a2a28-3ed7-481e-b415-ee3cce982aaa","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":536,"format":"PNG","height":354,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-28T15:53:36.188117Z","datetime_uploaded":"2022-09-28T15:53:36.082137Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/4654a098-ddef-4157-98f2-9c902b699bd8/image.png","original_filename":"image.png","size":4659,"url":"https://api.uploadcare.com/files/4654a098-ddef-4157-98f2-9c902b699bd8/","uuid":"4654a098-ddef-4157-98f2-9c902b699bd8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":274,"format":"PNG","height":367,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:38 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?from=2022-09-28T15:53:36.082137%2B00:00&limit=2&offset=0&ordering=-datetime_uploaded - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:38 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:0d7bb1c3580c5d56e4f30404e5b27f246d1febb5 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:38 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1777' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 380b512a-4f82-48da-b82b-7d5830b6ff0e - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&from=2022-09-23T11%3A29%3A25.213425%2B00%3A00&offset=0","previous":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&to=2022-09-28T15%3A21%3A26.699767%2B00%3A00","total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-09-28T15:21:26.793937Z","datetime_uploaded":"2022-09-28T15:21:26.699767Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/461bfed9-179f-4e4b-aab2-fbaf132e4fb5/image.png","original_filename":"image.png","size":61645,"url":"https://api.uploadcare.com/files/461bfed9-179f-4e4b-aab2-fbaf132e4fb5/","uuid":"461bfed9-179f-4e4b-aab2-fbaf132e4fb5","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":300,"format":"PNG","height":105,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-23T11:29:25.358080Z","datetime_uploaded":"2022-09-23T11:29:25.213425Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/bc37b996-916d-4ed7-b230-fa71a4290cb3/image.png","original_filename":"image.png","size":62167,"url":"https://api.uploadcare.com/files/bc37b996-916d-4ed7-b230-fa71a4290cb3/","uuid":"bc37b996-916d-4ed7-b230-fa71a4290cb3","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":300,"format":"PNG","height":105,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:38 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/?limit=2&offset=0&ordering=-datetime_uploaded&to=2022-09-28T15:21:26.699767%2B00:00 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.0/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Sun, 02 Oct 2022 10:26:38 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:bdf959af683533c1b5c9bcd5cb9b8167f4e39948 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Sun, 02 Oct 2022 10:26:39 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1656' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - f8b64a49-dfa6-43b2-85f8-244e9341bfd9 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/files/?limit=2&ordering=-datetime_uploaded&offset=0&from=2022-09-28T15%3A53%3A36.082137%2B00%3A00","previous":null,"total":129,"totals":{"removed":0,"stored":129,"unstored":0},"per_page":2,"results":[{"datetime_removed":null,"datetime_stored":"2022-10-01T08:31:32.834879Z","datetime_uploaded":"2022-10-01T08:31:32.730675Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/image.png","original_filename":"image.png","size":86745,"url":"https://api.uploadcare.com/files/8f0a2a28-3ed7-481e-b415-ee3cce982aaa/","uuid":"8f0a2a28-3ed7-481e-b415-ee3cce982aaa","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":536,"format":"PNG","height":354,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}},{"datetime_removed":null,"datetime_stored":"2022-09-28T15:53:36.188117Z","datetime_uploaded":"2022-09-28T15:53:36.082137Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/4654a098-ddef-4157-98f2-9c902b699bd8/image.png","original_filename":"image.png","size":4659,"url":"https://api.uploadcare.com/files/4654a098-ddef-4157-98f2-9c902b699bd8/","uuid":"4654a098-ddef-4157-98f2-9c902b699bd8","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":274,"format":"PNG","height":367,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}]}' - recorded_at: Sun, 02 Oct 2022 10:26:39 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_remote_copy.yml b/spec/fixtures/vcr_cassettes/rest_file_remote_copy.yml deleted file mode 100644 index 7d67ba7e..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_remote_copy.yml +++ /dev/null @@ -1,115 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/1b959c59-9605-4879-946f-08fdb5ea3e9d/ - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.1/ (Ruby/3.3.0) - Date: - - Tue, 28 May 2024 14:25:46 GMT - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 28 May 2024 14:25:47 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '712' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - f015aae5-5123-4ec7-9a00-f6f168b8f27a - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2024-05-23T22:39:29.307033Z","datetime_uploaded":"2024-05-23T22:39:29.139028Z","is_image":true,"is_ready":true,"mime_type":"image/png","original_file_url":"https://ucarecdn.com/1b959c59-9605-4879-946f-08fdb5ea3e9d/image.png","original_filename":"image.png","size":1048620,"url":"https://api.uploadcare.com/files/1b959c59-9605-4879-946f-08fdb5ea3e9d/","uuid":"1b959c59-9605-4879-946f-08fdb5ea3e9d","variations":null,"content_info":{"mime":{"mime":"image/png","type":"image","subtype":"png"},"image":{"dpi":null,"width":2560,"format":"PNG","height":523,"sequence":false,"color_mode":"RGBA","orientation":null,"geo_location":null,"datetime_original":null}},"metadata":{}}' - recorded_at: Tue, 28 May 2024 14:25:47 GMT -- request: - method: post - uri: https://api.uploadcare.com/files/remote_copy/ - body: - encoding: UTF-8 - string: '{"source":"1b959c59-9605-4879-946f-08fdb5ea3e9d","target":"uploadcare-test"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.1/ (Ruby/3.3.0) - Date: - - Tue, 28 May 2024 14:25:47 GMT - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 28 May 2024 14:25:48 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '99' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - aea85827-bab0-4760-865a-2dd43a830d5f - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"type":"url","result":"s3://uploadcare-test/1b959c59-9605-4879-946f-08fdb5ea3e9d/image.png"}' - recorded_at: Tue, 28 May 2024 14:25:48 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/rest_file_store.yml b/spec/fixtures/vcr_cassettes/rest_file_store.yml deleted file mode 100644 index 863b0d90..00000000 --- a/spec/fixtures/vcr_cassettes/rest_file_store.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: put - uri: https://api.uploadcare.com/files/e9a9f291-cc52-4388-bf65-9feec1c75ff9/storage/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Mon, 20 Jan 2020 16:05:13 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:427a8a7a3b35ceead97c28c0c133334e7ab55ad9 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 20 Jan 2020 16:05:13 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '629' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, POST, PUT - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2020-01-20T15:53:29.507216Z","datetime_uploaded":"2020-01-17T14:46:41.919939Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/e9a9f291-cc52-4388-bf65-9feec1c75ff9/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/e9a9f291-cc52-4388-bf65-9feec1c75ff9/","uuid":"e9a9f291-cc52-4388-bf65-9feec1c75ff9","source":null}' - http_version: - recorded_at: Mon, 20 Jan 2020 16:05:14 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_info_group.yml b/spec/fixtures/vcr_cassettes/rest_info_group.yml deleted file mode 100644 index 85ffe78a..00000000 --- a/spec/fixtures/vcr_cassettes/rest_info_group.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/groups/47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 30 Sep 2022 08:44:19 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:cc45fdba34edae2cc0d9eff88cfefb95b2ea1dc3 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 30 Sep 2022 08:44:19 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1627' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - d9db1cc2-2cca-4b40-ab8f-279521326aa0 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"id":"47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2","datetime_created":"2022-09-30T08:38:33.039356Z","files_count":2,"cdn_url":"https://ucarecdn.com/47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2/","url":"https://api.uploadcare.com/groups/47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2/","files":[{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:19.185151Z","datetime_uploaded":"2021-10-21T12:01:19.129147Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/32b70177-26f9-4281-bfe4-8ba284130c77/demo8.jpeg","original_filename":"demo8.jpeg","size":16120,"url":"https://api.uploadcare.com/files/32b70177-26f9-4281-bfe4-8ba284130c77/","uuid":"32b70177-26f9-4281-bfe4-8ba284130c77","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{},"default_effects":""},{"datetime_removed":null,"datetime_stored":"2021-10-21T11:59:58.520801Z","datetime_uploaded":"2021-10-21T11:59:58.459076Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/63947f94-568c-4719-ae9b-1a555240c925/demo6.jpeg","original_filename":"demo6.jpeg","size":24301,"url":"https://api.uploadcare.com/files/63947f94-568c-4719-ae9b-1a555240c925/","uuid":"63947f94-568c-4719-ae9b-1a555240c925","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{},"default_effects":""}]}' - recorded_at: Fri, 30 Sep 2022 08:44:19 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_list_groups.yml b/spec/fixtures/vcr_cassettes/rest_list_groups.yml deleted file mode 100644 index 995b8061..00000000 --- a/spec/fixtures/vcr_cassettes/rest_list_groups.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/groups/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 04 Feb 2020 12:16:10 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '2128' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":null,"previous":null,"total":7,"per_page":100,"results":[{"id":"0019d2f5-c245-4c39-b8fe-a7039df50291~1","datetime_created":"2020-01-16T14:48:47.721200Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/0019d2f5-c245-4c39-b8fe-a7039df50291~1/","url":"https://api.uploadcare.com/groups/0019d2f5-c245-4c39-b8fe-a7039df50291~1/"},{"id":"8705afee-c406-4279-bf33-ca1cd152b4a0~1","datetime_created":"2020-01-16T15:02:31.440287Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/8705afee-c406-4279-bf33-ca1cd152b4a0~1/","url":"https://api.uploadcare.com/groups/8705afee-c406-4279-bf33-ca1cd152b4a0~1/"},{"id":"d9860353-b276-4819-8b37-329ba1a3aae8~1","datetime_created":"2020-01-16T15:03:17.065399Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/d9860353-b276-4819-8b37-329ba1a3aae8~1/","url":"https://api.uploadcare.com/groups/d9860353-b276-4819-8b37-329ba1a3aae8~1/"},{"id":"134d766c-8d23-4c6f-9b53-f6f0c54893eb~1","datetime_created":"2020-01-20T09:16:38.231607Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/134d766c-8d23-4c6f-9b53-f6f0c54893eb~1/","url":"https://api.uploadcare.com/groups/134d766c-8d23-4c6f-9b53-f6f0c54893eb~1/"},{"id":"832b6dfc-619f-4d8a-9670-19b6abf85edd~1","datetime_created":"2020-01-20T09:16:47.207364Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/832b6dfc-619f-4d8a-9670-19b6abf85edd~1/","url":"https://api.uploadcare.com/groups/832b6dfc-619f-4d8a-9670-19b6abf85edd~1/"},{"id":"fc194fec-5793-4403-a593-686af4be412e~2","datetime_created":"2020-01-27T10:45:12.498576Z","datetime_stored":"2020-02-04T11:52:09.964351Z","files_count":2,"cdn_url":"https://ucarecdn.com/fc194fec-5793-4403-a593-686af4be412e~2/","url":"https://api.uploadcare.com/groups/fc194fec-5793-4403-a593-686af4be412e~2/"},{"id":"06500d4d-4eaf-4b83-bfe8-86a7bb2f160a~2","datetime_created":"2020-01-27T10:51:35.681035Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/06500d4d-4eaf-4b83-bfe8-86a7bb2f160a~2/","url":"https://api.uploadcare.com/groups/06500d4d-4eaf-4b83-bfe8-86a7bb2f160a~2/"}]}' - http_version: - recorded_at: Tue, 04 Feb 2020 12:16:10 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_list_groups_limited.yml b/spec/fixtures/vcr_cassettes/rest_list_groups_limited.yml deleted file mode 100644 index cb10c860..00000000 --- a/spec/fixtures/vcr_cassettes/rest_list_groups_limited.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/groups/?limit=2 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 04 Feb 2020 12:20:28 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '741' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"next":"https://api.uploadcare.com/groups/?from=2020-01-16T15%3A02%3A31.440287%2B00%3A00&limit=2&offset=0","previous":null,"total":7,"per_page":2,"results":[{"id":"0019d2f5-c245-4c39-b8fe-a7039df50291~1","datetime_created":"2020-01-16T14:48:47.721200Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/0019d2f5-c245-4c39-b8fe-a7039df50291~1/","url":"https://api.uploadcare.com/groups/0019d2f5-c245-4c39-b8fe-a7039df50291~1/"},{"id":"8705afee-c406-4279-bf33-ca1cd152b4a0~1","datetime_created":"2020-01-16T15:02:31.440287Z","datetime_stored":null,"files_count":1,"cdn_url":"https://ucarecdn.com/8705afee-c406-4279-bf33-ca1cd152b4a0~1/","url":"https://api.uploadcare.com/groups/8705afee-c406-4279-bf33-ca1cd152b4a0~1/"}]}' - http_version: - recorded_at: Tue, 04 Feb 2020 12:20:28 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_store_group.yml b/spec/fixtures/vcr_cassettes/rest_store_group.yml deleted file mode 100644 index 727dd1be..00000000 --- a/spec/fixtures/vcr_cassettes/rest_store_group.yml +++ /dev/null @@ -1,177 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/groups/47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 30 Sep 2022 09:53:43 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:a60670945ab4e1e26dc2fcbd7cc023999082136a - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 30 Sep 2022 09:53:44 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '1627' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 51a5fa7f-dddc-4880-960b-17379d567b21 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"id":"47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2","datetime_created":"2022-09-30T08:38:33.039356Z","files_count":2,"cdn_url":"https://ucarecdn.com/47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2/","url":"https://api.uploadcare.com/groups/47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2/","files":[{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:19.185151Z","datetime_uploaded":"2021-10-21T12:01:19.129147Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/32b70177-26f9-4281-bfe4-8ba284130c77/demo8.jpeg","original_filename":"demo8.jpeg","size":16120,"url":"https://api.uploadcare.com/files/32b70177-26f9-4281-bfe4-8ba284130c77/","uuid":"32b70177-26f9-4281-bfe4-8ba284130c77","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{},"default_effects":""},{"datetime_removed":null,"datetime_stored":"2021-10-21T11:59:58.520801Z","datetime_uploaded":"2021-10-21T11:59:58.459076Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/63947f94-568c-4719-ae9b-1a555240c925/demo6.jpeg","original_filename":"demo6.jpeg","size":24301,"url":"https://api.uploadcare.com/files/63947f94-568c-4719-ae9b-1a555240c925/","uuid":"63947f94-568c-4719-ae9b-1a555240c925","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{},"default_effects":""}]}' - recorded_at: Fri, 30 Sep 2022 09:53:44 GMT -- request: - method: put - uri: https://api.uploadcare.com/files/32b70177-26f9-4281-bfe4-8ba284130c77/storage/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 30 Sep 2022 09:53:44 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:0922c764505fc59b547adc597ff44acafe1951be - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 30 Sep 2022 09:53:44 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '653' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, POST, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 34c8540d-d0b7-44a5-85c4-0e3ae3ef489c - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2021-10-21T12:01:19.185151Z","datetime_uploaded":"2021-10-21T12:01:19.129147Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/32b70177-26f9-4281-bfe4-8ba284130c77/demo8.jpeg","original_filename":"demo8.jpeg","size":16120,"url":"https://api.uploadcare.com/files/32b70177-26f9-4281-bfe4-8ba284130c77/","uuid":"32b70177-26f9-4281-bfe4-8ba284130c77","variations":null,"content_info":{"image":{"dpi":[72,72],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}' - recorded_at: Fri, 30 Sep 2022 09:53:44 GMT -- request: - method: put - uri: https://api.uploadcare.com/files/63947f94-568c-4719-ae9b-1a555240c925/storage/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 30 Sep 2022 09:53:44 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:d6e24b595c59c8b4c2c65c2316ac953cc98dc10d - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 30 Sep 2022 09:53:45 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '653' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS, POST, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - f082ea38-237d-4492-b936-a531a9118007 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2021-10-21T11:59:58.520801Z","datetime_uploaded":"2021-10-21T11:59:58.459076Z","is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/63947f94-568c-4719-ae9b-1a555240c925/demo6.jpeg","original_filename":"demo6.jpeg","size":24301,"url":"https://api.uploadcare.com/files/63947f94-568c-4719-ae9b-1a555240c925/","uuid":"63947f94-568c-4719-ae9b-1a555240c925","variations":null,"content_info":{"image":{"dpi":[25,25],"width":536,"format":"JPEG","height":354,"sequence":false,"color_mode":"RGB","orientation":1,"geo_location":null,"datetime_original":null}},"metadata":{}}' - recorded_at: Fri, 30 Sep 2022 09:53:45 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_webhook_create.yml b/spec/fixtures/vcr_cassettes/rest_webhook_create.yml deleted file mode 100644 index 028ce499..00000000 --- a/spec/fixtures/vcr_cassettes/rest_webhook_create.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/webhooks/ - body: - encoding: UTF-8 - string: '{"target_url":"http://ohmyz.sh","event":"file.uploaded","is_active":true}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Wed, 05 Feb 2020 14:53:00 GMT - Authorization: - - Uploadcare ecd779dc169645ce3c91:069af1fe479aecb7248c6146e3674318660c11c6 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 201 - message: Created - headers: - Date: - - Wed, 05 Feb 2020 14:53:01 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '181' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"id":616294,"created":"2020-02-05T14:53:01.894511Z","updated":"2020-02-05T14:53:01.895307Z","event":"file.uploaded","target_url":"http://ohmyz.sh","project":86492,"is_active":true}' - http_version: - recorded_at: Wed, 05 Feb 2020 14:53:01 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_webhook_destroy.yml b/spec/fixtures/vcr_cassettes/rest_webhook_destroy.yml deleted file mode 100644 index b434df4a..00000000 --- a/spec/fixtures/vcr_cassettes/rest_webhook_destroy.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/webhooks/unsubscribe/ - body: - encoding: UTF-8 - string: '{"target_url":"http://example.com"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.0.2/demopublickey (Ruby/2.7.6) - Date: - - Sun, 25 Dec 2022 11:52:02 GMT - Authorization: - - Uploadcare demopublickey:002e6b7d40e088be059581433c1a298bd2b48b88 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 204 - message: No Content - headers: - Date: - - Sun, 25 Dec 2022 11:52:03 GMT - Content-Length: - - '0' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: You are using the demo project' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - e20e5691-2c74-4680-9a3f-1b3f92561fa3 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '' - recorded_at: Sun, 25 Dec 2022 11:52:03 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/rest_webhook_destroy1.yml b/spec/fixtures/vcr_cassettes/rest_webhook_destroy1.yml deleted file mode 100644 index 2d60a1f8..00000000 --- a/spec/fixtures/vcr_cassettes/rest_webhook_destroy1.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/webhooks/unsubscribe/ - body: - encoding: UTF-8 - string: '{"name":"example.com"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Tue, 21 Jan 2020 12:32:25 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:a672860ff4f46a95f3733b2bc08768c9bb4a734a - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: Success - headers: - Date: - - Tue, 21 Jan 2020 12:32:26 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '35' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"Webhook deleted"}' - http_version: - recorded_at: Tue, 21 Jan 2020 12:32:26 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_webhook_list.yml b/spec/fixtures/vcr_cassettes/rest_webhook_list.yml deleted file mode 100644 index aaabf718..00000000 --- a/spec/fixtures/vcr_cassettes/rest_webhook_list.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/webhooks/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Wed, 05 Feb 2020 14:32:03 GMT - Authorization: - - Uploadcare ecd779dc169645ce3c91:29e6862f7ae2df9eafc9051f8c70ad700d9ef4a2 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 05 Feb 2020 14:32:04 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '371' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '[{"id":616293,"created":"2020-02-05T14:31:59.280925Z","updated":"2020-02-05T14:31:59.280947Z","event":"file.uploaded","target_url":"http://example.org","project":86492,"is_active":true},{"id":616292,"created":"2020-02-05T14:21:45.985477Z","updated":"2020-02-05T14:21:45.985508Z","event":"file.uploaded","target_url":"http://example.com","project":86492,"is_active":true}]' - http_version: - recorded_at: Wed, 05 Feb 2020 14:32:04 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_webhook_list_unpaid.yml b/spec/fixtures/vcr_cassettes/rest_webhook_list_unpaid.yml deleted file mode 100644 index f39248c5..00000000 --- a/spec/fixtures/vcr_cassettes/rest_webhook_list_unpaid.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/webhooks/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Date: - - Tue, 21 Jan 2020 11:25:18 GMT - Authorization: - - Uploadcare c8499ee6dc44194c00d2:337781ec391e70ddaba72ae3f69659a686ef2cbd - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 403 - message: Forbidden - headers: - Date: - - Tue, 21 Jan 2020 11:25:19 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '35' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS, POST - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"detail":"You can''t use webhooks"}' - http_version: - recorded_at: Tue, 21 Jan 2020 11:25:19 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/rest_webhook_update.yml b/spec/fixtures/vcr_cassettes/rest_webhook_update.yml deleted file mode 100644 index b20d416b..00000000 --- a/spec/fixtures/vcr_cassettes/rest_webhook_update.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: put - uri: https://api.uploadcare.com/webhooks/887447/ - body: - encoding: UTF-8 - string: '{"target_url":"https://github.com","is_active":false,"signing_secret":"3ZsQRtl5Ez"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.1/demopublickey (Ruby/3.0.2) - Date: - - Tue, 16 Nov 2021 05:45:13 GMT - Authorization: - - Uploadcare demopublickey:858037812b3dca034de2b35df5c142788bf4bb7f - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 16 Nov 2021 05:45:14 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '213' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: You are using the demo project' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, PUT - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"id":887447,"created":"2021-11-16T05:41:51.040032Z","updated":"2021-11-16T05:45:14.422880Z","event":"file.uploaded","target_url":"https://github.com","project":159,"is_active":false,"signing_secret":"3ZsQRtl5Ez"}' - recorded_at: Tue, 16 Nov 2021 05:45:14 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/throttling.yml b/spec/fixtures/vcr_cassettes/throttling.yml deleted file mode 100644 index b8c4900a..00000000 --- a/spec/fixtures/vcr_cassettes/throttling.yml +++ /dev/null @@ -1,217 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - Retry-After: - - 10 - response: - status: - code: 429 - message: Not Found - headers: - Date: - - Thu, 06 Feb 2020 10:11:11 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - Retry-After: - - 10 - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - http_version: - recorded_at: Thu, 06 Feb 2020 10:11:11 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - Retry-After: - - 10 - response: - status: - code: 429 - message: Not Found - headers: - Date: - - Thu, 06 Feb 2020 10:11:13 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - Retry-After: - - 10 - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - http_version: - recorded_at: Thu, 06 Feb 2020 10:11:13 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 429 - message: Not Found - headers: - Date: - - Thu, 06 Feb 2020 10:11:14 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '23' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - Retry-After: - - 10 - body: - encoding: ASCII-8BIT - string: '{"detail":"Not found."}' - http_version: - recorded_at: Thu, 06 Feb 2020 10:11:14 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - Uploadcare.Simple c8499ee6dc44194c00d2:81d1ab1b5e9cb74d1ab1 - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 06 Feb 2020 10:11:16 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '629' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":"2020-01-16T15:03:15.315064Z","datetime_uploaded":"2020-01-16T15:03:14.676902Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":183,"width":190,"geo_location":null,"datetime_original":null,"dpi":null},"is_image":true,"is_ready":true,"mime_type":"image/jpeg","original_file_url":"https://ucarecdn.com/8f64f313-e6b1-4731-96c0-6751f1e7a50a/imagepng.jpeg","original_filename":"image.png.jpeg","size":5345,"url":"https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/","uuid":"8f64f313-e6b1-4731-96c0-6751f1e7a50a","source":null}' - http_version: - recorded_at: Thu, 06 Feb 2020 10:11:16 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan.yml b/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan.yml deleted file mode 100644 index 8d6bce03..00000000 --- a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/ - body: - encoding: UTF-8 - string: '{"target":"ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768","params":{"purge_infected":true}}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 10:02:58 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:591dbc7f8058e49694ccfe0eec56515c3f62337e - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Sep 2022 10:02:58 GMT - Content-Type: - - application/json - Content-Length: - - '54' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 34abf037-5384-4e38-bad4-97dd48e79acd - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"request_id": "34abf037-5384-4e38-bad4-97dd48e79acd"}' - recorded_at: Fri, 23 Sep 2022 10:02:58 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_nonexistent_uuid.yml deleted file mode 100644 index 470e13ac..00000000 --- a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/ - body: - encoding: UTF-8 - string: '{"target":"nonexistent","params":{}}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:57:58 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:b926e078c382ad9f39c1f7785a8d99885946da61 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Fri, 23 Sep 2022 11:57:59 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '36' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c2432813-4253-44ce-9e31-0aa280cb578a - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"target":["Must be a valid UUID."]}' - recorded_at: Fri, 23 Sep 2022 11:57:59 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_status.yml b/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_status.yml deleted file mode 100644 index 7c20dcd1..00000000 --- a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_status.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/?request_id=34abf037-5384-4e38-bad4-97dd48e79acd - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:14:02 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:a994c37e4a5f112379648467641d3d226de7357d - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Sep 2022 11:14:03 GMT - Content-Type: - - application/json - Content-Length: - - '18' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 12d3be24-840f-4885-a4e9-d654b7cbe88a - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"status": "done"}' - recorded_at: Fri, 23 Sep 2022 11:14:03 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_status_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_status_nonexistent_uuid.yml deleted file mode 100644 index 2681608b..00000000 --- a/spec/fixtures/vcr_cassettes/uc_clamav_virus_scan_status_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/?request_id=nonexistent - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:58:44 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:3836539f25d42c87919e273656f12990f76cda77 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Fri, 23 Sep 2022 11:58:45 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '40' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - f1873574-ee41-45fe-a50c-e50333ba1308 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Fri, 23 Sep 2022 11:58:45 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/upload_create_group.yml b/spec/fixtures/vcr_cassettes/upload_create_group.yml deleted file mode 100644 index f17f27a9..00000000 --- a/spec/fixtures/vcr_cassettes/upload_create_group.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/group/ - body: - encoding: UTF-8 - string: "-----------------------62bbc562d8a4b932833f093349c9182c5cec189103\r\nContent-Disposition: - form-data; name=\"pub_key\"\r\n\r\ndemopublickey\r\n-----------------------62bbc562d8a4b932833f093349c9182c5cec189103\r\nContent-Disposition: - form-data; name=\"files[0]\"\r\n\r\n8ca6e9fa-c6dd-4027-a0fc-b620611f7023\r\n-----------------------62bbc562d8a4b932833f093349c9182c5cec189103\r\nContent-Disposition: - form-data; name=\"files[1]\"\r\n\r\nb8a11440-6fcc-4285-a24d-cc8c60259fec\r\n-----------------------62bbc562d8a4b932833f093349c9182c5cec189103--\r\n" - headers: - User-Agent: - - UploadcareRuby/3.3.2/demopublickey (Ruby/2.6.0) - Content-Type: - - multipart/form-data; boundary=---------------------62bbc562d8a4b932833f093349c9182c5cec189103 - Connection: - - close - Host: - - upload.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 21 Sep 2022 12:48:35 GMT - Content-Type: - - application/json - Content-Length: - - '1806' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, POST, HEAD, OPTIONS - Access-Control-Allow-Headers: - - X-UC-User-Agent, DNT, X-PINGOTHER - Access-Control-Max-Age: - - '1' - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Expose-Headers: - - Warning - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 88700fbd-74c3-49b1-b070-86360c219ba9 - body: - encoding: UTF-8 - string: '{"id":"bbc75785-9016-4656-9c6e-64a76b45b0b8~2","datetime_created":"2022-09-21T12:48:35.157603Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/bbc75785-9016-4656-9c6e-64a76b45b0b8~2/","url":"https://api.uploadcare.com/groups/bbc75785-9016-4656-9c6e-64a76b45b0b8~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"8ca6e9fa-c6dd-4027-a0fc-b620611f7023","file_id":"8ca6e9fa-c6dd-4027-a0fc-b620611f7023","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"application/octet-stream","metadata":{},"default_effects":""},{"size":2375,"total":2375,"done":2375,"uuid":"b8a11440-6fcc-4285-a24d-cc8c60259fec","file_id":"b8a11440-6fcc-4285-a24d-cc8c60259fec","original_filename":"another_kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":100,"format":"JPEG","height":100,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":100,"format":"JPEG","height":100,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"another_kitten.jpeg","mime_type":"application/octet-stream","metadata":{},"default_effects":""}]}' - recorded_at: Wed, 21 Sep 2022 12:48:35 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/upload_create_group_from_files.yml b/spec/fixtures/vcr_cassettes/upload_create_group_from_files.yml deleted file mode 100644 index bde33d3b..00000000 --- a/spec/fixtures/vcr_cassettes/upload_create_group_from_files.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/group/ - body: - encoding: UTF-8 - string: "-----------------------2738ec4ef61112ed21629df7a08e15b49f29f9415c\r\nContent-Disposition: - form-data; name=\"pub_key\"\r\n\r\ndemopublickey\r\n-----------------------2738ec4ef61112ed21629df7a08e15b49f29f9415c\r\nContent-Disposition: - form-data; name=\"files[0]\"\r\n\r\n8ca6e9fa-c6dd-4027-a0fc-b620611f7023\r\n-----------------------2738ec4ef61112ed21629df7a08e15b49f29f9415c\r\nContent-Disposition: - form-data; name=\"files[1]\"\r\n\r\nb8a11440-6fcc-4285-a24d-cc8c60259fec\r\n-----------------------2738ec4ef61112ed21629df7a08e15b49f29f9415c--\r\n" - headers: - User-Agent: - - UploadcareRuby/3.3.2/demopublickey (Ruby/2.6.0) - Content-Type: - - multipart/form-data; boundary=---------------------2738ec4ef61112ed21629df7a08e15b49f29f9415c - Connection: - - close - Host: - - upload.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 21 Sep 2022 12:48:35 GMT - Content-Type: - - application/json - Content-Length: - - '1806' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, POST, HEAD, OPTIONS - Access-Control-Allow-Headers: - - X-UC-User-Agent, DNT, X-PINGOTHER - Access-Control-Max-Age: - - '1' - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Expose-Headers: - - Warning - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - edd8b795-4f02-4f5a-ac22-8a5035b3026e - body: - encoding: UTF-8 - string: '{"id":"9bc40957-1606-4f5a-b365-1abae129c218~2","datetime_created":"2022-09-21T12:48:35.754528Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/9bc40957-1606-4f5a-b365-1abae129c218~2/","url":"https://api.uploadcare.com/groups/9bc40957-1606-4f5a-b365-1abae129c218~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"8ca6e9fa-c6dd-4027-a0fc-b620611f7023","file_id":"8ca6e9fa-c6dd-4027-a0fc-b620611f7023","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"application/octet-stream","metadata":{},"default_effects":""},{"size":2375,"total":2375,"done":2375,"uuid":"b8a11440-6fcc-4285-a24d-cc8c60259fec","file_id":"b8a11440-6fcc-4285-a24d-cc8c60259fec","original_filename":"another_kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":100,"format":"JPEG","height":100,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":100,"format":"JPEG","height":100,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"another_kitten.jpeg","mime_type":"application/octet-stream","metadata":{},"default_effects":""}]}' - recorded_at: Wed, 21 Sep 2022 12:48:35 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/upload_error.yml b/spec/fixtures/vcr_cassettes/upload_error.yml deleted file mode 100644 index a31713d5..00000000 --- a/spec/fixtures/vcr_cassettes/upload_error.yml +++ /dev/null @@ -1,288 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/base/ - body: - encoding: ASCII-8BIT - string: !binary |- - LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1mNGE1YTdkMWE4NzUzMTEyMTNkMzQ0Y2Q2NGZjMjM0ZTgxNjhhOGJjNWUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVVBMT0FEQ0FSRV9QVUJfS0VZIg0KDQpmb28NCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tZjRhNWE3ZDFhODc1MzExMjEzZDM0NGNkNjRmYzIzNGU4MTY4YThiYzVlDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImtpdHRlbi5qcGVnIjsgZmlsZW5hbWU9ImtpdHRlbi5qcGVnIg0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0NCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLWY0YTVhN2QxYTg3NTMxMTIxM2QzNDRjZDY0ZmMyMzRlODE2OGE4YmM1ZS0tDQo= - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------f4a5a7d1a875311213d344cd64fc234e8168a8bc5e - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 17 Feb 2020 12:46:22 GMT - Content-Type: - - application/json - Content-Length: - - '76' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "UPLOADCARE_PUB_KEY is invalid.", "status_code": - 403}}' - http_version: - recorded_at: Mon, 17 Feb 2020 12:46:22 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/%7B:content=%3E%22UPLOADCARE_PUB_KEY%20is%20invalid.%22,%20:status_code=%3E403%7D/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 404 - message: Not Found - headers: - Date: - - Mon, 17 Feb 2020 12:46:26 GMT - Content-Type: - - text/html; charset=utf-8 - Content-Length: - - '19376' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Cookie - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - body: - encoding: UTF-8 - string: "\n\n\n\n\n\n\n \n \n \n Uploadcare\n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n \n \n \n\n \n \n \n\n \n \n\n\n \n\n \n\n \n \n\n\n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n - \ \n\n\n\n\n\n
\n \n \n\n\n
\n
\n - \
\n \n \n\n - \ \n \n \n \n \n
\n\n - \
\n \n\n \n \n - \ \n \n
\n
\n
\n\n \n\n \n\n \n\n
\n\n \"error\"\n\n - \

\n Page not found\n

\n\n

\n \n Error 404\n\n - \

\n\n \n Go - to main page\n \n\n
\n\n\n
\n\n\n\n\n
\n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n - \ \n - \

Cookies on Uploadcare

\n
\n - \
\n

\n - \ We have placed cookies on your device to improve your experience. \n - \ \n Learn - more about how this site works and our compliances\n \n \n \n

\n
\n
\n\n\n\n\n\n - \ \n\n\n\n" - http_version: - recorded_at: Mon, 17 Feb 2020 12:46:26 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_file_info.yml b/spec/fixtures/vcr_cassettes/upload_file_info.yml deleted file mode 100644 index 1b9a2578..00000000 --- a/spec/fixtures/vcr_cassettes/upload_file_info.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://upload.uploadcare.com/info/?file_id=a7f9751a-432b-4b05-936c-2f62d51d255d&pub_key=demopublickey - body: - encoding: UTF-8 - string: '' - headers: - User-Agent: - - UploadcareRuby/4.3.3/5d5bb5639e3f2df33674 (Ruby/3.2.1) - Connection: - - close - Host: - - upload.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 07 Apr 2023 12:32:21 GMT - Content-Type: - - application/json - Content-Length: - - '707' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, HEAD, OPTIONS - Access-Control-Allow-Headers: - - X-UC-User-Agent, X-PINGOTHER, DNT - Access-Control-Max-Age: - - '1' - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Expose-Headers: - - Warning, Retry-After - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 439981d3-901d-42e8-888e-d79f731236b1 - body: - encoding: UTF-8 - string: '{"size":1290,"total":1290,"done":1290,"uuid":"a7f9751a-432b-4b05-936c-2f62d51d255d","file_id":"a7f9751a-432b-4b05-936c-2f62d51d255d","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' - recorded_at: Fri, 07 Apr 2023 12:32:22 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/upload_from_url_basic.yml b/spec/fixtures/vcr_cassettes/upload_from_url_basic.yml new file mode 100644 index 00000000..b40541e5 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/upload_from_url_basic.yml @@ -0,0 +1,58 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/from_url/ + body: + encoding: UTF-8 + string: pub_key=&source_url=https%3A%2F%2Fplacekitten.com%2F200%2F200&store + headers: + User-Agent: + - Faraday v2.13.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 403 + message: Forbidden + headers: + Date: + - Fri, 06 Jun 2025 06:15:49 GMT + Content-Type: + - text/plain; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - OPTIONS, GET, POST + Access-Control-Allow-Headers: + - X-PINGOTHER, X-UC-User-Agent, DNT + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - b005d352-00c0-4fe6-b145-70df83f740ad + body: + encoding: ASCII-8BIT + string: pub_key is required. + recorded_at: Fri, 06 Jun 2025 06:15:49 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/upload_get_upload_from_url_status.yml b/spec/fixtures/vcr_cassettes/upload_get_upload_from_url_status.yml index 137a7360..c17c3f49 100644 --- a/spec/fixtures/vcr_cassettes/upload_get_upload_from_url_status.yml +++ b/spec/fixtures/vcr_cassettes/upload_get_upload_from_url_status.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://upload.uploadcare.com/from_url/status/?token=0313e4e2-f2ca-4564-833b-4f71bc8cba27 + uri: https://upload.uploadcare.com/from_url/status/?token= body: encoding: UTF-8 string: '' diff --git a/spec/fixtures/vcr_cassettes/upload_group_delete.yml b/spec/fixtures/vcr_cassettes/upload_group_delete.yml deleted file mode 100644 index 7856b977..00000000 --- a/spec/fixtures/vcr_cassettes/upload_group_delete.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: delete - uri: https://api.uploadcare.com/groups/bbc75785-9016-4656-9c6e-64a76b45b0b8~2/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Date: - - Wed, 21 Sep 2022 12:49:54 GMT - Authorization: - - Uploadcare 99d8e355c5811c2c26ae:15e6be2e6161f5cd832652c41444813cd02a12e5 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 204 - message: No Content - headers: - Date: - - Wed, 21 Sep 2022 12:49:55 GMT - Content-Length: - - '0' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - ed95dd9b-c623-4623-bf7e-cfdb454a338f - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '' - recorded_at: Wed, 21 Sep 2022 12:49:55 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/upload_group_info.yml b/spec/fixtures/vcr_cassettes/upload_group_info.yml deleted file mode 100644 index 2f787160..00000000 --- a/spec/fixtures/vcr_cassettes/upload_group_info.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://upload.uploadcare.com/group/info/?group_id=bbc75785-9016-4656-9c6e-64a76b45b0b8~2&pub_key=demopublickey - body: - encoding: UTF-8 - string: '' - headers: - User-Agent: - - UploadcareRuby/3.3.2/99d8e355c5811c2c26ae (Ruby/2.6.0) - Connection: - - close - Host: - - upload.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 21 Sep 2022 12:48:52 GMT - Content-Type: - - application/json - Content-Length: - - '1806' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, HEAD, OPTIONS - Access-Control-Allow-Headers: - - X-UC-User-Agent, DNT, X-PINGOTHER - Access-Control-Max-Age: - - '1' - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Expose-Headers: - - Warning - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 4bb71b05-ceb6-40a1-b984-8787fdaf03f8 - body: - encoding: UTF-8 - string: '{"id":"bbc75785-9016-4656-9c6e-64a76b45b0b8~2","datetime_created":"2022-09-21T12:48:35.157603Z","datetime_stored":null,"files_count":2,"cdn_url":"https://ucarecdn.com/bbc75785-9016-4656-9c6e-64a76b45b0b8~2/","url":"https://api.uploadcare.com/groups/bbc75785-9016-4656-9c6e-64a76b45b0b8~2/","files":[{"size":1290,"total":1290,"done":1290,"uuid":"8ca6e9fa-c6dd-4027-a0fc-b620611f7023","file_id":"8ca6e9fa-c6dd-4027-a0fc-b620611f7023","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"application/octet-stream","metadata":{},"default_effects":""},{"size":2375,"total":2375,"done":2375,"uuid":"b8a11440-6fcc-4285-a24d-cc8c60259fec","file_id":"b8a11440-6fcc-4285-a24d-cc8c60259fec","original_filename":"another_kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":100,"format":"JPEG","height":100,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":100,"format":"JPEG","height":100,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"another_kitten.jpeg","mime_type":"application/octet-stream","metadata":{},"default_effects":""}]}' - recorded_at: Wed, 21 Sep 2022 12:48:52 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete.yml deleted file mode 100644 index 1323c57f..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/complete/ - body: - encoding: UTF-8 - string: "-----------------------9dda007cd5345719a4a6e281318749ca789ea05adb\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------9dda007cd5345719a4a6e281318749ca789ea05adb\r\nContent-Disposition: - form-data; name=\"uuid\"\r\n\r\nd8c914e3-3aef-4976-b0b6-855a9638da2d\r\n-----------------------9dda007cd5345719a4a6e281318749ca789ea05adb--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------9dda007cd5345719a4a6e281318749ca789ea05adb - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 23 Jan 2020 13:38:17 GMT - Content-Type: - - application/json - Content-Length: - - '71' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File is already uploaded.", "status_code": 400}}' - http_version: - recorded_at: Thu, 23 Jan 2020 13:38:17 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete_unfinished.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete_unfinished.yml deleted file mode 100644 index e3af0ac7..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete_unfinished.yml +++ /dev/null @@ -1,60 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/complete/ - body: - encoding: UTF-8 - string: "-----------------------af9091278a3714aaa62fd46339365325bed5aa7b78\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------af9091278a3714aaa62fd46339365325bed5aa7b78\r\nContent-Disposition: - form-data; name=\"uuid\"\r\n\r\n7d9f495a-2834-4a2a-a2b3-07dbaf80ac79\r\n-----------------------af9091278a3714aaa62fd46339365325bed5aa7b78--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------af9091278a3714aaa62fd46339365325bed5aa7b78 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 23 Jan 2020 12:17:22 GMT - Content-Type: - - application/json - Content-Length: - - '89' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File size mismatch. Not all parts uploaded?", - "status_code": 400}}' - http_version: - recorded_at: Thu, 23 Jan 2020 12:17:22 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete_wrong_id.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete_wrong_id.yml deleted file mode 100644 index 3c9e117f..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_complete_wrong_id.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/complete/ - body: - encoding: UTF-8 - string: "-----------------------6f683a202a218fb4fb14bfe430a02eabd22ed765f5\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------6f683a202a218fb4fb14bfe430a02eabd22ed765f5\r\nContent-Disposition: - form-data; name=\"uuid\"\r\n\r\nnonexistent\r\n-----------------------6f683a202a218fb4fb14bfe430a02eabd22ed765f5--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------6f683a202a218fb4fb14bfe430a02eabd22ed765f5 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 23 Jan 2020 13:32:20 GMT - Content-Type: - - application/json - Content-Length: - - '64' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File is not found.", "status_code": 404}}' - http_version: - recorded_at: Thu, 23 Jan 2020 13:32:20 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_small.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_small.yml deleted file mode 100644 index 5396dd4a..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_small.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/start/ - body: - encoding: UTF-8 - string: "-----------------------530f9f66d729ba85d2263b46051af812e2c82ad02c\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------530f9f66d729ba85d2263b46051af812e2c82ad02c\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_STORE\"\r\n\r\n0\r\n-----------------------530f9f66d729ba85d2263b46051af812e2c82ad02c\r\nContent-Disposition: - form-data; name=\"filename\"\r\n\r\nkitten.jpeg\r\n-----------------------530f9f66d729ba85d2263b46051af812e2c82ad02c\r\nContent-Disposition: - form-data; name=\"size\"\r\n\r\n1290\r\n-----------------------530f9f66d729ba85d2263b46051af812e2c82ad02c\r\nContent-Disposition: - form-data; name=\"content_type\"\r\n\r\napplication/octet-stream\r\n-----------------------530f9f66d729ba85d2263b46051af812e2c82ad02c--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------530f9f66d729ba85d2263b46051af812e2c82ad02c - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 23 Jan 2020 13:42:06 GMT - Content-Type: - - application/json - Content-Length: - - '139' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File size can not be less than 10485760 bytes. - Please use direct upload instead of multipart.", "status_code": 400}}' - http_version: - recorded_at: Thu, 23 Jan 2020 13:42:06 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_start.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_start.yml deleted file mode 100644 index 752e5561..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_start.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/start/ - body: - encoding: UTF-8 - string: "-----------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_STORE\"\r\n\r\n0\r\n-----------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433\r\nContent-Disposition: - form-data; name=\"filename\"\r\n\r\nkitten.jpeg\r\n-----------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433\r\nContent-Disposition: - form-data; name=\"size\"\r\n\r\n1290\r\n-----------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433\r\nContent-Disposition: - form-data; name=\"content_type\"\r\n\r\napplication/octet-stream\r\n-----------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------946513b3bc837de6dd4fcb4f3c99db35ad4109e433 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 22 Jan 2020 15:43:47 GMT - Content-Type: - - application/json - Content-Length: - - '139' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File size can not be less than 10485760 bytes. - Please use direct upload instead of multipart.", "status_code": 400}}' - http_version: - recorded_at: Wed, 22 Jan 2020 15:43:47 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_start_large.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_start_large.yml deleted file mode 100644 index 1faccd67..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_start_large.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/start/ - body: - encoding: UTF-8 - string: "-----------------------7ee167956dccd9e447906b8247f13af10c23c4a81a\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------7ee167956dccd9e447906b8247f13af10c23c4a81a\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_STORE\"\r\n\r\n0\r\n-----------------------7ee167956dccd9e447906b8247f13af10c23c4a81a\r\nContent-Disposition: - form-data; name=\"filename\"\r\n\r\nkitten.jpeg\r\n-----------------------7ee167956dccd9e447906b8247f13af10c23c4a81a\r\nContent-Disposition: - form-data; name=\"size\"\r\n\r\n104857600\r\n-----------------------7ee167956dccd9e447906b8247f13af10c23c4a81a\r\nContent-Disposition: - form-data; name=\"content_type\"\r\n\r\napplication/octet-stream\r\n-----------------------7ee167956dccd9e447906b8247f13af10c23c4a81a--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------7ee167956dccd9e447906b8247f13af10c23c4a81a - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 22 Jan 2020 15:57:57 GMT - Content-Type: - - application/json - Content-Length: - - '10408' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"parts":["https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=1&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=79dd45f45a5ca039c7f2626132d522c967ca2650e613cd0e30a8feb766cbd236","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=2&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=ae767885ab99f2d5eb2a6cb7c1697353b7bcf54bb483bb0533481ae207854453","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=3&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=80f3d9a4fc6a626de38021ee29c57185dd0dd47c6f0407b5169708b1ef2cbc57","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=4&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=b3333eaee27fc612abd849994b536ca9c147b92af3d15413a869f6140cb57e51","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=5&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=e5f30798ed7cacd887f90d88614e45f1ae28652ab225a63feb69855832e356e3","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=6&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=db54a0deee458601ce3a40d7254d899da7471d5c7676781fda5bf068bc6fb431","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=7&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=bc890ed695b384babe2681c6068fc3e2c008ad2d61397e08741b86275836de06","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=8&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=13dad06f9e05d0ac2f255168a29ef9378bd681e86a8ad7dab37884a4b7eebd78","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=9&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=24fe73c0ae57bcff16455ea44d7262590321242a9d93ea5fe8d4839c00f957f7","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=10&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=cec5ecff0c738fb159937f5ad4162093e49389bb52b5bf27797d65ee3e0b27cd","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=11&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=9f9d80de693d99647a54d6f0a5393f016c4f1b4cdf37320ac79d3490197f4111","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=12&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=ce0d71fc1c2895660e59e303ac00afa031bf0d3fa5f9a768240fe0511647a3cd","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=13&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=3d3ea24bc3d6871d9c633107e701645423c250157809c7c5d4ad1199d781963a","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=14&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=72c6c455ba78c603cf44ad9287b469abc359b06a39cbd23d668376e92c989d6d","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=15&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=dc26e27402827dd3cf0bb15c2d854ebf5c90906937eb422b506e40b698975383","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=16&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=97c5c72372d7282d36a0024e651e64130f7ba83e618aa4bf41f75c890440cc39","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=17&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=602430aa34be4a3770cd8ad9bcd2bdf38c5564d03af1f9ba2417b33eebefed0f","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=18&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=571e202c2f7851891e18d924cd66bad44ce45d063be8fec872d943e1691d1df7","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=19&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=d5ee5436d3a0279a90724705de36b55b804b41effcda8c8f515273fdf0f9cb8e","https://uploadcare.s3-accelerate.amazonaws.com/7d9f495a-2834-4a2a-a2b3-07dbaf80ac79/kitten.jpeg?partNumber=20&uploadId=eJJuzJgRI9KvLkGtaP_cqsJNL46F7_D3IoqR.tcIB96eqI6_NYlHfatQzYoZtrIF.vHJbdW_l9dvKZsqD3a7aDBV2Z3XEtPnFgnJ7LEpY4s8xIUUWHCq2Gqf3RQzBRBU&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Credential=AKIAI24Z75ZC7N6QJQHA%2F20200122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20200122T155757Z&X-Amz-Signature=c47a0e3f91e0fd407a624917aee7f36b3173bc82cdf05128f1e56a0e57f3bec8"],"uuid":"7d9f495a-2834-4a2a-a2b3-07dbaf80ac79"}' - http_version: - recorded_at: Wed, 22 Jan 2020 15:57:57 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_multipart_upload_start_small.yml b/spec/fixtures/vcr_cassettes/upload_multipart_upload_start_small.yml deleted file mode 100644 index f7691a70..00000000 --- a/spec/fixtures/vcr_cassettes/upload_multipart_upload_start_small.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/start/ - body: - encoding: UTF-8 - string: "-----------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_PUB_KEY\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25\r\nContent-Disposition: - form-data; name=\"UPLOADCARE_STORE\"\r\n\r\n0\r\n-----------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25\r\nContent-Disposition: - form-data; name=\"filename\"\r\n\r\nkitten.jpeg\r\n-----------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25\r\nContent-Disposition: - form-data; name=\"size\"\r\n\r\n1290\r\n-----------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25\r\nContent-Disposition: - form-data; name=\"content_type\"\r\n\r\napplication/octet-stream\r\n-----------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------03a9b50fa296a8158c1df65f4d3f4c2877c4bbce25 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 22 Jan 2020 15:46:53 GMT - Content-Type: - - application/json - Content-Length: - - '139' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File size can not be less than 10485760 bytes. - Please use direct upload instead of multipart.", "status_code": 400}}' - http_version: - recorded_at: Wed, 22 Jan 2020 15:46:53 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_throttling.yml b/spec/fixtures/vcr_cassettes/upload_throttling.yml deleted file mode 100644 index 16b08d6b..00000000 --- a/spec/fixtures/vcr_cassettes/upload_throttling.yml +++ /dev/null @@ -1,164 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/base/ - body: - encoding: ASCII-8BIT - string: !binary |- - LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1jYWJmODc5NGEyNzhlZjMxNTcwMTI5MTdlYWI1MDM0NzA4ZjZjNDliOTgNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVVBMT0FEQ0FSRV9QVUJfS0VZIg0KDQplY2Q3NzlkYzE2OTY0NWNlM2M5MQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1jYWJmODc5NGEyNzhlZjMxNTcwMTI5MTdlYWI1MDM0NzA4ZjZjNDliOTgNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbQ0KDQr/2P/gABBKRklGAAEBAQBgAGAAAP/+ADtDUkVBVE9SOiBnZC1qcGVnIHYxLjAgKHVzaW5nIElKRyBKUEVHIHY2MiksIHF1YWxpdHkgPSA2NQr/2wBDAAsICAoIBwsKCQoNDAsNERwSEQ8PESIZGhQcKSQrKigkJyctMkA3LTA9MCcnOEw5PUNFSElIKzZPVU5GVEBHSEX/2wBDAQwNDREPESESEiFFLicuRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUX/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDQ0bTNP0czESm5MpBJnwduM9OOOtaw1C1jHyiMfQAV5cbq5UnddlgR0JpPOmfl7hwB/dY1r7VLZEcjPVn1RIlBZtoYZHvULa9AuMzL+dc5PcwSWFtBMjSl4UO0ZBIx1z2PvSW2g6HOFfN2fZpR+XSpVbujSVJrZnZW12l5aLMjBlJIyKTdl1x6GqWniG0g+yWsIjt4ySuGJJJPNWl/1yH/AGT/AEpSlzaolJx0ZYHQUU4LwKKgZ4QAxY4fGR1JqQCXGOG9hXUxeHWdwioWPbjrV8+EDEA1xJEnquct+QFU4W3BO5KVV9J068bI3W6xEn/ZyP6VU0xpbu7IktPs8C8IwbOR65remubObTUsYl2pAAqDvx/Wsi5JuUiiiYptIyBkZwfas2knqaJ30NnTLiZnkS5g8l1Yhec7l7GtZW+dT7H+lUBuMdtIwwSSpGParW/a6H2P9KtL3TNu7NIfdH0oqNZBtH0opAY91JHptp5ka4kk4B9BXN3OoSvvdHLYxitXxLdeXKI1I+UbQOwFc3HIApQEFj2Y9qU5XdzSEbKxIk+9w6khmOGH9avWxaOZQ3rWYVMLEoGI7j0p0WomOdEkG49j7VlfuW49jtXkHkQDsGz+lMnuEjRXdgqjuTioJpANPtpQchn4/KqGrvutLcesy/zrdfCYPc1f7ZtRwZun+yf8KKsiU4FFVyknKeJSftknPrWHBy7Z54NFFc8jpiS27sJYwGOCvrUurgCGJgMHPWiip6D6m9ESfD1jyf8AWf0NNv8A/j3tv+uy/wA6KK6I/AYS+I2cn1ooorQg/9kNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tY2FiZjg3OTRhMjc4ZWYzMTU3MDEyOTE3ZWFiNTAzNDcwOGY2YzQ5Yjk4LS0NCg== - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------cabf8794a278ef3157012917eab5034708f6c49b98 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 429 - message: OK - headers: - Date: - - Tue, 18 Feb 2020 10:13:40 GMT - Content-Type: - - application/json - Content-Length: - - '54' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: 'Request throttled.' - http_version: - recorded_at: Tue, 18 Feb 2020 10:13:40 GMT -- request: - method: post - uri: https://upload.uploadcare.com/base/ - body: - encoding: ASCII-8BIT - string: !binary |- - LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1kMDJjNmYxYTU1MDk0MDAxYzMwMjFhZDYwOThjMDJiNTU0NGM1OWEwOTcNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVVBMT0FEQ0FSRV9QVUJfS0VZIg0KDQplY2Q3NzlkYzE2OTY0NWNlM2M5MQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1kMDJjNmYxYTU1MDk0MDAxYzMwMjFhZDYwOThjMDJiNTU0NGM1OWEwOTcNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbQ0KDQr/2P/gABBKRklGAAEBAQBgAGAAAP/+ADtDUkVBVE9SOiBnZC1qcGVnIHYxLjAgKHVzaW5nIElKRyBKUEVHIHY2MiksIHF1YWxpdHkgPSA2NQr/2wBDAAsICAoIBwsKCQoNDAsNERwSEQ8PESIZGhQcKSQrKigkJyctMkA3LTA9MCcnOEw5PUNFSElIKzZPVU5GVEBHSEX/2wBDAQwNDREPESESEiFFLicuRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUX/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDQ0bTNP0czESm5MpBJnwduM9OOOtaw1C1jHyiMfQAV5cbq5UnddlgR0JpPOmfl7hwB/dY1r7VLZEcjPVn1RIlBZtoYZHvULa9AuMzL+dc5PcwSWFtBMjSl4UO0ZBIx1z2PvSW2g6HOFfN2fZpR+XSpVbujSVJrZnZW12l5aLMjBlJIyKTdl1x6GqWniG0g+yWsIjt4ySuGJJJPNWl/1yH/AGT/AEpSlzaolJx0ZYHQUU4LwKKgZ4QAxY4fGR1JqQCXGOG9hXUxeHWdwioWPbjrV8+EDEA1xJEnquct+QFU4W3BO5KVV9J068bI3W6xEn/ZyP6VU0xpbu7IktPs8C8IwbOR65remubObTUsYl2pAAqDvx/Wsi5JuUiiiYptIyBkZwfas2knqaJ30NnTLiZnkS5g8l1Yhec7l7GtZW+dT7H+lUBuMdtIwwSSpGParW/a6H2P9KtL3TNu7NIfdH0oqNZBtH0opAY91JHptp5ka4kk4B9BXN3OoSvvdHLYxitXxLdeXKI1I+UbQOwFc3HIApQEFj2Y9qU5XdzSEbKxIk+9w6khmOGH9avWxaOZQ3rWYVMLEoGI7j0p0WomOdEkG49j7VlfuW49jtXkHkQDsGz+lMnuEjRXdgqjuTioJpANPtpQchn4/KqGrvutLcesy/zrdfCYPc1f7ZtRwZun+yf8KKsiU4FFVyknKeJSftknPrWHBy7Z54NFFc8jpiS27sJYwGOCvrUurgCGJgMHPWiip6D6m9ESfD1jyf8AWf0NNv8A/j3tv+uy/wA6KK6I/AYS+I2cn1ooorQg/9kNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tZDAyYzZmMWE1NTA5NDAwMWMzMDIxYWQ2MDk4YzAyYjU1NDRjNTlhMDk3LS0NCg== - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------d02c6f1a55094001c3021ad6098c02b5544c59a097 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 18 Feb 2020 10:13:44 GMT - Content-Type: - - application/json - Content-Length: - - '54' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"kitten.jpeg":"f82c554c-5e74-4f1d-8fe2-380816b880c6"}' - http_version: - recorded_at: Tue, 18 Feb 2020 10:13:44 GMT -- request: - method: get - uri: https://api.uploadcare.com/files/f82c554c-5e74-4f1d-8fe2-380816b880c6/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - Authorization: - - "" - Connection: - - close - Host: - - api.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 18 Feb 2020 10:13:44 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '614' - Connection: - - close - Server: - - nginx - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - Access-Control-Allow-Origin: - - https://uploadcare.com - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":null,"datetime_stored":null,"datetime_uploaded":"2020-02-18T10:13:44.010674Z","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":50,"width":50,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_image":true,"is_ready":true,"mime_type":"application/octet-stream","original_file_url":"https://ucarecdn.com/f82c554c-5e74-4f1d-8fe2-380816b880c6/kitten.jpeg","original_filename":"kitten.jpeg","size":1290,"url":"https://api.uploadcare.com/files/f82c554c-5e74-4f1d-8fe2-380816b880c6/","uuid":"f82c554c-5e74-4f1d-8fe2-380816b880c6","source":null}' - http_version: - recorded_at: Tue, 18 Feb 2020 10:13:44 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_upload_files.yml b/spec/fixtures/vcr_cassettes/upload_upload_files.yml new file mode 100644 index 00000000..0926acf2 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/upload_upload_files.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://upload.uploadcare.com/base/ + body: + encoding: ASCII-8BIT + string: !binary |- + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LWVjNTFiM2ZhYWNhNjNkODc3NjFiMjQ5N2RmMTE2NjgyDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZWM1MWIzZmFhY2E2M2Q4Nzc2MWIyNDk3ZGYxMTY2ODINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LUxlbmd0aDogMTI5MA0KQ29udGVudC1UeXBlOiBpbWFnZS9qcGVnDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiaW5hcnkNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZWM1MWIzZmFhY2E2M2Q4Nzc2MWIyNDk3ZGYxMTY2ODINCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iYW5vdGhlcl9raXR0ZW4uanBlZyI7IGZpbGVuYW1lPSJhbm90aGVyX2tpdHRlbi5qcGVnIg0KQ29udGVudC1MZW5ndGg6IDIzNzUNCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5DQoNCv/Y/+AAEEpGSUYAAQEBAGAAYAAA//4AO0NSRUFUT1I6IGdkLWpwZWcgdjEuMCAodXNpbmcgSUpHIEpQRUcgdjYyKSwgcXVhbGl0eSA9IDY1Cv/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/AABEIAGQAZAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AKax+lTJEfSnx2l1MDLFHtQcZY9ad5ktsf8ASYSF7svIrkszp50PSI+lTqMU6OaF0DKwI+tNe5hi4LDNFguPANO6CqUurQiP5etRRazFgiTjjrTsxcyNJWJNSZGKyE1eEjripTqkWCAcntjvRZhzIuPjGTVWWVFYDIyantree9OWzFEB+J/wqdzYWQ+fyvfPJ/xNPk7idS2xnjDdKCpqwTBI7G3jCqaClZvRmsXdFXy6Ks7RRUlXK0GqXEdh/o7AhXIORniov7Xkb5biIOP7yjBFUNDLSLJESPnXjnHIrUFmNhDF1PqRmulao5HozNuZYxyuQrdxwazj5pmPmMWTs1ac8Kx/LuLD3qGRgbdlwN2Oo700hNmYZ8odvUdagjuC7kdOaiikBEqbgD2zUMUgEoX1YH9Kqwrmgk2+R17npWjazNbofLA3kcv3rBhmVrwlc4zWktz0Wpeg1qbAv7q5QRx7to7kZyfYdzVZ7aQXqJMwMrHlS2WH1qza3o8ry0YpkYLL1/PtUkH9n2jmWMtLMvXLZx+NQyka8NuIowAKdtp1tcGeMMV4PTipGWsWbpkG2ipdtFIZx1jcShQW+X3XitqXVX3KqkSKR0LYNc3pAlli+YnHbNaaW4WUKx59a6kjlluSTSGXccECs03K+YYl5kPA9K07mSG2T55VXHqetYF3JNdCWe3jjSCIgM8hxyelUkQ2V47R5zdEqRJCcnHpVWXJkOOtdToMS31tfsBsv4lDuobIlT1FUJrALctMAPLIyKG7DRkpA6XyRBto2biTU8EjSyEdQD1q3qyxRWtgFIWe6BZ3A5CdgKyIhtQSxNIvz7drUlqitmbYB8vIJAHaobaVvOKpn8ahh1CdG2OiHtxWpaIkgMgUA+lZtWLubMOrC3jVHIzjnmpV1yI/xCuL1ueUSkBdmB61iC9nU/fNCp31Hz2PVBrERHUUV5eNTuAPvUUeyD2hPp+ozwSDDEj0zXWR3JuovNVCGxXNQafLn7hxXUaNA0Iw44I7mtL6mcloY97aOjebMpdDzTJFF7uWARPFJgsksgjKH6ntXT6gA8Ria13Kf4l61zyaVKbkbY2EZP8AEaq9iNya1V9DvYr4XdvcSzDY0Nu+QicdTWhOgksw4KiPqFJ5NS22imS6+zOYVU9wcY/OsnU3m0p2srm3UyDhWByH+lJ6jRQnktr68T7Z5scUMe1TFgtkfWqkjwxSb42nlZT8nmqAB74BrcXQZlsklkOJJOSiKSVFTPocTQrktkDuoB/So5ki7MytNieQmSUjB7mt5IY4YzK0nbtUNvp0aNzuOOxNTz+SkLpySeoqG7lJHJ6nLvlZtpwf4mrJK8+tbc+ntI5KKcGoRo8p7GtU7ITRlbaK2RosmOhop8wcp2dhaRzK0rtshTqcdTT7geVt+zxO4J+8elOvi8NtBp8A+YjLE9BVeVTJCsUk/wBnkHAOeDTsZXNjTomvz5YQbgOc9quSeH7pHUxKsmfQ4ANZWg+bBfIpkWVSQNwP869HtVJU7sFVIPFZ8zbsVyrc497e9hXBQqy8cZNcxrFld3l0jyK26NwQTzzXrz2yMx3D7w+Wqd1oNtcYZlGMdV4od2OLseaxRSbB55AK9jzUdxcxxthuhrrNW8ISSKXs5iG6YccGudu/DGpqgZ7ZX255DdaixVyl5qSx5XH0PepLC2W6lwEyfTrUEcE0LfvIWjHoVrU0+UpdpthUDPJFNJCbLi+GQ3OP0qQeGVHb9K6+0CvCpIFWPJTHSunkRnc4r/hHF9P0ors/KT0oo5EFzyW9+0wSC5Vg3zHKk9qkN3aXVuNyKz9walu2ERWORN4fin2XhoNdYaIoF6+3pWcnYSVyTw9ZIbqRogw284Jr0TTD5kKfMcSdfwrE0/SxbRLgbWdsE+orpbaHyQxwAo4FZRTvdmjeliyMZLn+D5aUOI1UAEhqjJO1Vz2y1Rht8jOoOB0+taEkobMrH+BOaRxGvXB3dBTWISIIP4utUw7PdZb7qcVIySTT4H2mSNWJHcVRk0K0+9HGFxycCtcZ2c+vFI5xwOjUWC5Uij8hQqr8oFSCZTVl4xhQPxqBoVLcdqtTkhWQm4etFNKZop+08hcpxFhaRXt3aidchXBGPaupEalnOOuM0UUSJiTuoF2gA42k4qeJz5SjsW5ooqCyefiSTHZRTYycR+5oopvcBkxIM3t0qlE7GEc9WoopAagOcfSolJL89qKKGA5WJV6jZisRINFFACQcxAmiiimgP//ZDQotLS0tLS0tLS0tLS0tUnVieU11bHRpcGFydFBvc3QtZWM1MWIzZmFhY2E2M2Q4Nzc2MWIyNDk3ZGYxMTY2ODItLQ0K + headers: + User-Agent: + - Faraday v2.13.1 + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-ec51b3faaca63d87761b2497df116682 + Content-Length: + - '4332' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 403 + message: Forbidden + headers: + Date: + - Fri, 06 Jun 2025 06:15:49 GMT + Content-Type: + - text/plain; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Vary: + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, OPTIONS + Access-Control-Allow-Headers: + - X-PINGOTHER, X-UC-User-Agent, DNT + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Uploadcare-Request-Id: + - b4c8b7ef-9ba0-4756-80a0-8070e1d0ab16 + body: + encoding: ASCII-8BIT + string: UPLOADCARE_PUB_KEY is required. + recorded_at: Fri, 06 Jun 2025 06:15:49 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/upload_upload_from_url.yml b/spec/fixtures/vcr_cassettes/upload_upload_from_url.yml index d21b8bdf..4be9886e 100644 --- a/spec/fixtures/vcr_cassettes/upload_upload_from_url.yml +++ b/spec/fixtures/vcr_cassettes/upload_upload_from_url.yml @@ -55,11 +55,11 @@ http_interactions: - 4fceec68-f74a-4f2a-ba03-0e392a2327e7 body: encoding: UTF-8 - string: '{"type":"token","token":"0313e4e2-f2ca-4564-833b-4f71bc8cba27"}' + string: '{"type":"token","token":""}' recorded_at: Tue, 20 Sep 2022 13:24:01 GMT - request: method: get - uri: https://upload.uploadcare.com/from_url/status/?token=0313e4e2-f2ca-4564-833b-4f71bc8cba27 + uri: https://upload.uploadcare.com/from_url/status/?token= body: encoding: UTF-8 string: '' @@ -112,7 +112,7 @@ http_interactions: recorded_at: Tue, 20 Sep 2022 13:24:02 GMT - request: method: get - uri: https://upload.uploadcare.com/from_url/status/?token=0313e4e2-f2ca-4564-833b-4f71bc8cba27 + uri: https://upload.uploadcare.com/from_url/status/?token= body: encoding: UTF-8 string: '' diff --git a/spec/fixtures/vcr_cassettes/upload_upload_from_url_async.yml b/spec/fixtures/vcr_cassettes/upload_upload_from_url_async.yml deleted file mode 100644 index 9fe8e40f..00000000 --- a/spec/fixtures/vcr_cassettes/upload_upload_from_url_async.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/from_url/ - body: - encoding: UTF-8 - string: "-----------------------a992b9319d6cfa2c392d38c5e759bbcf37adcd84fe\r\nContent-Disposition: - form-data; name=\"pub_key\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------a992b9319d6cfa2c392d38c5e759bbcf37adcd84fe\r\nContent-Disposition: - form-data; name=\"source_url\"\r\n\r\nhttps://placekitten.com/225/225\r\n-----------------------a992b9319d6cfa2c392d38c5e759bbcf37adcd84fe\r\nContent-Disposition: - form-data; name=\"store\"\r\n\r\nfalse\r\n-----------------------a992b9319d6cfa2c392d38c5e759bbcf37adcd84fe\r\nContent-Disposition: - form-data; name=\"async\"\r\n\r\ntrue\r\n-----------------------a992b9319d6cfa2c392d38c5e759bbcf37adcd84fe--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------a992b9319d6cfa2c392d38c5e759bbcf37adcd84fe - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 27 Jan 2020 13:50:56 GMT - Content-Type: - - application/json - Content-Length: - - '63' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - OPTIONS, GET, POST - body: - encoding: ASCII-8BIT - string: '{"token":"ef2fd064-09d6-499a-9452-b982bf219df0","type":"token"}' - http_version: - recorded_at: Mon, 27 Jan 2020 13:50:56 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_upload_from_url_progress.yml b/spec/fixtures/vcr_cassettes/upload_upload_from_url_progress.yml deleted file mode 100644 index 1201bcc8..00000000 --- a/spec/fixtures/vcr_cassettes/upload_upload_from_url_progress.yml +++ /dev/null @@ -1,221 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/from_url/ - body: - encoding: UTF-8 - string: "-----------------------5e4852ca2659a7128e906d4f89adc0123496012c9d\r\nContent-Disposition: - form-data; name=\"pub_key\"\r\n\r\necd779dc169645ce3c91\r\n-----------------------5e4852ca2659a7128e906d4f89adc0123496012c9d\r\nContent-Disposition: - form-data; name=\"source_url\"\r\n\r\nhttps://placekitten.com/2250/2250\r\n-----------------------5e4852ca2659a7128e906d4f89adc0123496012c9d--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------5e4852ca2659a7128e906d4f89adc0123496012c9d - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 14 Feb 2020 10:17:10 GMT - Content-Type: - - application/json - Content-Length: - - '63' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - OPTIONS, GET, POST - body: - encoding: ASCII-8BIT - string: '{"token":"6191a97a-f501-4e37-8c8c-d8ec96e251ec","type":"token"}' - http_version: - recorded_at: Fri, 14 Feb 2020 10:17:10 GMT -- request: - method: get - uri: https://upload.uploadcare.com/from_url/status/?token=6191a97a-f501-4e37-8c8c-d8ec96e251ec - body: - encoding: UTF-8 - string: '' - headers: - Accept: - - application/json - Content-Type: - - application/json - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 14 Feb 2020 10:17:19 GMT - Content-Type: - - application/json - Content-Length: - - '468' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, POST, HEAD, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"status":"progress","done":"500","total":"400490"}' - http_version: - recorded_at: Fri, 14 Feb 2020 10:17:20 GMT -- request: - method: get - uri: https://upload.uploadcare.com/from_url/status/?token=6191a97a-f501-4e37-8c8c-d8ec96e251ec - body: - encoding: UTF-8 - string: '' - headers: - Accept: - - application/json - Content-Type: - - application/json - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 14 Feb 2020 10:17:20 GMT - Content-Type: - - application/json - Content-Length: - - '468' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, POST, HEAD, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"status":"progress","done":"1500","total":"400490"}' - http_version: - recorded_at: Fri, 14 Feb 2020 10:17:20 GMT -- request: - method: get - uri: https://upload.uploadcare.com/from_url/status/?token=6191a97a-f501-4e37-8c8c-d8ec96e251ec - body: - encoding: UTF-8 - string: '' - headers: - Accept: - - application/json - Content-Type: - - application/json - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 14 Feb 2020 10:17:21 GMT - Content-Type: - - application/json - Content-Length: - - '468' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, POST, HEAD, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"status":"success","uuid":"cb5fb771-1875-456c-9270-8b7d9cf1ae02","is_image":true,"filename":"2250","video_info":null,"is_stored":false,"done":400490,"file_id":"cb5fb771-1875-456c-9270-8b7d9cf1ae02","original_filename":"2250","image_info":{"color_mode":"RGB","orientation":null,"format":"JPEG","sequence":false,"height":2250,"width":2250,"geo_location":null,"datetime_original":null,"dpi":[96,96]},"is_ready":true,"total":400490,"mime_type":"image/jpeg","size":400490}' - http_version: - recorded_at: Fri, 14 Feb 2020 10:17:21 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_upload_from_url_with_signature.yml b/spec/fixtures/vcr_cassettes/upload_upload_from_url_with_signature.yml index 0e05e641..df18cb7c 100644 --- a/spec/fixtures/vcr_cassettes/upload_upload_from_url_with_signature.yml +++ b/spec/fixtures/vcr_cassettes/upload_upload_from_url_with_signature.yml @@ -6,14 +6,14 @@ http_interactions: body: encoding: ASCII-8BIT string: "-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: - form-data; name=\"pub_key\"\r\n\r\ndemopublickey\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: + form-data; name=\"pub_key\"\r\n\r\n\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\nhttps://placekitten.com/2250/2250\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: form-data; name=\"store\"\r\n\r\nauto\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: - form-data; name=\"signature\"\r\n\r\nf6b1a41383cb2179c57cd1baf967ace8\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: - form-data; name=\"expire\"\r\n\r\n1701130306\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab--\r\n" + form-data; name=\"signature\"\r\n\r\n\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab\r\nContent-Disposition: + form-data; name=\"expire\"\r\n\r\n\r\n-----------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab--\r\n" headers: User-Agent: - - UploadcareRuby/4.3.6/demopublickey (Ruby/3.0.5) + - UploadcareRuby/4.3.6/ (Ruby/3.0.5) Content-Type: - multipart/form-data; boundary=---------------------c5f0754b5a5d1bfb797b6944cc4c653cdf2b5c20ab Connection: @@ -58,17 +58,17 @@ http_interactions: - ffc67f72-2b8f-46c9-8239-1da1de4c869a body: encoding: UTF-8 - string: '{"type":"token","token":"ff450eec-48ad-491c-bfb7-804698c78951"}' + string: '{"type":"token","token":""}' recorded_at: Mon, 27 Nov 2023 23:41:47 GMT - request: method: get - uri: https://upload.uploadcare.com/from_url/status/?token=ff450eec-48ad-491c-bfb7-804698c78951 + uri: https://upload.uploadcare.com/from_url/status/?token= body: encoding: ASCII-8BIT string: '' headers: User-Agent: - - UploadcareRuby/4.3.6/demopublickey (Ruby/3.0.5) + - UploadcareRuby/4.3.6/ (Ruby/3.0.5) Connection: - close Host: diff --git a/spec/fixtures/vcr_cassettes/upload_upload_invalid.yml b/spec/fixtures/vcr_cassettes/upload_upload_invalid.yml deleted file mode 100644 index 243feb91..00000000 --- a/spec/fixtures/vcr_cassettes/upload_upload_invalid.yml +++ /dev/null @@ -1,114 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/from_url/ - body: - encoding: UTF-8 - string: "-----------------------7203b4b5ab386b7d87848488a144b18c0579597f98\r\nContent-Disposition: - form-data; name=\"pub_key\"\r\n\r\nc8499ee6dc44194c00d2\r\n-----------------------7203b4b5ab386b7d87848488a144b18c0579597f98\r\nContent-Disposition: - form-data; name=\"source_url\"\r\n\r\nhttps://example.com/foo/bar\r\n-----------------------7203b4b5ab386b7d87848488a144b18c0579597f98\r\nContent-Disposition: - form-data; name=\"store\"\r\n\r\nfalse\r\n-----------------------7203b4b5ab386b7d87848488a144b18c0579597f98--\r\n" - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------7203b4b5ab386b7d87848488a144b18c0579597f98 - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 27 Jan 2020 15:12:28 GMT - Content-Type: - - application/json - Content-Length: - - '63' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - OPTIONS, GET, POST - body: - encoding: ASCII-8BIT - string: '{"token":"c501c81c-5feb-4e16-9376-701b2073b1ab","type":"token"}' - http_version: - recorded_at: Mon, 27 Jan 2020 15:12:28 GMT -- request: - method: get - uri: https://upload.uploadcare.com/from_url/status/?token=c501c81c-5feb-4e16-9376-701b2073b1ab - body: - encoding: UTF-8 - string: '' - headers: - Accept: - - application/json - Content-Type: - - application/json - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 27 Jan 2020 15:12:29 GMT - Content-Type: - - application/json - Content-Length: - - '43' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - GET, POST, HEAD, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"status":"error","error":"HTTPError: 404"}' - http_version: - recorded_at: Mon, 27 Jan 2020 15:12:29 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/upload_upload_many.yml b/spec/fixtures/vcr_cassettes/upload_upload_many.yml index 29b2a905..ed5d0152 100644 --- a/spec/fixtures/vcr_cassettes/upload_upload_many.yml +++ b/spec/fixtures/vcr_cassettes/upload_upload_many.yml @@ -9,7 +9,7 @@ http_interactions: LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1lMThlNGYzZjk3MDE4MmIwMjEwZmVhZTU2NzRhYzI0OTViZjU5NzEyODUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVVBMT0FEQ0FSRV9QVUJfS0VZIg0KDQo1ZDViYjU2MzllM2YyZGYzMzY3NA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1lMThlNGYzZjk3MDE4MmIwMjEwZmVhZTU2NzRhYzI0OTViZjU5NzEyODUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LVR5cGU6IGltYWdlL2pwZWcNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLWUxOGU0ZjNmOTcwMTgyYjAyMTBmZWFlNTY3NGFjMjQ5NWJmNTk3MTI4NQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJhbm90aGVyX2tpdHRlbi5qcGVnIjsgZmlsZW5hbWU9ImFub3RoZXJfa2l0dGVuLmpwZWciDQpDb250ZW50LVR5cGU6IGltYWdlL2pwZWcNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAZABkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8AprH6VMkR9KfHaXUwMsUe1Bxlj1p3mS2x/wBJhIXuy8iuSzOnnQ9Ij6VOoxTo5oXQMrAj6017mGLgsM0WC48A07oKpS6tCI/l61FFrMWCJOOOtOzFzI0lYk1JkYrITV4SOuKlOqRYIBye2O9FmHMi4+MZNVZZUVgMjJqe2t5705bMUQH4n/Cp3NhZD5/K988n/E0+TuJ1LbGeMN0oKmrBMEjsbeMKpoKVm9Gaxd0VfLoqztFFSVcrQapcR2H+jsCFcg5GeKi/teRvluIg4/vKMEVQ0MtIskRI+deOccitQWY2EMXU+pGa6VqjkejM25ljHK5Ct3HBrOPmmY+YxZOzVpzwrH8u4sPeoZGBt2XA3Y6jvTSE2Zhnyh29R1qCO4LuR05qKKQESpuAPbNQxSAShfVgf0qrCuaCTb5HXuelaNrM1uh8sDeRy/esGGZWvCVzjNaS3PRal6DWpsC/urlBHHu2juRnJ9h3NVntpBeokzAyseVLZYfWrNrejyvLRimRgsvX8+1SQf2faOZYy0sy9ctnH41DKRrw24ijAAp22nW1wZ4wxXg9OKkZaxZumQbaKl20UhnHWNxKFBb5fdeK2pdVfcqqRIpHQtg1zekCWWL5icds1ppbhZQrHn1rqSOWW5JNIZdxwQKzTcr5hiXmQ8D0rTuZIbZPnlVcep61gXck10JZ7eONIIiAzyHHJ6VSRDZXjtHnN0SpEkJycelVZcmQ4611OgxLfW1+wGy/iUO6hsiVPUVQmsAty0wA8sjIobsNGSkDpfJEG2jZuJNTwSNLIR1APWrerLFFa2AUhZ7oFncDkJ2ArIiG1BLE0i/Pt2tSWqK2ZtgHy8gkAdqhtpW84qmfxqGHUJ0bY6Ie3FaloiSAyBQD6Vm1Yu5sw6sLeNUcjOOealXXIj/EK4vW55RKQF2YHrWIL2dT980KnfUfPY9UGsREdRRXl41O4A+9RR7IPaE+n6jPBIMMSPTNdZHcm6i81UIbFc1Bp8ufuHFdRo0DQjDjgjua0vqZyWhj3to6N5syl0PNMkUXu5YBE8UmCySyCMofqe1dPqADxGJrXcp/iXrXPJpUpuRtjYRk/wARqr2I3JrVX0O9ivhd29xLMNjQ275CJx1NaE6CSzDgqI+oUnk1LbaKZLr7M5hVT3Bxj86ydTebSnayubdTIOFYHIf6UnqNFCeS2vrxPtnmxxQx7VMWC2R9aqSPDFJvjaeVlPyeaoAHvgGtxdBmWySWQ4kk5KIpJUVM+hxNCuS2QO6gH9KjmSLszK02J5CZJSMHua3khjhjMrSdu1Q2+nRo3O447E1PP5KQunJJ6iobuUkcnqcu+Vm2nB/iaskrz61tz6e0jkopwahGjynsa1TshNGVtorZGiyY6GinzBynZ2FpHMrSu2yFOpx1NPuB5W37PE7gn7x6U6+Lw20GnwD5iMsT0FV5VMkKxST/AGeQcA54NOxlc2NOia/PlhBuA5z2q5J4fukdTEqyZ9DgA1laD5sF8imRZVJA3A/zr0e1UlTuwVUg8VnzNuxXKtzj3t72FcFCrLxxk1zGsWV3eXSPIrbo3BBPPNevPbIzHcPvD5ap3Wg21xhmUYx1Xih3Y4ux5rFFJsHnkAr2PNR3FzHG2G6Gus1bwhJIpezmIbphxwa5278MamqBntlfbnkN1qLFXKXmpLHlcfQ96ksLZbqXATJ9OtQRwTQt+8haMehWtTT5Sl2m2FQM8kU0kJsuL4ZDc4/SpB4ZUdv0rr7QK8KkgVY8lMdK6eRGdziv+EcX0/Siuz8pPSijkQXPJb37TBILlWDfMcqT2qQ3dpdW43IrP3BqW7YRFY5E3h+KfZeGg11hoigXr7elZydhJXJPD1khupGiDDbzgmvRNMPmQp8xxJ1/CsTT9LFtEuBtZ2wT6iultofJDHACjgVlFO92aN6WLIxkuf4PlpQ4jVQASGqMk7VXPbLVGG3yM6g4HT61oSShsysf4E5pHEa9cHd0FNYhIgg/i61TDs91lvupxUjJJNPgfaZI1YkdxVGTQrT70cYXHJwK1xnZz68UjnHA6NRYLlSKPyFCqvygVIJlNWXjGFA/GoGhUtx2q1OSFZCbh60U0pmin7TyFynEWFpFe3dqJ1yFcEY9q6kRqWc464zRRRImJO6gXaADjaTip4nPlKOxbmiioLJ5+JJMdlFNjJxH7miim9wGTEgze3SqUTsYRz1aiikBqA5x9KiUkvz2oooYDlYlXqNmKxEg0UUAJBzECaKKKaA//9kNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tZTE4ZTRmM2Y5NzAxODJiMDIxMGZlYWU1Njc0YWMyNDk1YmY1OTcxMjg1LS0NCg== headers: User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) + - UploadcareRuby/3.3.2/ (Ruby/2.6.0) Content-Type: - multipart/form-data; boundary=---------------------e18e4f3f970182b0210feae5674ac2495bf5971285 Connection: diff --git a/spec/fixtures/vcr_cassettes/upload_upload_one.yml b/spec/fixtures/vcr_cassettes/upload_upload_one.yml index b6803d7e..4f8a203e 100644 --- a/spec/fixtures/vcr_cassettes/upload_upload_one.yml +++ b/spec/fixtures/vcr_cassettes/upload_upload_one.yml @@ -9,7 +9,7 @@ http_interactions: LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1lM2U3MTA1NDRmMDBlNTBkNTJhMGFhMzEzYWEwNGE5MDBhNzhlODc3MzUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVVBMT0FEQ0FSRV9QVUJfS0VZIg0KDQo1ZDViYjU2MzllM2YyZGYzMzY3NA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1lM2U3MTA1NDRmMDBlNTBkNTJhMGFhMzEzYWEwNGE5MDBhNzhlODc3MzUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LVR5cGU6IGltYWdlL2pwZWcNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLWUzZTcxMDU0NGYwMGU1MGQ1MmEwYWEzMTNhYTA0YTkwMGE3OGU4NzczNS0tDQo= headers: User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) + - UploadcareRuby/3.3.2/ (Ruby/2.6.0) Content-Type: - multipart/form-data; boundary=---------------------e3e710544f00e50d52a0aa313aa04a900a78e87735 Connection: @@ -68,11 +68,11 @@ http_interactions: Accept: - application/vnd.uploadcare-v0.7+json User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) + - UploadcareRuby/3.3.2/ (Ruby/2.6.0) Date: - Fri, 23 Sep 2022 19:29:53 GMT Authorization: - - Uploadcare 5d5bb5639e3f2df33674:3dd0b4540e531b44d5277e4655ae9536692c39d4 + - Uploadcare : Connection: - close Host: diff --git a/spec/fixtures/vcr_cassettes/upload_upload_one_without_secret_key.yml b/spec/fixtures/vcr_cassettes/upload_upload_one_without_secret_key.yml index cf5e71b9..667d2a30 100644 --- a/spec/fixtures/vcr_cassettes/upload_upload_one_without_secret_key.yml +++ b/spec/fixtures/vcr_cassettes/upload_upload_one_without_secret_key.yml @@ -6,29 +6,31 @@ http_interactions: body: encoding: ASCII-8BIT string: !binary |- - LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1lM2U3MTA1NDRmMDBlNTBkNTJhMGFhMzEzYWEwNGE5MDBhNzhlODc3MzUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVVBMT0FEQ0FSRV9QVUJfS0VZIg0KDQo1ZDViYjU2MzllM2YyZGYzMzY3NA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1lM2U3MTA1NDRmMDBlNTBkNTJhMGFhMzEzYWEwNGE5MDBhNzhlODc3MzUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ia2l0dGVuLmpwZWciOyBmaWxlbmFtZT0ia2l0dGVuLmpwZWciDQpDb250ZW50LVR5cGU6IGltYWdlL2pwZWcNCg0K/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gNjUK/9sAQwALCAgKCAcLCgkKDQwLDREcEhEPDxEiGRoUHCkkKyooJCcnLTJANy0wPTAnJzhMOT1DRUhJSCs2T1VORlRAR0hF/9sAQwEMDQ0RDxEhEhIhRS4nLkVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVF/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A0NG0zT9HMxEpuTKQSZ8HbjPTjjrWsNQtYx8ojH0AFeXG6uVJ3XZYEdCaTzpn5e4cAf3WNa+1S2RHIz1Z9USJQWbaGGR71C2vQLjMy/nXOT3MElhbQTI0peFDtGQSMdc9j70ltoOhzhXzdn2aUfl0qVW7o0lSa2Z2VtdpeWizIwZSSMik3Zdcehqlp4htIPslrCI7eMkrhiSSTzVpf9ch/wBk/wBKUpc2qJScdGWB0FFOC8CioGeEAMWOHxkdSakAlxjhvYV1MXh1ncIqFj2461fPhAxANcSRJ6rnLfkBVOFtwTuSlVfSdOvGyN1usRJ/2cj+lVNMaW7uyJLT7PAvCMGzkeua3prmzm01LGJdqQAKg78f1rIuSblIoomKbSMgZGcH2rNpJ6mid9DZ0y4mZ5EuYPJdWIXnO5exrWVvnU+x/pVAbjHbSMMEkqRj2q1v2uh9j/SrS90zbuzSH3R9KKjWQbR9KKQGPdSR6baeZGuJJOAfQVzdzqEr73Ry2MYrV8S3XlyiNSPlG0DsBXNxyAKUBBY9mPalOV3c0hGysSJPvcOpIZjhh/Wr1sWjmUN61mFTCxKBiO49KdFqJjnRJBuPY+1ZX7luPY7V5B5EA7Bs/pTJ7hI0V3YKo7k4qCaQDT7aUHIZ+Pyqhq77rS3HrMv863XwmD3NX+2bUcGbp/sn/CirIlOBRVcpJyniUn7ZJz61hwcu2eeDRRXPI6Yktu7CWMBjgr61Lq4AhiYDBz1ooqeg+pvREnw9Y8n/AFn9DTb/AP497b/rsv8AOiiuiPwGEviNnJ9aKKK0IP/ZDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLWUzZTcxMDU0NGYwMGU1MGQ1MmEwYWEzMTNhYTA0YTkwMGE3OGU4NzczNS0tDQo= + LS0tLS0tLS0tLS0tLVJ1YnlNdWx0aXBhcnRQb3N0LTQ4OTRiNDg1Y2E3Nzg2NjJlMWM0MDY2MDUxY2I3ZjlhDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IlVQTE9BRENBUkVfUFVCX0tFWSINCg0KPHVwbG9hZGNhcmVfcHVibGljX2tleT4NCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC00ODk0YjQ4NWNhNzc4NjYyZTFjNDA2NjA1MWNiN2Y5YQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJraXR0ZW4uanBlZyI7IGZpbGVuYW1lPSJraXR0ZW4uanBlZyINCkNvbnRlbnQtTGVuZ3RoOiAxMjkwDQpDb250ZW50LVR5cGU6IGltYWdlL2pwZWcNCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJpbmFyeQ0KDQr/2P/gABBKRklGAAEBAQBgAGAAAP/+ADtDUkVBVE9SOiBnZC1qcGVnIHYxLjAgKHVzaW5nIElKRyBKUEVHIHY2MiksIHF1YWxpdHkgPSA2NQr/2wBDAAsICAoIBwsKCQoNDAsNERwSEQ8PESIZGhQcKSQrKigkJyctMkA3LTA9MCcnOEw5PUNFSElIKzZPVU5GVEBHSEX/2wBDAQwNDREPESESEiFFLicuRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUX/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDQ0bTNP0czESm5MpBJnwduM9OOOtaw1C1jHyiMfQAV5cbq5UnddlgR0JpPOmfl7hwB/dY1r7VLZEcjPVn1RIlBZtoYZHvULa9AuMzL+dc5PcwSWFtBMjSl4UO0ZBIx1z2PvSW2g6HOFfN2fZpR+XSpVbujSVJrZnZW12l5aLMjBlJIyKTdl1x6GqWniG0g+yWsIjt4ySuGJJJPNWl/1yH/AGT/AEpSlzaolJx0ZYHQUU4LwKKgZ4QAxY4fGR1JqQCXGOG9hXUxeHWdwioWPbjrV8+EDEA1xJEnquct+QFU4W3BO5KVV9J068bI3W6xEn/ZyP6VU0xpbu7IktPs8C8IwbOR65remubObTUsYl2pAAqDvx/Wsi5JuUiiiYptIyBkZwfas2knqaJ30NnTLiZnkS5g8l1Yhec7l7GtZW+dT7H+lUBuMdtIwwSSpGParW/a6H2P9KtL3TNu7NIfdH0oqNZBtH0opAY91JHptp5ka4kk4B9BXN3OoSvvdHLYxitXxLdeXKI1I+UbQOwFc3HIApQEFj2Y9qU5XdzSEbKxIk+9w6khmOGH9avWxaOZQ3rWYVMLEoGI7j0p0WomOdEkG49j7VlfuW49jtXkHkQDsGz+lMnuEjRXdgqjuTioJpANPtpQchn4/KqGrvutLcesy/zrdfCYPc1f7ZtRwZun+yf8KKsiU4FFVyknKeJSftknPrWHBy7Z54NFFc8jpiS27sJYwGOCvrUurgCGJgMHPWiip6D6m9ESfD1jyf8AWf0NNv8A/j3tv+uy/wA6KK6I/AYS+I2cn1ooorQg/9kNCi0tLS0tLS0tLS0tLS1SdWJ5TXVsdGlwYXJ0UG9zdC00ODk0YjQ4NWNhNzc4NjYyZTFjNDA2NjA1MWNiN2Y5YS0tDQo= headers: User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) + - Faraday v2.14.0 Content-Type: - - multipart/form-data; boundary=---------------------e3e710544f00e50d52a0aa313aa04a900a78e87735 - Connection: - - close - Host: - - upload.uploadcare.com + - multipart/form-data; boundary=-----------RubyMultipartPost-4894b485ca778662e1c4066051cb7f9a + Content-Length: + - '1733' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" response: status: code: 200 message: OK headers: Date: - - Fri, 23 Sep 2022 19:29:53 GMT + - Thu, 20 Nov 2025 14:30:15 GMT Content-Type: - application/json - Content-Length: - - '54' + Transfer-Encoding: + - chunked Connection: - - close + - keep-alive Server: - nginx Vary: @@ -39,69 +41,82 @@ http_interactions: Access-Control-Allow-Methods: - POST, OPTIONS Access-Control-Allow-Headers: - - X-UC-User-Agent, DNT, X-PINGOTHER + - X-UC-User-Agent, X-PINGOTHER, DNT Access-Control-Max-Age: - '1' Access-Control-Allow-Credentials: - 'true' Access-Control-Expose-Headers: - - Warning + - Warning, Retry-After X-Xss-Protection: - 1; mode=block X-Content-Type-Options: - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' X-Uploadcare-Request-Id: - - d0463b5b-0711-412e-b9bc-b02993370e5c + - ade727c9-23a6-4260-a316-5b64e5bd831e body: - encoding: UTF-8 - string: '{"kitten.jpeg":"564f8639-df90-4aa8-a58d-22399a9e0ca0"}' - recorded_at: Fri, 23 Sep 2022 19:29:53 GMT + encoding: ASCII-8BIT + string: '{"kitten.jpeg":"c9704a50-424c-4ceb-b851-f129d752bd98"}' + recorded_at: Thu, 20 Nov 2025 14:30:17 GMT - request: method: get - uri: https://upload.uploadcare.com/info/?file_id=564f8639-df90-4aa8-a58d-22399a9e0ca0&pub_key=demopublickey + uri: https://upload.uploadcare.com/info/?file_id=c9704a50-424c-4ceb-b851-f129d752bd98&pub_key= body: - encoding: UTF-8 + encoding: US-ASCII string: '' headers: User-Agent: - - UploadcareRuby/4.3.3/5d5bb5639e3f2df33674 (Ruby/3.2.1) - Connection: - - close - Host: - - upload.uploadcare.com + - Faraday v2.14.0 + Accept: + - application/vnd.uploadcare-v0.7+json + Content-Type: + - application/json + Authorization: + - 'Uploadcare.Simple ' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 response: status: code: 200 message: OK headers: Date: - - Fri, 23 Sep 2022 19:29:53 GMT + - Thu, 20 Nov 2025 14:30:16 GMT Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '600' + - application/json + Transfer-Encoding: + - chunked Connection: - - close + - keep-alive Server: - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS + - Accept-Encoding + - Origin + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET, HEAD, OPTIONS + Access-Control-Allow-Headers: + - X-UC-User-Agent, X-PINGOTHER, DNT + Access-Control-Max-Age: + - '1' + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Warning, Retry-After X-Xss-Protection: - 1; mode=block X-Content-Type-Options: - nosniff + Warning: + - '199 Miscellaneous warning: You should use secure requests to Uploadcare' X-Uploadcare-Request-Id: - - 2adfdb0b-4cdd-4ada-a37a-4dd2efd88098 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive + - 13850446-3e09-4a9a-bbeb-0faddff7737f body: - encoding: UTF-8 - string: '{"size":1290,"total":1290,"done":1290,"uuid":"a7f9751a-432b-4b05-936c-2f62d51d255d","file_id":"a7f9751a-432b-4b05-936c-2f62d51d255d","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' - recorded_at: Fri, 23 Sep 2022 19:29:53 GMT -recorded_with: VCR 6.1.0 + encoding: ASCII-8BIT + string: '{"size":1290,"total":1290,"done":1290,"uuid":"c9704a50-424c-4ceb-b851-f129d752bd98","file_id":"c9704a50-424c-4ceb-b851-f129d752bd98","original_filename":"kitten.jpeg","is_image":true,"is_stored":true,"image_info":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null},"video_info":null,"content_info":{"mime":{"mime":"image/jpeg","type":"image","subtype":"jpeg"},"image":{"dpi":[96,96],"width":50,"format":"JPEG","height":50,"sequence":false,"color_mode":"RGB","orientation":null,"geo_location":null,"datetime_original":null}},"is_ready":true,"filename":"kitten.jpeg","mime_type":"image/jpeg","metadata":{}}' + recorded_at: Thu, 20 Nov 2025 14:30:18 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/uploader_multipart_upload.yml b/spec/fixtures/vcr_cassettes/uploader_multipart_upload.yml deleted file mode 100644 index f569762e..00000000 --- a/spec/fixtures/vcr_cassettes/uploader_multipart_upload.yml +++ /dev/null @@ -1,58 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://upload.uploadcare.com/multipart/start/ - body: - encoding: UTF-8 - string: "..." - headers: - Accept: - - application/json - Content-Type: - - multipart/form-data; boundary=---------------------a3964b7080329ae2511b1968b3516fbaa26f662cde - Connection: - - close - Host: - - upload.uploadcare.com - User-Agent: - - http.rb/4.3.0 - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 29 Jan 2020 15:33:49 GMT - Content-Type: - - application/json - Content-Length: - - '78' - Connection: - - close - Server: - - nginx - Vary: - - Accept-Encoding - - Origin - Access-Control-Allow-Headers: - - DNT, X-PINGOTHER, X-UC-User-Agent - X-Content-Type-Options: - - nosniff - Access-Control-Max-Age: - - '1' - X-Xss-Protection: - - 1; mode=block - Access-Control-Allow-Credentials: - - 'true' - Access-Control-Allow-Origin: - - "*" - Access-Control-Allow-Methods: - - POST, OPTIONS - body: - encoding: ASCII-8BIT - string: '{"error": {"content": "File size exceeds project limit.", "status_code": - 400}}' - http_version: - recorded_at: Wed, 29 Jan 2020 15:33:49 GMT -recorded_with: VCR 5.0.0 diff --git a/spec/fixtures/vcr_cassettes/video_convert_convert_many.yml b/spec/fixtures/vcr_cassettes/video_convert_convert_many.yml deleted file mode 100644 index 8e644100..00000000 --- a/spec/fixtures/vcr_cassettes/video_convert_convert_many.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/video/ - body: - encoding: UTF-8 - string: '{"paths":["e30112d7-3a90-4931-b2c5-688cbb46d3ac/video/-/size/600x400/change_ratio/-/quality/best/-/format/ogg/-/cut/0:0:0.0/0:0:1.0/-/thumbs~2/1/"]}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/2.6.6) - Date: - - Fri, 16 Jul 2021 11:40:25 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:661e37fbd7b07f40b3e13abc38df58974609b199 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 16 Jul 2021 11:40:26 GMT - Content-Type: - - application/json - Content-Length: - - '322' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.6' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"result": [{"original_source": "e30112d7-3a90-4931-b2c5-688cbb46d3ac/video/-/size/600x400/change_ratio/-/quality/best/-/format/ogg/-/cut/0:0:0.0/0:0:1.0/-/thumbs~2/1/", - "token": 911162353, "uuid": "f1821874-3d2e-439b-9e13-d0795a3c1d98", "thumbnails_group_uuid": - "b99de71f-0ae2-4ac5-806a-c6f0f1744369~2"}], "problems": {}}' - recorded_at: Fri, 16 Jul 2021 11:40:26 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/video_convert_convert_many_with_error.yml b/spec/fixtures/vcr_cassettes/video_convert_convert_many_with_error.yml deleted file mode 100644 index 8388c964..00000000 --- a/spec/fixtures/vcr_cassettes/video_convert_convert_many_with_error.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/convert/video/ - body: - encoding: UTF-8 - string: '{"paths":["e30112d7-3a90-4931-b2c5-688cbb46d3ac/video/-/size/x/change_ratio/-/quality/best/-/format/ogg/-/cut/0:0:0.0/0:0:1.0/-/thumbs~2/1/"],"store":"0"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/2.7.2) - Date: - - Tue, 20 Jul 2021 12:58:35 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:e60e226aeaf002aaec1cc0e2414556f14991cd31 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Tue, 20 Jul 2021 12:58:36 GMT - Content-Type: - - application/json - Content-Length: - - '290' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.6' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"result": [], "problems": {"e30112d7-3a90-4931-b2c5-688cbb46d3ac/video/-/size/x/change_ratio/-/quality/best/-/format/ogg/-/cut/0:0:0.0/0:0:1.0/-/thumbs~2/1/": - "CDN Path error: Mandatory argument \"OneOrTwoDimensionsDivisibleByFour(size)\" - not found while parsing \"size/x/change_ratio\""}}' - recorded_at: Tue, 20 Jul 2021 12:58:36 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/video_convert_file_info.yml b/spec/fixtures/vcr_cassettes/video_convert_file_info.yml deleted file mode 100644 index 0073f493..00000000 --- a/spec/fixtures/vcr_cassettes/video_convert_file_info.yml +++ /dev/null @@ -1,57 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/files/f1821874-3d2e-439b-9e13-d0795a3c1d98/ - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/3.0.2) - Date: - - Mon, 06 Sep 2021 05:08:26 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:98c10bf7844648236704a5dbfb117da9a69f3817 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 06 Sep 2021 05:08:29 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '408' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - DELETE, GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"datetime_removed":"2021-07-19T08:32:39.552772Z","datetime_stored":null,"datetime_uploaded":"2021-07-16T11:40:25.916749Z","image_info":null,"is_image":false,"is_ready":true,"mime_type":"audio/ogg","original_file_url":null,"original_filename":"video.ogg","size":276660,"url":"https://api.uploadcare.com/files/f1821874-3d2e-439b-9e13-d0795a3c1d98/","uuid":"f1821874-3d2e-439b-9e13-d0795a3c1d98","source":null}' - recorded_at: Mon, 06 Sep 2021 05:08:29 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/video_convert_get_status.yml b/spec/fixtures/vcr_cassettes/video_convert_get_status.yml deleted file mode 100644 index 78edb8cf..00000000 --- a/spec/fixtures/vcr_cassettes/video_convert_get_status.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/convert/video/status/911933811/ - body: - encoding: UTF-8 - string: '' - headers: - Date: - - Mon, 19 Jul 2021 08:57:32 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:aa3ac538117d01dfa58f78524a7c735467b27374 - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.1.0/5d5bb5639e3f2df33674 (Ruby/2.7.2) - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Mon, 19 Jul 2021 08:57:33 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '156' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.6' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: ASCII-8BIT - string: '{"status":"finished","error":null,"result":{"uuid":"1b73cddb-8e07-44f0-a7ba-724a8300a7a9","thumbnails_group_uuid":"0f181f24-7551-42e5-bebc-14b15d9d3838~2"}}' - recorded_at: Mon, 19 Jul 2021 08:57:33 GMT -recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels.yml deleted file mode 100644 index a5cefb8a..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/ - body: - encoding: UTF-8 - string: '{"target":"ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:26:10 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:33112e124819aed499753dd8035394536a15e55c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Sep 2022 11:26:10 GMT - Content-Type: - - application/json - Content-Length: - - '54' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 0f4598dd-d168-4272-b49e-e7f9d2543542 - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"request_id": "0f4598dd-d168-4272-b49e-e7f9d2543542"}' - recorded_at: Fri, 23 Sep 2022 11:26:10 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_nonexistent_uuid.yml deleted file mode 100644 index bc1e7f75..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/?request_id=nonexistent - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:59:24 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:e300b5cbae7aaec2862e2625245578052f841c3c - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Fri, 23 Sep 2022 11:59:25 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '40' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c6cf2016-87fc-41dd-a089-e9032509eb77 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Fri, 23 Sep 2022 11:59:25 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_status.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_status.yml deleted file mode 100644 index d2aa9706..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_status.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/status/?request_id=0f4598dd-d168-4272-b49e-e7f9d2543542 - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 11:28:43 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:2b5bce3caafb3c58798eb57b487fe64f882f97b1 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 23 Sep 2022 11:28:44 GMT - Content-Type: - - application/json - Content-Length: - - '18' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 7625f6b7-8688-42aa-87c1-437a80bac0ad - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"status": "done"}' - recorded_at: Fri, 23 Sep 2022 11:28:44 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_status_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_status_nonexistent_uuid.yml deleted file mode 100644 index 3497fa27..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_labels_status_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/?request_id=nonexistent - body: - encoding: UTF-8 - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/3.3.2/5d5bb5639e3f2df33674 (Ruby/2.6.0) - Date: - - Fri, 23 Sep 2022 12:04:33 GMT - Authorization: - - Uploadcare 5d5bb5639e3f2df33674:1468ab44ed949391defe8d2d322c33f48cd6cafc - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Fri, 23 Sep 2022 12:04:33 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '40' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: Please use API version 0.5, not 0.7' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 2212c84d-b3e3-4abd-9c8d-bf256d0b5ab4 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Fri, 23 Sep 2022 12:04:33 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels.yml deleted file mode 100644 index de8064e8..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels.yml +++ /dev/null @@ -1,57 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/ - body: - encoding: UTF-8 - string: '{"target":"ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.0/demopublickey (Ruby/3.1.3) - Date: - - Wed, 17 Apr 2024 12:15:08 GMT - Authorization: - - Uploadcare demopublickey:792d1155f4329982a1f672aaa8f5b605a8a9e223 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: Forbidden - headers: - Date: - - Wed, 17 Apr 2024 12:15:09 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '61' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - ae97adb2-97a9-49c1-b81e-58a9c226e468 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id": "0f4598dd-d168-4272-b49e-e7f9d2543542"}' - recorded_at: Wed, 17 Apr 2024 12:15:09 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_nonexistent_uuid.yml deleted file mode 100644 index 138bf147..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_nonexistent_uuid.yml +++ /dev/null @@ -1,57 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/ - body: - encoding: UTF-8 - string: '{"target":"nonexistent"}' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.0/demopublickey (Ruby/3.1.3) - Date: - - Wed, 17 Apr 2024 12:16:44 GMT - Authorization: - - Uploadcare demopublickey:644d7e043cd742d7acd8507b9be34c4f64255dcd - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 403 - message: Forbidden - headers: - Date: - - Wed, 17 Apr 2024 12:16:45 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '61' - Connection: - - close - Server: - - nginx - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - OPTIONS, POST - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - c8ef2f5f-5493-4b73-b5a4-53c6eee4e1f0 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Wed, 17 Apr 2024 12:16:45 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_status.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_status.yml deleted file mode 100644 index 8d864c58..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_status.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/status/?request_id=0f4598dd-d168-4272-b49e-e7f9d2543542 - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.0/demopublickey (Ruby/3.1.3) - Date: - - Wed, 17 Apr 2024 12:18:49 GMT - Authorization: - - Uploadcare demopublickey:8d1013b2dbb9eda1d1120df7cb7d5b9e0daf06e3 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 200 - message: OK - headers: - Date: - - Wed, 17 Apr 2024 12:18:50 GMT - Content-Type: - - application/json - Content-Length: - - '21' - Connection: - - close - Server: - - nginx - Vary: - - Accept - - Accept-Encoding - Warning: - - '199 Miscellaneous warning: You are using the demo project' - Access-Control-Allow-Origin: - - https://uploadcare.com - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - 51c872c4-8922-4485-8c44-9b4e4e9b708e - X-Frame-Options: - - SAMEORIGIN - X-Robots-Tag: - - noindex, nofollow, nosnippet, noarchive - body: - encoding: UTF-8 - string: '{"status": "done"}' - recorded_at: Wed, 17 Apr 2024 12:18:50 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_status_nonexistent_uuid.yml b/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_status_nonexistent_uuid.yml deleted file mode 100644 index 9aa3dcc0..00000000 --- a/spec/fixtures/vcr_cassettes/ws_rekognition_detect_moderation_labels_status_nonexistent_uuid.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/status/?request_id=nonexistent - body: - encoding: ASCII-8BIT - string: '' - headers: - Content-Type: - - application/json - Accept: - - application/vnd.uploadcare-v0.7+json - User-Agent: - - UploadcareRuby/4.4.0/demopublickey (Ruby/3.1.3) - Date: - - Wed, 17 Apr 2024 12:20:47 GMT - Authorization: - - Uploadcare demopublickey:f958c811e1eacfb1654de2aa3e3a0048e04bb1a2 - Connection: - - close - Host: - - api.uploadcare.com - response: - status: - code: 400 - message: Bad Request - headers: - Date: - - Wed, 17 Apr 2024 12:20:48 GMT - Content-Type: - - application/vnd.uploadcare-v0.7+json - Content-Length: - - '40' - Connection: - - close - Server: - - nginx - Warning: - - '199 Miscellaneous warning: You are using the demo project' - Access-Control-Allow-Origin: - - https://uploadcare.com - Vary: - - Accept - Allow: - - GET, HEAD, OPTIONS - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Uploadcare-Request-Id: - - e36a1b0b-99aa-4923-9fde-8b645fc61f97 - X-Frame-Options: - - SAMEORIGIN - body: - encoding: ASCII-8BIT - string: '{"request_id":["Must be a valid UUID."]}' - recorded_at: Wed, 17 Apr 2024 12:20:49 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d8ddfda9..5fbd7feb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,24 @@ # frozen_string_literal: true require 'bundler/setup' -require 'dry/monads' -require 'api_struct' +require 'simplecov' + +SimpleCov.start do + add_filter '/spec/' + add_filter '/bin/' + add_filter '/examples/' + add_filter '/api_examples/' + + add_group 'Api', 'lib/uploadcare/api' + add_group 'Internal', 'lib/uploadcare/internal' + add_group 'Resources', 'lib/uploadcare/resources' + add_group 'Collections', 'lib/uploadcare/collections' + add_group 'Operations', 'lib/uploadcare/operations' + add_group 'Core', 'lib/uploadcare' + + minimum_coverage 90 +end + require 'byebug' require 'webmock/rspec' require 'uploadcare' @@ -11,13 +27,19 @@ RSpec.configure do |config| include Uploadcare::Exception - # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = '.rspec_status' - - # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.expect_with :rspec do |c| c.syntax = :expect end + + config.before(:all) do + Uploadcare.configure do |c| + c.public_key = ENV.fetch('UPLOADCARE_PUBLIC_KEY', 'demopublickey') + c.secret_key = ENV.fetch('UPLOADCARE_SECRET_KEY', 'demosecretkey') + c.auth_type = 'Uploadcare.Simple' + c.rest_api_root = 'https://api.uploadcare.com' + end + end end diff --git a/spec/support/hashie.rb b/spec/support/hashie.rb deleted file mode 100644 index 76475a38..00000000 --- a/spec/support/hashie.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -# Supress this warning: -# -# `You are setting a key that conflicts with a built-in method Hashie::Mash#size defined in Hash.`` -Hashie.logger.level = Logger.const_get 'ERROR' diff --git a/spec/support/reset_config.rb b/spec/support/reset_config.rb deleted file mode 100644 index a9547ecb..00000000 --- a/spec/support/reset_config.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -RSpec.configure do |config| - config.before(:each) do - Uploadcare.config.public_key = 'demopublickey' - Uploadcare.config.secret_key = 'demoprivatekey' - Uploadcare.config.auth_type = 'Uploadcare' - Uploadcare.config.multipart_size_threshold = 100 * 1024 * 1024 - end -end diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb index 4f811d58..bdce645f 100644 --- a/spec/support/vcr.rb +++ b/spec/support/vcr.rb @@ -6,12 +6,39 @@ VCR.configure do |config| config.cassette_library_dir = 'spec/fixtures/vcr_cassettes' config.hook_into :webmock - config.filter_sensitive_data('') { Uploadcare.config.public_key } - config.filter_sensitive_data('') { Uploadcare.config.secret_key } + config.filter_sensitive_data('') do + ENV.fetch('UPLOADCARE_PUBLIC_KEY', Uploadcare.configuration.public_key) + end + config.filter_sensitive_data('') do + ENV.fetch('UPLOADCARE_SECRET_KEY', Uploadcare.configuration.secret_key) + end + config.filter_sensitive_data('') { 'demopublickey' } + config.filter_sensitive_data('') { 'demosecretkey' } config.before_record do |i| if i.request.body && i.request.body.size > 1024 * 1024 i.request.body = "Big string (#{i.request.body.size / (1024 * 1024)}) MB" end + + if (auth = i.request.headers['Authorization']&.first) + auth = auth.gsub(/\AUploadcare\.Simple\s+[^:\s]+:[^\s]+\z/, + 'Uploadcare.Simple ') + auth = auth.gsub(/\AUploadcare\s+[^:\s]+:[^\s]+\z/, 'Uploadcare ') + i.request.headers['Authorization'] = [auth] + end + + i.request.uri = i.request.uri.gsub(/([?&]token=)[^&]+/, '\1') if i.request.uri + + if i.request.body + i.request.body = i.request.body.gsub(/("token"\s*:\s*")[^"]+("\s*(?:\x7D|\x5D))/, + '\1\2') + i.request.body = i.request.body.gsub(/(name="signature"\r\n\r\n)[^\r\n]+/, '\1') + i.request.body = i.request.body.gsub(/(name="expire"\r\n\r\n)[^\r\n]+/, '\1') + end + + if i.response.body.is_a?(String) + i.response.body = i.response.body.gsub(/("token"\s*:\s*")[^"]+("\s*(?:\x7D|\x5D))/, + '\1\2') + end end config.configure_rspec_metadata! end diff --git a/spec/uploadcare/api/api_spec.rb b/spec/uploadcare/api/api_spec.rb deleted file mode 100644 index aedd2c36..00000000 --- a/spec/uploadcare/api/api_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - RSpec.describe Api do - subject { Api.new } - - it 'responds to expected REST methods' do - %i[file file_list store_files delete_files project].each do |method| - expect(subject).to respond_to(method) - end - end - - it 'responds to expected Upload methods' do - %i[upload upload_files upload_url].each do |method| - expect(subject).to respond_to(method) - end - end - - it 'views file info' do - VCR.use_cassette('rest_file_info') do - uuid = '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' - file = subject.file(uuid) - expect(file.uuid).to eq(uuid) - end - end - end -end diff --git a/spec/uploadcare/api/rest/addons_spec.rb b/spec/uploadcare/api/rest/addons_spec.rb new file mode 100644 index 00000000..e719670b --- /dev/null +++ b/spec/uploadcare/api/rest/addons_spec.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::Addons do + subject(:addons) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + let(:request_id) { 'req-abc-123' } + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(addons.rest).to eq(rest) + end + end + + describe '#aws_rekognition_detect_labels' do + before do + stub_request(:post, 'https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/') + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'executes label detection and returns a request_id' do + result = addons.aws_rekognition_detect_labels(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['request_id']).to eq(request_id) + end + + it 'sends the file UUID as the target parameter' do + stub = stub_request(:post, 'https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/') + .with(body: hash_including('target' => file_uuid)) + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + addons.aws_rekognition_detect_labels(uuid: file_uuid) + + expect(stub).to have_been_requested + end + end + + describe '#aws_rekognition_detect_labels_status' do + before do + stub_request(:get, 'https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/status/') + .with(query: { request_id: request_id }) + .to_return( + status: 200, + body: { status: 'done', result: { labels: [{ name: 'Cat', confidence: 99.5 }] } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the status of label detection' do + result = addons.aws_rekognition_detect_labels_status(request_id: request_id) + + expect(result).to be_success + expect(result.value!['status']).to eq('done') + end + end + + describe '#aws_rekognition_detect_moderation_labels' do + before do + stub_request(:post, 'https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/') + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'executes moderation label detection and returns a request_id' do + result = addons.aws_rekognition_detect_moderation_labels(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['request_id']).to eq(request_id) + end + + it 'sends the file UUID as the target parameter' do + stub = stub_request(:post, 'https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/') + .with(body: hash_including('target' => file_uuid)) + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + addons.aws_rekognition_detect_moderation_labels(uuid: file_uuid) + + expect(stub).to have_been_requested + end + end + + describe '#aws_rekognition_detect_moderation_labels_status' do + before do + stub_request(:get, 'https://api.uploadcare.com/addons/aws_rekognition_detect_moderation_labels/execute/status/') + .with(query: { request_id: request_id }) + .to_return( + status: 200, + body: { status: 'done', result: { moderation_labels: [] } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the status of moderation label detection' do + result = addons.aws_rekognition_detect_moderation_labels_status(request_id: request_id) + + expect(result).to be_success + expect(result.value!['status']).to eq('done') + end + end + + describe '#uc_clamav_virus_scan' do + before do + stub_request(:post, 'https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/') + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'executes a ClamAV virus scan and returns a request_id' do + result = addons.uc_clamav_virus_scan(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['request_id']).to eq(request_id) + end + + it 'sends the file UUID as the target parameter' do + stub = stub_request(:post, 'https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/') + .with(body: hash_including('target' => file_uuid)) + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + addons.uc_clamav_virus_scan(uuid: file_uuid) + + expect(stub).to have_been_requested + end + + it 'merges additional params' do + stub = stub_request(:post, 'https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/') + .with(body: hash_including('target' => file_uuid, 'purge_infected' => true)) + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + addons.uc_clamav_virus_scan(uuid: file_uuid, params: { purge_infected: true }) + + expect(stub).to have_been_requested + end + end + + describe '#uc_clamav_virus_scan_status' do + before do + stub_request(:get, 'https://api.uploadcare.com/addons/uc_clamav_virus_scan/execute/status/') + .with(query: { request_id: request_id }) + .to_return( + status: 200, + body: { status: 'done', result: { infected: false } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the status of a ClamAV scan' do + result = addons.uc_clamav_virus_scan_status(request_id: request_id) + + expect(result).to be_success + expect(result.value!['status']).to eq('done') + expect(result.value!['result']['infected']).to be false + end + end + + describe '#remove_bg' do + before do + stub_request(:post, 'https://api.uploadcare.com/addons/remove_bg/execute/') + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'executes background removal and returns a request_id' do + result = addons.remove_bg(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['request_id']).to eq(request_id) + end + + it 'sends the target and params in the request body' do + stub = stub_request(:post, 'https://api.uploadcare.com/addons/remove_bg/execute/') + .with(body: hash_including('target' => file_uuid, 'params' => { 'crop' => true })) + .to_return( + status: 200, + body: { request_id: request_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + addons.remove_bg(uuid: file_uuid, params: { crop: true }) + + expect(stub).to have_been_requested + end + end + + describe '#remove_bg_status' do + before do + stub_request(:get, 'https://api.uploadcare.com/addons/remove_bg/execute/status/') + .with(query: { request_id: request_id }) + .to_return( + status: 200, + body: { status: 'done', result: { uuid: 'new-uuid-no-bg' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the status of background removal' do + result = addons.remove_bg_status(request_id: request_id) + + expect(result).to be_success + expect(result.value!['status']).to eq('done') + expect(result.value!['result']['uuid']).to eq('new-uuid-no-bg') + end + end +end diff --git a/spec/uploadcare/api/rest/document_conversions_spec.rb b/spec/uploadcare/api/rest/document_conversions_spec.rb new file mode 100644 index 00000000..fb3633af --- /dev/null +++ b/spec/uploadcare/api/rest/document_conversions_spec.rb @@ -0,0 +1,228 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::DocumentConversions do + subject(:document_conversions) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(document_conversions.rest).to eq(rest) + end + end + + describe '#info' do + let(:encoded_uuid) { URI.encode_www_form_component(file_uuid) } + + before do + stub_request(:get, "https://api.uploadcare.com/convert/document/#{encoded_uuid}/") + .to_return( + status: 200, + body: { + format: { name: 'pdf' }, + converted_groups: { 'pdf' => { uuid: 'group-uuid' } } + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns document format info' do + result = document_conversions.info(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['format']['name']).to eq('pdf') + end + + it 'URI-encodes the UUID in the path' do + special_uuid = 'uuid/with spaces' + encoded_special_uuid = URI.encode_www_form_component(special_uuid) + + stub = stub_request(:get, "https://api.uploadcare.com/convert/document/#{encoded_special_uuid}/") + .to_return( + status: 200, + body: { format: { name: 'pdf' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.info(uuid: special_uuid) + + expect(stub).to have_been_requested + end + + it 'returns a failure Result when file is not found' do + stub_request(:get, 'https://api.uploadcare.com/convert/document/nonexistent/') + .to_return( + status: 404, + body: { detail: 'Not found.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = document_conversions.info(uuid: 'nonexistent') + + expect(result).to be_failure + end + end + + describe '#convert' do + let(:conversion_path) { "#{file_uuid}/document/-/format/pdf/" } + + before do + stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .to_return( + status: 200, + body: { + result: [ + { original_source: file_uuid, token: 12_345, uuid: 'converted-uuid' } + ], + problems: {} + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'converts a document and returns the result' do + result = document_conversions.convert(paths: [conversion_path]) + + expect(result).to be_success + expect(result.value!['result'].first['token']).to eq(12_345) + expect(result.value!['problems']).to eq({}) + end + + it 'sends paths in the request body' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .with(body: hash_including('paths' => [conversion_path])) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.convert(paths: [conversion_path]) + + expect(stub).to have_been_requested + end + + it 'accepts store option' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .with(body: hash_including('paths' => [conversion_path], 'store' => '1')) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.convert(paths: [conversion_path], options: { store: true }) + + expect(stub).to have_been_requested + end + + it 'accepts save_in_group option' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .with(body: hash_including('save_in_group' => '1')) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.convert(paths: [conversion_path], options: { save_in_group: true }) + + expect(stub).to have_been_requested + end + + it 'normalizes boolean store parameter' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .with(body: hash_including('store' => '0')) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.convert(paths: [conversion_path], options: { store: false }) + + expect(stub).to have_been_requested + end + + it 'normalizes case-insensitive string booleans' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .with(body: hash_including('store' => '0', 'save_in_group' => '1')) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.convert(paths: [conversion_path], options: { store: 'False', save_in_group: 'TRUE' }) + + expect(stub).to have_been_requested + end + + it 'passes through unsupported boolean-like values' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .with do |request| + body = JSON.parse(request.body) + body['paths'] == [conversion_path] && body['store'] == 'no' + end + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + document_conversions.convert(paths: [conversion_path], options: { store: 'no' }) + + expect(stub).to have_been_requested + end + end + + describe '#status' do + let(:token) { 12_345 } + + before do + stub_request(:get, "https://api.uploadcare.com/convert/document/status/#{token}/") + .to_return( + status: 200, + body: { + status: 'finished', + result: { uuid: 'converted-uuid' }, + error: nil + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the conversion job status' do + result = document_conversions.status(token: token) + + expect(result).to be_success + expect(result.value!['status']).to eq('finished') + expect(result.value!['result']['uuid']).to eq('converted-uuid') + end + + it 'handles pending status' do + stub_request(:get, "https://api.uploadcare.com/convert/document/status/#{token}/") + .to_return( + status: 200, + body: { status: 'pending', result: nil, error: nil }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = document_conversions.status(token: token) + + expect(result).to be_success + expect(result.value!['status']).to eq('pending') + end + end +end diff --git a/spec/uploadcare/api/rest/file_metadata_spec.rb b/spec/uploadcare/api/rest/file_metadata_spec.rb new file mode 100644 index 00000000..1c03729e --- /dev/null +++ b/spec/uploadcare/api/rest/file_metadata_spec.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::FileMetadata do + subject(:file_metadata) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(file_metadata.rest).to eq(rest) + end + end + + describe '#index' do + before do + stub_request(:get, "https://api.uploadcare.com/files/#{file_uuid}/metadata/") + .to_return( + status: 200, + body: { 'key1' => 'value1', 'key2' => 'value2' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns all metadata for a file' do + result = file_metadata.index(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!).to eq({ 'key1' => 'value1', 'key2' => 'value2' }) + end + + it 'URI-encodes the file UUID' do + uuid_with_special = 'uuid+special' + encoded_uuid = URI.encode_www_form_component(uuid_with_special) + + stub_request(:get, "https://api.uploadcare.com/files/#{encoded_uuid}/metadata/") + .to_return( + status: 200, + body: {}.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = file_metadata.index(uuid: uuid_with_special) + + expect(result).to be_success + end + + it 'returns a failure Result when file is not found' do + stub_request(:get, 'https://api.uploadcare.com/files/nonexistent/metadata/') + .to_return( + status: 404, + body: { detail: 'Not found.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = file_metadata.index(uuid: 'nonexistent') + + expect(result).to be_failure + end + end + + describe '#show' do + before do + stub_request(:get, "https://api.uploadcare.com/files/#{file_uuid}/metadata/my-key/") + .to_return( + status: 200, + body: '"my-value"', + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the value for a specific metadata key' do + result = file_metadata.show(uuid: file_uuid, key: 'my-key') + + expect(result).to be_success + expect(result.value!).to eq('my-value') + end + + it 'URI-encodes both uuid and key' do + uuid = 'test-uuid' + key_with_special = 'key with spaces' + encoded_key = URI.encode_www_form_component(key_with_special) + + stub_request(:get, "https://api.uploadcare.com/files/#{uuid}/metadata/#{encoded_key}/") + .to_return( + status: 200, + body: '"value"', + headers: { 'Content-Type' => 'application/json' } + ) + + result = file_metadata.show(uuid: uuid, key: key_with_special) + + expect(result).to be_success + end + end + + describe '#update' do + before do + stub_request(:put, "https://api.uploadcare.com/files/#{file_uuid}/metadata/my-key/") + .to_return( + status: 200, + body: '"new-value"', + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'updates a metadata key and returns the new value' do + result = file_metadata.update(uuid: file_uuid, key: 'my-key', value: 'new-value') + + expect(result).to be_success + expect(result.value!).to eq('new-value') + end + + it 'sends the value as a JSON string in the request body' do + stub = stub_request(:put, "https://api.uploadcare.com/files/#{file_uuid}/metadata/my-key/") + .with(body: '"new-value"') + .to_return( + status: 200, + body: '"new-value"', + headers: { 'Content-Type' => 'application/json' } + ) + + file_metadata.update(uuid: file_uuid, key: 'my-key', value: 'new-value') + + expect(stub).to have_been_requested + end + end + + describe '#delete' do + before do + stub_request(:delete, "https://api.uploadcare.com/files/#{file_uuid}/metadata/my-key/") + .to_return( + status: 200, + body: ''.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'deletes a metadata key and returns a Result' do + result = file_metadata.delete(uuid: file_uuid, key: 'my-key') + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + + it 'URI-encodes both uuid and key' do + encoded_uuid = URI.encode_www_form_component('test-uuid') + encoded_key = URI.encode_www_form_component('special/key') + + stub = stub_request(:delete, "https://api.uploadcare.com/files/#{encoded_uuid}/metadata/#{encoded_key}/") + .to_return( + status: 200, + body: ''.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + file_metadata.delete(uuid: 'test-uuid', key: 'special/key') + + expect(stub).to have_been_requested + end + end +end diff --git a/spec/uploadcare/api/rest/files_spec.rb b/spec/uploadcare/api/rest/files_spec.rb new file mode 100644 index 00000000..e6bd0cb8 --- /dev/null +++ b/spec/uploadcare/api/rest/files_spec.rb @@ -0,0 +1,294 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::Files do + subject(:files) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(files.rest).to eq(rest) + end + end + + describe '#list' do + before do + stub_request(:get, 'https://api.uploadcare.com/files/') + .to_return( + status: 200, + body: { + next: nil, + previous: nil, + total: 2, + per_page: 100, + results: [ + { uuid: 'uuid-1', filename: 'file1.jpg' }, + { uuid: 'uuid-2', filename: 'file2.png' } + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns a successful Result with paginated file list' do + result = files.list + + expect(result).to be_success + expect(result.value!['results'].length).to eq(2) + expect(result.value!['total']).to eq(2) + end + + it 'passes query params' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .with(query: { limit: '10', ordering: '-datetime_uploaded' }) + .to_return( + status: 200, + body: { results: [], total: 0 }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.list(params: { limit: '10', ordering: '-datetime_uploaded' }) + + expect(result).to be_success + end + end + + describe '#info' do + let(:encoded_uuid) { URI.encode_www_form_component(file_uuid) } + + before do + stub_request(:get, "https://api.uploadcare.com/files/#{encoded_uuid}/") + .to_return( + status: 200, + body: { + uuid: file_uuid, + filename: 'test.jpg', + size: 12_345, + is_stored: true, + is_image: true, + mime_type: 'image/jpeg' + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns file details for the given UUID' do + result = files.info(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['uuid']).to eq(file_uuid) + expect(result.value!['filename']).to eq('test.jpg') + end + + it 'accepts additional params like include' do + stub_request(:get, "https://api.uploadcare.com/files/#{encoded_uuid}/") + .with(query: { include: 'appdata' }) + .to_return( + status: 200, + body: { uuid: file_uuid, appdata: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.info(uuid: file_uuid, params: { include: 'appdata' }) + + expect(result).to be_success + end + + it 'returns a failure Result when file is not found' do + stub_request(:get, 'https://api.uploadcare.com/files/nonexistent/') + .to_return( + status: 404, + body: { detail: 'Not found.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.info(uuid: 'nonexistent') + + expect(result).to be_failure + expect(result.error).to be_a(Uploadcare::Exception::NotFoundError) + end + + it 'URI-encodes the UUID in the path' do + special_uuid = 'uuid/with spaces' + encoded_special_uuid = URI.encode_www_form_component(special_uuid) + + stub = stub_request(:get, "https://api.uploadcare.com/files/#{encoded_special_uuid}/") + .to_return( + status: 200, + body: { uuid: special_uuid }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + files.info(uuid: special_uuid) + + expect(stub).to have_been_requested + end + end + + describe '#store' do + let(:encoded_uuid) { URI.encode_www_form_component(file_uuid) } + + before do + stub_request(:put, "https://api.uploadcare.com/files/#{encoded_uuid}/storage/") + .to_return( + status: 200, + body: { uuid: file_uuid, is_stored: true }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'stores a file and returns updated file details' do + result = files.store(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['is_stored']).to be true + end + end + + describe '#delete' do + let(:encoded_uuid) { URI.encode_www_form_component(file_uuid) } + + before do + stub_request(:delete, "https://api.uploadcare.com/files/#{encoded_uuid}/storage/") + .to_return( + status: 200, + body: { uuid: file_uuid }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'deletes a file and returns file details' do + result = files.delete(uuid: file_uuid) + + expect(result).to be_success + expect(result.value!['uuid']).to eq(file_uuid) + end + end + + describe '#batch_store' do + let(:uuids) { %w[uuid-1 uuid-2 uuid-3] } + + before do + stub_request(:put, 'https://api.uploadcare.com/files/storage/') + .to_return( + status: 200, + body: { + result: [ + { uuid: 'uuid-1' }, + { uuid: 'uuid-2' }, + { uuid: 'uuid-3' } + ], + problems: {} + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'batch stores files and returns result with problems' do + result = files.batch_store(uuids: uuids) + + expect(result).to be_success + expect(result.value!['result'].length).to eq(3) + expect(result.value!['problems']).to eq({}) + end + end + + describe '#batch_delete' do + let(:uuids) { %w[uuid-1 uuid-2] } + + before do + stub_request(:delete, 'https://api.uploadcare.com/files/storage/') + .to_return( + status: 200, + body: { + result: [{ uuid: 'uuid-1' }, { uuid: 'uuid-2' }], + problems: {} + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'batch deletes files and returns result' do + result = files.batch_delete(uuids: uuids) + + expect(result).to be_success + expect(result.value!['result'].length).to eq(2) + end + end + + describe '#local_copy' do + before do + stub_request(:post, 'https://api.uploadcare.com/files/local_copy/') + .to_return( + status: 200, + body: { type: 'file', result: { uuid: 'new-uuid' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'copies a file to local storage' do + result = files.local_copy(source: file_uuid) + + expect(result).to be_success + expect(result.value!['type']).to eq('file') + expect(result.value!['result']['uuid']).to eq('new-uuid') + end + + it 'accepts additional options like store' do + stub_request(:post, 'https://api.uploadcare.com/files/local_copy/') + .with(body: hash_including('source' => file_uuid, 'store' => true)) + .to_return( + status: 200, + body: { type: 'file', result: { uuid: 'new-uuid' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.local_copy(source: file_uuid, options: { store: true }) + + expect(result).to be_success + end + end + + describe '#remote_copy' do + before do + stub_request(:post, 'https://api.uploadcare.com/files/remote_copy/') + .to_return( + status: 200, + body: { type: 'url', result: 'https://s3.amazonaws.com/bucket/file.jpg' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'copies a file to remote storage' do + result = files.remote_copy(source: file_uuid, target: 'my-s3-storage') + + expect(result).to be_success + expect(result.value!['type']).to eq('url') + end + + it 'accepts additional options like make_public' do + stub_request(:post, 'https://api.uploadcare.com/files/remote_copy/') + .with(body: hash_including('source' => file_uuid, 'target' => 'my-s3', 'make_public' => true)) + .to_return( + status: 200, + body: { type: 'url', result: 'https://example.com/file.jpg' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.remote_copy(source: file_uuid, target: 'my-s3', options: { make_public: true }) + + expect(result).to be_success + end + end +end diff --git a/spec/uploadcare/api/rest/groups_spec.rb b/spec/uploadcare/api/rest/groups_spec.rb new file mode 100644 index 00000000..935eadb4 --- /dev/null +++ b/spec/uploadcare/api/rest/groups_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::Groups do + subject(:groups) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:group_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890~3' } + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(groups.rest).to eq(rest) + end + end + + describe '#list' do + before do + stub_request(:get, 'https://api.uploadcare.com/groups/') + .to_return( + status: 200, + body: { + next: nil, + previous: nil, + total: 1, + per_page: 100, + results: [ + { id: group_uuid, files_count: 3, datetime_created: '2024-01-01T00:00:00Z' } + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns a paginated list of groups' do + result = groups.list + + expect(result).to be_success + expect(result.value!['results'].length).to eq(1) + expect(result.value!['total']).to eq(1) + end + + it 'passes query params' do + stub_request(:get, 'https://api.uploadcare.com/groups/') + .with(query: { limit: '5' }) + .to_return( + status: 200, + body: { results: [], total: 0 }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = groups.list(params: { limit: '5' }) + + expect(result).to be_success + end + end + + describe '#info' do + before do + stub_request(:get, %r{https://api\.uploadcare\.com/groups/.*}) + .to_return( + status: 200, + body: { + id: group_uuid, + files_count: 3, + files: [ + { uuid: 'file-1' }, + { uuid: 'file-2' }, + { uuid: 'file-3' } + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns group details for the given UUID' do + result = groups.info(uuid: group_uuid) + + expect(result).to be_success + expect(result.value!['id']).to eq(group_uuid) + expect(result.value!['files_count']).to eq(3) + end + + it 'URI-encodes the tilde character in the group UUID' do + stub = stub_request(:get, 'https://api.uploadcare.com/groups/a1b2c3d4-e5f6-7890-abcd-ef1234567890~3/') + .to_return( + status: 200, + body: { id: group_uuid }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + groups.info(uuid: group_uuid) + + expect(stub).to have_been_requested + end + end + + describe '#delete' do + before do + stub_request(:delete, %r{https://api\.uploadcare\.com/groups/.*}) + .to_return( + status: 200, + body: ''.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'deletes a group and returns a Result' do + result = groups.delete(uuid: group_uuid) + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + end +end diff --git a/spec/uploadcare/api/rest/project_spec.rb b/spec/uploadcare/api/rest/project_spec.rb new file mode 100644 index 00000000..b4d655c7 --- /dev/null +++ b/spec/uploadcare/api/rest/project_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::Project do + subject(:project) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(project.rest).to eq(rest) + end + end + + describe '#show' do + before do + stub_request(:get, 'https://api.uploadcare.com/project/') + .to_return( + status: 200, + body: { + name: 'Demo Project', + pub_key: 'demopublickey', + autostore_enabled: true, + collaborators: [ + { name: 'User One', email: 'user1@example.com' } + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns project details' do + result = project.show + + expect(result).to be_success + expect(result.value!['name']).to eq('Demo Project') + expect(result.value!['pub_key']).to eq('demopublickey') + expect(result.value!['autostore_enabled']).to be true + end + + it 'includes collaborator information' do + result = project.show + + expect(result).to be_success + collaborators = result.value!['collaborators'] + expect(collaborators.length).to eq(1) + expect(collaborators.first['email']).to eq('user1@example.com') + end + + it 'returns a failure Result on authentication error' do + stub_request(:get, 'https://api.uploadcare.com/project/') + .to_return( + status: 401, + body: { detail: 'Authentication credentials were not provided.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = project.show + + expect(result).to be_failure + expect(result.error).to be_a(Uploadcare::Exception::RequestError) + end + end +end diff --git a/spec/uploadcare/api/rest/video_conversions_spec.rb b/spec/uploadcare/api/rest/video_conversions_spec.rb new file mode 100644 index 00000000..fdf8f7f2 --- /dev/null +++ b/spec/uploadcare/api/rest/video_conversions_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::VideoConversions do + subject(:video_conversions) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(video_conversions.rest).to eq(rest) + end + end + + describe '#convert' do + let(:conversion_path) { "#{file_uuid}/video/-/format/mp4/-/quality/normal/" } + + before do + stub_request(:post, 'https://api.uploadcare.com/convert/video/') + .to_return( + status: 200, + body: { + result: [ + { original_source: file_uuid, token: 67_890, uuid: 'converted-video-uuid' } + ], + problems: {} + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'converts a video and returns the result' do + result = video_conversions.convert(paths: [conversion_path]) + + expect(result).to be_success + expect(result.value!['result'].first['token']).to eq(67_890) + expect(result.value!['problems']).to eq({}) + end + + it 'sends paths in the request body' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/video/') + .with(body: hash_including('paths' => [conversion_path])) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + video_conversions.convert(paths: [conversion_path]) + + expect(stub).to have_been_requested + end + + it 'merges additional options like store' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/video/') + .with(body: hash_including('paths' => [conversion_path], 'store' => '1')) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + video_conversions.convert(paths: [conversion_path], options: { store: '1' }) + + expect(stub).to have_been_requested + end + + it 'normalizes boolean store values' do + stub = stub_request(:post, 'https://api.uploadcare.com/convert/video/') + .with(body: hash_including('paths' => [conversion_path], 'store' => '1')) + .to_return( + status: 200, + body: { result: [], problems: {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + video_conversions.convert(paths: [conversion_path], options: { store: true }) + + expect(stub).to have_been_requested + end + + it 'returns a failure Result on error' do + stub_request(:post, 'https://api.uploadcare.com/convert/video/') + .to_return( + status: 400, + body: { detail: 'Invalid conversion path.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = video_conversions.convert(paths: ['invalid-path']) + + expect(result).to be_failure + expect(result.error).to be_a(Uploadcare::Exception::InvalidRequestError) + end + end + + describe '#status' do + let(:token) { 67_890 } + + before do + stub_request(:get, "https://api.uploadcare.com/convert/video/status/#{token}/") + .to_return( + status: 200, + body: { + status: 'finished', + result: { uuid: 'converted-video-uuid', thumbnails_group_uuid: 'thumb-group-uuid' }, + error: nil + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns the video conversion job status' do + result = video_conversions.status(token: token) + + expect(result).to be_success + expect(result.value!['status']).to eq('finished') + expect(result.value!['result']['uuid']).to eq('converted-video-uuid') + end + + it 'handles processing status' do + stub_request(:get, "https://api.uploadcare.com/convert/video/status/#{token}/") + .to_return( + status: 200, + body: { status: 'processing', result: nil, error: nil }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = video_conversions.status(token: token) + + expect(result).to be_success + expect(result.value!['status']).to eq('processing') + end + + it 'handles error status' do + stub_request(:get, "https://api.uploadcare.com/convert/video/status/#{token}/") + .to_return( + status: 200, + body: { status: 'failed', result: nil, error: 'Conversion failed' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = video_conversions.status(token: token) + + expect(result).to be_success + expect(result.value!['status']).to eq('failed') + expect(result.value!['error']).to eq('Conversion failed') + end + end +end diff --git a/spec/uploadcare/api/rest/webhooks_spec.rb b/spec/uploadcare/api/rest/webhooks_spec.rb new file mode 100644 index 00000000..2f85c0db --- /dev/null +++ b/spec/uploadcare/api/rest/webhooks_spec.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest::Webhooks do + subject(:webhooks) { described_class.new(rest: rest) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + describe '#initialize' do + it 'stores the rest client' do + expect(webhooks.rest).to eq(rest) + end + end + + describe '#list' do + before do + stub_request(:get, 'https://api.uploadcare.com/webhooks/') + .to_return( + status: 200, + body: [ + { + id: 1, + target_url: 'https://example.com/webhook', + event: 'file.uploaded', + is_active: true, + project: 12_345 + } + ].to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns a list of webhooks' do + result = webhooks.list + + expect(result).to be_success + expect(result.value!).to be_an(Array) + expect(result.value!.first['target_url']).to eq('https://example.com/webhook') + end + end + + describe '#create' do + before do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .to_return( + status: 200, + body: { + id: 123, + target_url: 'https://example.com/hook', + event: 'file.uploaded', + is_active: true + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'creates a webhook with the given target URL' do + result = webhooks.create(options: { target_url: 'https://example.com/hook' }) + + expect(result).to be_success + expect(result.value!['id']).to eq(123) + expect(result.value!['target_url']).to eq('https://example.com/hook') + end + + it 'defaults event to file.uploaded' do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .with(body: hash_including('event' => 'file.uploaded')) + .to_return( + status: 200, + body: { id: 123, event: 'file.uploaded' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = webhooks.create(options: { target_url: 'https://example.com/hook' }) + + expect(result).to be_success + end + + it 'defaults is_active to true' do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .with(body: hash_including('is_active' => true)) + .to_return( + status: 200, + body: { id: 123, is_active: true }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = webhooks.create(options: { target_url: 'https://example.com/hook' }) + + expect(result).to be_success + end + + it 'accepts optional signing_secret' do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .with(body: hash_including('signing_secret' => 'my-secret')) + .to_return( + status: 200, + body: { id: 123, signing_secret: 'my-secret' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = webhooks.create(options: { target_url: 'https://example.com/hook', signing_secret: 'my-secret' }) + + expect(result).to be_success + end + + it 'accepts optional version' do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .with(body: hash_including('version' => '0.7')) + .to_return( + status: 200, + body: { id: 123, version: '0.7' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = webhooks.create(options: { target_url: 'https://example.com/hook', version: '0.7' }) + + expect(result).to be_success + end + + it 'allows setting is_active to false' do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .with(body: hash_including('is_active' => false)) + .to_return( + status: 200, + body: { id: 123, is_active: false }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = webhooks.create(options: { target_url: 'https://example.com/hook', is_active: false }) + + expect(result).to be_success + end + end + + describe '#update' do + before do + stub_request(:put, 'https://api.uploadcare.com/webhooks/123/') + .to_return( + status: 200, + body: { + id: 123, + target_url: 'https://example.com/updated-hook', + event: 'file.uploaded', + is_active: true + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'updates a webhook by ID' do + result = webhooks.update(id: 123, options: { target_url: 'https://example.com/updated-hook' }) + + expect(result).to be_success + expect(result.value!['target_url']).to eq('https://example.com/updated-hook') + end + + it 'can update is_active' do + stub_request(:put, 'https://api.uploadcare.com/webhooks/123/') + .with(body: hash_including('is_active' => false)) + .to_return( + status: 200, + body: { id: 123, is_active: false }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = webhooks.update(id: 123, options: { is_active: false }) + + expect(result).to be_success + expect(result.value!['is_active']).to be false + end + end + + describe '#delete' do + before do + stub_request(:delete, 'https://api.uploadcare.com/webhooks/unsubscribe/') + .to_return( + status: 200, + body: ''.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'deletes a webhook by target URL' do + result = webhooks.delete(target_url: 'https://example.com/hook') + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + + it 'sends the target_url in the request body' do + stub = stub_request(:delete, 'https://api.uploadcare.com/webhooks/unsubscribe/') + .with(body: hash_including('target_url' => 'https://example.com/hook')) + .to_return( + status: 200, + body: ''.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + webhooks.delete(target_url: 'https://example.com/hook') + + expect(stub).to have_been_requested + end + end +end diff --git a/spec/uploadcare/api/rest_spec.rb b/spec/uploadcare/api/rest_spec.rb new file mode 100644 index 00000000..3091748e --- /dev/null +++ b/spec/uploadcare/api/rest_spec.rb @@ -0,0 +1,379 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Rest do + subject(:rest) { described_class.new(config: config) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + + describe '#initialize' do + it 'stores the config' do + expect(rest.config).to eq(config) + end + + it 'creates a Faraday connection to the REST API root' do + expect(rest.connection).to be_a(Faraday::Connection) + expect(rest.connection.url_prefix.to_s).to eq('https://api.uploadcare.com/') + end + + it 'creates an authenticator' do + expect(rest.authenticator).to be_a(Uploadcare::Internal::Authenticator) + end + + it 'defaults to global configuration when no config is provided' do + Uploadcare.configure do |c| + c.public_key = 'globalpubkey' + c.secret_key = 'globalseckey' + c.auth_type = 'Uploadcare.Simple' + end + + default_rest = described_class.new + expect(default_rest.config.public_key).to eq('globalpubkey') + end + end + + describe '#get' do + it 'returns a successful Result on 200' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .to_return( + status: 200, + body: { results: [], next: nil }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.get(path: '/files/', params: {}, headers: {}, request_options: {}) + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + expect(result.value!).to eq({ 'results' => [], 'next' => nil }) + end + + it 'returns a failure Result on API error' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .to_return( + status: 404, + body: { detail: 'Not found.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.get(path: '/files/', params: {}, headers: {}, request_options: {}) + + expect(result).to be_failure + expect(result.error).to be_a(Uploadcare::Exception::NotFoundError) + end + + it 'passes query params for GET requests' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .with(query: { limit: '10', ordering: '-datetime_uploaded' }) + .to_return( + status: 200, + body: { results: [] }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.get( + path: '/files/', + params: { limit: '10', ordering: '-datetime_uploaded' }, + headers: {}, + request_options: {} + ) + + expect(result).to be_success + end + + it 'signs GET URI with the same nested params encoding used by Faraday' do + authenticator = instance_double(Uploadcare::Internal::Authenticator) + allow(authenticator).to receive(:default_headers).and_return( + { + 'Accept' => 'application/vnd.uploadcare-v0.7+json', + 'Content-Type' => 'application/json' + } + ) + allow(authenticator).to receive(:headers) + .with('GET', '/files/?tags%5B%5D=a&tags%5B%5D=b', '', 'application/json') + .and_return( + { + 'Accept' => 'application/vnd.uploadcare-v0.7+json', + 'Authorization' => 'Uploadcare.Simple demopublickey:demosecretkey', + 'Content-Type' => 'application/json' + } + ) + rest.instance_variable_set(:@authenticator, authenticator) + + stub_request(:get, %r{\Ahttps://api\.uploadcare\.com/files/\?tags%5B%5D=a&tags%5B%5D=b\z}) + .to_return( + status: 200, + body: { results: [] }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.get(path: '/files/', params: { tags: %w[a b] }, headers: {}, request_options: {}) + + expect(result).to be_success + end + end + + describe '#post' do + it 'returns a successful Result on 200' do + stub_request(:post, 'https://api.uploadcare.com/files/local_copy/') + .to_return( + status: 200, + body: { type: 'file', result: { uuid: 'new-uuid' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.post( + path: '/files/local_copy/', + params: { source: 'some-uuid' }, + headers: {}, + request_options: {} + ) + + expect(result).to be_success + expect(result.value!['type']).to eq('file') + end + + it 'returns a failure Result on 400' do + stub_request(:post, 'https://api.uploadcare.com/files/local_copy/') + .to_return( + status: 400, + body: { detail: 'Invalid source.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.post( + path: '/files/local_copy/', + params: { source: '' }, + headers: {}, + request_options: {} + ) + + expect(result).to be_failure + expect(result.error).to be_a(Uploadcare::Exception::InvalidRequestError) + end + + it 'uses the resolved Content-Type consistently for signing and request headers' do + authenticator = instance_double(Uploadcare::Internal::Authenticator) + allow(authenticator).to receive(:default_headers).and_return( + { 'Accept' => 'application/vnd.uploadcare-v0.7+json', 'Content-Type' => 'application/json' } + ) + allow(authenticator).to receive(:headers) + .with('POST', '/files/local_copy/', 'plain body', 'text/plain') + .and_return( + { + 'Accept' => 'application/vnd.uploadcare-v0.7+json', + 'Authorization' => 'Uploadcare.Simple demopublickey:demosecretkey', + 'Content-Type' => 'text/plain' + } + ) + + rest.instance_variable_set(:@authenticator, authenticator) + + stub = stub_request(:post, 'https://api.uploadcare.com/files/local_copy/') + .with(body: 'plain body', headers: { 'Content-Type' => 'text/plain' }) + .to_return( + status: 200, + body: { type: 'file', result: { uuid: 'new-uuid' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.post( + path: '/files/local_copy/', + params: 'plain body', + headers: { 'content-type' => 'text/plain' }, + request_options: {} + ) + + expect(result).to be_success + expect(stub).to have_been_requested + end + end + + describe '#put' do + it 'returns a successful Result on 200' do + stub_request(:put, 'https://api.uploadcare.com/files/test-uuid/storage/') + .to_return( + status: 200, + body: { uuid: 'test-uuid', is_stored: true }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.put( + path: '/files/test-uuid/storage/', + params: {}, + headers: {}, + request_options: {} + ) + + expect(result).to be_success + expect(result.value!['is_stored']).to be true + end + end + + describe '#delete' do + it 'returns a successful Result on 200' do + stub_request(:delete, 'https://api.uploadcare.com/files/test-uuid/storage/') + .to_return( + status: 200, + body: { uuid: 'test-uuid' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.delete( + path: '/files/test-uuid/storage/', + params: {}, + headers: {}, + request_options: {} + ) + + expect(result).to be_success + end + end + + describe '#make_request' do + it 'returns the parsed response body directly' do + stub_request(:get, 'https://api.uploadcare.com/project/') + .to_return( + status: 200, + body: { name: 'Test Project' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + body = rest.make_request(method: :get, path: '/project/', params: {}, headers: {}, request_options: {}) + + expect(body).to eq({ 'name' => 'Test Project' }) + end + + it 'raises an error on failure instead of returning a Result' do + stub_request(:get, 'https://api.uploadcare.com/project/') + .to_return( + status: 404, + body: { detail: 'Not found.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + expect do + rest.make_request(method: :get, path: '/project/', params: {}, headers: {}, request_options: {}) + end.to raise_error(Uploadcare::Exception::NotFoundError) + end + end + + describe '#request' do + it 'wraps make_request in a Result' do + stub_request(:delete, 'https://api.uploadcare.com/files/test-uuid/storage/') + .to_return( + status: 200, + body: { uuid: 'test-uuid' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.request( + method: :delete, + path: '/files/test-uuid/storage/', + params: {}, + headers: {}, + request_options: {} + ) + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + end + + describe 'endpoint accessors' do + it 'returns a Files endpoint' do + expect(rest.files).to be_a(Uploadcare::Api::Rest::Files) + end + + it 'returns a Groups endpoint' do + expect(rest.groups).to be_a(Uploadcare::Api::Rest::Groups) + end + + it 'returns a Project endpoint' do + expect(rest.project).to be_a(Uploadcare::Api::Rest::Project) + end + + it 'returns a Webhooks endpoint' do + expect(rest.webhooks).to be_a(Uploadcare::Api::Rest::Webhooks) + end + + it 'returns a FileMetadata endpoint' do + expect(rest.file_metadata).to be_a(Uploadcare::Api::Rest::FileMetadata) + end + + it 'returns an Addons endpoint' do + expect(rest.addons).to be_a(Uploadcare::Api::Rest::Addons) + end + + it 'returns a DocumentConversions endpoint' do + expect(rest.document_conversions).to be_a(Uploadcare::Api::Rest::DocumentConversions) + end + + it 'returns a VideoConversions endpoint' do + expect(rest.video_conversions).to be_a(Uploadcare::Api::Rest::VideoConversions) + end + + it 'memoizes endpoint instances' do + files = rest.files + groups = rest.groups + project = rest.project + webhooks = rest.webhooks + file_metadata = rest.file_metadata + addons = rest.addons + document_conversions = rest.document_conversions + video_conversions = rest.video_conversions + + expect(rest.files).to be(files) + expect(rest.groups).to be(groups) + expect(rest.project).to be(project) + expect(rest.webhooks).to be(webhooks) + expect(rest.file_metadata).to be(file_metadata) + expect(rest.addons).to be(addons) + expect(rest.document_conversions).to be(document_conversions) + expect(rest.video_conversions).to be(video_conversions) + end + end + + describe 'request options' do + it 'applies timeout from request_options' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .to_return( + status: 200, + body: { results: [] }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.get( + path: '/files/', + params: {}, + headers: {}, + request_options: { timeout: 30, open_timeout: 10 } + ) + + expect(result).to be_success + end + end + + describe 'authentication headers' do + it 'includes authorization headers in requests' do + stub_request(:get, 'https://api.uploadcare.com/project/') + .with(headers: { 'Authorization' => /Uploadcare.Simple demopublickey:demosecretkey/ }) + .to_return( + status: 200, + body: { name: 'Demo' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = rest.get(path: '/project/', params: {}, headers: {}, request_options: {}) + + expect(result).to be_success + end + end +end diff --git a/spec/uploadcare/api/upload/files_spec.rb b/spec/uploadcare/api/upload/files_spec.rb new file mode 100644 index 00000000..98075bfb --- /dev/null +++ b/spec/uploadcare/api/upload/files_spec.rb @@ -0,0 +1,388 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tempfile' +require 'stringio' + +RSpec.describe Uploadcare::Api::Upload::Files do + subject(:files) { described_class.new(upload: upload_client) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + + let(:upload_client) { Uploadcare::Api::Upload.new(config: config) } + + describe '#initialize' do + it 'stores the upload client' do + expect(files.upload).to eq(upload_client) + end + end + + describe '#direct' do + let(:tempfile) do + file = Tempfile.new(['test', '.jpg']) + file.write('fake image data') + file.rewind + file + end + + after { tempfile.close! } + + it 'uploads a file and returns a Result' do + stub_request(:post, 'https://upload.uploadcare.com/base/') + .to_return( + status: 200, + body: { 'test.jpg' => 'uploaded-uuid-123' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.direct(file: tempfile) + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + + it 'raises ArgumentError when file does not respond to #read' do + result = files.direct(file: 'not-a-file') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('file must be a readable IO object') + end + + it 'uploads a StringIO by normalizing it to a temp file' do + io = StringIO.new('fake image data') + + stub_request(:post, 'https://upload.uploadcare.com/base/') + .to_return( + status: 200, + body: { 'upload.bin' => 'uploaded-uuid-123' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.direct(file: io) + + expect(result).to be_success + expect(result.value!).to eq({ 'upload.bin' => 'uploaded-uuid-123' }) + end + end + + describe '#direct_many' do + it 'raises ArgumentError when files is not an array' do + result = files.direct_many(files: 'not-an-array') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('files must be an array') + end + + it 'raises ArgumentError when files is empty' do + result = files.direct_many(files: []) + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('files cannot be empty') + end + + it 'uses a stable encoded field key when duplicate filenames collide' do + params = {} + io_class = Class.new(StringIO) do + def original_filename + 'photo.jpg' + end + end + + io_one = Uploadcare::Internal::UploadIo.wrap(io_class.new('a')) + io_two = Uploadcare::Internal::UploadIo.wrap(io_class.new('b')) + + begin + files.send(:form_data_for, io_one, params, field_index: 0) + files.send(:form_data_for, io_two, params, field_index: 1) + ensure + io_one.close! + io_two.close! + end + + keys = params.keys + expect(keys.length).to eq(2) + expect(keys.first).not_to match(/\A__uploadcare_form_/) + expect(keys.last).to match(/\A__uploadcare_form_1__/) + end + end + + describe '#from_url' do + let(:source_url) { 'https://example.com/image.jpg' } + + it 'uploads from URL in async mode and returns the token' do + stub_request(:post, 'https://upload.uploadcare.com/from_url/') + .to_return( + status: 200, + body: { token: 'upload-token-abc' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.from_url(source_url: source_url, async: true) + + expect(result).to be_success + expect(result.value!['token']).to eq('upload-token-abc') + end + + it 'raises ArgumentError for empty URL' do + result = files.from_url(source_url: '') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('URL cannot be empty') + end + + it 'raises ArgumentError for non-HTTP URL' do + result = files.from_url(source_url: 'ftp://example.com/file.jpg') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('URL must be HTTP or HTTPS') + end + + it 'includes pub_key in the request params' do + stub = stub_request(:post, 'https://upload.uploadcare.com/from_url/') + .with(body: hash_including('pub_key' => 'demopublickey', 'source_url' => source_url)) + .to_return( + status: 200, + body: { token: 'upload-token' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + files.from_url(source_url: source_url, async: true) + + expect(stub).to have_been_requested + end + + it 'preserves explicit false duplicate flags in the request params' do + stub = stub_request(:post, 'https://upload.uploadcare.com/from_url/') + .with( + body: hash_including( + 'check_URL_duplicates' => 'false', + 'save_URL_duplicates' => 'false' + ) + ) + .to_return( + status: 200, + body: { token: 'upload-token' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + files.from_url( + source_url: source_url, + async: true, + check_URL_duplicates: false, + save_URL_duplicates: false + ) + + expect(stub).to have_been_requested + end + + it 'computes exponential polling intervals with max cap' do + expect(files.send(:next_poll_sleep, initial: 1, max_interval: 2, attempt: 0)).to eq(1.0) + expect(files.send(:next_poll_sleep, initial: 1, max_interval: 2, attempt: 1)).to eq(2.0) + expect(files.send(:next_poll_sleep, initial: 1, max_interval: 2, attempt: 5)).to eq(2.0) + end + end + + describe '#from_url_status' do + it 'returns the upload status for a given token' do + stub_request(:get, 'https://upload.uploadcare.com/from_url/status/') + .with(query: hash_including('token' => 'my-token')) + .to_return( + status: 200, + body: { status: 'success', uuid: 'file-uuid', filename: 'image.jpg' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = files.from_url_status(token: 'my-token') + + expect(result).to be_success + expect(result.value!['status']).to eq('success') + end + + it 'raises ArgumentError for empty token' do + result = files.from_url_status(token: '') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('token cannot be empty') + end + end + + describe '#multipart_start' do + before do + stub_request(:post, 'https://upload.uploadcare.com/multipart/start/') + .to_return( + status: 200, + body: { + uuid: 'multipart-uuid', + parts: [ + 'https://s3.amazonaws.com/bucket/part1?sig=abc', + 'https://s3.amazonaws.com/bucket/part2?sig=def' + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'starts a multipart upload and returns UUID and presigned URLs' do + result = files.multipart_start( + filename: 'large-video.mp4', + size: 100_000_000, + content_type: 'video/mp4' + ) + + expect(result).to be_success + expect(result.value!['uuid']).to eq('multipart-uuid') + expect(result.value!['parts'].length).to eq(2) + end + + it 'raises ArgumentError for empty filename' do + result = files.multipart_start(filename: '', size: 100, content_type: 'video/mp4') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('filename cannot be empty') + end + + it 'raises ArgumentError for non-positive size' do + result = files.multipart_start(filename: 'test.mp4', size: 0, content_type: 'video/mp4') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('size must be a positive integer') + end + + it 'raises ArgumentError for empty content_type' do + result = files.multipart_start(filename: 'test.mp4', size: 100, content_type: '') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('content_type cannot be empty') + end + + it 'includes UPLOADCARE_PUB_KEY in the params' do + stub = stub_request(:post, 'https://upload.uploadcare.com/multipart/start/') + .with(body: hash_including('UPLOADCARE_PUB_KEY' => 'demopublickey')) + .to_return( + status: 200, + body: { uuid: 'mp-uuid', parts: [] }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + files.multipart_start(filename: 'test.mp4', size: 100_000_000, content_type: 'video/mp4') + + expect(stub).to have_been_requested + end + + it 'does not send part_size to multipart/start endpoint' do + stub = stub_request(:post, 'https://upload.uploadcare.com/multipart/start/') + .with do |request| + body = URI.decode_www_form(request.body).to_h + !body.key?('part_size') + end + .to_return( + status: 200, + body: { uuid: 'mp-uuid', parts: [] }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + files.multipart_start( + filename: 'test.mp4', + size: 100_000_000, + content_type: 'video/mp4', + part_size: 1024 + ) + + expect(stub).to have_been_requested + end + end + + describe '#multipart_complete' do + before do + stub_request(:post, 'https://upload.uploadcare.com/multipart/complete/') + .to_return( + status: 200, + body: { + uuid: 'multipart-uuid', + filename: 'large-video.mp4', + size: 100_000_000, + is_stored: true + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'completes a multipart upload and returns file info' do + result = files.multipart_complete(uuid: 'multipart-uuid') + + expect(result).to be_success + expect(result.value!['uuid']).to eq('multipart-uuid') + expect(result.value!['filename']).to eq('large-video.mp4') + end + + it 'raises ArgumentError for empty uuid' do + result = files.multipart_complete(uuid: '') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('uuid cannot be empty') + end + + it 'includes UPLOADCARE_PUB_KEY and uuid in the params' do + stub = stub_request(:post, 'https://upload.uploadcare.com/multipart/complete/') + .with(body: hash_including('UPLOADCARE_PUB_KEY' => 'demopublickey', 'uuid' => 'mp-uuid')) + .to_return( + status: 200, + body: { uuid: 'mp-uuid' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + files.multipart_complete(uuid: 'mp-uuid') + + expect(stub).to have_been_requested + end + end + + describe '#info' do + before do + stub_request(:get, 'https://upload.uploadcare.com/info/') + .with(query: hash_including('pub_key' => 'demopublickey', 'file_id' => 'file-uuid')) + .to_return( + status: 200, + body: { + uuid: 'file-uuid', + filename: 'test.jpg', + size: 12_345, + is_image: true + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns file info from the Upload API' do + result = files.info(file_id: 'file-uuid') + + expect(result).to be_success + expect(result.value!['uuid']).to eq('file-uuid') + expect(result.value!['filename']).to eq('test.jpg') + end + + it 'raises ArgumentError for empty file_id' do + result = files.info(file_id: '') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('file_id cannot be empty') + end + end +end diff --git a/spec/uploadcare/api/upload/groups_spec.rb b/spec/uploadcare/api/upload/groups_spec.rb new file mode 100644 index 00000000..3a9f857b --- /dev/null +++ b/spec/uploadcare/api/upload/groups_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Upload::Groups do + subject(:groups) { described_class.new(upload: upload_client) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + + let(:upload_client) { Uploadcare::Api::Upload.new(config: config) } + + describe '#initialize' do + it 'stores the upload client' do + expect(groups.upload).to eq(upload_client) + end + end + + describe '#create' do + let(:file_uuids) { %w[uuid-1 uuid-2 uuid-3] } + + before do + stub_request(:post, 'https://upload.uploadcare.com/group/') + .to_return( + status: 200, + body: { + id: 'group-uuid~3', + files_count: 3, + files: [ + { uuid: 'uuid-1' }, + { uuid: 'uuid-2' }, + { uuid: 'uuid-3' } + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'creates a group from file UUIDs and returns group info' do + result = groups.create(files: file_uuids) + + expect(result).to be_success + expect(result.value!['id']).to eq('group-uuid~3') + expect(result.value!['files_count']).to eq(3) + end + + it 'includes pub_key in the request params' do + stub = stub_request(:post, 'https://upload.uploadcare.com/group/') + .with(body: hash_including('pub_key' => 'demopublickey')) + .to_return( + status: 200, + body: { id: 'group-uuid~3', files_count: 3 }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + groups.create(files: file_uuids) + + expect(stub).to have_been_requested + end + + it 'sends each file UUID as files[N] parameter' do + stub = stub_request(:post, 'https://upload.uploadcare.com/group/') + .with(body: /files%5B0%5D=uuid-1.*files%5B1%5D=uuid-2.*files%5B2%5D=uuid-3/) + .to_return( + status: 200, + body: { id: 'group-uuid~3', files_count: 3 }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + groups.create(files: file_uuids) + + expect(stub).to have_been_requested + end + + it 'raises ArgumentError when files is not an array' do + result = groups.create(files: 'not-an-array') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('files must be an array') + end + + it 'raises ArgumentError when files array is empty' do + result = groups.create(files: []) + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('files cannot be empty') + end + + it 'accepts objects responding to #uuid' do + file_obj = double('file', uuid: 'obj-uuid-1') + + stub = stub_request(:post, 'https://upload.uploadcare.com/group/') + .with(body: /files%5B0%5D=obj-uuid-1/) + .to_return( + status: 200, + body: { id: 'group-uuid~1', files_count: 1 }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + groups.create(files: [file_obj]) + + expect(stub).to have_been_requested + end + + it 'accepts optional signature and expire parameters' do + stub = stub_request(:post, 'https://upload.uploadcare.com/group/') + .with(body: hash_including('signature' => 'abc123', 'expire' => '1700000000')) + .to_return( + status: 200, + body: { id: 'group-uuid~1', files_count: 1 }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + groups.create(files: ['uuid-1'], signature: 'abc123', expire: '1700000000') + + expect(stub).to have_been_requested + end + end + + describe '#info' do + let(:group_id) { 'group-uuid~3' } + + before do + stub_request(:get, 'https://upload.uploadcare.com/group/info/') + .with(query: hash_including('pub_key' => 'demopublickey', 'group_id' => group_id)) + .to_return( + status: 200, + body: { + id: group_id, + files_count: 3, + files: [ + { uuid: 'uuid-1' }, + { uuid: 'uuid-2' }, + { uuid: 'uuid-3' } + ] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns group info from the Upload API' do + result = groups.info(group_id: group_id) + + expect(result).to be_success + expect(result.value!['id']).to eq(group_id) + expect(result.value!['files_count']).to eq(3) + end + + it 'raises ArgumentError for empty group_id' do + result = groups.info(group_id: '') + + expect(result).to be_failure + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('group_id cannot be empty') + end + + it 'includes pub_key in the query params' do + stub = stub_request(:get, 'https://upload.uploadcare.com/group/info/') + .with(query: hash_including('pub_key' => 'demopublickey')) + .to_return( + status: 200, + body: { id: group_id }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + groups.info(group_id: group_id) + + expect(stub).to have_been_requested + end + end +end diff --git a/spec/uploadcare/api/upload_spec.rb b/spec/uploadcare/api/upload_spec.rb new file mode 100644 index 00000000..5982d4ff --- /dev/null +++ b/spec/uploadcare/api/upload_spec.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Api::Upload do + subject(:upload) { described_class.new(config: config) } + + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + + describe '#initialize' do + it 'stores the config' do + expect(upload.config).to eq(config) + end + + it 'creates a Faraday connection to the upload API root' do + expect(upload.connection).to be_a(Faraday::Connection) + expect(upload.connection.url_prefix.to_s).to eq('https://upload.uploadcare.com/') + end + + it 'defaults to global configuration when no config is provided' do + Uploadcare.configure do |c| + c.public_key = 'globalpubkey' + c.secret_key = 'globalseckey' + c.auth_type = 'Uploadcare.Simple' + end + + default_upload = described_class.new + expect(default_upload.config.public_key).to eq('globalpubkey') + end + end + + describe '#get' do + it 'returns a successful Result on 200' do + stub_request(:get, 'https://upload.uploadcare.com/info/') + .with(query: hash_including('pub_key' => 'demopublickey')) + .to_return( + status: 200, + body: { uuid: 'file-uuid', filename: 'test.jpg' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = upload.get( + path: 'info/', + params: { 'pub_key' => 'demopublickey', 'file_id' => 'file-uuid' }, + headers: {}, + request_options: {} + ) + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + + it 'returns a failure Result on error' do + stub_request(:get, 'https://upload.uploadcare.com/info/') + .with(query: hash_including({})) + .to_return( + status: 404, + body: { detail: 'Not found.' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = upload.get(path: 'info/', params: { 'pub_key' => 'demopublickey' }, headers: {}, request_options: {}) + + expect(result).to be_failure + end + end + + describe '#post' do + it 'returns a successful Result on 200' do + stub_request(:post, 'https://upload.uploadcare.com/base/') + .to_return( + status: 200, + body: { 'test.jpg' => 'file-uuid-123' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = upload.post( + path: 'base/', + params: { 'UPLOADCARE_PUB_KEY' => 'demopublickey' }, + headers: {}, + request_options: {} + ) + + expect(result).to be_a(Uploadcare::Result) + expect(result).to be_success + end + + it 'returns a failure Result on error' do + stub_request(:post, 'https://upload.uploadcare.com/base/') + .to_return( + status: 400, + body: { detail: 'Bad request' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = upload.post(path: 'base/', params: {}, headers: {}, request_options: {}) + + expect(result).to be_failure + end + end + + describe '#upload_part_to_url' do + let(:presigned_url) { 'https://s3.amazonaws.com/bucket/part?signature=abc123' } + + it 'uploads binary data to the presigned URL via PUT' do + stub_request(:put, presigned_url) + .with( + body: 'binary-data-here', + headers: { 'Content-Type' => 'application/octet-stream' } + ) + .to_return(status: 200, body: '', headers: {}) + + result = upload.upload_part_to_url(presigned_url, 'binary-data-here') + + expect(result).to be true + end + + it 'reads IO objects before uploading' do + io = StringIO.new('io-binary-data') + + stub_request(:put, presigned_url) + .with( + body: 'io-binary-data', + headers: { 'Content-Type' => 'application/octet-stream' } + ) + .to_return(status: 200, body: '', headers: {}) + + result = upload.upload_part_to_url(presigned_url, io) + + expect(result).to be true + end + + it 'rejects non-https presigned URLs' do + expect do + upload.upload_part_to_url('http://s3.amazonaws.com/bucket/part', 'data') + end.to raise_error(ArgumentError, 'presigned_url must use HTTPS') + end + + it 'rejects localhost presigned URLs' do + expect do + upload.upload_part_to_url('https://localhost/bucket/part', 'data') + end.to raise_error(ArgumentError, 'presigned_url cannot target localhost') + end + + it 'rejects private IP presigned URLs' do + expect do + upload.upload_part_to_url('https://10.0.0.5/bucket/part', 'data') + end.to raise_error(ArgumentError, 'presigned_url cannot target a private address') + end + + it 'raises MultipartUploadError after max retries on non-2xx responses' do + stub_request(:put, presigned_url) + .to_return(status: 500, body: 'Internal Server Error', headers: {}) # force Zeitwerk to load upload_error.rb + expect do + upload.upload_part_to_url(presigned_url, 'data', max_retries: 1) + end.to raise_error(Uploadcare::Exception::MultipartUploadError, /Failed to upload part/) + end + + it 'retries on failure up to max_retries times' do + call_count = 0 + stub_request(:put, presigned_url).to_return do |_request| + call_count += 1 + if call_count < 2 + { status: 500, body: 'error' } + else + { status: 200, body: '' } + end + end + + result = upload.upload_part_to_url(presigned_url, 'data', max_retries: 3) + + expect(result).to be true + expect(call_count).to eq(2) + end + + it 'treats max_retries as the number of retries after the initial attempt' do + call_count = 0 + + stub_request(:put, presigned_url).to_return do |_request| + call_count += 1 + { status: 500, body: 'error' } + end + + expect do + upload.upload_part_to_url(presigned_url, 'data', max_retries: 2) + end.to raise_error(Uploadcare::Exception::MultipartUploadError, /Failed to upload part after 2 retries/) + + expect(call_count).to eq(3) + end + end + + describe 'endpoint accessors' do + it 'returns a Files endpoint' do + expect(upload.files).to be_a(Uploadcare::Api::Upload::Files) + end + + it 'returns a Groups endpoint' do + expect(upload.groups).to be_a(Uploadcare::Api::Upload::Groups) + end + + it 'memoizes endpoint instances' do + files = upload.files + groups = upload.groups + + expect(upload.files).to be(files) + expect(upload.groups).to be(groups) + end + end + + describe 'request options' do + it 'applies timeout from request_options' do + stub_request(:get, 'https://upload.uploadcare.com/info/') + .with(query: hash_including({})) + .to_return( + status: 200, + body: { uuid: 'test' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = upload.get( + path: 'info/', + params: { 'pub_key' => 'demopublickey' }, + headers: {}, + request_options: { timeout: 30, open_timeout: 10 } + ) + + expect(result).to be_success + end + end +end diff --git a/spec/uploadcare/client/addons_client_spec.rb b/spec/uploadcare/client/addons_client_spec.rb deleted file mode 100644 index 0d2bb291..00000000 --- a/spec/uploadcare/client/addons_client_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe AddonsClient do - subject { AddonsClient.new } - - describe 'uc_clamav_virus_scan' do - it 'scans the file for viruses' do - VCR.use_cassette('uc_clamav_virus_scan') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - params = { purge_infected: true } - response = subject.uc_clamav_virus_scan(uuid, params) - expect(response.success).to eq({ request_id: '34abf037-5384-4e38-bad4-97dd48e79acd' }) - end - end - end - - describe 'uc_clamav_virus_scan_status' do - it 'checking the status of a virus scanned file' do - VCR.use_cassette('uc_clamav_virus_scan_status') do - uuid = '34abf037-5384-4e38-bad4-97dd48e79acd' - response = subject.uc_clamav_virus_scan_status(uuid) - expect(response.success).to eq({ status: 'done' }) - end - end - end - - describe 'ws_rekognition_detect_labels' do - it 'executes aws rekognition' do - VCR.use_cassette('ws_rekognition_detect_labels') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - response = subject.ws_rekognition_detect_labels(uuid) - expect(response.success).to eq({ request_id: '0f4598dd-d168-4272-b49e-e7f9d2543542' }) - end - end - end - - describe 'ws_rekognition_detect_labels_status' do - it 'checking the status of a recognized file' do - VCR.use_cassette('ws_rekognition_detect_labels_status') do - uuid = '0f4598dd-d168-4272-b49e-e7f9d2543542' - response = subject.ws_rekognition_detect_labels_status(uuid) - expect(response.success).to eq({ status: 'done' }) - end - end - end - - describe 'remove_bg' do - it 'executes background image removal' do - VCR.use_cassette('remove_bg') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - params = { crop: true, type_level: '2' } - response = subject.remove_bg(uuid, params) - expect(response.success).to eq({ request_id: 'c3446e41-9eb0-4301-aeb4-356d0fdcf9af' }) - end - end - end - - describe 'remove_bg_status' do - it 'checking the status background image removal file' do - VCR.use_cassette('remove_bg_status') do - uuid = 'c3446e41-9eb0-4301-aeb4-356d0fdcf9af' - response = subject.remove_bg_status(uuid) - expect(response.success).to( - eq({ status: 'done', result: { file_id: 'bc37b996-916d-4ed7-b230-fa71a4290cb3' } }) - ) - end - end - end - - describe 'ws_rekognition_detect_moderation_labels' do - it 'executes aws rekognition' do - VCR.use_cassette('ws_rekognition_detect_moderation_labels') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - response = subject.ws_rekognition_detect_moderation_labels(uuid) - expect(response.success).to eq({ request_id: '0f4598dd-d168-4272-b49e-e7f9d2543542' }) - end - end - end - - describe 'ws_rekognition_detect_moderation_labels_status' do - it 'checking the status of a recognized file' do - VCR.use_cassette('ws_rekognition_detect_moderation_labels_status') do - uuid = '0f4598dd-d168-4272-b49e-e7f9d2543542' - response = subject.ws_rekognition_detect_moderation_labels_status(uuid) - expect(response.success).to eq({ status: 'done' }) - end - end - end - end - end -end diff --git a/spec/uploadcare/client/conversion/document_conversion_client_spec.rb b/spec/uploadcare/client/conversion/document_conversion_client_spec.rb deleted file mode 100644 index ef4d3a87..00000000 --- a/spec/uploadcare/client/conversion/document_conversion_client_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - module Conversion - RSpec.describe DocumentConversionClient do - describe 'successfull conversion' do - describe 'convert_many' do - subject { described_class.new.convert_many(array_of_params, **options) } - - shared_examples 'succeeds documents conversion' do - it 'returns a convert documents response' do - expect(subject).to be_success - end - end - - let(:array_of_params) do - [ - { - uuid: 'a4b9db2f-1591-4f4c-8f68-94018924525d', - format: 'png', - page: 1 - } - ] - end - let(:options) { { store: false } } - - context 'when all params are present', vcr: 'document_convert_convert_many' do - it_behaves_like 'succeeds documents conversion' - end - - context 'multipage conversion', vcr: 'document_convert_to_multipage' do - let(:array_of_params) do - [ - { - uuid: '23d29586-713e-4152-b400-05fb54730453', - format: 'png' - } - ] - end - let(:options) { { store: '0', save_in_group: '1' } } - - it_behaves_like 'succeeds documents conversion' - end - end - - describe 'get document conversion status' do - subject { described_class.new.get_conversion_status(token) } - - let(:token) { '21120333' } - - it 'returns a document conversion status data' do - VCR.use_cassette('document_convert_get_status') do - expect(subject).to be_success - end - end - end - end - - describe 'conversion with error' do - shared_examples 'failed document conversion' do - it 'raises a conversion error' do - VCR.use_cassette('document_convert_convert_many_with_error') do - expect(subject).to be_failure - end - end - end - - describe 'convert_many' do - subject { described_class.new.convert_many(array_of_params, **options) } - - let(:array_of_params) do - [ - { - uuid: '86c54d9a-3453-4b12-8dcc-49883ae8f084', - format: 'jpg', - page: 1 - } - ] - end - let(:options) { { store: false } } - - context 'when the target_format is not a supported' do - let(:message) { /target_format is not a supported/ } - - it_behaves_like 'failed document conversion' - end - end - end - end - end - end -end diff --git a/spec/uploadcare/client/conversion/video_convertion_client_spec.rb b/spec/uploadcare/client/conversion/video_convertion_client_spec.rb deleted file mode 100644 index f8b22354..00000000 --- a/spec/uploadcare/client/conversion/video_convertion_client_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - module Conversion - RSpec.describe Uploadcare::Client::Conversion::VideoConversionClient do - describe 'successfull conversion' do - describe 'convert_many' do - subject { described_class.new.convert_many(array_of_params, **options) } - - shared_examples 'requesting video conversion' do - it 'returns a convert video response' do - VCR.use_cassette('video_convert_convert_many') do - expect(subject).to be_success - end - end - end - - let(:array_of_params) do - [ - { - uuid: 'e30112d7-3a90-4931-b2c5-688cbb46d3ac', - size: { resize_mode: 'change_ratio', width: '600', height: '400' }, - quality: 'best', - format: 'ogg', - cut: { start_time: '0:0:0.0', length: '0:0:1.0' }, - thumbs: { N: 2, number: 1 } - } - ] - end - let(:options) { { store: false } } - - context 'when all params are present' do - it_behaves_like 'requesting video conversion' - end - - %i[size quality format cut thumbs].each do |param| - context "when only :#{param} param is present" do - let(:arguments) { super().slice(:uuid, param) } - - it_behaves_like 'requesting video conversion' - end - end - end - - describe 'get video conversion status' do - subject { described_class.new.get_conversion_status(token) } - - let(:token) { '911933811' } - - it 'returns a video conversion status data' do - VCR.use_cassette('video_convert_get_status') do - expect(subject).to be_success - end - end - end - end - - describe 'conversion with error' do - shared_examples 'requesting video conversion' do - it 'raises a conversion error' do - VCR.use_cassette('video_convert_convert_many_with_error') do - expect(subject).to be_failure - end - end - end - - describe 'convert_many' do - subject { described_class.new.convert_many(array_of_params, **options) } - - let(:array_of_params) do - [ - { - uuid: 'e30112d7-3a90-4931-b2c5-688cbb46d3ac', - size: { resize_mode: 'change_ratio' }, - quality: 'best', - format: 'ogg', - cut: { start_time: '0:0:0.0', length: '0:0:1.0' }, - thumbs: { N: 2, number: 1 } - } - ] - end - let(:options) { { store: false } } - - context 'when no width and height are provided' do - let(:message) { /CDN Path error/ } - - it_behaves_like 'requesting video conversion' - end - end - end - end - end - end -end diff --git a/spec/uploadcare/client/file_client_spec.rb b/spec/uploadcare/client/file_client_spec.rb deleted file mode 100644 index fe6818bd..00000000 --- a/spec/uploadcare/client/file_client_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe FileClient do - subject { FileClient.new } - - describe 'info' do - it 'shows insider info about that file' do - VCR.use_cassette('rest_file_info') do - uuid = '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' - file = subject.info(uuid) - expect(file.value![:uuid]).to eq(uuid) - end - end - - it 'show raise argument error if public_key is blank' do - Uploadcare.config.public_key = '' - VCR.use_cassette('rest_file_info') do - uuid = '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' - expect { subject.info(uuid) }.to raise_error(AuthError, 'Public Key is blank.') - end - end - - it 'show raise argument error if secret_key is blank' do - Uploadcare.config.secret_key = '' - VCR.use_cassette('rest_file_info') do - uuid = '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' - expect { subject.info(uuid) }.to raise_error(AuthError, 'Secret Key is blank.') - end - end - - it 'show raise argument error if secret_key is nil' do - Uploadcare.config.secret_key = nil - VCR.use_cassette('rest_file_info') do - uuid = '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' - expect { subject.info(uuid) }.to raise_error(AuthError, 'Secret Key is blank.') - end - end - - it 'supports extra params like include' do - VCR.use_cassette('rest_file_info') do - uuid = '640fe4b7-7352-42ca-8d87-0e4387957157' - file = subject.info(uuid, { include: 'appdata' }) - expect(file.value![:uuid]).to eq(uuid) - expect(file.value![:appdata]).not_to be_empty - end - end - - it 'shows nothing on invalid file' do - VCR.use_cassette('rest_file_info_fail') do - uuid = 'nonexistent' - expect { subject.info(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'delete' do - it 'deletes a file' do - VCR.use_cassette('rest_file_delete') do - uuid = '158e7c82-8246-4017-9f17-0798e18c91b0' - response = subject.delete(uuid) - response_value = response.value! - expect(response_value[:datetime_removed]).not_to be_empty - expect(response_value[:uuid]).to eq(uuid) - end - end - end - - describe 'store' do - it 'changes file`s status to stored' do - VCR.use_cassette('rest_file_store') do - uuid = 'e9a9f291-cc52-4388-bf65-9feec1c75ff9' - response = subject.store(uuid) - expect(response.value![:datetime_stored]).not_to be_empty - end - end - end - end - end -end diff --git a/spec/uploadcare/client/file_list_client_spec.rb b/spec/uploadcare/client/file_list_client_spec.rb deleted file mode 100644 index e0ac1a37..00000000 --- a/spec/uploadcare/client/file_list_client_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe FileListClient do - subject { FileListClient.new } - - describe 'file_list' do - it 'returns paginated list with files data' do - VCR.use_cassette('rest_file_list') do - file_list = subject.file_list.value! - expected_fields = %i[total per_page results] - expected_fields.each do |field| - expect(file_list[field]).not_to be_nil - end - end - end - - it 'processes options' do - VCR.use_cassette('rest_file_list_limited') do - first_page = subject.file_list(limit: 2).value! - second_page = subject.file_list(limit: 2).value! - expect(first_page[:per_page]).to eq(2) - expect(first_page[:results].length).to eq(2) - expect(first_page[:results]).not_to eq(second_page[:result]) - end - end - end - - describe 'batch_store' do - it 'changes files` statuses to stored' do - VCR.use_cassette('rest_file_batch_store') do - uuids = %w[e9a9f291-cc52-4388-bf65-9feec1c75ff9 c724feac-86f7-447c-b2d6-b0ced220173d] - response = subject.batch_store(uuids) - response_value = response.value! - expect(uuids.all? { |uuid| response_value.to_s.include?(uuid) }).to be true - end - end - - context 'invalid uuids' do - it 'returns a list of problems' do - VCR.use_cassette('rest_file_batch_store_fail') do - uuids = %w[nonexistent other_nonexistent] - response = subject.batch_store(uuids) - expect(response.success[:files]).to be_nil - expect(response.success[:problems]).not_to be_empty - end - end - end - end - - describe 'batch_delete' do - it 'changes files` statuses to stored' do - VCR.use_cassette('rest_file_batch_delete') do - uuids = %w[935ff093-a5cf-48c5-81cf-208511bac6e6 63be5a6e-9b6b-454b-8aec-9136d5f83d0c] - response = subject.batch_delete(uuids) - response_value = response.value! - expect(response_value[:result][0][:datetime_removed]).not_to be_empty - end - end - - context 'invalid uuids' do - it 'returns a list of problems' do - VCR.use_cassette('rest_file_batch_delete_fail') do - uuids = %w[nonexistent other_nonexistent] - response = subject.batch_delete(uuids) - expect(response.success[:files]).to be_nil - expect(response.success[:problems]).not_to be_empty - end - end - end - end - end - end -end diff --git a/spec/uploadcare/client/file_metadata_client_spec.rb b/spec/uploadcare/client/file_metadata_client_spec.rb deleted file mode 100644 index 3edf8a00..00000000 --- a/spec/uploadcare/client/file_metadata_client_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe FileMetadataClient do - subject { FileMetadataClient.new } - - let(:uuid) { '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' } - let(:key) { 'subsystem' } - - describe 'index' do - it 'shows file metadata keys and values' do - VCR.use_cassette('file_metadata_index') do - response = subject.index(uuid) - expect(response.value![:subsystem]).to eq('test') - end - end - end - - describe 'show' do - it 'shows file metadata value by key' do - VCR.use_cassette('file_metadata_show') do - response = subject.show(uuid, key) - expect(response.value!).to eq('test') - end - end - end - - describe 'update' do - it 'updates file metadata value by key' do - VCR.use_cassette('file_metadata_update') do - new_value = 'new test value' - response = subject.update(uuid, key, new_value) - expect(response.value!).to eq(new_value) - end - end - end - - describe 'delete' do - it 'deletes a file metadata key' do - VCR.use_cassette('file_metadata_delete') do - response = subject.delete(uuid, key) - expect(response.value!).to be_nil - expect(response.success?).to be_truthy - end - end - end - end - end -end diff --git a/spec/uploadcare/client/group_client_spec.rb b/spec/uploadcare/client/group_client_spec.rb deleted file mode 100644 index 1e97ed6e..00000000 --- a/spec/uploadcare/client/group_client_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe GroupClient do - subject { GroupClient.new } - let!(:uuids) { %w[8ca6e9fa-c6dd-4027-a0fc-b620611f7023 b8a11440-6fcc-4285-a24d-cc8c60259fec] } - - describe 'create' do - it 'creates a group' do - VCR.use_cassette('upload_create_group') do - response = subject.create(uuids) - response_body = response.success - expect(response_body[:files_count]).to eq 2 - %i[id datetime_created datetime_stored files_count cdn_url url files].each do |key| - expect(response_body).to have_key key - end - expect(response_body[:url]).to include 'https://api.uploadcare.com/groups' - end - end - context 'array of Entity::Files' do - it 'creates a group' do - VCR.use_cassette('upload_create_group_from_files') do - files = uuids.map { |uuid| Uploadcare::Entity::File.new(uuid: uuid) } - response = subject.create(files) - response_body = response.success - expect(response_body[:files_count]).to eq 2 - end - end - end - end - - describe 'info' do - it 'returns group info' do - VCR.use_cassette('upload_group_info') do - response = subject.info('bbc75785-9016-4656-9c6e-64a76b45b0b8~2') - response_body = response.success - %i[id datetime_created datetime_stored files_count cdn_url url files].each do |key| - expect(response_body).to have_key key - end - end - end - end - end - end -end diff --git a/spec/uploadcare/client/multipart_upload/chunks_client_spec.rb b/spec/uploadcare/client/multipart_upload/chunks_client_spec.rb deleted file mode 100644 index 4296cc50..00000000 --- a/spec/uploadcare/client/multipart_upload/chunks_client_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - module MultipartUpload - RSpec.describe ChunksClient do - subject { ChunksClient } - # Replace this file with actual big file when rewriting fixtures - let!(:big_file) { ::File.open('spec/fixtures/big.jpeg') } - - describe 'upload_parts' do - it 'returns raw document part data' do - VCR.use_cassette('amazon_upload') do - stub = stub_request(:put, /uploadcare.s3-accelerate.amazonaws.com/) - start_response = MultipartUploaderClient.new.upload_start(big_file) - subject.upload_chunks(big_file, start_response.success[:parts]) - expect(stub).to have_been_requested.at_least_times(3) - end - end - end - end - end - end -end diff --git a/spec/uploadcare/client/multipart_upload_client_spec.rb b/spec/uploadcare/client/multipart_upload_client_spec.rb deleted file mode 100644 index 5842eb59..00000000 --- a/spec/uploadcare/client/multipart_upload_client_spec.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe MultipartUploaderClient do - subject { MultipartUploaderClient.new } - let!(:small_file) { ::File.open('spec/fixtures/kitten.jpeg') } - # Replace this file with actual big file when rewriting fixtures - let!(:big_file) { ::File.open('spec/fixtures/big.jpeg') } - - describe 'upload_start' do - context 'small file' do - it 'doesnt upload small files' do - VCR.use_cassette('upload_multipart_upload_start_small') do - expect { subject.upload_start(small_file) }.to raise_error(RequestError) - end - end - end - - context 'large file' do - it 'returns links for upload' do - allow_any_instance_of(HTTP::FormData::File).to receive(:size).and_return(100 * 1024 * 1024) - VCR.use_cassette('upload_multipart_upload_start_large') do - response = subject.upload_start(small_file) - expect(response.success[:parts].count).to eq 20 - end - end - end - end - - describe 'upload_complete' do - context 'unfinished' do - it 'informs about unfinished upload' do - VCR.use_cassette('upload_multipart_upload_complete_unfinished') do - uuid = '7d9f495a-2834-4a2a-a2b3-07dbaf80ac79' - msg = 'File size mismatch. Not all parts uploaded?' - expect { subject.upload_complete(uuid) }.to raise_error(RequestError, /#{msg}/) - end - end - end - - context 'wrong uid' do - it 'informs that file is not found' do - VCR.use_cassette('upload_multipart_upload_complete_wrong_id') do - msg = 'File is not found' - expect { subject.upload_complete('nonexistent') }.to raise_error(RequestError, /#{msg}/) - end - end - end - - context 'already uploaded' do - it 'returns file data' do - VCR.use_cassette('upload_multipart_upload_complete') do - uuid = 'd8c914e3-3aef-4976-b0b6-855a9638da2d' - msg = 'File is already uploaded' - expect { subject.upload_complete(uuid) }.to raise_error(RequestError, /#{msg}/) - end - end - end - end - - describe 'upload' do - it 'does the entire multipart upload routine' do - VCR.use_cassette('upload_multipart_upload') do - # Minimum size for size to be valid for multiupload is 10 mb - Uploadcare.config.multipart_size_threshold = 10 * 1024 * 1024 - response = subject.upload(big_file) - response_value = response.value! - expect(response_value[:uuid]).not_to be_empty - end - end - - it 'returns server answer if file is too small' do - VCR.use_cassette('upload_multipart_upload_small') do - msg = 'File size can not be less than 10485760 bytes' - expect { subject.upload(small_file) }.to raise_error(RequestError, /#{msg}/) - end - end - end - end - end -end diff --git a/spec/uploadcare/client/project_client_spec.rb b/spec/uploadcare/client/project_client_spec.rb deleted file mode 100644 index 72a8e6cd..00000000 --- a/spec/uploadcare/client/project_client_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe ProjectClient do - before do - Uploadcare.config.public_key = 'foo' - end - - it 'requests info about target project' do - VCR.use_cassette('project') do - response = ProjectClient.new.show - expect(response.value![:pub_key]).to eq(Uploadcare.config.public_key) - end - end - end - end -end diff --git a/spec/uploadcare/client/rest_group_client_spec.rb b/spec/uploadcare/client/rest_group_client_spec.rb deleted file mode 100644 index 654ed506..00000000 --- a/spec/uploadcare/client/rest_group_client_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe RestGroupClient do - subject { RestGroupClient.new } - - describe 'store' do - it 'stores all files in a group' do - VCR.use_cassette('rest_store_group') do - group_id = '47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2' - response = subject.store(group_id) - expect(response.success).to be_nil - end - end - end - - describe 'info' do - it 'gets a file group by its ID.' do - VCR.use_cassette('rest_info_group') do - group_id = '47e6cf32-e5a8-4ff4-b48f-14d7304b42dd~2' - response = subject.info(group_id) - response_body = response.success - expect(response_body[:files_count]).to eq(2) - %i[id datetime_created files_count cdn_url url files].each { |key| expect(response_body).to have_key(key) } - end - end - end - - describe 'list' do - it 'returns paginated list of groups' do - VCR.use_cassette('rest_list_groups') do - response = subject.list - response_value = response.value! - expect(response_value[:results]).to be_a_kind_of(Array) - expect(response_value[:total]).to be_a_kind_of(Integer) - end - end - - it 'accepts params' do - VCR.use_cassette('rest_list_groups_limited') do - response = subject.list(limit: 2) - response_value = response.value! - expect(response_value[:per_page]).to eq 2 - end - end - end - - describe 'delete' do - it 'deletes a file group' do - VCR.use_cassette('upload_group_delete') do - response = subject.delete('bbc75785-9016-4656-9c6e-64a76b45b0b8~2') - expect(response.value!).to be_nil - expect(response.success?).to be_truthy - end - end - end - end - end -end diff --git a/spec/uploadcare/client/uploader_client_spec.rb b/spec/uploadcare/client/uploader_client_spec.rb deleted file mode 100644 index d4bc797f..00000000 --- a/spec/uploadcare/client/uploader_client_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe UploaderClient do - subject { described_class.new } - - describe 'upload' do - let(:file) { ::File.open('spec/fixtures/kitten.jpeg') } - let(:another_file) { ::File.open('spec/fixtures/another_kitten.jpeg') } - - it 'uploads a file' do - VCR.use_cassette('upload_upload') do - response = subject.upload(file, metadata: { subsystem: 'test' }) - expect(response.success?).to be true - end - end - - it 'uploads multiple files in one request' do - VCR.use_cassette('upload_upload_many') do - response = subject.upload_many([file, another_file]) - expect(response.success?).to be true - expect(response.success.length).to eq 2 - end - end - end - end - end -end diff --git a/spec/uploadcare/client/webhook_client_spec.rb b/spec/uploadcare/client/webhook_client_spec.rb deleted file mode 100644 index d3857027..00000000 --- a/spec/uploadcare/client/webhook_client_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Client - RSpec.describe WebhookClient do - subject { WebhookClient.new } - - describe 'create' do - shared_examples 'creating a webhook' do - it 'creates a webhook' do - VCR.use_cassette('rest_webhook_create') do - response = subject.create(params) - response_value = response.value! - - expect(response_value[:id]).not_to be nil - end - end - - it 'sends the :post with params' do - VCR.use_cassette('rest_webhook_create') do - expect_any_instance_of(described_class).to receive(:post).with( - uri: '/webhooks/', - content: expected_params.to_json - ) - subject.create(params) - end - end - end - - let(:params) { { target_url: 'http://ohmyz.sh', event: 'file.uploaded' } } - - context 'when a new webhook is enabled' do - let(:is_active) { true } - let(:expected_params) { params } - - context 'and when sending "true"' do - it_behaves_like 'creating a webhook' do - let(:params) { super().merge(is_active: true) } - end - end - - context 'and when sending "nil"' do - it_behaves_like 'creating a webhook' do - let(:expected_params) { params.merge(is_active: true) } - let(:params) { super().merge(is_active: nil) } - end - end - - context 'and when not sending the param' do - let(:expected_params) { params.merge(is_active: true) } - it_behaves_like 'creating a webhook' - end - - context 'and when sending a signing secret' do - let(:params) do - super().merge(is_active: true, signing_secret: '1234') - end - - it 'sends the :post with params' do - VCR.use_cassette('rest_webhook_create') do - expect_any_instance_of(described_class).to receive(:post).with( - uri: '/webhooks/', - content: params.to_json - ) - subject.create(params) - end - end - end - end - - context 'when a new webhook is disabled' do - let(:is_active) { false } - let(:expected_params) { params } - - context 'and when sending "false"' do - it_behaves_like 'creating a webhook' do - let(:params) { super().merge(is_active: false) } - end - end - end - end - - describe 'list' do - it 'lists an array of webhooks' do - VCR.use_cassette('rest_webhook_list') do - response = subject.list - response_value = response.value! - expect(response_value).to be_a_kind_of(Array) - end - end - end - - describe 'delete' do - it 'destroys a webhook' do - VCR.use_cassette('rest_webhook_destroy') do - response = subject.delete('http://example.com') - response_value = response.value! - expect(response_value).to be_nil - expect(response.success?).to be true - end - end - end - - describe 'update' do - it 'updates a webhook' do - VCR.use_cassette('rest_webhook_update') do - sub_id = 887_447 - target_url = 'https://github.com' - is_active = false - sign_secret = '1234' - response = subject.update(sub_id, target_url: target_url, is_active: is_active, signing_secret: sign_secret) - response_value = response.value! - expect(response_value[:id]).to eq(sub_id) - expect(response_value[:target_url]).to eq(target_url) - expect(response_value[:is_active]).to eq(is_active) - end - end - end - end - end -end diff --git a/spec/uploadcare/client_spec.rb b/spec/uploadcare/client_spec.rb new file mode 100644 index 00000000..ce952cc4 --- /dev/null +++ b/spec/uploadcare/client_spec.rb @@ -0,0 +1,484 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Client do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { described_class.new(config: config) } + + describe '#initialize' do + it 'accepts a config object' do + expect(client.config).to be_a(Uploadcare::Configuration) + expect(client.config).not_to equal(config) + expect(client.config.to_h).to eq(config.to_h) + end + + it 'defaults to Uploadcare.configuration when no config given' do + default_client = described_class.new + expect(default_client.config).to be_a(Uploadcare::Configuration) + end + + it 'applies overrides to the config' do + custom_client = described_class.new(config: config, public_key: 'overridden') + expect(custom_client.config.public_key).to eq('overridden') + expect(custom_client.config.secret_key).to eq('demosecretkey') + end + + it 'does not mutate the original config' do + described_class.new(config: config, public_key: 'overridden') + expect(config.public_key).to eq('demopublickey') + end + end + + describe '#with' do + it 'creates a new client with overridden config' do + new_client = client.with(public_key: 'new-key') + expect(new_client).to be_a(described_class) + expect(new_client.config.public_key).to eq('new-key') + expect(new_client.config.secret_key).to eq('demosecretkey') + end + + it 'does not modify the original client' do + client.with(public_key: 'changed') + expect(client.config.public_key).to eq('demopublickey') + end + end + + describe '#api' do + it 'returns an Api instance' do + expect(client.api).to be_a(Uploadcare::Client::Api) + end + + it 'memoizes the api instance' do + expect(client.api).to equal(client.api) + end + end + + describe Uploadcare::Client::Api do + let(:api) { Uploadcare::Client::Api.new(config: config) } + + describe '#rest' do + it 'returns a Rest API client' do + expect(api.rest).to be_a(Uploadcare::Api::Rest) + end + + it 'memoizes the rest client' do + expect(api.rest).to equal(api.rest) + end + end + + describe '#upload' do + it 'returns an Upload API client' do + expect(api.upload).to be_a(Uploadcare::Api::Upload) + end + + it 'memoizes the upload client' do + expect(api.upload).to equal(api.upload) + end + end + end + + describe '#files' do + it 'returns a FilesAccessor' do + expect(client.files).to be_a(Uploadcare::Client::FilesAccessor) + end + + it 'memoizes the accessor' do + expect(client.files).to equal(client.files) + end + end + + describe '#groups' do + it 'returns a GroupsAccessor' do + expect(client.groups).to be_a(Uploadcare::Client::GroupsAccessor) + end + + it 'memoizes the accessor' do + expect(client.groups).to equal(client.groups) + end + end + + describe '#uploads' do + it 'returns an UploadRouter' do + expect(client.uploads).to be_a(Uploadcare::Operations::UploadRouter) + end + + it 'memoizes the router' do + expect(client.uploads).to equal(client.uploads) + end + end + + describe '#project' do + it 'returns a ProjectAccessor' do + expect(client.project).to be_a(Uploadcare::Client::ProjectAccessor) + end + + it 'memoizes the accessor' do + expect(client.project).to equal(client.project) + end + end + + describe '#webhooks' do + it 'returns a WebhooksAccessor' do + expect(client.webhooks).to be_a(Uploadcare::Client::WebhooksAccessor) + end + + it 'memoizes the accessor' do + expect(client.webhooks).to equal(client.webhooks) + end + end + + describe '#addons' do + it 'returns an AddonsAccessor' do + expect(client.addons).to be_a(Uploadcare::Client::AddonsAccessor) + end + + it 'memoizes the accessor' do + expect(client.addons).to equal(client.addons) + end + end + + describe '#file_metadata' do + it 'returns a FileMetadataAccessor' do + expect(client.file_metadata).to be_a(Uploadcare::Client::FileMetadataAccessor) + end + + it 'memoizes the accessor' do + expect(client.file_metadata).to equal(client.file_metadata) + end + end + + describe '#conversions' do + it 'returns a ConversionsAccessor' do + expect(client.conversions).to be_a(Uploadcare::Client::ConversionsAccessor) + end + + it 'provides documents sub-accessor' do + expect(client.conversions.documents).to be_a(Uploadcare::Client::DocumentConversionsAccessor) + end + + it 'provides videos sub-accessor' do + expect(client.conversions.videos).to be_a(Uploadcare::Client::VideoConversionsAccessor) + end + end + + describe 'DocumentConversionsAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:document_conversions) { instance_double(Uploadcare::Api::Rest::DocumentConversions) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:document_conversions).and_return(document_conversions) + end + + it 'delegates status without constructing a throwaway resource in the accessor' do + allow(document_conversions).to receive(:status) + .with(token: 'doc-token', request_options: {}) + .and_return(Uploadcare::Result.success({ 'status' => 'finished' })) + + result = client.conversions.documents.status(token: 'doc-token') + expect(result).to be_a(Uploadcare::Resources::DocumentConversion) + expect(result.status).to eq('finished') + end + + it 'delegates info through the resource class' do + allow(document_conversions).to receive(:info) + .with(uuid: 'doc-uuid', request_options: {}) + .and_return(Uploadcare::Result.success({ 'format' => 'pdf' })) + + result = client.conversions.documents.info(uuid: 'doc-uuid') + expect(result).to be_a(Uploadcare::Resources::DocumentConversion) + expect(result.format).to eq('pdf') + end + end + + describe 'VideoConversionsAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:video_conversions) { instance_double(Uploadcare::Api::Rest::VideoConversions) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:video_conversions).and_return(video_conversions) + end + + it 'delegates status through the resource class' do + allow(video_conversions).to receive(:status) + .with(token: 'video-token', request_options: {}) + .and_return(Uploadcare::Result.success({ 'status' => 'processing' })) + + result = client.conversions.videos.status(token: 'video-token') + expect(result).to be_a(Uploadcare::Resources::VideoConversion) + expect(result.status).to eq('processing') + end + end + + describe '#upload' do + it 'delegates to uploads.upload' do + uploads = instance_double(Uploadcare::Operations::UploadRouter) + allow(client).to receive(:uploads).and_return(uploads) + + expect(uploads).to receive(:upload).with('https://example.com/img.jpg', request_options: {}) + client.upload('https://example.com/img.jpg') + end + end + + describe 'FilesAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_files) { instance_double(Uploadcare::Api::Rest::Files) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:files).and_return(rest_files) + end + + it 'delegates find to Resources::File.find' do + allow(rest_files).to receive(:info) + .and_return(Uploadcare::Result.success({ 'uuid' => file_uuid })) + + result = client.files.find(uuid: file_uuid) + expect(result).to be_a(Uploadcare::Resources::File) + end + + it 'delegates list to Resources::File.list' do + allow(rest_files).to receive(:list) + .and_return(Uploadcare::Result.success({ + 'results' => [], 'next' => nil, 'previous' => nil, + 'per_page' => 10, 'total' => 0 + })) + + result = client.files.list + expect(result).to be_a(Uploadcare::Collections::Paginated) + end + + it 'delegates batch_store to Resources::File.batch_store' do + allow(rest_files).to receive(:batch_store) + .and_return(Uploadcare::Result.success({ 'status' => 'ok', 'result' => [], 'problems' => {} })) + + result = client.files.batch_store(uuids: [file_uuid]) + expect(result).to be_a(Uploadcare::Collections::BatchResult) + end + + it 'delegates batch_delete to Resources::File.batch_delete' do + allow(rest_files).to receive(:batch_delete) + .and_return(Uploadcare::Result.success({ 'status' => 'ok', 'result' => [], 'problems' => {} })) + + result = client.files.batch_delete(uuids: [file_uuid]) + expect(result).to be_a(Uploadcare::Collections::BatchResult) + end + + it 'delegates copy_to_local to Resources::File.local_copy' do + allow(rest_files).to receive(:local_copy) + .and_return(Uploadcare::Result.success({ 'result' => { 'uuid' => file_uuid } })) + + result = client.files.copy_to_local(source: file_uuid) + expect(result).to be_a(Uploadcare::Resources::File) + end + + it 'delegates copy_to_remote to Resources::File.remote_copy' do + allow(rest_files).to receive(:remote_copy) + .and_return(Uploadcare::Result.success({ 'result' => 's3://bucket/file' })) + + result = client.files.copy_to_remote(source: file_uuid, target: 'my-storage') + expect(result).to eq('s3://bucket/file') + end + end + + describe 'GroupsAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_groups) { instance_double(Uploadcare::Api::Rest::Groups) } + let(:upload_api) { instance_double(Uploadcare::Api::Upload) } + let(:upload_groups) { double('upload_groups') } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest, upload: upload_api) } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:groups).and_return(rest_groups) + allow(upload_api).to receive(:groups).and_return(upload_groups) + end + + it 'delegates find to Resources::Group.find' do + allow(rest_groups).to receive(:info) + .and_return(Uploadcare::Result.success({ 'id' => 'group-id~3' })) + + result = client.groups.find(group_id: 'group-id~3') + expect(result).to be_a(Uploadcare::Resources::Group) + end + + it 'delegates list to Resources::Group.list' do + allow(rest_groups).to receive(:list) + .and_return(Uploadcare::Result.success({ + 'results' => [], 'next' => nil, 'previous' => nil, + 'per_page' => 10, 'total' => 0 + })) + + result = client.groups.list + expect(result).to be_a(Uploadcare::Collections::Paginated) + end + + it 'delegates create to Resources::Group.create' do + allow(upload_groups).to receive(:create) + .and_return(Uploadcare::Result.success({ 'id' => 'group-id~2' })) + + result = client.groups.create(uuids: %w[uuid-1 uuid-2]) + expect(result).to be_a(Uploadcare::Resources::Group) + end + end + + describe 'ProjectAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_project) { instance_double(Uploadcare::Api::Rest::Project) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:project).and_return(rest_project) + end + + it 'delegates current to Resources::Project.current' do + allow(rest_project).to receive(:show) + .and_return(Uploadcare::Result.success({ 'name' => 'Test' })) + + result = client.project.current + expect(result).to be_a(Uploadcare::Resources::Project) + expect(result.name).to eq('Test') + end + end + + describe 'WebhooksAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_webhooks) { instance_double(Uploadcare::Api::Rest::Webhooks) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:webhooks).and_return(rest_webhooks) + end + + it 'delegates list to Resources::Webhook.list' do + allow(rest_webhooks).to receive(:list) + .and_return(Uploadcare::Result.success([])) + + result = client.webhooks.list + expect(result).to be_an(Array) + end + + it 'delegates create to Resources::Webhook.create' do + allow(rest_webhooks).to receive(:create) + .and_return(Uploadcare::Result.success({ + 'id' => 1, 'target_url' => 'https://example.com', + 'event' => 'file.uploaded', 'is_active' => true + })) + + result = client.webhooks.create(target_url: 'https://example.com') + expect(result).to be_a(Uploadcare::Resources::Webhook) + end + + it 'delegates update to Resources::Webhook.update' do + allow(rest_webhooks).to receive(:update) + .and_return(Uploadcare::Result.success({ 'id' => 1, 'target_url' => 'https://new.com' })) + + result = client.webhooks.update(id: 1, target_url: 'https://new.com') + expect(result).to be_a(Uploadcare::Resources::Webhook) + end + + it 'delegates delete to Resources::Webhook.delete' do + allow(rest_webhooks).to receive(:delete) + .and_return(Uploadcare::Result.success(nil)) + + expect do + client.webhooks.delete(target_url: 'https://example.com') + end.not_to raise_error + end + end + + describe 'AddonsAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_addons) { instance_double(Uploadcare::Api::Rest::Addons) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:addons).and_return(rest_addons) + end + + it 'delegates aws_rekognition_detect_labels' do + allow(rest_addons).to receive(:aws_rekognition_detect_labels) + .and_return(Uploadcare::Result.success({ 'request_id' => 'req-1' })) + + result = client.addons.aws_rekognition_detect_labels(uuid: file_uuid) + expect(result).to be_a(Uploadcare::Resources::AddonExecution) + end + + it 'delegates uc_clamav_virus_scan' do + allow(rest_addons).to receive(:uc_clamav_virus_scan) + .and_return(Uploadcare::Result.success({ 'request_id' => 'req-2' })) + + result = client.addons.uc_clamav_virus_scan(uuid: file_uuid) + expect(result).to be_a(Uploadcare::Resources::AddonExecution) + end + + it 'delegates remove_bg' do + allow(rest_addons).to receive(:remove_bg) + .and_return(Uploadcare::Result.success({ 'request_id' => 'req-3' })) + + result = client.addons.remove_bg(uuid: file_uuid) + expect(result).to be_a(Uploadcare::Resources::AddonExecution) + end + end + + describe 'FileMetadataAccessor delegation' do + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_metadata) { instance_double(Uploadcare::Api::Rest::FileMetadata) } + let(:api_instance) { instance_double(Uploadcare::Client::Api, rest: rest) } + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api_instance) + allow(rest).to receive(:file_metadata).and_return(rest_metadata) + end + + it 'delegates index' do + allow(rest_metadata).to receive(:index) + .and_return(Uploadcare::Result.success({ 'key' => 'val' })) + + result = client.file_metadata.index(uuid: file_uuid) + expect(result).to eq({ 'key' => 'val' }) + end + + it 'delegates show' do + allow(rest_metadata).to receive(:show) + .and_return(Uploadcare::Result.success('val')) + + result = client.file_metadata.show(uuid: file_uuid, key: 'key') + expect(result).to eq('val') + end + + it 'delegates update' do + allow(rest_metadata).to receive(:update) + .and_return(Uploadcare::Result.success('new-val')) + + result = client.file_metadata.update(uuid: file_uuid, key: 'key', value: 'new-val') + expect(result).to eq('new-val') + end + + it 'delegates delete' do + allow(rest_metadata).to receive(:delete) + .and_return(Uploadcare::Result.success(nil)) + + expect do + client.file_metadata.delete(uuid: file_uuid, key: 'key') + end.not_to raise_error + end + end +end diff --git a/spec/uploadcare/cname_generator_spec.rb b/spec/uploadcare/cname_generator_spec.rb index 457eca69..39545d19 100644 --- a/spec/uploadcare/cname_generator_spec.rb +++ b/spec/uploadcare/cname_generator_spec.rb @@ -5,8 +5,10 @@ RSpec.describe Uploadcare::CnameGenerator do before do # Reset memoized variables between tests - described_class.instance_variable_set(:@custom_cname, nil) - described_class.instance_variable_set(:@cdn_base_postfix, nil) + described_class.instance_variable_set(:@custom_cname_cache, nil) + described_class.instance_variable_set(:@cdn_base_postfix_cache, nil) + # Reset configuration + Uploadcare.instance_variable_set(:@configuration, nil) end describe '.generate_cname' do @@ -21,7 +23,7 @@ describe '.cdn_base_postfix' do before do - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return('https://ucarecd.net/') + allow(Uploadcare.configuration).to receive(:cdn_base_postfix).and_return('https://ucarecd.net/') allow(described_class).to receive(:custom_cname).and_return('abc123def') end @@ -31,8 +33,8 @@ end it 'handles different CDN bases' do - described_class.instance_variable_set(:@cdn_base_postfix, nil) - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return('https://example.com') + described_class.instance_variable_set(:@cdn_base_postfix_cache, nil) + allow(Uploadcare.configuration).to receive(:cdn_base_postfix).and_return('https://example.com') allow(described_class).to receive(:custom_cname).and_return('xyz789') result = described_class.cdn_base_postfix @@ -48,8 +50,8 @@ end it 'handles CDN base with path' do - described_class.instance_variable_set(:@cdn_base_postfix, nil) - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return('https://cdn.example.com/path/') + described_class.instance_variable_set(:@cdn_base_postfix_cache, nil) + allow(Uploadcare.configuration).to receive(:cdn_base_postfix).and_return('https://cdn.example.com/path/') allow(described_class).to receive(:custom_cname).and_return('prefix123') result = described_class.cdn_base_postfix @@ -70,8 +72,8 @@ invalid_urls.each do |invalid_url| # Reset memoization for each test - described_class.instance_variable_set(:@cdn_base_postfix, nil) - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return(invalid_url) + described_class.instance_variable_set(:@cdn_base_postfix_cache, nil) + allow(Uploadcare.configuration).to receive(:cdn_base_postfix).and_return(invalid_url) allow(described_class).to receive(:generate_cname).and_return('test123') expect { described_class.cdn_base_postfix }.to raise_error( @@ -84,7 +86,7 @@ describe '.custom_cname' do before do - allow(Uploadcare.config).to receive(:public_key).and_return('test_public_key') + allow(Uploadcare.configuration).to receive(:public_key).and_return('test_public_key') end it 'generates CNAME prefix from public key' do @@ -104,13 +106,13 @@ end it 'generates different results for different public keys' do - allow(Uploadcare.config).to receive(:public_key).and_return('key1') + allow(Uploadcare.configuration).to receive(:public_key).and_return('key1') result1 = described_class.send(:custom_cname) # Reset memoization - described_class.instance_variable_set(:@custom_cname, nil) + described_class.instance_variable_set(:@custom_cname_cache, nil) - allow(Uploadcare.config).to receive(:public_key).and_return('key2') + allow(Uploadcare.configuration).to receive(:public_key).and_return('key2') result2 = described_class.send(:custom_cname) expect(result1).not_to eq(result2) @@ -127,7 +129,7 @@ end it 'handles empty public key' do - allow(Uploadcare.config).to receive(:public_key).and_return('') + allow(Uploadcare.configuration).to receive(:public_key).and_return('') result = described_class.send(:custom_cname) expect(result).to be_a(String) @@ -135,7 +137,7 @@ end it 'handles nil public key' do - allow(Uploadcare.config).to receive(:public_key).and_return(nil) + allow(Uploadcare.configuration).to receive(:public_key).and_return(nil) # Should raise ConfigurationError for nil public key expect { described_class.send(:custom_cname) }.to raise_error( @@ -145,7 +147,7 @@ end it 'handles special characters in public key' do - allow(Uploadcare.config).to receive(:public_key).and_return('key!@#$%^&*()') + allow(Uploadcare.configuration).to receive(:public_key).and_return('key!@#$%^&*()') result = described_class.send(:custom_cname) expect(result).to be_a(String) @@ -156,7 +158,7 @@ it 'generates expected CNAME for known public key' do # Test with a specific known public key to verify the algorithm known_public_key = 'demopublickey' - allow(Uploadcare.config).to receive(:public_key).and_return(known_public_key) + allow(Uploadcare.configuration).to receive(:public_key).and_return(known_public_key) # Manual calculation of expected CNAME: # 1. SHA256 hash of 'demopublickey' @@ -181,8 +183,7 @@ describe 'integration tests' do context 'with known public key' do before do - allow(Uploadcare.config).to receive(:public_key).and_return('test_key_123') - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return('https://ucarecd.net/') + allow(Uploadcare.configuration).to receive_messages(public_key: 'test_key_123', cdn_base_postfix: 'https://ucarecd.net/') end it 'generates consistent CNAME across method calls' do @@ -211,8 +212,8 @@ ] test_cases.each do |cdn_base| - described_class.instance_variable_set(:@cdn_base_postfix, nil) - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return(cdn_base) + described_class.instance_variable_set(:@cdn_base_postfix_cache, nil) + allow(Uploadcare.configuration).to receive(:cdn_base_postfix).and_return(cdn_base) allow(described_class).to receive(:custom_cname).and_return('test123') result = described_class.cdn_base_postfix @@ -221,12 +222,11 @@ end end - context 'manual CNAME generation verification' do + context 'when manually verifying CNAME generation' do it 'generates expected CNAME and CDN base for real-world scenario' do # Real-world test with a specific public key test_public_key = 'pub_12345test' - allow(Uploadcare.config).to receive(:public_key).and_return(test_public_key) - allow(Uploadcare.config).to receive(:cdn_base_postfix).and_return('https://ucarecd.net/') + allow(Uploadcare.configuration).to receive_messages(public_key: test_public_key, cdn_base_postfix: 'https://ucarecd.net/') # Calculate expected CNAME manually sha256_hex = Digest::SHA256.hexdigest(test_public_key) diff --git a/spec/uploadcare/collections/batch_result_spec.rb b/spec/uploadcare/collections/batch_result_spec.rb new file mode 100644 index 00000000..53e26c95 --- /dev/null +++ b/spec/uploadcare/collections/batch_result_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Collections::BatchResult do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + let(:file_data) do + { + 'uuid' => file_uuid, + 'original_filename' => 'photo.jpg', + 'size' => 12_345, + 'is_ready' => true + } + end + + describe '#initialize' do + it 'creates File objects from result data' do + batch = described_class.new( + status: 'ok', + result: [file_data], + problems: {}, + client: client + ) + + expect(batch.status).to eq('ok') + expect(batch.result).to be_an(Array) + expect(batch.result.length).to eq(1) + expect(batch.result.first).to be_a(Uploadcare::Resources::File) + expect(batch.result.first.uuid).to eq(file_uuid) + expect(batch.result.first.original_filename).to eq('photo.jpg') + expect(batch.problems).to eq({}) + end + + it 'handles nil result gracefully' do + batch = described_class.new( + status: 'ok', + result: nil, + problems: {}, + client: client + ) + + expect(batch.result).to eq([]) + end + + it 'handles nil problems' do + batch = described_class.new( + status: 'ok', + result: [], + problems: nil, + client: client + ) + + expect(batch.problems).to eq({}) + end + + it 'stores problems hash' do + problems = { + 'bad-uuid-1' => 'File not found.', + 'bad-uuid-2' => 'File is already stored.' + } + + batch = described_class.new( + status: 'ok', + result: [file_data], + problems: problems, + client: client + ) + + expect(batch.problems).to eq(problems) + expect(batch.problems['bad-uuid-1']).to eq('File not found.') + end + end + + describe '#status' do + it 'returns the status' do + batch = described_class.new(status: 200, result: [], problems: {}, client: client) + expect(batch.status).to eq(200) + end + + it 'can be nil' do + batch = described_class.new(status: nil, result: [], problems: {}, client: client) + expect(batch.status).to be_nil + end + end + + describe '#result' do + it 'returns File objects with proper client context' do + batch = described_class.new( + status: 'ok', + result: [file_data, file_data.merge('uuid' => 'second-uuid')], + problems: {}, + client: client + ) + + expect(batch.result.length).to eq(2) + batch.result.each do |file| + expect(file).to be_a(Uploadcare::Resources::File) + expect(file.client).to eq(client) + end + end + end +end diff --git a/spec/uploadcare/collections/paginated_spec.rb b/spec/uploadcare/collections/paginated_spec.rb new file mode 100644 index 00000000..0a04d2ad --- /dev/null +++ b/spec/uploadcare/collections/paginated_spec.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Collections::Paginated do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:api_client) { double('api_client') } + let(:resource_class) { Uploadcare::Resources::File } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + let(:file_resource) do + resource_class.new({ 'uuid' => file_uuid, 'original_filename' => 'photo.jpg' }, client) + end + + let(:collection) do + described_class.new( + resources: [file_resource], + next_page: 'https://api.uploadcare.com/files/?limit=10&offset=10', + previous_page: nil, + per_page: 10, + total: 25, + api_client: api_client, + resource_class: resource_class, + client: client, + request_options: { timeout: 5 } + ) + end + + describe '#initialize' do + it 'stores all provided parameters' do + expect(collection.resources).to eq([file_resource]) + expect(collection.next_page_url).to eq('https://api.uploadcare.com/files/?limit=10&offset=10') + expect(collection.previous_page_url).to be_nil + expect(collection.per_page).to eq(10) + expect(collection.total).to eq(25) + expect(collection.api_client).to eq(api_client) + expect(collection.resource_class).to eq(resource_class) + expect(collection.client).to eq(client) + expect(collection.request_options).to eq(timeout: 5) + end + + it 'defaults resources to empty array' do + empty = described_class.new + expect(empty.resources).to eq([]) + end + end + + describe 'Enumerable' do + it 'includes Enumerable' do + expect(described_class).to include(Enumerable) + end + + it 'delegates each to resources' do + uuids = collection.map(&:uuid) + expect(uuids).to eq([file_uuid]) + end + + it 'supports map' do + names = collection.map(&:original_filename) + expect(names).to eq(['photo.jpg']) + end + + it 'supports count' do + expect(collection.count).to eq(1) + end + + it 'supports first' do + expect(collection.first).to eq(file_resource) + end + + it 'supports to_a' do + expect(collection.to_a).to eq([file_resource]) + end + end + + describe '#next_page' do + it 'fetches the next page of results' do + next_file_attrs = { 'uuid' => 'next-page-uuid', 'original_filename' => 'page2.jpg' } + next_response = { + 'results' => [next_file_attrs], + 'next' => nil, + 'previous' => 'https://api.uploadcare.com/files/?limit=10&offset=0', + 'per_page' => 10, + 'total' => 25 + } + + allow(api_client).to receive(:list) + .with(params: { 'limit' => '10', 'offset' => '10' }, request_options: { timeout: 5 }) + .and_return(Uploadcare::Result.success(next_response)) + + page2 = collection.next_page + expect(page2).to be_a(described_class) + expect(page2.resources.length).to eq(1) + expect(page2.resources.first.uuid).to eq('next-page-uuid') + expect(page2.next_page_url).to be_nil + expect(page2.previous_page_url).to eq('https://api.uploadcare.com/files/?limit=10&offset=0') + end + + it 'returns nil when on the last page' do + last_page = described_class.new( + resources: [file_resource], + next_page: nil, + per_page: 10, + total: 5, + api_client: api_client, + resource_class: resource_class, + client: client + ) + expect(last_page.next_page).to be_nil + end + end + + describe '#previous_page' do + it 'fetches the previous page of results' do + prev_collection = described_class.new( + resources: [file_resource], + next_page: nil, + previous_page: 'https://api.uploadcare.com/files/?limit=10&offset=0', + per_page: 10, + total: 25, + api_client: api_client, + resource_class: resource_class, + client: client + ) + + prev_file_attrs = { 'uuid' => 'prev-page-uuid' } + prev_response = { + 'results' => [prev_file_attrs], + 'next' => 'https://api.uploadcare.com/files/?limit=10&offset=10', + 'previous' => nil, + 'per_page' => 10, + 'total' => 25 + } + + allow(api_client).to receive(:list) + .with(params: { 'limit' => '10', 'offset' => '0' }, request_options: {}) + .and_return(Uploadcare::Result.success(prev_response)) + + page1 = prev_collection.previous_page + expect(page1).to be_a(described_class) + expect(page1.resources.first.uuid).to eq('prev-page-uuid') + end + + it 'returns nil when on the first page' do + expect(collection.previous_page).to be_nil + end + end + + describe '#all' do + it 'fetches all resources across multiple pages' do + file2_attrs = { 'uuid' => 'page2-uuid' } + page2_response = { + 'results' => [file2_attrs], + 'next' => nil, + 'previous' => nil, + 'per_page' => 10, + 'total' => 2 + } + + allow(api_client).to receive(:list) + .with(params: { 'limit' => '10', 'offset' => '10' }, request_options: { timeout: 5 }) + .and_return(Uploadcare::Result.success(page2_response)) + + all_items = collection.all + expect(all_items.length).to eq(2) + expect(all_items.first.uuid).to eq(file_uuid) + expect(all_items.last.uuid).to eq('page2-uuid') + end + + it 'returns resources from single page when no next_page' do + single_page = described_class.new( + resources: [file_resource], + next_page: nil, + per_page: 10, + total: 1, + api_client: api_client, + resource_class: resource_class, + client: client + ) + + all_items = single_page.all + expect(all_items.length).to eq(1) + expect(all_items.first).to eq(file_resource) + end + + it 'returns empty array when no resources' do + empty = described_class.new( + resources: [], + next_page: nil, + per_page: 10, + total: 0, + api_client: api_client, + resource_class: resource_class, + client: client + ) + expect(empty.all).to eq([]) + end + + it 'respects a positive limit' do + file2_attrs = { 'uuid' => 'page2-uuid' } + page2_response = { + 'results' => [file2_attrs], + 'next' => nil, + 'previous' => nil, + 'per_page' => 10, + 'total' => 2 + } + + allow(api_client).to receive(:list) + .with(params: { 'limit' => '10', 'offset' => '10' }, request_options: { timeout: 5 }) + .and_return(Uploadcare::Result.success(page2_response)) + + all_items = collection.all(limit: 1) + expect(all_items.length).to eq(1) + expect(all_items.first.uuid).to eq(file_uuid) + end + + it 'returns empty array for a non-positive limit' do + expect(collection.all(limit: 0)).to eq([]) + end + end +end diff --git a/spec/uploadcare/concerns/throttle_handler_spec.rb b/spec/uploadcare/concerns/throttle_handler_spec.rb deleted file mode 100644 index f9aaca6b..00000000 --- a/spec/uploadcare/concerns/throttle_handler_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'uploadcare/concern/throttle_handler' - -module Uploadcare - RSpec.describe Concerns::ThrottleHandler do - include Concerns::ThrottleHandler - - def sleep(_time); end - - before { @called = 0 } - - let(:throttler) do - lambda do - @called += 1 - raise ThrottleError if @called < 3 - - "Throttler has been called #{@called} times" - end - end - - describe 'throttling handling' do - it 'attempts to call block multiple times' do - result = handle_throttling { throttler.call } - - expect(result).to eq 'Throttler has been called 3 times' - end - end - end -end diff --git a/spec/uploadcare/configuration_spec.rb b/spec/uploadcare/configuration_spec.rb new file mode 100644 index 00000000..f844d872 --- /dev/null +++ b/spec/uploadcare/configuration_spec.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Configuration do + describe 'DEFAULTS' do + it 'defines all expected default values' do + defaults = described_class::DEFAULTS + + expect(defaults[:public_key]).to be_nil + expect(defaults[:secret_key]).to be_nil + expect(defaults[:auth_type]).to eq('Uploadcare') + expect(defaults[:multipart_size_threshold]).to eq(100 * 1024 * 1024) + expect(defaults[:rest_api_root]).to eq('https://api.uploadcare.com') + expect(defaults[:upload_api_root]).to eq('https://upload.uploadcare.com') + expect(defaults[:max_request_tries]).to eq(100) + expect(defaults[:base_request_sleep]).to eq(1) + expect(defaults[:max_request_sleep]).to eq(60.0) + expect(defaults[:sign_uploads]).to be false + expect(defaults[:upload_signature_lifetime]).to eq(30 * 60) + expect(defaults[:max_throttle_attempts]).to eq(5) + expect(defaults[:upload_threads]).to eq(2) + expect(defaults[:framework_data]).to eq('') + expect(defaults[:file_chunk_size]).to eq(100) + expect(defaults[:logger]).to be_nil + expect(defaults[:use_subdomains]).to be false + expect(defaults[:cdn_base_postfix]).to eq('https://ucarecd.net/') + expect(defaults[:default_cdn_base]).to eq('https://ucarecdn.com/') + expect(defaults[:multipart_chunk_size]).to eq(5 * 1024 * 1024) + expect(defaults[:upload_timeout]).to eq(60) + expect(defaults[:max_upload_retries]).to eq(3) + end + + it 'is frozen' do + expect(described_class::DEFAULTS).to be_frozen + end + end + + describe '#initialize' do + it 'uses defaults when no options given' do + config = described_class.new + expect(config.auth_type).to eq('Uploadcare') + expect(config.multipart_size_threshold).to eq(100 * 1024 * 1024) + expect(config.rest_api_root).to eq('https://api.uploadcare.com') + end + + it 'overrides defaults with provided options' do + config = described_class.new( + public_key: 'my-key', + secret_key: 'my-secret', + auth_type: 'Uploadcare.Simple' + ) + expect(config.public_key).to eq('my-key') + expect(config.secret_key).to eq('my-secret') + expect(config.auth_type).to eq('Uploadcare.Simple') + end + + it 'falls back to ENV for public_key when nil' do + original_env = ENV.fetch('UPLOADCARE_PUBLIC_KEY', nil) + ENV['UPLOADCARE_PUBLIC_KEY'] = 'env-public-key' + + config = described_class.new + expect(config.public_key).to eq('env-public-key') + + if original_env + ENV['UPLOADCARE_PUBLIC_KEY'] = original_env + else + ENV.delete('UPLOADCARE_PUBLIC_KEY') + end + end + + it 'falls back to ENV for secret_key when nil' do + original_env = ENV.fetch('UPLOADCARE_SECRET_KEY', nil) + ENV['UPLOADCARE_SECRET_KEY'] = 'env-secret-key' + + config = described_class.new + expect(config.secret_key).to eq('env-secret-key') + + if original_env + ENV['UPLOADCARE_SECRET_KEY'] = original_env + else + ENV.delete('UPLOADCARE_SECRET_KEY') + end + end + + it 'creates a logger by default' do + config = described_class.new + expect(config.logger).to be_a(Logger) + end + + it 'uses a provided logger' do + custom_logger = Logger.new($stderr) + config = described_class.new(logger: custom_logger) + expect(config.logger).to eq(custom_logger) + end + end + + describe '#with' do + it 'creates a new configuration with overrides' do + config = described_class.new(public_key: 'original', secret_key: 'secret') + new_config = config.with(public_key: 'overridden') + + expect(new_config.public_key).to eq('overridden') + expect(new_config.secret_key).to eq('secret') + end + + it 'does not mutate the original config' do + config = described_class.new(public_key: 'original') + config.with(public_key: 'changed') + + expect(config.public_key).to eq('original') + end + + it 'returns a new Configuration instance' do + config = described_class.new + new_config = config.with(auth_type: 'Uploadcare.Simple') + + expect(new_config).to be_a(described_class) + expect(new_config).not_to equal(config) + end + end + + describe '#to_h' do + it 'returns a hash of all configuration values' do + config = described_class.new( + public_key: 'pk', + secret_key: 'sk', + auth_type: 'Uploadcare.Simple' + ) + hash = config.to_h + + expect(hash).to be_a(Hash) + expect(hash[:public_key]).to eq('pk') + expect(hash[:secret_key]).to eq('sk') + expect(hash[:auth_type]).to eq('Uploadcare.Simple') + expect(hash[:rest_api_root]).to eq('https://api.uploadcare.com') + end + + it 'includes all DEFAULTS keys' do + config = described_class.new + hash = config.to_h + + described_class::DEFAULTS.each_key do |key| + expect(hash).to have_key(key) + end + end + end + + describe '#cdn_base' do + it 'returns default_cdn_base when use_subdomains is false' do + config = described_class.new( + public_key: 'pk', + use_subdomains: false, + default_cdn_base: 'https://ucarecdn.com/' + ) + expect(config.cdn_base).to eq('https://ucarecdn.com/') + end + + it 'generates subdomain-based CDN base when use_subdomains is true' do + config = described_class.new( + public_key: 'pk', + use_subdomains: true, + cdn_base_postfix: 'https://ucarecd.net/' + ) + cdn = config.cdn_base + expect(cdn).to include('ucarecd.net') + expect(cdn).to start_with('https://') + end + end + + describe '#custom_cname' do + it 'generates a CNAME from the public key' do + config = described_class.new(public_key: 'demopublickey') + cname = config.custom_cname + expect(cname).to be_a(String) + expect(cname.length).to eq(10) + end + + it 'generates consistent cnames for the same key' do + config = described_class.new(public_key: 'test-key') + custom_cname = config.custom_cname + expect(config.custom_cname).to eq(custom_cname) + end + + it 'generates different cnames for different keys' do + config1 = described_class.new(public_key: 'key-alpha') + config2 = described_class.new(public_key: 'key-beta') + expect(config1.custom_cname).not_to eq(config2.custom_cname) + end + end + + describe 'attr_accessors' do + it 'allows setting and getting all configuration attributes' do + config = described_class.new + + config.public_key = 'new-pk' + expect(config.public_key).to eq('new-pk') + + config.max_request_tries = 50 + expect(config.max_request_tries).to eq(50) + + config.sign_uploads = true + expect(config.sign_uploads).to be true + end + end +end diff --git a/spec/uploadcare/coverage_boost_spec.rb b/spec/uploadcare/coverage_boost_spec.rb new file mode 100644 index 00000000..ff162daa --- /dev/null +++ b/spec/uploadcare/coverage_boost_spec.rb @@ -0,0 +1,383 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Coverage: edge cases and error paths' do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + + describe 'Api::Rest error paths' do + let(:rest) { Uploadcare::Api::Rest.new(config: config) } + + it 'handles Faraday errors in make_request' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .to_return(status: 500, body: '{"detail":"Server Error"}', headers: { 'Content-Type' => 'application/json' }) + + expect { rest.make_request(method: :get, path: '/files/', params: {}, headers: {}) } + .to raise_error(Uploadcare::Exception::RequestError) + end + + it 'wraps request in Result on failure' do + stub_request(:get, 'https://api.uploadcare.com/test/') + .to_return(status: 404, body: '{"detail":"Not found"}', headers: { 'Content-Type' => 'application/json' }) + + result = rest.get(path: '/test/', params: {}, headers: {}) + expect(result.failure?).to be(true) + end + + it 'handles request_options timeout' do + stub_request(:get, 'https://api.uploadcare.com/files/') + .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) + + result = rest.get(path: '/files/', params: {}, headers: {}, request_options: { timeout: 30, open_timeout: 5 }) + expect(result.success?).to be(true) + end + + it 'builds URI with query params for GET requests' do + stub_request(:get, %r{api\.uploadcare\.com/files/}) + .to_return(status: 200, body: '{"results":[]}', headers: { 'Content-Type' => 'application/json' }) + + result = rest.get(path: '/files/', params: { limit: 10 }, headers: {}) + expect(result.success?).to be(true) + end + + it 'handles DELETE with params as body' do + stub_request(:delete, 'https://api.uploadcare.com/files/storage/') + .to_return(status: 200, body: '{"result":[]}', headers: { 'Content-Type' => 'application/json' }) + + result = rest.delete(path: '/files/storage/', params: ['uuid-1'], headers: {}) + expect(result.success?).to be(true) + end + + it 'handles string body content for signature' do + stub_request(:put, /api\.uploadcare\.com/) + .to_return(status: 200, body: '"value"', headers: { 'Content-Type' => 'application/json' }) + + result = rest.put(path: '/files/uuid/metadata/key/', params: '"value"', headers: {}) + expect(result.success?).to be(true) + end + end + + describe 'Api::Upload error paths' do + let(:upload) { Uploadcare::Api::Upload.new(config: config) } + + it 'handles upload API errors' do + stub_request(:post, 'https://upload.uploadcare.com/base/') + .to_return(status: 500, body: 'Server Error') + + result = upload.post(path: 'base/', params: {}) + expect(result.failure?).to be(true) + end + + it 'handles empty response bodies' do + stub_request(:post, 'https://upload.uploadcare.com/test/') + .to_return(status: 200, body: '', headers: { 'Content-Type' => 'application/json' }) + + result = upload.post(path: 'test/', params: {}) + expect(result.success?).to be(true) + end + + it 'handles JSON parse errors gracefully' do + stub_request(:post, 'https://upload.uploadcare.com/test/') + .to_return(status: 200, body: 'not json', headers: { 'Content-Type' => 'text/plain' }) + + result = upload.post(path: 'test/', params: {}) + expect(result.success?).to be(true) + end + end + + describe 'Api::Upload::Files edge cases' do + let(:upload) { Uploadcare::Api::Upload.new(config: config) } + let(:files_endpoint) { upload.files } + + it 'validates empty URL in from_url' do + result = files_endpoint.from_url(source_url: '') + expect(result.failure?).to be(true) + end + + it 'validates invalid URL scheme in from_url' do + result = files_endpoint.from_url(source_url: 'ftp://example.com/file') + expect(result.failure?).to be(true) + end + + it 'validates empty filename in multipart_start' do + result = files_endpoint.multipart_start(filename: '', size: 100, content_type: 'image/jpeg') + expect(result.failure?).to be(true) + end + + it 'validates invalid size in multipart_start' do + result = files_endpoint.multipart_start(filename: 'test.jpg', size: -1, content_type: 'image/jpeg') + expect(result.failure?).to be(true) + end + + it 'validates empty content_type in multipart_start' do + result = files_endpoint.multipart_start(filename: 'test.jpg', size: 100, content_type: '') + expect(result.failure?).to be(true) + end + + it 'validates empty uuid in multipart_complete' do + result = files_endpoint.multipart_complete(uuid: '') + expect(result.failure?).to be(true) + end + + it 'validates empty token in from_url_status' do + result = files_endpoint.from_url_status(token: '') + expect(result.failure?).to be(true) + end + + it 'validates empty file_id in info' do + result = files_endpoint.info(file_id: '') + expect(result.failure?).to be(true) + end + + it 'validates non-IO object in direct upload' do + result = files_endpoint.direct(file: 'not_a_file') + expect(result.failure?).to be(true) + end + end + + describe 'Api::Upload::Groups edge cases' do + let(:upload) { Uploadcare::Api::Upload.new(config: config) } + let(:groups_endpoint) { upload.groups } + + it 'validates non-array files' do + result = groups_endpoint.create(files: 'not_array') + expect(result.failure?).to be(true) + end + + it 'validates empty files array' do + result = groups_endpoint.create(files: []) + expect(result.failure?).to be(true) + end + + it 'validates empty group_id in info' do + result = groups_endpoint.info(group_id: '') + expect(result.failure?).to be(true) + end + end + + describe 'Operations::MultipartUpload edge cases' do + let(:upload_client) { instance_double(Uploadcare::Api::Upload) } + let(:mp) { Uploadcare::Operations::MultipartUpload.new(upload_client: upload_client, config: config) } + + it 'validates non-IO file objects' do + result = mp.upload(file: 'not_a_file') + expect(result.failure?).to be(true) + end + end + + describe 'Operations::UploadRouter edge cases' do + let(:router) { Uploadcare::Operations::UploadRouter.new(client: client) } + + it 'raises ArgumentError for invalid source types' do + expect { router.upload(12_345) }.to raise_error(ArgumentError, /Expected input/) + end + end + + describe 'Resources::File edge cases' do + it 'extracts uuid from URL' do + file = Uploadcare::Resources::File.new( + { 'original_file_url' => 'https://ucarecdn.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/' }, + client + ) + expect(file.uuid).to eq('a1b2c3d4-e5f6-7890-abcd-ef1234567890') + end + + it 'returns cdn_url from url attribute' do + file = Uploadcare::Resources::File.new( + { 'url' => 'https://ucarecdn.com/test-uuid/' }, + client + ) + expect(file.cdn_url).to eq('https://ucarecdn.com/test-uuid/') + end + + it 'builds cdn_url from uuid and config' do + file = Uploadcare::Resources::File.new({ 'uuid' => 'test-uuid-1234' }, client) + expect(file.cdn_url).to include('test-uuid-1234') + end + end + + describe 'Resources::Group edge cases' do + it 'extracts id from cdn_url' do + group = Uploadcare::Resources::Group.new( + { 'cdn_url' => 'https://ucarecdn.com/group-id~3/' }, + client + ) + expect(group.id).to eq('group-id~3') + end + + it 'returns empty file_cdn_urls when files_count is nil' do + group = Uploadcare::Resources::Group.new({}, client) + expect(group.file_cdn_urls).to eq([]) + end + end + + describe 'Collections::Paginated edge cases' do + it 'handles empty resources' do + collection = Uploadcare::Collections::Paginated.new(resources: []) + expect(collection.count).to eq(0) + expect(collection.all).to eq([]) + end + + it 'returns nil for next_page when no URL' do + collection = Uploadcare::Collections::Paginated.new(resources: [], next_page: nil) + expect(collection.next_page).to be_nil + end + + it 'returns nil for previous_page when no URL' do + collection = Uploadcare::Collections::Paginated.new(resources: [], previous_page: nil) + expect(collection.previous_page).to be_nil + end + end + + describe 'Collections::BatchResult' do + it 'handles nil result array' do + result = Uploadcare::Collections::BatchResult.new(status: 200, result: nil, problems: {}, client: client) + expect(result.result).to eq([]) + end + + it 'handles nil problems' do + result = Uploadcare::Collections::BatchResult.new(status: 200, result: [], problems: nil, client: client) + expect(result.problems).to eq({}) + end + end + + describe 'Internal::ErrorHandler edge cases' do + let(:handler) { Class.new { include Uploadcare::Internal::ErrorHandler }.new } + + it 'handles error with nil response' do + error = Faraday::Error.new('connection refused') + expect { handler.handle_error(error) }.to raise_error(Uploadcare::Exception::RequestError) + end + + it 'handles 400 status' do + error = Faraday::ClientError.new(nil, { status: 400, body: '{"detail":"Bad request"}' }) + expect { handler.handle_error(error) }.to raise_error(Uploadcare::Exception::InvalidRequestError) + end + + it 'handles 404 status' do + error = Faraday::ResourceNotFound.new(nil, { status: 404, body: '{"detail":"Not found"}' }) + expect { handler.handle_error(error) }.to raise_error(Uploadcare::Exception::NotFoundError) + end + + it 'handles 429 status with retry-after header' do + error = Faraday::ClientError.new(nil, { status: 429, body: '{"detail":"Too many requests"}', + headers: { 'retry-after' => '5' } }) + expect { handler.handle_error(error) }.to raise_error(Uploadcare::Exception::ThrottleError) + end + + it 'handles upload API error (200 with error body)' do + error = Faraday::ClientError.new(nil, { status: 200, body: '{"error":"Upload failed"}' }) + expect { handler.handle_error(error) }.to raise_error(Uploadcare::Exception::RequestError, 'Upload failed') + end + + it 'handles non-JSON error body' do + error = Faraday::ClientError.new(nil, { status: 500, body: 'Internal Server Error' }) + expect { handler.handle_error(error) }.to raise_error(Uploadcare::Exception::RequestError) + end + end + + describe 'Internal::ThrottleHandler edge cases' do + let(:handler_class) do + Class.new do + include Uploadcare::Internal::ThrottleHandler + + def config + Uploadcare::Configuration.new(max_throttle_attempts: 2) + end + end + end + let(:handler) { handler_class.new } + + it 'uses config max_throttle_attempts when not specified' do + call_count = 0 + expect do + handler.handle_throttling do + call_count += 1 + raise Uploadcare::Exception::ThrottleError.new(timeout: 0.001) if call_count < 2 + + 'success' + end + end.not_to raise_error + end + end + + describe 'Result edge cases' do + it 'raises string errors' do + result = Uploadcare::Result.failure('string error') + expect { result.value! }.to raise_error(RuntimeError, 'string error') + end + + it 'captures successful blocks' do + result = Uploadcare::Result.capture { 42 } + expect(result.success?).to be(true) + expect(result.value!).to eq(42) + end + end + + describe 'Configuration edge cases' do + it 'reads from environment variables' do + config = Uploadcare::Configuration.new + expect(config.public_key).to be_a(String) + end + + it 'supports with for creating copies' do + original = Uploadcare::Configuration.new(public_key: 'original') + copy = original.with(public_key: 'modified') + expect(copy.public_key).to eq('modified') + expect(original.public_key).to eq('original') + end + + it 'exposes to_h' do + config = Uploadcare::Configuration.new(public_key: 'test') + h = config.to_h + expect(h).to be_a(Hash) + expect(h[:public_key]).to eq('test') + end + end + + describe 'Client edge cases' do + it 'creates client with keyword options' do + c = Uploadcare::Client.new(public_key: 'test', secret_key: 'secret') + expect(c.config.public_key).to eq('test') + end + + it 'creates client with config object' do + c = Uploadcare::Client.new(config: config) + expect(c.config).not_to equal(config) + expect(c.config.to_h).to eq(config.to_h) + end + + it 'creates new client via with' do + c1 = Uploadcare::Client.new(config: config) + c2 = c1.with(public_key: 'new_key') + expect(c2.config.public_key).to eq('new_key') + expect(c1.config.public_key).to eq('demopublickey') + end + + it 'exposes all domain accessors' do + expect(client.files).to be_a(Uploadcare::Client::FilesAccessor) + expect(client.groups).to be_a(Uploadcare::Client::GroupsAccessor) + expect(client.uploads).to be_a(Uploadcare::Operations::UploadRouter) + expect(client.project).to be_a(Uploadcare::Client::ProjectAccessor) + expect(client.webhooks).to be_a(Uploadcare::Client::WebhooksAccessor) + expect(client.addons).to be_a(Uploadcare::Client::AddonsAccessor) + expect(client.file_metadata).to be_a(Uploadcare::Client::FileMetadataAccessor) + expect(client.conversions).to be_a(Uploadcare::Client::ConversionsAccessor) + expect(client.conversions.documents).to be_a(Uploadcare::Client::DocumentConversionsAccessor) + expect(client.conversions.videos).to be_a(Uploadcare::Client::VideoConversionsAccessor) + end + + it 'exposes raw API access' do + expect(client.api).to be_a(Uploadcare::Client::Api) + expect(client.api.rest).to be_a(Uploadcare::Api::Rest) + expect(client.api.upload).to be_a(Uploadcare::Api::Upload) + end + end +end diff --git a/spec/uploadcare/entity/addons_spec.rb b/spec/uploadcare/entity/addons_spec.rb deleted file mode 100644 index db74dcd9..00000000 --- a/spec/uploadcare/entity/addons_spec.rb +++ /dev/null @@ -1,157 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe Addons do - subject { Addons } - - it 'responds to expected methods' do - methods = %i[uc_clamav_virus_scan uc_clamav_virus_scan_status ws_rekognition_detect_labels - ws_rekognition_detect_labels_status remove_bg remove_bg_status - ws_rekognition_detect_moderation_labels ws_rekognition_detect_moderation_labels_status] - expect(subject).to respond_to(*methods) - end - - describe 'uc_clamav_virus_scan' do - it 'scans the file for viruses' do - VCR.use_cassette('uc_clamav_virus_scan') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - params = { purge_infected: false } - response = subject.uc_clamav_virus_scan(uuid, params) - expect(response.request_id).to eq('34abf037-5384-4e38-bad4-97dd48e79acd') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('uc_clamav_virus_scan_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.uc_clamav_virus_scan(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'uc_clamav_virus_scan_status' do - it 'checking the status of a virus scanned file' do - VCR.use_cassette('uc_clamav_virus_scan_status') do - uuid = '34abf037-5384-4e38-bad4-97dd48e79acd' - response = subject.uc_clamav_virus_scan_status(uuid) - expect(response.status).to eq('done') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('uc_clamav_virus_scan_status_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.uc_clamav_virus_scan_status(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'ws_rekognition_detect_labels' do - it 'executes aws rekognition' do - VCR.use_cassette('ws_rekognition_detect_labels') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - response = subject.ws_rekognition_detect_labels(uuid) - expect(response.request_id).to eq('0f4598dd-d168-4272-b49e-e7f9d2543542') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('ws_rekognition_detect_labels_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.uc_clamav_virus_scan_status(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'ws_rekognition_detect_labels_status' do - it 'checking the status of a recognized file' do - VCR.use_cassette('ws_rekognition_detect_labels_status') do - uuid = '0f4598dd-d168-4272-b49e-e7f9d2543542' - response = subject.ws_rekognition_detect_labels_status(uuid) - expect(response.status).to eq('done') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('ws_rekognition_detect_labels_status_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.uc_clamav_virus_scan_status(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'remove_bg' do - it 'executes background image removal' do - VCR.use_cassette('remove_bg') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - params = { crop: true, type_level: '2' } - response = subject.remove_bg(uuid, params) - expect(response.request_id).to eq('c3446e41-9eb0-4301-aeb4-356d0fdcf9af') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('remove_bg_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.uc_clamav_virus_scan_status(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'remove_bg_status' do - it 'checking the status background image removal file' do - VCR.use_cassette('remove_bg_status') do - uuid = 'c3446e41-9eb0-4301-aeb4-356d0fdcf9af' - response = subject.remove_bg_status(uuid) - expect(response.status).to eq('done') - expect(response.result).to eq({ 'file_id' => 'bc37b996-916d-4ed7-b230-fa71a4290cb3' }) - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('remove_bg_status_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.uc_clamav_virus_scan_status(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'ws_rekognition_detect_moderation_labels' do - it 'executes aws rekognition detect moderation' do - VCR.use_cassette('ws_rekognition_detect_moderation_labels') do - uuid = 'ff4d3d37-4de0-4f6d-a7db-8cdabe7fc768' - response = subject.ws_rekognition_detect_moderation_labels(uuid) - expect(response.request_id).to eq('0f4598dd-d168-4272-b49e-e7f9d2543542') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('ws_rekognition_detect_moderation_labels_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.ws_rekognition_detect_moderation_labels(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'ws_rekognition_detect_moderation_labels_status' do - it 'checking the status of a recognized file' do - VCR.use_cassette('ws_rekognition_detect_moderation_labels_status') do - uuid = '0f4598dd-d168-4272-b49e-e7f9d2543542' - response = subject.ws_rekognition_detect_moderation_labels_status(uuid) - expect(response.status).to eq('done') - end - end - - it 'raises error for nonexistent file uuid' do - VCR.use_cassette('ws_rekognition_detect_moderation_labels_status_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.ws_rekognition_detect_moderation_labels_status(uuid) }.to raise_error(RequestError) - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/conversion/document_converter_spec.rb b/spec/uploadcare/entity/conversion/document_converter_spec.rb deleted file mode 100644 index ef507cb9..00000000 --- a/spec/uploadcare/entity/conversion/document_converter_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - module Conversion - RSpec.describe DocumentConverter do - subject { Uploadcare::DocumentConverter } - - describe 'convert' do - shared_examples 'converts documents' do |multipage: false, group: false| - it 'returns a result with document data', :aggregate_failures do - response_value = subject.convert(params, **options).success - result = response_value[:result].first - - expect(response_value[:problems]).to be_empty - expect(result[:uuid]).not_to be_nil - - [doc_params[:uuid], :format].each do |param| - expect(result[:original_source]).to match(param.to_s) - end - expect(result[:original_source]).to match('page') if doc_params[:page] - - next unless multipage - - info_response_values = subject.info(doc_params[:uuid]) # get info about that document - if group - expect( - info_response_values.success.dig(:format, :converted_groups, doc_params[:format].to_sym) - ).not_to be_empty - else - expect(info_response_values.success.dig(:format, :converted_groups)).to be_nil - end - end - end - - let(:doc_params) do - { - uuid: 'a4b9db2f-1591-4f4c-8f68-94018924525d', - format: 'png', - page: 1 - } - end - let(:options) { { store: false } } - - context 'when sending params as an Array', vcr: 'document_convert_convert_many' do - let(:params) { [doc_params] } - - it_behaves_like 'converts documents' - end - - context 'when sending params as a Hash', vcr: 'document_convert_convert_many' do - let(:params) { doc_params } - - it_behaves_like 'converts documents' - end - - # Ref: https://uploadcare.com/docs/transformations/document-conversion/#multipage-conversion - describe 'multipage conversion' do - context 'when not saved in group', vcr: 'document_convert_convert_multipage_zip' do - let(:doc_params) do - { - uuid: 'd95309eb-50bd-4594-bd7a-950011578480', - format: 'jpg' - } - end - let(:options) { { store: '1', save_in_group: '0' } } - let(:params) { doc_params } - - it_behaves_like 'converts documents', { multipage: true, group: false } - end - - context 'when saved in group', vcr: 'document_convert_convert_multipage_group' do - let(:doc_params) do - { - uuid: '23d29586-713e-4152-b400-05fb54730453', - format: 'jpg' - } - end - let(:options) { { store: '0', save_in_group: '1' } } - let(:params) { doc_params } - - it_behaves_like 'converts documents', { multipage: true, group: true } - end - end - end - - describe 'get document conversion status' do - let(:token) { '21120333' } - - it 'returns a document conversion status data', :aggregate_failures do - VCR.use_cassette('document_convert_get_status') do - response_value = subject.status(token).success - - expect(response_value[:status]).to eq 'finished' - expect(response_value[:error]).to be_nil - expect(response_value[:result].keys).to contain_exactly(:uuid) - end - end - end - - describe 'info' do - it 'shows info about that document' do - VCR.use_cassette('document_convert_info') do - uuid = 'cd7a51d4-9776-4749-b749-c9fc691891f1' - response = subject.info(uuid) - expect(response.value!.key?(:format)).to be_truthy - document_formats = response.value![:format] - expect(document_formats.key?(:conversion_formats)).to be_truthy - end - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/conversion/video_converter_spec.rb b/spec/uploadcare/entity/conversion/video_converter_spec.rb deleted file mode 100644 index bd6c7601..00000000 --- a/spec/uploadcare/entity/conversion/video_converter_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - module Conversion - RSpec.describe VideoConverter do - subject { Uploadcare::VideoConverter } - - describe 'successfull conversion' do - describe 'convert_many' do - shared_examples 'converts videos' do - it 'returns a result with video data', :aggregate_failures do - VCR.use_cassette('video_convert_convert_many') do - response_value = subject.convert(array_of_params, **options).success - result = response_value[:result].first - - expect(response_value[:problems]).to be_empty - expect(result[:uuid]).not_to be_nil - - [video_params[:uuid], :size, :quality, :format, :cut, :thumbs].each do |param| - expect(result[:original_source]).to match(param.to_s) - end - end - end - end - - let(:array_of_params) { [video_params] } - let(:video_params) do - { - uuid: 'e30112d7-3a90-4931-b2c5-688cbb46d3ac', - size: { resize_mode: 'change_ratio', width: '600', height: '400' }, - quality: 'best', - format: 'ogg', - cut: { start_time: '0:0:0.0', length: '0:0:1.0' }, - thumbs: { thumbs_n: 2, number: 1 } - } - end - let(:options) { { store: false } } - - context 'when all params are present' do - it_behaves_like 'converts videos' - end - - %i[size quality format cut thumbs].each do |param| - context "when only :#{param} param is present" do - let(:arguments) { super().slice(:uuid, param) } - - it_behaves_like 'converts videos' - end - end - end - - describe 'get video conversion status' do - let(:token) { '911933811' } - - it 'returns a video conversion status data', :aggregate_failures do - VCR.use_cassette('video_convert_get_status') do - response_value = subject.status(token).success - - expect(response_value[:status]).to eq 'finished' - expect(response_value[:error]).to be_nil - expect(response_value[:result].keys).to contain_exactly(:uuid, :thumbnails_group_uuid) - end - end - end - end - - describe 'conversion with error' do - shared_examples 'requesting video conversion' do - it 'raises a conversion error' do - VCR.use_cassette('video_convert_convert_many_with_error') do - expect(subject).to be_failure - end - end - end - - describe 'convert_many' do - subject { described_class.convert(array_of_params, **options) } - - let(:array_of_params) do - [ - { - uuid: 'e30112d7-3a90-4931-b2c5-688cbb46d3ac', - size: { resize_mode: 'change_ratio' }, - quality: 'best', - format: 'ogg', - cut: { start_time: '0:0:0.0', length: '0:0:1.0' }, - thumbs: { N: 2, number: 1 } - } - ] - end - let(:options) { { store: false } } - - context 'when no width and height are provided' do - let(:message) { /CDN Path error/ } - - it_behaves_like 'requesting video conversion' - end - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/decorator/paginator_spec.rb b/spec/uploadcare/entity/decorator/paginator_spec.rb deleted file mode 100644 index 72be90b4..00000000 --- a/spec/uploadcare/entity/decorator/paginator_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - module Decorator - RSpec.describe Paginator do - describe 'meta' do - it 'accepts arguments' do - VCR.use_cassette('rest_file_list_params') do - fl_with_params = FileList.file_list(limit: 2, ordering: '-datetime_uploaded') - expect(fl_with_params.meta.per_page).to eq 2 - end - end - end - - describe 'next_page' do - it 'loads a next page as separate object' do - VCR.use_cassette('rest_file_list_pages') do - fl_with_params = FileList.file_list(limit: 2, ordering: '-datetime_uploaded') - next_page = fl_with_params.next_page - expect(next_page.previous).not_to be_nil - expect(fl_with_params).not_to eq(next_page) - end - end - end - - describe 'previous_page' do - it 'loads a previous page as separate object' do - VCR.use_cassette('rest_file_list_previous_page') do - fl_with_params = FileList.file_list(limit: 2, ordering: '-datetime_uploaded') - next_page = fl_with_params.next_page - previous_page = next_page.previous_page - expect(previous_page.next).not_to be_nil - fl_path = fl_with_params.delete(:next) - previous_page_path = previous_page.delete(:next) - expect(fl_with_params).to eq(previous_page) - fl_query = URI.decode_www_form(URI.parse(fl_path).query).sort - previous_page_query = URI.decode_www_form(URI.parse(previous_page_path).query).sort - expect(fl_query).to eq(previous_page_query) - end - end - end - - describe 'load' do - it 'loads all objects' do - VCR.use_cassette('rest_file_list_load') do - fl_with_params = FileList.file_list(limit: 2, ordering: '-datetime_uploaded') - fl_with_params.load - expect(fl_with_params.results.length).to eq fl_with_params.total - end - end - end - - describe 'each' do - it 'iterates each file in list' do - VCR.use_cassette('rest_file_list_each') do - fl_with_params = FileList.file_list(limit: 2) - # rubocop:disable Style/MapIntoArray - entities = [] - fl_with_params.each do |file| - entities << file - end - # rubocop:enable Style/MapIntoArray - expect(entities.length).to eq fl_with_params.total - end - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/file_list_spec.rb b/spec/uploadcare/entity/file_list_spec.rb deleted file mode 100644 index 201188d8..00000000 --- a/spec/uploadcare/entity/file_list_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe FileList do - subject { FileList } - - it 'responds to expected methods' do - expect(subject).to respond_to(:file_list, :batch_store, :batch_delete) - end - - it 'represents a file as entity' do - VCR.use_cassette('rest_file_list') do - file_list = subject.file_list - expect(file_list).to respond_to(:next, :previous, :results, :total, :files) - expect(file_list.meta).to respond_to(:next, :previous, :total, :per_page) - end - end - - it 'accepts arguments' do - VCR.use_cassette('rest_file_list_params') do - fl_with_params = FileList.file_list(limit: 2, ordering: '-datetime_uploaded') - expect(fl_with_params.meta.per_page).to eq 2 - end - end - - context 'batch_store' do - it 'returns a list of stored files' do - VCR.use_cassette('rest_file_batch_store') do - uuids = %w[e9a9f291-cc52-4388-bf65-9feec1c75ff9 c724feac-86f7-447c-b2d6-b0ced220173d] - response = subject.batch_store(uuids) - expect(response.files.length).to eq 2 - expect(response.files[0]).to be_a_kind_of(Uploadcare::Entity::File) - end - end - - it 'returns empty list if those files don`t exist' do - VCR.use_cassette('rest_file_batch_store_fail') do - uuids = %w[nonexistent another_nonexistent] - response = subject.batch_store(uuids) - expect(response.files).to be_empty - end - end - end - - context 'batch_delete' do - it 'returns a list of deleted files' do - VCR.use_cassette('rest_file_batch_delete') do - uuids = %w[935ff093-a5cf-48c5-81cf-208511bac6e6 63be5a6e-9b6b-454b-8aec-9136d5f83d0c] - response = subject.batch_delete(uuids) - expect(response.files.length).to eq 2 - expect(response.files[0]).to be_a_kind_of(Uploadcare::Entity::File) - end - end - - it 'returns empty list if those files don`t exist' do - VCR.use_cassette('rest_file_batch_delete_fail') do - uuids = %w[nonexistent another_nonexistent] - response = subject.batch_delete(uuids) - expect(response.files).to be_empty - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/file_metadata_spec.rb b/spec/uploadcare/entity/file_metadata_spec.rb deleted file mode 100644 index 4a1931fc..00000000 --- a/spec/uploadcare/entity/file_metadata_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe FileMetadata do - subject { FileMetadata } - - let(:uuid) { '2e17f5d1-d423-4de6-8ee5-6773cc4a7fa6' } - let(:key) { 'subsystem' } - - it 'responds to expected methods' do - expect(subject).to respond_to(:index, :show, :update, :delete) - end - - it 'represents a file_metadata as string' do - VCR.use_cassette('file_metadata_index') do - response = subject.index(uuid) - expect(response[:subsystem]).to eq('test') - end - end - - it 'raises error for nonexistent file' do - VCR.use_cassette('file_metadata_index_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.index(uuid) }.to raise_error(RequestError) - end - end - - it 'shows file_metadata' do - VCR.use_cassette('file_metadata_show') do - response = subject.show(uuid, key) - expect(response).to eq('test') - end - end - - it 'raises error when trying to show nonexistent key' do - VCR.use_cassette('file_metadata_show_nonexistent_key') do - key = 'nonexistent' - expect { subject.show(uuid, key) }.to raise_error(RequestError) - end - end - - it 'updates file_metadata' do - VCR.use_cassette('file_metadata_update') do - new_value = 'new test value' - response = subject.update(uuid, key, new_value) - expect(response).to eq(new_value) - end - end - - it 'creates file_metadata if it does not exist' do - VCR.use_cassette('file_metadata_create') do - key = 'new_key' - value = 'some value' - response = subject.update(uuid, key, value) - expect(response).to eq(value) - end - end - - it 'deletes file_metadata' do - VCR.use_cassette('file_metadata_delete') do - response = subject.delete(uuid, key) - expect(response).to eq('200 OK') - end - end - end - end -end diff --git a/spec/uploadcare/entity/file_spec.rb b/spec/uploadcare/entity/file_spec.rb deleted file mode 100644 index 162c59f8..00000000 --- a/spec/uploadcare/entity/file_spec.rb +++ /dev/null @@ -1,239 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe File do - subject { File } - it 'responds to expected methods' do - expect(subject).to respond_to(:info, :delete, :store, :local_copy, :remote_copy) - end - - it 'represents a file as entity' do - VCR.use_cassette('file_info') do - uuid = '8f64f313-e6b1-4731-96c0-6751f1e7a50a' - file = subject.info(uuid) - expect(file).to be_a_kind_of(subject) - expect(file).to respond_to(*File::RESPONSE_PARAMS) - expect(file.uuid).to eq(uuid) - end - end - - it 'raises error for nonexistent file' do - VCR.use_cassette('rest_file_info_fail') do - uuid = 'nonexistent' - expect { subject.info(uuid) }.to raise_error(RequestError) - end - end - - it 'raises error when trying to delete nonexistent file' do - VCR.use_cassette('rest_file_delete_nonexistent') do - uuid = 'nonexistent' - expect { subject.delete(uuid) }.to raise_error(RequestError) - end - end - - describe 'internal_copy' do - it 'copies file to same project' do - VCR.use_cassette('rest_file_internal_copy') do - file = subject.file('5632fc94-9dff-499f-a373-f69ea6f67ff8') - file.local_copy - end - end - end - - describe 'external_copy' do - it 'copies file to remote storage' do - VCR.use_cassette('rest_file_remote_copy') do - target = 'uploadcare-test' - uuid = '1b959c59-9605-4879-946f-08fdb5ea3e9d' - file = subject.file(uuid) - expect(file.remote_copy(target)).to match(%r{#{target}/#{uuid}/}) - end - end - - it 'raises an error when project does not have given storage' do - VCR.use_cassette('rest_file_external_copy') do - file = subject.file('5632fc94-9dff-499f-a373-f69ea6f67ff8') - # I don't have custom storage, but this error recognises what this method tries to do - msg = 'Project has no storage with provided name.' - expect { file.remote_copy('16d8625b4c5c4a372a8f') }.to raise_error(RequestError, msg) - end - end - end - - describe 'uuid' do - it 'returns uuid, even if only url is defined' do - file = File.new(url: 'https://ucarecdn.com/35b7fcd7-9bca-40e1-99b1-2adcc21c405d/123.jpg') - expect(file.uuid).to eq '35b7fcd7-9bca-40e1-99b1-2adcc21c405d' - end - end - - describe 'datetime_stored' do - it 'returns datetime_stored, with deprecated warning' do - VCR.use_cassette('file_info') do - url = 'https://ucarecdn.com/8f64f313-e6b1-4731-96c0-6751f1e7a50a' - file = File.new(url: url) - logger = Uploadcare.config.logger - file.load - allow(logger).to receive(:warn).with('datetime_stored property has been deprecated, and will be removed without a replacement in future.') - datetime_stored = file.datetime_stored - expect(logger).to have_received(:warn).with('datetime_stored property has been deprecated, and will be removed without a replacement in future.') - expect(datetime_stored).not_to be_nil - end - end - end - - describe 'load' do - it 'performs load request' do - VCR.use_cassette('file_info') do - url = 'https://ucarecdn.com/8f64f313-e6b1-4731-96c0-6751f1e7a50a' - file = File.new(url: url) - file.load - expect(file.datetime_uploaded).not_to be_nil - end - end - end - - describe 'cdn_url' do - let(:test_uuid) { '8f64f313-e6b1-4731-96c0-6751f1e7a50a' } - let(:file) { File.new(uuid: test_uuid) } - - before do - # Reset any memoized config values - allow(Uploadcare.config).to receive(:cdn_base).and_call_original - end - - it 'generates CDN URL using cdn_base config' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://example.ucarecdn.com' }) - - result = file.cdn_url - expect(result).to eq("https://example.ucarecdn.com#{test_uuid}/") - end - - it 'handles different CDN base configurations' do - test_cases = [ - { base: 'https://custom.cdn.com', expected: "https://custom.cdn.com#{test_uuid}/" }, - { base: 'https://subdomain.ucarecdn.com', expected: "https://subdomain.ucarecdn.com#{test_uuid}/" }, - { base: 'https://cdn.example.org', expected: "https://cdn.example.org#{test_uuid}/" } - ] - - test_cases.each do |test_case| - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { test_case[:base] }) - expect(file.cdn_url).to eq(test_case[:expected]) - end - end - - it 'works with file initialized from URL' do - url_file = File.new(url: "https://ucarecdn.com/#{test_uuid}/image.jpg") - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://test.cdn.com' }) - - result = url_file.cdn_url - expect(result).to eq("https://test.cdn.com#{test_uuid}/") - end - - it 'calls cdn_base each time for dynamic config updates' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://first.cdn.com' }) - first_call = file.cdn_url - - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://second.cdn.com' }) - second_call = file.cdn_url - - expect(first_call).to eq("https://first.cdn.com#{test_uuid}/") - expect(second_call).to eq("https://second.cdn.com#{test_uuid}/") - end - - it 'handles CDN base with trailing slashes correctly' do - test_cases = [ - { base: 'https://cdn.com/', expected: "https://cdn.com/#{test_uuid}/" }, - { base: 'https://cdn.com', expected: "https://cdn.com#{test_uuid}/" } - ] - - test_cases.each do |test_case| - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { test_case[:base] }) - expect(file.cdn_url).to eq(test_case[:expected]) - end - end - - it 'includes cdn_url in RESPONSE_PARAMS' do - expect(File::RESPONSE_PARAMS).to include(:cdn_url) - end - - it 'works with subdomains when enabled' do - allow(Uploadcare.config).to receive(:use_subdomains).and_return(true) - allow(Uploadcare.config).to receive(:public_key).and_return('test_public_key') - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://abc123def.ucarecdn.com' }) - - result = file.cdn_url - expect(result).to eq("https://abc123def.ucarecdn.com#{test_uuid}/") - end - - it 'handles custom CNAME domains' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://my-custom-domain.com' }) - - result = file.cdn_url - expect(result).to eq("https://my-custom-domain.com#{test_uuid}/") - end - - context 'integration with real config' do - it 'generates valid CDN URL with default config' do - # Don't mock cdn_base to test real integration - result = file.cdn_url - - expect(result).to be_a(String) - expect(result).to include(test_uuid) - expect(result).to end_with('/') - expect(result).to match(%r{\Ahttps?://}) - end - end - end - - describe 'file conversion' do - let(:url) { "https://ucarecdn.com/#{source_file_uuid}" } - let(:file) { File.new(url: url) } - - shared_examples 'new file conversion' do - it 'performs a convert request', :aggregate_failures do - VCR.use_cassette(convert_cassette) do - VCR.use_cassette(get_file_cassette) do - expect(new_file.uuid).not_to be_empty - expect(new_file.uuid).not_to eq source_file_uuid - end - end - end - end - - context 'when converting a document' do - let(:source_file_uuid) { '8f64f313-e6b1-4731-96c0-6751f1e7a50a' } - let(:new_file) { file.convert_document({ format: 'png', page: 1 }) } - - it_behaves_like 'new file conversion' do - let(:convert_cassette) { 'document_convert_convert_many' } - let(:get_file_cassette) { 'document_convert_file_info' } - end - end - - context 'when converting a video' do - let(:source_file_uuid) { 'e30112d7-3a90-4931-b2c5-688cbb46d3ac' } - let(:new_file) do - file.convert_video( - { - format: 'ogg', - quality: 'best', - cut: { start_time: '0:0:0.0', length: 'end' }, - size: { resize_mode: 'change_ratio', width: '600', height: '400' }, - thumb: { N: 1, number: 2 } - } - ) - end - - it_behaves_like 'new file conversion' do - let(:convert_cassette) { 'video_convert_convert_many' } - let(:get_file_cassette) { 'video_convert_file_info' } - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/group_list_spec.rb b/spec/uploadcare/entity/group_list_spec.rb deleted file mode 100644 index 6bda6505..00000000 --- a/spec/uploadcare/entity/group_list_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe GroupList do - subject { GroupList } - it 'responds to expected methods' do - %i[list].each do |method| - expect(subject).to respond_to(method) - end - end - - context 'list' do - before do - VCR.use_cassette('rest_list_groups_limited') do - @groups = subject.list(limit: 2) - end - end - - it 'represents a file group' do - expect(@groups.groups[0]).to be_a_kind_of(Group) - end - - it 'responds to pagination methods' do - %i[previous_page next_page load].each do |method| - expect(@groups).to respond_to(method) - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/group_spec.rb b/spec/uploadcare/entity/group_spec.rb deleted file mode 100644 index 93cb782a..00000000 --- a/spec/uploadcare/entity/group_spec.rb +++ /dev/null @@ -1,311 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe Group do - subject { Group } - it 'responds to expected methods' do - %i[create info store delete].each do |method| - expect(subject).to respond_to(method) - end - end - - context 'info' do - before do - VCR.use_cassette('upload_group_info') do - @group = subject.info('bbc75785-9016-4656-9c6e-64a76b45b0b8~2') - end - end - - it 'represents a file group' do - file_fields = %i[id datetime_created datetime_stored files_count cdn_url url files] - file_fields.each do |method| - expect(@group).to respond_to(method) - end - end - - it 'has files' do - expect(@group.files).not_to be_empty - expect(@group.files.first).to be_a_kind_of(Uploadcare::Entity::File) - end - end - - describe 'id' do - it 'returns id, even if only cdn_url is defined' do - group = Group.new(cdn_url: 'https://ucarecdn.com/bbc75785-9016-4656-9c6e-64a76b45b0b8~2') - expect(group.id).to eq 'bbc75785-9016-4656-9c6e-64a76b45b0b8~2' - end - end - - describe 'load' do - it 'performs load request' do - VCR.use_cassette('upload_group_info') do - cdn_url = 'https://ucarecdn.com/bbc75785-9016-4656-9c6e-64a76b45b0b8~2' - group = Group.new(cdn_url: cdn_url) - group.load - expect(group.files_count).not_to be_nil - end - end - end - - describe 'delete' do - it 'deletes a file group' do - VCR.use_cassette('upload_group_delete') do - response = subject.delete('bbc75785-9016-4656-9c6e-64a76b45b0b8~2') - expect(response).to eq('200 OK') - end - end - - it 'raises error for nonexistent file' do - VCR.use_cassette('group_delete_nonexistent_uuid') do - uuid = 'nonexistent' - expect { subject.delete(uuid) }.to raise_error(RequestError) - end - end - end - - describe 'cdn_url' do - let(:test_group_id) { 'bbc75785-9016-4656-9c6e-64a76b45b0b8~2' } - let(:group) { Group.new(id: test_group_id) } - - before do - # Reset any memoized config values - allow(Uploadcare.config).to receive(:cdn_base).and_call_original - end - - it 'generates CDN URL using cdn_base config' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://example.ucarecdn.com' }) - - result = group.cdn_url - expect(result).to eq("https://example.ucarecdn.com#{test_group_id}/") - end - - it 'handles different CDN base configurations' do - test_cases = [ - { base: 'https://custom.cdn.com', expected: "https://custom.cdn.com#{test_group_id}/" }, - { base: 'https://subdomain.ucarecdn.com', expected: "https://subdomain.ucarecdn.com#{test_group_id}/" }, - { base: 'https://cdn.example.org', expected: "https://cdn.example.org#{test_group_id}/" } - ] - - test_cases.each do |test_case| - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { test_case[:base] }) - expect(group.cdn_url).to eq(test_case[:expected]) - end - end - - it 'works with group initialized from cdn_url' do - cdn_url_group = Group.new(cdn_url: "https://ucarecdn.com/#{test_group_id}/") - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://test.cdn.com' }) - - result = cdn_url_group.cdn_url - expect(result).to eq("https://test.cdn.com#{test_group_id}/") - end - - it 'calls cdn_base each time for dynamic config updates' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://first.cdn.com' }) - first_call = group.cdn_url - - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://second.cdn.com' }) - second_call = group.cdn_url - - expect(first_call).to eq("https://first.cdn.com#{test_group_id}/") - expect(second_call).to eq("https://second.cdn.com#{test_group_id}/") - end - - it 'handles CDN base with trailing slashes correctly' do - test_cases = [ - { base: 'https://cdn.com/', expected: "https://cdn.com/#{test_group_id}/" }, - { base: 'https://cdn.com', expected: "https://cdn.com#{test_group_id}/" } - ] - - test_cases.each do |test_case| - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { test_case[:base] }) - expect(group.cdn_url).to eq(test_case[:expected]) - end - end - - it 'includes cdn_url in entity attributes' do - expect(Group.new({})).to respond_to(:cdn_url) - end - - it 'works with subdomains when enabled' do - allow(Uploadcare.config).to receive(:use_subdomains).and_return(true) - allow(Uploadcare.config).to receive(:public_key).and_return('test_public_key') - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://abc123def.ucarecdn.com' }) - - result = group.cdn_url - expect(result).to eq("https://abc123def.ucarecdn.com#{test_group_id}/") - end - - it 'handles custom CNAME domains' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://my-custom-domain.com' }) - - result = group.cdn_url - expect(result).to eq("https://my-custom-domain.com#{test_group_id}/") - end - - context 'integration with real config' do - it 'generates valid CDN URL with default config' do - # Don't mock cdn_base to test real integration - result = group.cdn_url - - expect(result).to be_a(String) - expect(result).to include(test_group_id) - expect(result).to end_with('/') - expect(result).to match(%r{\Ahttps?://}) - end - end - end - - describe 'file_cdn_urls' do - let(:test_group_id) { 'bbc75785-9016-4656-9c6e-64a76b45b0b8~2' } - let(:group) { Group.new(id: test_group_id) } - - before do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://ucarecdn.com/' }) - end - - it 'includes file_cdn_urls in entity attributes' do - expect(Group.new({})).to respond_to(:file_cdn_urls) - end - - it 'returns empty array for group with no files' do - files_collection = double('files_collection', count: 0) - allow(group).to receive(:files).and_return(files_collection) - - result = group.file_cdn_urls - expect(result).to eq([]) - end - - it 'generates CDN URLs using group CDN URL and file indices' do - files_collection = double('files_collection', count: 3) - allow(group).to receive(:files).and_return(files_collection) - - result = group.file_cdn_urls - - expect(result).to be_an(Array) - expect(result.length).to eq(3) - expect(result[0]).to eq("https://ucarecdn.com/#{test_group_id}/nth/0/") - expect(result[1]).to eq("https://ucarecdn.com/#{test_group_id}/nth/1/") - expect(result[2]).to eq("https://ucarecdn.com/#{test_group_id}/nth/2/") - end - - it 'uses group cdn_url method for base URL' do - files_collection = double('files_collection', count: 2) - allow(group).to receive(:files).and_return(files_collection) - allow(group).to receive(:cdn_url).and_return('https://custom.cdn.com/group123/') - - result = group.file_cdn_urls - - expect(result).to eq([ - 'https://custom.cdn.com/group123/nth/0/', - 'https://custom.cdn.com/group123/nth/1/' - ]) - end - - it 'handles single file' do - files_collection = double('files_collection', count: 1) - allow(group).to receive(:files).and_return(files_collection) - - result = group.file_cdn_urls - - expect(result).to eq(["https://ucarecdn.com/#{test_group_id}/nth/0/"]) - end - - it 'works with different CDN base configurations' do - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://subdomain.ucarecdn.com/' }) - files_collection = double('files_collection', count: 2) - allow(group).to receive(:files).and_return(files_collection) - - result = group.file_cdn_urls - - expect(result).to eq([ - "https://subdomain.ucarecdn.com/#{test_group_id}/nth/0/", - "https://subdomain.ucarecdn.com/#{test_group_id}/nth/1/" - ]) - end - - it 'reflects dynamic CDN configuration changes' do - files_collection = double('files_collection', count: 1) - allow(group).to receive(:files).and_return(files_collection) - - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://first.cdn.com/' }) - first_result = group.file_cdn_urls - - allow(Uploadcare.config).to receive(:cdn_base).and_return(-> { 'https://second.cdn.com/' }) - second_result = group.file_cdn_urls - - expect(first_result).to eq(["https://first.cdn.com/#{test_group_id}/nth/0/"]) - expect(second_result).to eq(["https://second.cdn.com/#{test_group_id}/nth/0/"]) - end - - it 'generates URLs with correct index sequence' do - files_collection = double('files_collection', count: 5) - allow(group).to receive(:files).and_return(files_collection) - - result = group.file_cdn_urls - - expect(result.length).to eq(5) - (0...5).each do |i| - expect(result[i]).to eq("https://ucarecdn.com/#{test_group_id}/nth/#{i}/") - end - end - - context 'integration with real File entities' do - it 'works with actual File objects and VCR' do - VCR.use_cassette('upload_group_info') do - group = Group.info('bbc75785-9016-4656-9c6e-64a76b45b0b8~2') - - urls = group.file_cdn_urls - expect(urls).to be_an(Array) - - # Each URL should follow the nth pattern - urls.each_with_index do |url, index| - expect(url).to be_a(String) - expect(url).to match(%r{\Ahttps?://}) - expect(url).to end_with("/nth/#{index}/") - expect(url).to include(group.id) - end - end - end - end - - context 'performance considerations' do - it 'efficiently processes large number of files' do - files_collection = double('files_collection', count: 100) - allow(group).to receive(:files).and_return(files_collection) - - start_time = Time.now - result = group.file_cdn_urls - end_time = Time.now - - expect(result.length).to eq(100) - expect(end_time - start_time).to be < 0.1 # Should complete very quickly - - # Verify the pattern is correct for the last few - expect(result[99]).to eq("https://ucarecdn.com/#{test_group_id}/nth/99/") - expect(result[0]).to eq("https://ucarecdn.com/#{test_group_id}/nth/0/") - end - end - - context 'error handling' do - it 'handles zero files gracefully' do - files_collection = double('files_collection', count: 0) - allow(group).to receive(:files).and_return(files_collection) - - result = group.file_cdn_urls - expect(result).to eq([]) - end - - it 'handles nil files collection' do - allow(group).to receive(:files).and_return(nil) - - expect { group.file_cdn_urls }.to raise_error(NoMethodError) - end - end - end - end - end -end diff --git a/spec/uploadcare/entity/project_spec.rb b/spec/uploadcare/entity/project_spec.rb deleted file mode 100644 index 3256bcda..00000000 --- a/spec/uploadcare/entity/project_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe Project do - before do - VCR.use_cassette('project') do - @project = Project.show - end - end - - it 'represents a project as an entity' do - expect(@project).to be_kind_of Uploadcare::Entity::Project - end - - it 'responds to project api methods' do - expect(@project).to respond_to(:collaborators, :name, :pub_key, :autostore_enabled) - end - end - end -end diff --git a/spec/uploadcare/entity/uploader_spec.rb b/spec/uploadcare/entity/uploader_spec.rb deleted file mode 100644 index 683323d6..00000000 --- a/spec/uploadcare/entity/uploader_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Uploadcare::Entity::Uploader do - subject { Uploadcare::Entity::Uploader } - let!(:file) { File.open('spec/fixtures/kitten.jpeg') } - let!(:another_file) { File.open('spec/fixtures/another_kitten.jpeg') } - let!(:big_file) { File.open('spec/fixtures/big.jpeg') } - - describe 'upload_many' do - it 'returns a hash of filenames and uids', :aggregate_failures do - VCR.use_cassette('upload_upload_many') do - uploads_list = subject.upload([file, another_file]) - expect(uploads_list.length).to eq 2 - first_upload = uploads_list.first - expect(first_upload.original_filename).not_to be_empty - expect(first_upload.uuid).not_to be_empty - end - end - - describe 'upload_one' do - it 'returns a file', :aggregate_failures do - VCR.use_cassette('upload_upload_one') do - upload = subject.upload(file) - expect(upload).to be_kind_of(Uploadcare::Entity::File) - expect(file.path).to end_with(upload.original_filename.to_s) - expect(file.size).to eq(upload.size) - end - end - - context 'when the secret key is missing' do - it 'returns a file without details', :aggregate_failures do - Uploadcare.config.secret_key = nil - - VCR.use_cassette('upload_upload_one_without_secret_key') do - upload = subject.upload(file) - expect(upload).to be_kind_of(Uploadcare::Entity::File) - expect(file.path).to end_with(upload.original_filename.to_s) - expect(file.size).to eq(upload.size) - end - end - end - end - - describe 'upload_from_url' do - let(:url) { 'https://placekitten.com/2250/2250' } - - before do - allow(HTTP::FormData::Multipart).to receive(:new).and_call_original - end - - it 'polls server and returns array of files' do - VCR.use_cassette('upload_upload_from_url') do - upload = subject.upload(url) - expect(upload[0]).to be_kind_of(Uploadcare::Entity::File) - expect(HTTP::FormData::Multipart).to have_received(:new).with( - a_hash_including( - 'source_url' => url - ) - ) - end - end - - context 'when signed uploads are enabled' do - before do - allow(Uploadcare.config).to receive(:sign_uploads).and_return(true) - end - - it 'includes signature' do - VCR.use_cassette('upload_upload_from_url_with_signature') do - upload = subject.upload(url) - expect(upload[0]).to be_kind_of(Uploadcare::Entity::File) - expect(HTTP::FormData::Multipart).to have_received(:new).with( - a_hash_including( - signature: instance_of(String), - expire: instance_of(Integer) - ) - ) - end - end - end - - it 'raises error with information if file upload takes time' do - Uploadcare.config.max_request_tries = 1 - VCR.use_cassette('upload_upload_from_url') do - url = 'https://placekitten.com/2250/2250' - error_str = 'Upload is taking longer than expected. Try increasing the max_request_tries config if you know your file uploads will take more time.' - expect { subject.upload(url) }.to raise_error(RetryError, error_str) - end - end - end - - describe 'multipart_upload' do - let!(:some_var) { nil } - - it 'uploads a file', :aggregate_failures do - VCR.use_cassette('upload_multipart_upload') do - # Minimal size for file to be valid for multipart upload is 10 mb - Uploadcare.config.multipart_size_threshold = 10 * 1024 * 1024 - expect(some_var).to receive(:to_s).at_least(:once).and_call_original - file = subject.multipart_upload(big_file) { some_var } - expect(file).to be_kind_of(Uploadcare::Entity::File) - expect(file.uuid).not_to be_empty - end - end - end - - describe 'get_upload_from_url_status' do - it 'gets a status of upload-from-URL' do - VCR.use_cassette('upload_get_upload_from_url_status') do - token = '0313e4e2-f2ca-4564-833b-4f71bc8cba27' - status_info = subject.get_upload_from_url_status(token).success - expect(status_info[:status]).to eq 'success' - end - end - end - end - - describe 'file_info' do - it 'returns file info without the secret key', :aggregate_failures do - uuid = 'a7f9751a-432b-4b05-936c-2f62d51d255d' - - VCR.use_cassette('upload_file_info') do - file_info = subject.file_info(uuid).success - expect(file_info[:original_filename]).not_to be_empty - expect(file_info[:size]).to be >= 0 - expect(file_info[:uuid]).to eq uuid - end - end - end -end diff --git a/spec/uploadcare/entity/webhook_spec.rb b/spec/uploadcare/entity/webhook_spec.rb deleted file mode 100644 index cc4a16ce..00000000 --- a/spec/uploadcare/entity/webhook_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - module Entity - RSpec.describe Webhook do - subject { Webhook } - it 'responds to expected methods' do - %i[list delete update].each do |method| - expect(subject).to respond_to(method) - end - end - - describe 'create' do - it 'represents a webhook' do - VCR.use_cassette('rest_webhook_create') do - target_url = 'http://ohmyz.sh' - webhook = subject.create(target_url: target_url) - %i[created event id is_active project target_url updated].each do |field| - expect(webhook[field]).not_to be_nil - end - end - end - end - - describe 'list' do - it 'returns list of webhooks' do - VCR.use_cassette('rest_webhook_list') do - webhooks = subject.list - expect(webhooks).to be_kind_of(ApiStruct::Collection) - end - end - end - end - end -end diff --git a/spec/uploadcare/exception_spec.rb b/spec/uploadcare/exception_spec.rb new file mode 100644 index 00000000..c7e23312 --- /dev/null +++ b/spec/uploadcare/exception_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +RSpec.describe 'Uploadcare exceptions' do + it 'initializes ThrottleError with timeout' do + error = Uploadcare::Exception::ThrottleError.new(timeout: 3.5) + + expect(error.timeout).to eq(3.5) + end + + it 'defines core exception classes' do + expect(Uploadcare::Exception::AuthError).to be < StandardError + expect(Uploadcare::Exception::ConversionError).to be < StandardError + expect(Uploadcare::Exception::RequestError).to be < StandardError + expect(Uploadcare::Exception::RetryError).to be < StandardError + end +end diff --git a/spec/uploadcare/features/error_spec.rb b/spec/uploadcare/features/error_spec.rb deleted file mode 100644 index 92a396e4..00000000 --- a/spec/uploadcare/features/error_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - RSpec.describe 'User-friendly errors' do - # Ideally, this gem should raise errors as they are described in API - let!(:file) { ::File.open('spec/fixtures/kitten.jpeg') } - - context 'REST API' do - it 'raises a readable error on failed requests' do - VCR.use_cassette('rest_file_info_fail') do - uuid = 'nonexistent' - expect { Entity::File.info(uuid) }.to raise_error(RequestError, 'Not found.') - end - end - end - - context 'Upload API' do - # For some reason, upload errors come with status 200; - # You need to actually read the response to find out that it is in fact an error - it 'raises readable errors with incorrect 200 responses' do - VCR.use_cassette('upload_error') do - Uploadcare.config.public_key = 'baz' - begin - Entity::Uploader.upload(file) - rescue StandardError => e - expect(e.to_s).to include('UPLOADCARE_PUB_KEY is invalid') - end - end - end - end - end -end diff --git a/spec/uploadcare/features/throttling_spec.rb b/spec/uploadcare/features/throttling_spec.rb deleted file mode 100644 index 6b4a3528..00000000 --- a/spec/uploadcare/features/throttling_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Uploadcare - RSpec.describe 'throttling' do - let!(:file) { ::File.open('spec/fixtures/kitten.jpeg') } - Kernel.class_eval do - # prevent waiting time - def sleep(_time); end - end - - context 'REST API' do - context 'cassette with 3 throttled responses and one proper response' do - it 'makes multiple attempts on throttled requests' do - VCR.use_cassette('throttling') do - expect { Entity::File.info('8f64f313-e6b1-4731-96c0-6751f1e7a50a') }.not_to raise_error - # make sure this cassette actually had 3 throttled responses - assert_requested(:get, 'https://api.uploadcare.com/files/8f64f313-e6b1-4731-96c0-6751f1e7a50a/', times: 4) - end - end - end - end - - context 'Upload API' do - context 'cassette with a throttled response' do - it 'makes multiple attempts on throttled requests' do - VCR.use_cassette('upload_throttling') do - expect { Entity::Uploader.upload(file) }.not_to raise_error - # make sure this cassette actually had a throttled response - assert_requested(:post, 'https://upload.uploadcare.com/base/', times: 2) - end - end - end - end - end -end diff --git a/spec/uploadcare/integration_spec.rb b/spec/uploadcare/integration_spec.rb new file mode 100644 index 00000000..b66327bb --- /dev/null +++ b/spec/uploadcare/integration_spec.rb @@ -0,0 +1,387 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Integration: end-to-end workflows' do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + rest_api_root: 'https://api.uploadcare.com', + upload_api_root: 'https://upload.uploadcare.com' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + let(:file_data) do + { + 'uuid' => file_uuid, + 'original_filename' => 'test.jpg', + 'size' => 1024, + 'mime_type' => 'image/jpeg', + 'is_ready' => true, + 'is_image' => true, + 'datetime_uploaded' => '2025-01-01T00:00:00Z', + 'url' => "https://ucarecdn.com/#{file_uuid}/" + } + end + + describe 'file lifecycle: find → store → delete' do + before do + stub_request(:get, "https://api.uploadcare.com/files/#{file_uuid}/") + .to_return( + status: 200, + body: file_data.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:put, "https://api.uploadcare.com/files/#{file_uuid}/storage/") + .to_return( + status: 200, + body: file_data.merge('datetime_stored' => '2025-01-02T00:00:00Z').to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:delete, "https://api.uploadcare.com/files/#{file_uuid}/storage/") + .to_return( + status: 200, + body: file_data.merge('datetime_removed' => '2025-01-03T00:00:00Z').to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'performs the full lifecycle' do + file = client.files.find(uuid: file_uuid) + expect(file).to be_a(Uploadcare::Resources::File) + expect(file.uuid).to eq(file_uuid) + expect(file.original_filename).to eq('test.jpg') + + file.store + expect(file.datetime_stored).to eq('2025-01-02T00:00:00Z') + + file.delete + expect(file.datetime_removed).to eq('2025-01-03T00:00:00Z') + end + end + + describe 'file listing with pagination' do + before do + stub_request(:get, 'https://api.uploadcare.com/files/') + .with(query: hash_including({})) + .to_return( + status: 200, + body: { + 'results' => [file_data], + 'next' => 'https://api.uploadcare.com/files/?limit=1&offset=1', + 'previous' => nil, + 'per_page' => 1, + 'total' => 2 + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:get, 'https://api.uploadcare.com/files/') + .with(query: { 'limit' => '1', 'offset' => '1' }) + .to_return( + status: 200, + body: { + 'results' => [file_data.merge('uuid' => 'second-uuid')], + 'next' => nil, + 'previous' => 'https://api.uploadcare.com/files/?limit=1', + 'per_page' => 1, + 'total' => 2 + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'lists files and navigates pages' do + page1 = client.files.list(limit: 1) + expect(page1).to be_a(Uploadcare::Collections::Paginated) + expect(page1.total).to eq(2) + expect(page1.count).to eq(1) + expect(page1.first.uuid).to eq(file_uuid) + + page2 = page1.next_page + expect(page2).to be_a(Uploadcare::Collections::Paginated) + expect(page2.first.uuid).to eq('second-uuid') + expect(page2.next_page).to be_nil + end + end + + describe 'batch file operations' do + let(:uuids) { %w[uuid-1 uuid-2] } + + before do + stub_request(:put, 'https://api.uploadcare.com/files/storage/') + .to_return( + status: 200, + body: { + 'status' => 'ok', + 'result' => uuids.map { |u| { 'uuid' => u } }, + 'problems' => {} + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'batch stores files' do + result = client.files.batch_store(uuids: uuids) + expect(result).to be_a(Uploadcare::Collections::BatchResult) + expect(result.result.length).to eq(2) + expect(result.problems).to be_empty + end + end + + describe 'group lifecycle: create → find → delete' do + let(:group_id) { "#{file_uuid}~2" } + let(:group_data) do + { + 'id' => group_id, + 'files_count' => 2, + 'datetime_created' => '2025-01-01T00:00:00Z', + 'files' => [file_data, file_data.merge('uuid' => 'uuid-2')] + } + end + + before do + stub_request(:post, 'https://upload.uploadcare.com/group/') + .to_return( + status: 200, + body: group_data.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:get, "https://api.uploadcare.com/groups/#{URI.encode_www_form_component(group_id)}/") + .to_return( + status: 200, + body: group_data.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:delete, "https://api.uploadcare.com/groups/#{URI.encode_www_form_component(group_id)}/") + .to_return(status: 204, body: '', headers: {}) + end + + it 'performs the full group lifecycle' do + group = client.groups.create(uuids: [file_uuid, 'uuid-2']) + expect(group).to be_a(Uploadcare::Resources::Group) + expect(group.id).to eq(group_id) + expect(group.files_count).to eq(2) + + found = client.groups.find(group_id: group_id) + expect(found.id).to eq(group_id) + + expect(found.file_cdn_urls.length).to eq(2) + end + end + + describe 'webhook lifecycle: create → list → update → delete' do + let(:webhook_data) do + { + 'id' => 123, + 'target_url' => 'https://example.com/webhook', + 'event' => 'file.uploaded', + 'is_active' => true, + 'project' => 42 + } + end + + before do + stub_request(:post, 'https://api.uploadcare.com/webhooks/') + .to_return( + status: 200, + body: webhook_data.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:get, 'https://api.uploadcare.com/webhooks/') + .to_return( + status: 200, + body: [webhook_data].to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:put, 'https://api.uploadcare.com/webhooks/123/') + .to_return( + status: 200, + body: webhook_data.merge('is_active' => false).to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:delete, 'https://api.uploadcare.com/webhooks/unsubscribe/') + .to_return(status: 204, body: '', headers: {}) + end + + it 'performs the full webhook lifecycle' do + webhook = client.webhooks.create(target_url: 'https://example.com/webhook') + expect(webhook).to be_a(Uploadcare::Resources::Webhook) + expect(webhook.id).to eq(123) + + webhooks = client.webhooks.list + expect(webhooks.length).to eq(1) + + updated = client.webhooks.update(id: 123, is_active: false) + expect(updated.is_active).to be(false) + + client.webhooks.delete(target_url: 'https://example.com/webhook') + end + end + + describe 'project info' do + before do + stub_request(:get, 'https://api.uploadcare.com/project/') + .to_return( + status: 200, + body: { 'name' => 'Test Project', 'pub_key' => 'demopublickey', 'autostore_enabled' => true }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'retrieves project info' do + project = client.project.current + expect(project).to be_a(Uploadcare::Resources::Project) + expect(project.name).to eq('Test Project') + expect(project.pub_key).to eq('demopublickey') + end + end + + describe 'file metadata lifecycle' do + before do + stub_request(:get, "https://api.uploadcare.com/files/#{file_uuid}/metadata/") + .to_return( + status: 200, + body: { 'key1' => 'value1' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:put, "https://api.uploadcare.com/files/#{file_uuid}/metadata/key2/") + .to_return( + status: 200, + body: '"value2"', + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:get, "https://api.uploadcare.com/files/#{file_uuid}/metadata/key1/") + .to_return( + status: 200, + body: '"value1"', + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:delete, "https://api.uploadcare.com/files/#{file_uuid}/metadata/key1/") + .to_return(status: 204, body: '', headers: {}) + end + + it 'performs metadata operations' do + metadata = client.file_metadata.index(uuid: file_uuid) + expect(metadata).to eq({ 'key1' => 'value1' }) + + client.file_metadata.update(uuid: file_uuid, key: 'key2', value: 'value2') + + value = client.file_metadata.show(uuid: file_uuid, key: 'key1') + expect(value).to eq('value1') + + client.file_metadata.delete(uuid: file_uuid, key: 'key1') + end + end + + describe 'multi-account support' do + let(:config_a) { Uploadcare::Configuration.new(public_key: 'key_a', secret_key: 'secret_a', auth_type: 'Uploadcare.Simple') } + let(:config_b) { Uploadcare::Configuration.new(public_key: 'key_b', secret_key: 'secret_b', auth_type: 'Uploadcare.Simple') } + let(:client_a) { Uploadcare::Client.new(config: config_a) } + let(:client_b) { Uploadcare::Client.new(config: config_b) } + + it 'maintains independent configurations' do + expect(client_a.config.public_key).to eq('key_a') + expect(client_b.config.public_key).to eq('key_b') + expect(client_a.config).not_to equal(client_b.config) + end + + it 'creates independent API clients' do + expect(client_a.api.rest).not_to equal(client_b.api.rest) + expect(client_a.api.upload).not_to equal(client_b.api.upload) + end + + it 'supports config.with for overrides' do + client_c = client_a.with(public_key: 'key_c') + expect(client_c.config.public_key).to eq('key_c') + expect(client_c.config.secret_key).to eq('secret_a') + expect(client_a.config.public_key).to eq('key_a') + end + end + + describe 'add-on execution' do + before do + stub_request(:post, 'https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/') + .to_return( + status: 200, + body: { 'request_id' => 'req-123' }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:get, 'https://api.uploadcare.com/addons/aws_rekognition_detect_labels/execute/status/') + .with(query: { 'request_id' => 'req-123' }) + .to_return( + status: 200, + body: { 'status' => 'done', 'result' => { 'labels' => ['cat'] } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'executes addon and checks status' do + execution = client.addons.aws_rekognition_detect_labels(uuid: file_uuid) + expect(execution).to be_a(Uploadcare::Resources::AddonExecution) + expect(execution.request_id).to eq('req-123') + + status = client.addons.aws_rekognition_detect_labels_status(request_id: 'req-123') + expect(status.status).to eq('done') + end + end + + describe 'conversion workflow' do + before do + stub_request(:post, 'https://api.uploadcare.com/convert/document/') + .to_return( + status: 200, + body: { 'result' => [{ 'uuid' => 'converted-uuid', 'token' => 123 }], 'problems' => {} }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + stub_request(:get, 'https://api.uploadcare.com/convert/document/status/123/') + .to_return( + status: 200, + body: { 'status' => 'finished', 'result' => { 'uuid' => 'converted-uuid' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'converts document and checks status' do + result = client.conversions.documents.convert(uuid: file_uuid, format: 'pdf') + expect(result).to be_a(Hash) + expect(result['result'].first['uuid']).to eq('converted-uuid') + + status = client.conversions.documents.status(token: 123) + expect(status.status).to eq('finished') + end + end + + describe 'global convenience accessors' do + it 'provides files through Uploadcare.files' do + expect(Uploadcare.files).to be_a(Uploadcare::Client::FilesAccessor) + end + + it 'provides groups through Uploadcare.groups' do + expect(Uploadcare.groups).to be_a(Uploadcare::Client::GroupsAccessor) + end + + it 'provides uploads through Uploadcare.uploads' do + expect(Uploadcare.uploads).to be_a(Uploadcare::Operations::UploadRouter) + end + + it 'provides project through Uploadcare.project' do + expect(Uploadcare.project).to be_a(Uploadcare::Client::ProjectAccessor) + end + end +end diff --git a/spec/uploadcare/internal/authenticator_spec.rb b/spec/uploadcare/internal/authenticator_spec.rb new file mode 100644 index 00000000..efa835f8 --- /dev/null +++ b/spec/uploadcare/internal/authenticator_spec.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Internal::Authenticator do + subject(:authenticator) { described_class.new(config: config) } + + let(:public_key) { 'test-public-key' } + let(:secret_key) { 'test-secret-key' } + + let(:config) do + Uploadcare::Configuration.new( + public_key: public_key, + secret_key: secret_key, + auth_type: auth_type + ) + end + + describe '#initialize' do + let(:auth_type) { 'Uploadcare.Simple' } + + it 'sets default headers with Accept and User-Agent' do + headers = authenticator.default_headers + expect(headers['Accept']).to eq('application/vnd.uploadcare-v0.7+json') + expect(headers['User-Agent']).to include('UploadcareRuby/') + expect(headers['User-Agent']).to include(public_key) + end + end + + describe '#headers' do + context 'with simple auth' do + let(:auth_type) { 'Uploadcare.Simple' } + + it 'returns headers with simple authorization' do + result = authenticator.headers('GET', '/files/') + expect(result['Authorization']).to eq("Uploadcare.Simple #{public_key}:#{secret_key}") + end + + it 'includes Content-Type defaulting to application/json' do + result = authenticator.headers('GET', '/files/') + expect(result['Content-Type']).to eq('application/json') + end + + it 'uses custom content_type when provided' do + result = authenticator.headers('GET', '/files/', '', 'multipart/form-data') + expect(result['Content-Type']).to eq('multipart/form-data') + end + + it 'includes default headers' do + result = authenticator.headers('GET', '/files/') + expect(result['Accept']).to eq('application/vnd.uploadcare-v0.7+json') + expect(result['User-Agent']).to include('UploadcareRuby/') + end + + it 'raises AuthError when secret_key is blank' do + blank_config = Uploadcare::Configuration.new( + public_key: public_key, + secret_key: '', + auth_type: 'Uploadcare.Simple' + ) + auth = described_class.new(config: blank_config) + expect { auth.headers('GET', '/files/') }.to raise_error( + Uploadcare::Exception::AuthError, /Secret Key is blank/ + ) + end + + it 'raises AuthError when public_key is blank' do + blank_config = Uploadcare::Configuration.new( + public_key: '', + secret_key: secret_key, + auth_type: 'Uploadcare.Simple' + ) + auth = described_class.new(config: blank_config) + expect { auth.headers('GET', '/files/') }.to raise_error( + Uploadcare::Exception::AuthError, /Public Key is blank/ + ) + end + end + + context 'with secure auth' do + let(:auth_type) { 'Uploadcare' } + + it 'returns headers with HMAC-signed authorization' do + result = authenticator.headers('GET', '/files/', '') + expect(result['Authorization']).to start_with("Uploadcare #{public_key}:") + end + + it 'includes a Date header' do + result = authenticator.headers('GET', '/files/', '') + expect(result['Date']).to match(/\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT/) + end + + it 'includes Content-Type header' do + result = authenticator.headers('POST', '/files/', '{"key":"val"}') + expect(result['Content-Type']).to eq('application/json') + end + + it 'uses custom content_type when provided' do + result = authenticator.headers('POST', '/files/', '', 'text/plain') + expect(result['Content-Type']).to eq('text/plain') + end + + it 'generates different signatures for different methods' do + get_headers = authenticator.headers('GET', '/files/', '') + post_headers = authenticator.headers('POST', '/files/', '') + get_sig = get_headers['Authorization'].split(':').last + post_sig = post_headers['Authorization'].split(':').last + expect(get_sig).not_to eq(post_sig) + end + + it 'generates different signatures for different URIs' do + a = authenticator.headers('GET', '/files/', '') + b = authenticator.headers('GET', '/groups/', '') + sig_a = a['Authorization'].split(':').last + sig_b = b['Authorization'].split(':').last + expect(sig_a).not_to eq(sig_b) + end + + it 'generates different signatures for different bodies' do + a = authenticator.headers('POST', '/files/', '{"a":1}') + b = authenticator.headers('POST', '/files/', '{"b":2}') + sig_a = a['Authorization'].split(':').last + sig_b = b['Authorization'].split(':').last + expect(sig_a).not_to eq(sig_b) + end + + it 'raises AuthError when secret_key is blank' do + blank_config = Uploadcare::Configuration.new( + public_key: public_key, + secret_key: '', + auth_type: 'Uploadcare' + ) + auth = described_class.new(config: blank_config) + expect { auth.headers('GET', '/files/') }.to raise_error( + Uploadcare::Exception::AuthError, /Secret Key is blank/ + ) + end + + it 'raises AuthError when secret_key is nil' do + nil_config = Uploadcare::Configuration.new( + public_key: public_key, + secret_key: nil, + auth_type: 'Uploadcare' + ) + auth = described_class.new(config: nil_config) + expect { auth.headers('GET', '/files/') }.to raise_error( + Uploadcare::Exception::AuthError, /Secret Key is blank/ + ) + end + + it 'raises AuthError when public_key is blank' do + blank_pk_config = Uploadcare::Configuration.new( + public_key: '', + secret_key: secret_key, + auth_type: 'Uploadcare' + ) + auth = described_class.new(config: blank_pk_config) + expect { auth.headers('GET', '/files/') }.to raise_error( + Uploadcare::Exception::AuthError, /Public Key is blank/ + ) + end + + it 'raises AuthError when public_key is nil' do + nil_pk_config = Uploadcare::Configuration.new( + public_key: nil, + secret_key: secret_key, + auth_type: 'Uploadcare' + ) + auth = described_class.new(config: nil_pk_config) + expect { auth.headers('GET', '/files/') }.to raise_error( + Uploadcare::Exception::AuthError, /Public Key is blank/ + ) + end + + it 'normalizes URI without leading slash' do + with_slash = authenticator.headers('GET', '/files/', '') + without_slash = authenticator.headers('GET', 'files/', '') + sig_with = with_slash['Authorization'].split(':').last + sig_without = without_slash['Authorization'].split(':').last + expect(sig_with).to eq(sig_without) + end + + it 'uses MD5 for body digest required by Uploadcare REST signing' do + expect(authenticator.send(:body_digest, 'abc')).to eq(OpenSSL::Digest.new('MD5').hexdigest('abc')) + end + + it 'uses SHA1 for HMAC signature required by Uploadcare REST signing' do + expect(authenticator.send(:signature_digest).name).to eq('SHA1') + end + end + end +end diff --git a/spec/uploadcare/internal/error_handler_spec.rb b/spec/uploadcare/internal/error_handler_spec.rb new file mode 100644 index 00000000..cc604bdb --- /dev/null +++ b/spec/uploadcare/internal/error_handler_spec.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Internal::ErrorHandler do + let(:handler) do + Class.new { include Uploadcare::Internal::ErrorHandler }.new + end + + def faraday_error(status:, body:, headers: {}) + response = { status: status, body: body, headers: headers } + Faraday::ClientError.new('request failed', response) + end + + describe '#handle_error' do + context 'when response is nil' do + it 'raises RequestError with the error message' do + error = Faraday::ClientError.new('connection refused') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError, 'connection refused' + ) + end + end + + context 'with HTTP 400' do + it 'raises InvalidRequestError' do + error = faraday_error(status: 400, body: '{"detail":"Bad request param"}') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::InvalidRequestError, 'Bad request param' + ) + end + end + + context 'with HTTP 404' do + it 'raises NotFoundError' do + error = faraday_error(status: 404, body: '{"detail":"Not found."}') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::NotFoundError, 'Not found.' + ) + end + end + + context 'with HTTP 429' do + it 'raises ThrottleError' do + error = faraday_error( + status: 429, + body: '{"detail":"Request was throttled."}', + headers: { 'retry-after' => '5' } + ) + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::ThrottleError + ) + end + + it 'sets timeout from retry-after header' do + error = faraday_error( + status: 429, + body: '{"detail":"Throttled"}', + headers: { 'retry-after' => '7.5' } + ) + begin + handler.handle_error(error) + rescue Uploadcare::Exception::ThrottleError => e + expect(e.timeout).to eq(7.5) + end + end + + it 'defaults timeout to 10.0 when retry-after is missing' do + error = faraday_error(status: 429, body: '{"detail":"Throttled"}') + begin + handler.handle_error(error) + rescue Uploadcare::Exception::ThrottleError => e + expect(e.timeout).to eq(10.0) + end + end + + it 'defaults timeout to 10.0 when retry-after is zero' do + error = faraday_error( + status: 429, + body: '{"detail":"Throttled"}', + headers: { 'retry-after' => '0' } + ) + begin + handler.handle_error(error) + rescue Uploadcare::Exception::ThrottleError => e + expect(e.timeout).to eq(10.0) + end + end + + it 'reads Retry-After with capitalized header name' do + error = faraday_error( + status: 429, + body: '{"detail":"Throttled"}', + headers: { 'Retry-After' => '3' } + ) + begin + handler.handle_error(error) + rescue Uploadcare::Exception::ThrottleError => e + expect(e.timeout).to eq(3.0) + end + end + + it 'parses Retry-After as an HTTP-date' do + future_time = (Time.now + 5).httpdate + error = faraday_error( + status: 429, + body: '{"detail":"Throttled"}', + headers: { 'Retry-After' => future_time } + ) + + begin + handler.handle_error(error) + rescue Uploadcare::Exception::ThrottleError => e + expect(e.timeout).to be_between(1.0, 5.5) + end + end + end + + context 'with other HTTP status codes' do + it 'raises RequestError for 500' do + error = faraday_error(status: 500, body: '{"detail":"Internal error"}') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError, 'Internal error' + ) + end + + it 'raises RequestError for 403' do + error = faraday_error(status: 403, body: '{"detail":"Forbidden"}') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError, 'Forbidden' + ) + end + end + + context 'with upload API errors (status 200 with error in body)' do + it 'raises RequestError when body contains error field' do + error = faraday_error( + status: 200, + body: '{"error":"File is too large."}' + ) + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError, 'File is too large.' + ) + end + + it 'does not raise for status 200 without error field' do + error = faraday_error( + status: 200, + body: '{"file":"some-uuid"}' + ) + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError + ) + end + end + + context 'with non-JSON response body' do + it 'uses raw body as error message' do + error = faraday_error(status: 500, body: 'Something went wrong') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError, 'Something went wrong' + ) + end + + it 'handles empty body' do + error = faraday_error(status: 500, body: '') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError + ) + end + + it 'handles nil body' do + error = faraday_error(status: 500, body: nil) + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::RequestError + ) + end + end + + context 'with JSON body without detail key' do + it 'formats all key-value pairs into the message' do + error = faraday_error(status: 400, body: '{"field":"is required","name":"cannot be blank"}') + expect { handler.handle_error(error) }.to raise_error( + Uploadcare::Exception::InvalidRequestError, 'field: is required; name: cannot be blank' + ) + end + end + end +end diff --git a/spec/uploadcare/internal/signature_generator_spec.rb b/spec/uploadcare/internal/signature_generator_spec.rb new file mode 100644 index 00000000..965b117b --- /dev/null +++ b/spec/uploadcare/internal/signature_generator_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Internal::SignatureGenerator do + describe '.call' do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'test-public', + secret_key: secret_key, + upload_signature_lifetime: lifetime + ) + end + + context 'with valid config' do + let(:secret_key) { 'my-secret-key' } + let(:lifetime) { 600 } + + it 'returns a hash with :signature and :expire keys' do + result = described_class.call(config: config) + expect(result).to be_a(Hash) + expect(result).to have_key(:signature) + expect(result).to have_key(:expire) + end + + it 'returns a hex string signature' do + result = described_class.call(config: config) + expect(result[:signature]).to match(/\A[a-f0-9]{64}\z/) + end + + it 'returns an expire timestamp in the future' do + result = described_class.call(config: config) + expect(result[:expire]).to be > Time.now.to_i + expect(result[:expire]).to be <= Time.now.to_i + lifetime + 1 + end + + it 'generates a valid HMAC-SHA256 signature' do + now = Time.now.to_i + allow(Time).to receive(:now).and_return(Time.at(now)) + + result = described_class.call(config: config) + expected_expire = now + lifetime + expected_sig = OpenSSL::HMAC.hexdigest('sha256', secret_key, expected_expire.to_s) + + expect(result[:expire]).to eq(expected_expire) + expect(result[:signature]).to eq(expected_sig) + end + + it 'generates different signatures for different secret keys' do + config_a = Uploadcare::Configuration.new( + public_key: 'pk', secret_key: 'key-a', upload_signature_lifetime: 600 + ) + config_b = Uploadcare::Configuration.new( + public_key: 'pk', secret_key: 'key-b', upload_signature_lifetime: 600 + ) + + now = Time.now.to_i + allow(Time).to receive(:now).and_return(Time.at(now)) + + result_a = described_class.call(config: config_a) + result_b = described_class.call(config: config_b) + + expect(result_a[:signature]).not_to eq(result_b[:signature]) + end + end + + context 'with empty secret_key' do + let(:secret_key) { '' } + let(:lifetime) { 600 } + + it 'raises ArgumentError' do + expect { described_class.call(config: config) }.to raise_error( + ArgumentError, /secret_key is required/ + ) + end + end + + context 'with nil secret_key' do + let(:secret_key) { nil } + let(:lifetime) { 600 } + + it 'raises ArgumentError' do + expect { described_class.call(config: config) }.to raise_error( + ArgumentError, /secret_key is required/ + ) + end + end + + context 'with invalid lifetime' do + let(:secret_key) { 'my-secret' } + + context 'when lifetime is zero' do + let(:lifetime) { 0 } + + it 'raises ArgumentError' do + expect { described_class.call(config: config) }.to raise_error( + ArgumentError, /upload_signature_lifetime must be a positive Integer/ + ) + end + end + + context 'when lifetime is negative' do + let(:lifetime) { -10 } + + it 'raises ArgumentError' do + expect { described_class.call(config: config) }.to raise_error( + ArgumentError, /upload_signature_lifetime must be a positive Integer/ + ) + end + end + + context 'when lifetime is a float' do + let(:lifetime) { 30.5 } + + it 'raises ArgumentError' do + expect { described_class.call(config: config) }.to raise_error( + ArgumentError, /upload_signature_lifetime must be a positive Integer/ + ) + end + end + + context 'when lifetime is a string' do + let(:lifetime) { '600' } + + it 'raises ArgumentError' do + expect { described_class.call(config: config) }.to raise_error( + ArgumentError, /upload_signature_lifetime must be a positive Integer/ + ) + end + end + end + end +end diff --git a/spec/uploadcare/internal/throttle_handler_spec.rb b/spec/uploadcare/internal/throttle_handler_spec.rb new file mode 100644 index 00000000..6c49e44b --- /dev/null +++ b/spec/uploadcare/internal/throttle_handler_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Internal::ThrottleHandler do + let(:handler_class) do + Class.new do + include Uploadcare::Internal::ThrottleHandler + end + end + let(:handler) { handler_class.new } + + describe '#handle_throttling' do + it 'returns the block result when no throttle error occurs' do + result = handler.handle_throttling(max_attempts: 3) { 'success' } + expect(result).to eq('success') + end + + it 'retries on ThrottleError and returns result on success' do + call_count = 0 + allow(handler).to receive(:sleep) + + result = handler.handle_throttling(max_attempts: 3) do + call_count += 1 + raise Uploadcare::Exception::ThrottleError.new(timeout: 0.01) if call_count < 2 + + 'recovered' + end + + expect(result).to eq('recovered') + expect(call_count).to eq(2) + end + + it 'raises ThrottleError when max attempts are exhausted' do + allow(handler).to receive(:sleep) + + expect do + handler.handle_throttling(max_attempts: 3) do + raise Uploadcare::Exception::ThrottleError.new(timeout: 0.01) + end + end.to raise_error(Uploadcare::Exception::ThrottleError) + end + + it 'calls sleep with exponential backoff based on timeout' do + call_count = 0 + allow(handler).to receive(:sleep) + + begin + handler.handle_throttling(max_attempts: 4) do + call_count += 1 + raise Uploadcare::Exception::ThrottleError.new(timeout: 2.0) + end + rescue Uploadcare::Exception::ThrottleError + # expected + end + + expect(handler).to have_received(:sleep).with(2.0).ordered # 2.0 * 2^0 + expect(handler).to have_received(:sleep).with(4.0).ordered # 2.0 * 2^1 + expect(handler).to have_received(:sleep).with(8.0).ordered # 2.0 * 2^2 + end + + it 'retries exactly max_attempts - 1 times before final attempt' do + call_count = 0 + allow(handler).to receive(:sleep) + + begin + handler.handle_throttling(max_attempts: 5) do + call_count += 1 + raise Uploadcare::Exception::ThrottleError.new(timeout: 0.01) + end + rescue Uploadcare::Exception::ThrottleError + # expected + end + + expect(call_count).to eq(5) + expect(handler).to have_received(:sleep).exactly(4).times + end + + it 'does not retry when max_attempts is 1' do + call_count = 0 + allow(handler).to receive(:sleep) + + expect do + handler.handle_throttling(max_attempts: 1) do + call_count += 1 + raise Uploadcare::Exception::ThrottleError.new(timeout: 0.01) + end + end.to raise_error(Uploadcare::Exception::ThrottleError) + + expect(call_count).to eq(1) + expect(handler).not_to have_received(:sleep) + end + + it 'raises ArgumentError when max_attempts is not positive' do + expect do + handler.handle_throttling(max_attempts: 0) { 'nope' } + end.to raise_error(ArgumentError, 'max_attempts must be at least 1') + end + + it 'does not catch non-ThrottleError exceptions' do + expect do + handler.handle_throttling(max_attempts: 3) do + raise StandardError, 'something else' + end + end.to raise_error(StandardError, 'something else') + end + + context 'when max_attempts is nil and handler responds to config' do + it 'uses config.max_throttle_attempts' do + config = Uploadcare::Configuration.new( + public_key: 'test', + secret_key: 'test', + max_throttle_attempts: 2 + ) + handler_with_config = handler_class.new + allow(handler_with_config).to receive(:config).and_return(config) + allow(handler_with_config).to receive(:sleep) + + call_count = 0 + begin + handler_with_config.handle_throttling do + call_count += 1 + raise Uploadcare::Exception::ThrottleError.new(timeout: 0.01) + end + rescue Uploadcare::Exception::ThrottleError + # expected + end + + expect(call_count).to eq(2) + end + end + end +end diff --git a/spec/uploadcare/internal/upload_io_spec.rb b/spec/uploadcare/internal/upload_io_spec.rb new file mode 100644 index 00000000..1f3c36f8 --- /dev/null +++ b/spec/uploadcare/internal/upload_io_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tempfile' +require 'stringio' + +RSpec.describe Uploadcare::Internal::UploadIo do + describe '.wrap' do + it 'returns a wrapper around a path-backed file without copying it' do + file = Tempfile.new(['upload-io', '.jpg']) + file.write('abc') + file.rewind + + wrapped = described_class.wrap(file) + + expect(wrapped.path).to eq(file.path) + expect(wrapped.original_filename).to match(/upload-io.*\.jpg/) + expect(wrapped.size).to eq(3) + + wrapped.close! + expect(File.exist?(file.path)).to be(true) + file.close! + end + + it 'normalizes a non-path IO object into a temp file' do + wrapped = described_class.wrap(StringIO.new('stream-data')) + + expect(File.exist?(wrapped.path)).to be(true) + expect(wrapped.original_filename).to eq('upload.bin') + expect(wrapped.size).to eq(11) + + path = wrapped.path + wrapped.close! + expect(File.exist?(path)).to be(false) + end + + it 'preserves original_filename when available on the source object' do + io = StringIO.new('abc') + io.define_singleton_method(:original_filename) { 'avatar.png' } + + wrapped = described_class.wrap(io) + + expect(wrapped.original_filename).to eq('avatar.png') + expect(File.extname(wrapped.path)).to eq('.png') + wrapped.close! + end + + it 'raises for unreadable input' do + expect do + described_class.wrap('not-io') + end.to raise_error(ArgumentError, /readable IO/) + end + + it 'does not treat directories as path-backed files' do + Dir.mktmpdir do |directory| + source = StringIO.new('stream-data') + source.define_singleton_method(:path) { directory } + + wrapped = described_class.wrap(source, filename: 'directory.txt') + + expect(File.file?(wrapped.path)).to be(true) + expect(wrapped.original_filename).to eq('directory.txt') + wrapped.close! + end + end + end +end diff --git a/spec/uploadcare/internal/upload_params_generator_spec.rb b/spec/uploadcare/internal/upload_params_generator_spec.rb new file mode 100644 index 00000000..518f7869 --- /dev/null +++ b/spec/uploadcare/internal/upload_params_generator_spec.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Internal::UploadParamsGenerator do + describe '.call' do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'test-pub-key', + secret_key: 'test-secret-key', + sign_uploads: sign_uploads, + upload_signature_lifetime: 600 + ) + end + let(:sign_uploads) { false } + + context 'with default options' do + it 'returns hash with UPLOADCARE_PUB_KEY' do + result = described_class.call(options: {}, config: config) + expect(result['UPLOADCARE_PUB_KEY']).to eq('test-pub-key') + end + + it 'does not include UPLOADCARE_STORE when store is not specified' do + result = described_class.call(options: {}, config: config) + expect(result).not_to have_key('UPLOADCARE_STORE') + end + + it 'does not include signature params when sign_uploads is false' do + result = described_class.call(options: {}, config: config) + expect(result).not_to have_key('signature') + expect(result).not_to have_key('expire') + end + end + + context 'with store option' do + it 'sets UPLOADCARE_STORE to "1" for true' do + result = described_class.call(options: { store: true }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('1') + end + + it 'sets UPLOADCARE_STORE to "0" for false' do + result = described_class.call(options: { store: false }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('0') + end + + it 'sets UPLOADCARE_STORE to "1" for integer 1' do + result = described_class.call(options: { store: 1 }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('1') + end + + it 'sets UPLOADCARE_STORE to "0" for integer 0' do + result = described_class.call(options: { store: 0 }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('0') + end + + it 'sets UPLOADCARE_STORE to "1" for string "1"' do + result = described_class.call(options: { store: '1' }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('1') + end + + it 'sets UPLOADCARE_STORE to "0" for string "0"' do + result = described_class.call(options: { store: '0' }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('0') + end + + it 'converts other values to string via to_s' do + result = described_class.call(options: { store: 'auto' }, config: config) + expect(result['UPLOADCARE_STORE']).to eq('auto') + end + + it 'omits UPLOADCARE_STORE when store is nil' do + result = described_class.call(options: { store: nil }, config: config) + expect(result).not_to have_key('UPLOADCARE_STORE') + end + end + + context 'with metadata option' do + it 'formats metadata keys as metadata[key]' do + options = { metadata: { 'tag' => 'photo', 'source' => 'web' } } + result = described_class.call(options: options, config: config) + expect(result['metadata[tag]']).to eq('photo') + expect(result['metadata[source]']).to eq('web') + end + + it 'converts metadata values to strings' do + options = { metadata: { count: 42 } } + result = described_class.call(options: options, config: config) + expect(result['metadata[count]']).to eq('42') + end + + it 'handles symbol keys in metadata' do + options = { metadata: { category: 'docs' } } + result = described_class.call(options: options, config: config) + expect(result['metadata[category]']).to eq('docs') + end + + it 'skips metadata when nil' do + result = described_class.call(options: { metadata: nil }, config: config) + expect(result.keys.select { |k| k.start_with?('metadata[') }).to be_empty + end + + it 'raises ArgumentError when metadata is not a hash' do + expect do + described_class.call(options: { metadata: 'invalid' }, config: config) + end.to raise_error(ArgumentError, /metadata must be a hash/) + end + + it 'handles empty metadata hash' do + result = described_class.call(options: { metadata: {} }, config: config) + expect(result.keys.select { |k| k.start_with?('metadata[') }).to be_empty + end + end + + context 'with explicit signature options' do + it 'uses provided signature and expire' do + options = { signature: 'abc123', expire: 9_999_999 } + result = described_class.call(options: options, config: config) + expect(result['signature']).to eq('abc123') + expect(result['expire']).to eq(9_999_999) + end + + it 'uses provided signature without expire' do + options = { signature: 'abc123' } + result = described_class.call(options: options, config: config) + expect(result['signature']).to eq('abc123') + expect(result).not_to have_key('expire') + end + + it 'ignores sign_uploads config when explicit signature is provided' do + sign_config = Uploadcare::Configuration.new( + public_key: 'pk', + secret_key: 'sk', + sign_uploads: true, + upload_signature_lifetime: 600 + ) + options = { signature: 'explicit-sig', expire: 12_345 } + result = described_class.call(options: options, config: sign_config) + expect(result['signature']).to eq('explicit-sig') + expect(result['expire']).to eq(12_345) + end + end + + context 'with sign_uploads enabled in config' do + let(:sign_uploads) { true } + + it 'generates signature and expire from SignatureGenerator' do + allow(Uploadcare::Internal::SignatureGenerator).to receive(:call) + .with(config: config) + .and_return({ signature: 'generated-sig', expire: 1_700_000 }) + + result = described_class.call(options: {}, config: config) + expect(result['signature']).to eq('generated-sig') + expect(result['expire']).to eq(1_700_000) + end + end + + context 'with combined options' do + it 'includes all params together' do + options = { + store: true, + metadata: { 'env' => 'test' }, + signature: 'combo-sig', + expire: 12_345 + } + result = described_class.call(options: options, config: config) + expect(result['UPLOADCARE_PUB_KEY']).to eq('test-pub-key') + expect(result['UPLOADCARE_STORE']).to eq('1') + expect(result['metadata[env]']).to eq('test') + expect(result['signature']).to eq('combo-sig') + expect(result['expire']).to eq(12_345) + end + end + end +end diff --git a/spec/uploadcare/internal/user_agent_spec.rb b/spec/uploadcare/internal/user_agent_spec.rb new file mode 100644 index 00000000..32e7c015 --- /dev/null +++ b/spec/uploadcare/internal/user_agent_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Internal::UserAgent do + describe '.call' do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'my-pub-key', + secret_key: 'my-secret-key', + framework_data: framework_data + ) + end + + context 'without framework_data' do + let(:framework_data) { '' } + + it 'returns a properly formatted user agent string' do + result = described_class.call(config: config) + expect(result).to eq( + "UploadcareRuby/#{Uploadcare::VERSION}/my-pub-key (Ruby/#{RUBY_VERSION})" + ) + end + end + + context 'with framework_data' do + let(:framework_data) { 'Rails/7.1.0' } + + it 'appends framework data in parentheses' do + result = described_class.call(config: config) + expect(result).to eq( + "UploadcareRuby/#{Uploadcare::VERSION}/my-pub-key (Ruby/#{RUBY_VERSION}; Rails/7.1.0)" + ) + end + end + + context 'with nil framework_data' do + let(:framework_data) { nil } + + it 'omits framework data' do + result = described_class.call(config: config) + expect(result).to eq( + "UploadcareRuby/#{Uploadcare::VERSION}/my-pub-key (Ruby/#{RUBY_VERSION})" + ) + end + end + + it 'includes the gem version' do + config = Uploadcare::Configuration.new(public_key: 'pk', secret_key: 'sk') + result = described_class.call(config: config) + expect(result).to include(Uploadcare::VERSION) + end + + it 'includes the public key' do + config = Uploadcare::Configuration.new(public_key: 'unique-key-123', secret_key: 'sk') + result = described_class.call(config: config) + expect(result).to include('unique-key-123') + end + + it 'includes the Ruby version' do + config = Uploadcare::Configuration.new(public_key: 'pk', secret_key: 'sk') + result = described_class.call(config: config) + expect(result).to include("Ruby/#{RUBY_VERSION}") + end + end +end diff --git a/spec/uploadcare/multi_account_spec.rb b/spec/uploadcare/multi_account_spec.rb new file mode 100644 index 00000000..fb6d3103 --- /dev/null +++ b/spec/uploadcare/multi_account_spec.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Multi-account support' do + let(:config_a) do + Uploadcare::Configuration.new( + public_key: 'account-a-public', + secret_key: 'account-a-secret', + auth_type: 'Uploadcare.Simple' + ) + end + + let(:config_b) do + Uploadcare::Configuration.new( + public_key: 'account-b-public', + secret_key: 'account-b-secret', + auth_type: 'Uploadcare.Simple' + ) + end + + let(:client_a) { Uploadcare::Client.new(config: config_a) } + let(:client_b) { Uploadcare::Client.new(config: config_b) } + + describe 'independent client configurations' do + it 'maintains separate configurations' do + expect(client_a.config.public_key).to eq('account-a-public') + expect(client_b.config.public_key).to eq('account-b-public') + + expect(client_a.config.secret_key).to eq('account-a-secret') + expect(client_b.config.secret_key).to eq('account-b-secret') + end + + it 'has independent API instances' do + expect(client_a.api).not_to equal(client_b.api) + expect(client_a.api.config.public_key).to eq('account-a-public') + expect(client_b.api.config.public_key).to eq('account-b-public') + end + + it 'has independent domain accessors' do + expect(client_a.files).not_to equal(client_b.files) + expect(client_a.groups).not_to equal(client_b.groups) + expect(client_a.uploads).not_to equal(client_b.uploads) + expect(client_a.project).not_to equal(client_b.project) + expect(client_a.webhooks).not_to equal(client_b.webhooks) + expect(client_a.addons).not_to equal(client_b.addons) + expect(client_a.file_metadata).not_to equal(client_b.file_metadata) + expect(client_a.conversions).not_to equal(client_b.conversions) + end + end + + describe 'operations with different clients' do + let(:rest_a) { instance_double(Uploadcare::Api::Rest) } + let(:rest_b) { instance_double(Uploadcare::Api::Rest) } + let(:files_a) { instance_double(Uploadcare::Api::Rest::Files) } + let(:files_b) { instance_double(Uploadcare::Api::Rest::Files) } + let(:api_a) { instance_double(Uploadcare::Client::Api, rest: rest_a) } + let(:api_b) { instance_double(Uploadcare::Client::Api, rest: rest_b) } + + before do + allow(client_a).to receive(:api).and_return(api_a) + allow(client_b).to receive(:api).and_return(api_b) + allow(rest_a).to receive(:files).and_return(files_a) + allow(rest_b).to receive(:files).and_return(files_b) + end + + it 'can find files independently on different accounts' do + file_a_attrs = { 'uuid' => 'uuid-from-account-a', 'original_filename' => 'a.jpg' } + file_b_attrs = { 'uuid' => 'uuid-from-account-b', 'original_filename' => 'b.jpg' } + + allow(files_a).to receive(:info) + .with(uuid: 'uuid-from-account-a', params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(file_a_attrs)) + + allow(files_b).to receive(:info) + .with(uuid: 'uuid-from-account-b', params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(file_b_attrs)) + + file_a = Uploadcare::Resources::File.find(uuid: 'uuid-from-account-a', client: client_a) + file_b = Uploadcare::Resources::File.find(uuid: 'uuid-from-account-b', client: client_b) + + expect(file_a.uuid).to eq('uuid-from-account-a') + expect(file_a.client).to eq(client_a) + expect(file_a.config.public_key).to eq('account-a-public') + + expect(file_b.uuid).to eq('uuid-from-account-b') + expect(file_b.client).to eq(client_b) + expect(file_b.config.public_key).to eq('account-b-public') + end + + it 'can list files independently on different accounts' do + list_response = { + 'results' => [], + 'next' => nil, + 'previous' => nil, + 'per_page' => 10, + 'total' => 0 + } + + allow(files_a).to receive(:list) + .with(params: { limit: 5 }, request_options: {}) + .and_return(Uploadcare::Result.success(list_response)) + + allow(files_b).to receive(:list) + .with(params: { limit: 20 }, request_options: {}) + .and_return(Uploadcare::Result.success(list_response)) + + result_a = Uploadcare::Resources::File.list(options: { limit: 5 }, client: client_a) + result_b = Uploadcare::Resources::File.list(options: { limit: 20 }, client: client_b) + + expect(result_a).to be_a(Uploadcare::Collections::Paginated) + expect(result_b).to be_a(Uploadcare::Collections::Paginated) + expect(result_a.client).to eq(client_a) + expect(result_b.client).to eq(client_b) + end + end + + describe 'client#with for temporary overrides' do + it 'creates a derived client with a different key' do + derived = client_a.with(public_key: 'temporary-key') + + expect(derived.config.public_key).to eq('temporary-key') + expect(derived.config.secret_key).to eq('account-a-secret') + expect(client_a.config.public_key).to eq('account-a-public') + end + + it 'derived client operates independently' do + derived = client_a.with(auth_type: 'Uploadcare') + + expect(derived.config.auth_type).to eq('Uploadcare') + expect(client_a.config.auth_type).to eq('Uploadcare.Simple') + end + end + + describe 'resource objects retain their client context' do + let(:rest_a) { instance_double(Uploadcare::Api::Rest) } + let(:files_a) { instance_double(Uploadcare::Api::Rest::Files) } + let(:api_a) { instance_double(Uploadcare::Client::Api, rest: rest_a) } + + before do + allow(client_a).to receive(:api).and_return(api_a) + allow(rest_a).to receive(:files).and_return(files_a) + end + + it 'file instances use the correct client for subsequent operations' do + file_attrs = { 'uuid' => 'file-uuid', 'original_filename' => 'test.jpg' } + stored_attrs = file_attrs.merge('datetime_stored' => '2025-01-01T00:00:00Z') + + allow(files_a).to receive(:info) + .with(uuid: 'file-uuid', params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(file_attrs)) + + allow(files_a).to receive(:store) + .with(uuid: 'file-uuid', request_options: {}) + .and_return(Uploadcare::Result.success(stored_attrs)) + + file = Uploadcare::Resources::File.find(uuid: 'file-uuid', client: client_a) + expect(file.client).to eq(client_a) + + file.store + expect(file.datetime_stored).to eq('2025-01-01T00:00:00Z') + expect(file.client).to eq(client_a) + end + end + + describe 'global vs explicit client' do + after do + Uploadcare.instance_variable_set(:@client, nil) + Uploadcare.instance_variable_set(:@configuration, nil) + end + + it 'global client and explicit client can coexist' do + Uploadcare.configure do |c| + c.public_key = 'global-key' + c.secret_key = 'global-secret' + end + + global_client = Uploadcare.client + expect(global_client.config.public_key).to eq('global-key') + + expect(client_a.config.public_key).to eq('account-a-public') + expect(client_b.config.public_key).to eq('account-b-public') + end + end +end diff --git a/spec/uploadcare/operations/multipart_upload_spec.rb b/spec/uploadcare/operations/multipart_upload_spec.rb new file mode 100644 index 00000000..5c86d045 --- /dev/null +++ b/spec/uploadcare/operations/multipart_upload_spec.rb @@ -0,0 +1,604 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tempfile' +require 'stringio' + +RSpec.describe Uploadcare::Operations::MultipartUpload do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 1024 + ) + end + let(:upload_client) { instance_double(Uploadcare::Api::Upload) } + let(:upload_files_api) { double('upload_files') } + let(:uploader) { described_class.new(upload_client: upload_client, config: config) } + + before do + allow(upload_client).to receive(:files).and_return(upload_files_api) + end + + describe 'CHUNK_SIZE' do + it 'equals 5MB' do + expect(described_class::CHUNK_SIZE).to eq(5_242_880) + end + end + + describe '#initialize' do + it 'stores upload_client' do + expect(uploader.upload_client).to eq(upload_client) + end + + it 'stores config' do + expect(uploader.config).to eq(config) + end + end + + describe '#upload' do + let(:file_content) { 'A' * 3072 } # 3KB — will need 3 parts with 1024 chunk size + let(:tempfile) do + f = Tempfile.new(['multipart_test', '.jpg']) + f.binmode + f.write(file_content) + f.rewind + f + end + let(:presigned_urls) do + ['https://s3.example.com/part0', 'https://s3.example.com/part1', 'https://s3.example.com/part2'] + end + let(:start_response) do + { 'uuid' => 'mp-uuid-123', 'parts' => presigned_urls } + end + + after { tempfile.close! } + + context 'when file is not a valid IO object' do + it 'returns a failure Result with ArgumentError for a string' do + result = uploader.upload(file: 'not-a-file') + expect(result).to be_a(Uploadcare::Result) + expect(result.failure?).to be(true) + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to include('readable IO object') + end + + it 'returns a failure Result for an integer' do + result = uploader.upload(file: 42) + expect(result.failure?).to be(true) + expect(result.error).to be_a(ArgumentError) + end + + it 'returns a failure Result for nil' do + result = uploader.upload(file: nil) + expect(result.failure?).to be(true) + end + + it 'accepts an object with read but no path by normalizing it to a temp file' do + obj = StringIO.new(file_content) + + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success(start_response), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + result = uploader.upload(file: obj) + expect(result.success?).to be(true) + expect(result.value!).to eq({ 'uuid' => 'mp-uuid-123' }) + end + end + + context 'when upload options are invalid' do + it 'returns a failure Result when threads is less than 1' do + result = uploader.upload(file: tempfile, threads: 0) + + expect(result.failure?).to be(true) + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to eq('threads must be >= 1') + end + + it 'returns a failure Result when part_size is not positive' do + result = uploader.upload(file: tempfile, part_size: 0) + + expect(result.failure?).to be(true) + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to eq('part_size must be > 0') + end + + it 'returns a failure Result when threads exceeds config.upload_threads' do + result = uploader.upload(file: tempfile, threads: 3) + + expect(result.failure?).to be(true) + expect(result.error).to be_a(ArgumentError) + expect(result.error.message).to eq('threads must be <= 2') + end + end + + context 'when performing sequential upload (threads <= 1)' do + before do + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success(start_response), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + end + + it 'returns a successful Result with the UUID' do + result = uploader.upload(file: tempfile) + expect(result).to be_a(Uploadcare::Result) + expect(result.success?).to be(true) + expect(result.value!).to eq({ 'uuid' => 'mp-uuid-123' }) + end + + it 'calls multipart_start with correct filename, size, and content_type' do + expect(upload_files_api).to receive(:multipart_start).with( + hash_including( + filename: a_string_matching(/multipart_test.*\.jpg/), + size: file_content.bytesize, + content_type: 'image/jpeg' + ) + ).and_return(Uploadcare::Result.success(start_response)) + + uploader.upload(file: tempfile) + end + + it 'uploads each part to its presigned URL' do + expect(upload_client).to receive(:upload_part_to_url) + .with('https://s3.example.com/part0', anything, hash_including(max_retries: 3, timeout: 60)).ordered + expect(upload_client).to receive(:upload_part_to_url) + .with('https://s3.example.com/part1', anything, hash_including(max_retries: 3, timeout: 60)).ordered + expect(upload_client).to receive(:upload_part_to_url) + .with('https://s3.example.com/part2', anything, hash_including(max_retries: 3, timeout: 60)).ordered + + uploader.upload(file: tempfile) + end + + it 'reads correct chunk sizes for each part' do + chunks = [] + allow(upload_client).to receive(:upload_part_to_url) do |_url, data| + chunks << data.bytesize + end + + uploader.upload(file: tempfile) + expect(chunks).to eq([1024, 1024, 1024]) + end + + it 'forwards config upload timeout and retry settings to part uploads' do + tuned_config = Uploadcare::Configuration.new( + public_key: 'pk', + secret_key: 'sk', + auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 1024, + upload_timeout: 45, + max_upload_retries: 7 + ) + tuned_uploader = described_class.new(upload_client: upload_client, config: tuned_config) + + expect(upload_client).to receive(:upload_part_to_url) + .with(anything, anything, hash_including(timeout: 45, max_retries: 7)) + .at_least(:once) + + tuned_uploader.upload(file: tempfile) + end + + it 'calls multipart_complete with the UUID' do + expect(upload_files_api).to receive(:multipart_complete) + .with(uuid: 'mp-uuid-123', request_options: {}) + .and_return(Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + uploader.upload(file: tempfile) + end + + it 'passes request_options through to start and complete' do + opts = { timeout: 60 } + expect(upload_files_api).to receive(:multipart_start) + .with(hash_including(request_options: opts)) + .and_return(Uploadcare::Result.success(start_response)) + expect(upload_files_api).to receive(:multipart_complete) + .with(uuid: 'mp-uuid-123', request_options: opts) + .and_return(Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + uploader.upload(file: tempfile, request_options: opts) + end + + it 'passes extra options (store, metadata) to multipart_start' do + expect(upload_files_api).to receive(:multipart_start) + .with(hash_including(store: true, metadata: { key: 'val' })) + .and_return(Uploadcare::Result.success(start_response)) + + uploader.upload(file: tempfile, store: true, metadata: { key: 'val' }) + end + + it 'uses custom part_size from options' do + custom_config = Uploadcare::Configuration.new( + public_key: 'pk', secret_key: 'sk', auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 2048 + ) + custom_uploader = described_class.new(upload_client: upload_client, config: custom_config) + + chunks = [] + allow(upload_client).to receive(:upload_part_to_url) { |_url, data| chunks << data.bytesize } + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success({ + 'uuid' => 'mp-uuid-123', + 'parts' => ['https://s3.example.com/p0', 'https://s3.example.com/p1'] + }), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + custom_uploader.upload(file: tempfile, part_size: 512) + expect(chunks.first).to eq(512) + end + + it 'falls back to config.multipart_chunk_size when part_size not specified' do + chunks = [] + allow(upload_client).to receive(:upload_part_to_url) { |_url, data| chunks << data.bytesize } + + uploader.upload(file: tempfile) + expect(chunks.first).to eq(1024) + end + end + + context 'when reporting progress via block callback' do + before do + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success(start_response), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + end + + it 'calls the block for each uploaded part' do + progress_calls = [] + uploader.upload(file: tempfile) { |p| progress_calls << p } + + expect(progress_calls.length).to eq(3) + end + + it 'reports correct progress data for each part' do + progress_calls = [] + uploader.upload(file: tempfile) { |p| progress_calls << p } + + expect(progress_calls[0]).to eq(uploaded: 1024, total: 3072, part: 1, total_parts: 3) + expect(progress_calls[1]).to eq(uploaded: 2048, total: 3072, part: 2, total_parts: 3) + expect(progress_calls[2]).to eq(uploaded: 3072, total: 3072, part: 3, total_parts: 3) + end + + it 'works without a block (no error)' do + expect { uploader.upload(file: tempfile) }.not_to raise_error + end + end + + context 'when performing parallel upload (threads > 1)' do + before do + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success(start_response), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + end + + it 'returns a successful Result' do + result = uploader.upload(file: tempfile, threads: 2) + expect(result.success?).to be(true) + expect(result.value!).to eq({ 'uuid' => 'mp-uuid-123' }) + end + + it 'uploads all parts to presigned URLs' do + uploaded_urls = [] + allow(upload_client).to receive(:upload_part_to_url) do |url, _data| + uploaded_urls << url + end + + uploader.upload(file: tempfile, threads: 2) + expect(uploaded_urls.sort).to eq(presigned_urls.sort) + end + + it 'uploads correct data chunks in parallel' do + high_thread_config = Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 1024, + upload_threads: 3 + ) + high_thread_uploader = described_class.new(upload_client: upload_client, config: high_thread_config) + chunks = Mutex.new + all_chunks = {} + allow(upload_client).to receive(:upload_part_to_url) do |url, data| + chunks.synchronize { all_chunks[url] = data.bytesize } + end + + high_thread_uploader.upload(file: tempfile, threads: 3) + expect(all_chunks.values).to all(eq(1024)) + expect(all_chunks.size).to eq(3) + end + + it 'reports progress via block callback' do + progress_calls = [] + progress_mutex = Mutex.new + + uploader.upload(file: tempfile, threads: 2) do |p| + progress_mutex.synchronize { progress_calls << p } + end + + expect(progress_calls.length).to eq(3) + progress_calls.each do |p| + expect(p).to include(:uploaded, :total, :part, :total_parts) + expect(p[:total]).to eq(3072) + expect(p[:total_parts]).to eq(3) + end + final_uploaded = progress_calls.map { |p| p[:uploaded] }.max + expect(final_uploaded).to eq(3072) + end + + it 'works with more threads than parts when allowed by config' do + high_thread_config = Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 1024, + upload_threads: 10 + ) + high_thread_uploader = described_class.new(upload_client: upload_client, config: high_thread_config) + + result = high_thread_uploader.upload(file: tempfile, threads: 10) + expect(result.success?).to be(true) + end + + it 'works with exactly 2 threads' do + uploaded_urls = [] + allow(upload_client).to receive(:upload_part_to_url) do |url, _data| + uploaded_urls << url + end + + result = uploader.upload(file: tempfile, threads: 2) + expect(result.success?).to be(true) + expect(uploaded_urls.sort).to eq(presigned_urls.sort) + end + + it 'raises error if a worker thread fails' do + call_count = 0 + allow(upload_client).to receive(:upload_part_to_url) do |_url, _data| + call_count += 1 + raise StandardError, 'S3 upload failed' if call_count == 2 + end + + result = uploader.upload(file: tempfile, threads: 2) + expect(result.failure?).to be(true) + expect(result.error.message).to include('S3 upload failed') + end + + it 'cancels remaining queued parts after first worker error' do + call_count = 0 + allow(upload_client).to receive(:upload_part_to_url) do |_url, _data| + call_count += 1 + raise StandardError, 'S3 upload failed' if call_count == 1 + end + + result = uploader.upload(file: tempfile, threads: 2) + + expect(result.failure?).to be(true) + expect(call_count).to be < presigned_urls.length + end + + it 'propagates the first error from parallel workers' do + high_thread_config = Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 1024, + upload_threads: 3 + ) + high_thread_uploader = described_class.new(upload_client: upload_client, config: high_thread_config) + allow(upload_client).to receive(:upload_part_to_url) do + raise 'network timeout' + end + + result = high_thread_uploader.upload(file: tempfile, threads: 3) + expect(result.failure?).to be(true) + expect(result.error).to be_a(RuntimeError) + end + end + + context 'when file does not respond to #size' do + it 'falls back to ::File.size(file.path)' do + file_obj = Class.new do + attr_reader :path + + def initialize(path, content) + @path = path + @content = content + @pos = 0 + end + + def read(length = nil) + return nil if @pos >= @content.bytesize + + data = length ? @content[@pos, length] : @content[@pos..] + @pos += data.bytesize + data + end + + def seek(pos) + @pos = pos + end + end.new(tempfile.path, file_content) + + allow(upload_files_api).to receive(:multipart_start) do |args| + expect(args[:size]).to eq(File.size(tempfile.path)) + Uploadcare::Result.success(start_response) + end + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive(:multipart_complete) + .and_return(Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + result = uploader.upload(file: file_obj) + expect(result.success?).to be(true) + end + end + + context 'when file responds to #original_filename' do + it 'uses original_filename instead of basename' do + file_obj = Class.new do + attr_reader :path, :original_filename + + def initialize(path, size) + @path = path + @original_filename = 'user_avatar.png' + @size = size + end + + def read(_length = nil) = 'data' + def seek(_pos) = nil + attr_reader :size + end.new(tempfile.path, file_content.bytesize) + + allow(upload_files_api).to receive(:multipart_start) do |args| + expect(args[:filename]).to eq('user_avatar.png') + Uploadcare::Result.success(start_response) + end + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive(:multipart_complete) + .and_return(Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + result = uploader.upload(file: file_obj) + expect(result.success?).to be(true) + end + end + + context 'when file has unknown MIME type' do + let(:unknown_file) do + f = Tempfile.new(['test', '.xyz_unknown_ext']) + f.binmode + f.write('A' * 2048) + f.rewind + f + end + + after { unknown_file.close! } + + it 'falls back to application/octet-stream' do + allow(upload_files_api).to receive(:multipart_start) do |args| + expect(args[:content_type]).to eq('application/octet-stream') + Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123', 'parts' => ['https://s3.example.com/p0'] }) + end + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive(:multipart_complete) + .and_return(Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + result = uploader.upload(file: unknown_file) + expect(result.success?).to be(true) + end + end + + context 'when multipart_start fails' do + it 'returns a failure Result' do + allow(upload_files_api).to receive(:multipart_start) + .and_return(Uploadcare::Result.failure(StandardError.new('start failed'))) + + result = uploader.upload(file: tempfile) + expect(result.failure?).to be(true) + expect(result.error.message).to include('start failed') + end + end + + context 'when multipart_complete fails' do + it 'returns a failure Result' do + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success(start_response), multipart_complete: Uploadcare::Result.failure(StandardError.new('complete failed'))) + + result = uploader.upload(file: tempfile) + expect(result.failure?).to be(true) + expect(result.error.message).to include('complete failed') + end + end + + context 'when upload_part_to_url raises during sequential upload' do + it 'returns a failure Result' do + allow(upload_files_api).to receive(:multipart_start) + .and_return(Uploadcare::Result.success(start_response)) + allow(upload_client).to receive(:upload_part_to_url) + .and_raise(Uploadcare::Exception::MultipartUploadError, 'part upload failed') + + result = uploader.upload(file: tempfile) + expect(result.failure?).to be(true) + expect(result.error).to be_a(Uploadcare::Exception::MultipartUploadError) + end + end + + context 'when presigned_urls has more URLs than needed for file size' do + it 'stops uploading when read returns nil' do + small_content = 'B' * 512 + small_file = Tempfile.new(['small', '.bin']) + small_file.binmode + small_file.write(small_content) + small_file.rewind + + many_urls = 5.times.map { |i| "https://s3.example.com/part#{i}" } + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123', 'parts' => many_urls }), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + uploaded_count = 0 + allow(upload_client).to receive(:upload_part_to_url) { uploaded_count += 1 } + + uploader.upload(file: small_file) + expect(uploaded_count).to eq(1) # only 512 bytes < 1024 chunk, so 1 part + + small_file.close! + end + end + + context 'when performing parallel upload with offset >= total_size' do + it 'handles more presigned URLs than needed' do + high_thread_config = Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + multipart_chunk_size: 1024, + upload_threads: 3 + ) + high_thread_uploader = described_class.new(upload_client: upload_client, config: high_thread_config) + small_content = 'C' * 1024 + small_file = Tempfile.new(['small_parallel', '.bin']) + small_file.binmode + small_file.write(small_content) + small_file.rewind + + many_urls = 5.times.map { |i| "https://s3.example.com/part#{i}" } + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123', 'parts' => many_urls }), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + uploaded_urls = [] + allow(upload_client).to receive(:upload_part_to_url) do |url, _data| + uploaded_urls << url + end + + result = high_thread_uploader.upload(file: small_file, threads: 3) + expect(result.success?).to be(true) + expect(uploaded_urls.length).to eq(1) + + small_file.close! + end + end + + context 'when parallel worker encounters nil part_data' do + it 'stops processing that worker gracefully' do + allow(upload_files_api).to receive_messages(multipart_start: Uploadcare::Result.success(start_response), multipart_complete: Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + allow(upload_client).to receive(:upload_part_to_url) + + result = uploader.upload(file: tempfile, threads: 2) + expect(result.success?).to be(true) + end + end + + context 'with a .png file extension' do + let(:png_file) do + f = Tempfile.new(['image', '.png']) + f.binmode + f.write('P' * 2048) + f.rewind + f + end + + after { png_file.close! } + + it 'detects image/png content type' do + allow(upload_files_api).to receive(:multipart_start) do |args| + expect(args[:content_type]).to eq('image/png') + Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123', 'parts' => ['https://s3.example.com/p0'] }) + end + allow(upload_client).to receive(:upload_part_to_url) + allow(upload_files_api).to receive(:multipart_complete) + .and_return(Uploadcare::Result.success({ 'uuid' => 'mp-uuid-123' })) + + uploader.upload(file: png_file) + end + end + end +end diff --git a/spec/uploadcare/operations/upload_router_spec.rb b/spec/uploadcare/operations/upload_router_spec.rb new file mode 100644 index 00000000..c825e645 --- /dev/null +++ b/spec/uploadcare/operations/upload_router_spec.rb @@ -0,0 +1,256 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tempfile' +require 'stringio' + +RSpec.describe Uploadcare::Operations::UploadRouter do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple', + multipart_size_threshold: 100 * 1024 * 1024 + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:upload_api) { instance_double(Uploadcare::Api::Upload) } + let(:upload_files_api) { double('upload_files') } + let(:api) { instance_double(Uploadcare::Client::Api, upload: upload_api) } + let(:router) { described_class.new(client: client) } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api) + allow(upload_api).to receive(:files).and_return(upload_files_api) + end + + describe '#initialize' do + it 'stores client reference' do + expect(router.client).to eq(client) + end + end + + describe '#upload' do + context 'with a small file' do + it 'routes to upload_file for small File objects' do + tempfile = Tempfile.new('small') + tempfile.write('x' * 1024) + tempfile.rewind + + allow(upload_files_api).to receive(:direct_many) + .with(files: [tempfile], request_options: {}) + .and_return(Uploadcare::Result.success({ 'small.txt' => file_uuid })) + + result = router.upload(tempfile) + expect(result).to be_a(Uploadcare::Resources::File) + expect(result.uuid).to eq(file_uuid) + + tempfile.close! + end + end + + context 'with a non-path IO object' do + it 'routes to upload_file' do + io = StringIO.new('stream data') + + allow(upload_files_api).to receive(:direct_many) + .with(files: [io], request_options: {}) + .and_return(Uploadcare::Result.success({ 'upload.bin' => file_uuid })) + + result = router.upload(io) + expect(result).to be_a(Uploadcare::Resources::File) + expect(result.uuid).to eq(file_uuid) + end + end + + context 'with a string URL' do + it 'routes to upload_from_url' do + url = 'https://example.com/photo.jpg' + response = { 'uuid' => file_uuid, 'original_filename' => 'photo.jpg' } + + allow(upload_files_api).to receive(:from_url) + .with(source_url: url, request_options: {}) + .and_return(Uploadcare::Result.success(response)) + + result = router.upload(url) + expect(result).to be_a(Uploadcare::Resources::File) + end + end + + context 'with an array of files' do + it 'routes to upload_files' do + file1 = Tempfile.new('f1') + file2 = Tempfile.new('f2') + file1.write('data1') + file2.write('data2') + file1.rewind + file2.rewind + + allow(upload_files_api).to receive(:direct_many) + .and_return(Uploadcare::Result.success({ 'f1' => 'uuid-1', 'f2' => 'uuid-2' })) + + result = router.upload([file1, file2]) + expect(result).to be_an(Array) + expect(result.length).to eq(2) + expect(result).to all(be_a(Uploadcare::Resources::File)) + + file1.close! + file2.close! + end + end + + context 'with an unsupported type' do + it 'raises ArgumentError' do + expect do + router.upload(12_345) + end.to raise_error(ArgumentError, %r{Expected input to be a File/Array/URL}) + end + end + end + + describe '#upload_file' do + it 'uploads a single file directly' do + tempfile = Tempfile.new('upload') + tempfile.write('content') + tempfile.rewind + + allow(upload_files_api).to receive(:direct_many) + .with(files: [tempfile], request_options: {}, store: true) + .and_return(Uploadcare::Result.success({ 'upload.txt' => file_uuid })) + + result = router.upload_file(file: tempfile, store: true) + expect(result).to be_a(Uploadcare::Resources::File) + expect(result.uuid).to eq(file_uuid) + expect(result.original_filename).to eq('upload.txt') + + tempfile.close! + end + end + + describe '#upload_files' do + it 'uploads multiple files directly' do + f1 = Tempfile.new('a') + f2 = Tempfile.new('b') + + allow(upload_files_api).to receive(:direct_many) + .with(files: [f1, f2], request_options: {}) + .and_return(Uploadcare::Result.success({ 'a.txt' => 'uuid-a', 'b.txt' => 'uuid-b' })) + + result = router.upload_files(files: [f1, f2]) + expect(result.length).to eq(2) + expect(result.map(&:uuid)).to contain_exactly('uuid-a', 'uuid-b') + + f1.close! + f2.close! + end + + it 'decodes encoded multipart form field names back to original filenames' do + f1 = Tempfile.new('a') + f2 = Tempfile.new('b') + + allow(upload_files_api).to receive(:direct_many) + .with(files: [f1, f2], request_options: {}) + .and_return(Uploadcare::Result.success( + { + 'photo.jpg' => 'uuid-a', + '__uploadcare_form_1__photo.jpg' => 'uuid-b' + } + )) + + result = router.upload_files(files: [f1, f2]) + expect(result.map(&:original_filename)).to eq(['photo.jpg', 'photo.jpg']) + + f1.close! + f2.close! + end + end + + describe '#upload_from_url' do + it 'uploads from URL and returns a File resource' do + url = 'https://example.com/image.png' + response = { 'uuid' => file_uuid } + + allow(upload_files_api).to receive(:from_url) + .with(source_url: url, request_options: {}) + .and_return(Uploadcare::Result.success(response)) + + result = router.upload_from_url(url: url) + expect(result).to be_a(Uploadcare::Resources::File) + expect(result.uuid).to eq(file_uuid) + end + + it 'returns raw response when async option is true' do + url = 'https://example.com/image.png' + response = { 'token' => 'upload-token-123', 'type' => 'token' } + + allow(upload_files_api).to receive(:from_url) + .with(source_url: url, request_options: {}, async: true) + .and_return(Uploadcare::Result.success(response)) + + result = router.upload_from_url(url: url, async: true) + expect(result).to be_a(Hash) + expect(result['token']).to eq('upload-token-123') + end + end + + describe '#multipart_upload' do + it 'delegates to MultipartUpload and returns a File resource' do + tempfile = Tempfile.new('large') + tempfile.write('x' * 1024) + tempfile.rewind + + multipart = instance_double(Uploadcare::Operations::MultipartUpload) + allow(Uploadcare::Operations::MultipartUpload).to receive(:new) + .with(upload_client: upload_api, config: client.config) + .and_return(multipart) + + allow(multipart).to receive(:upload) + .and_return(Uploadcare::Result.success({ 'uuid' => file_uuid })) + + result = router.multipart_upload(file: tempfile) + expect(result).to be_a(Uploadcare::Resources::File) + expect(result.uuid).to eq(file_uuid) + + tempfile.close! + end + + it 'returns raw response when result is not a hash with uuid' do + tempfile = Tempfile.new('large') + tempfile.write('x' * 1024) + tempfile.rewind + + multipart = instance_double(Uploadcare::Operations::MultipartUpload) + allow(Uploadcare::Operations::MultipartUpload).to receive(:new).and_return(multipart) + allow(multipart).to receive(:upload).and_return(Uploadcare::Result.success('unexpected')) + + result = router.multipart_upload(file: tempfile) + expect(result).to eq('unexpected') + + tempfile.close! + end + end + + describe '#upload_from_url_status' do + it 'returns upload status' do + allow(upload_files_api).to receive(:from_url_status) + .with(token: 'upload-token', request_options: {}) + .and_return(Uploadcare::Result.success({ 'status' => 'progress', 'done' => 50, 'total' => 100 })) + + result = router.upload_from_url_status(token: 'upload-token') + expect(result['status']).to eq('progress') + end + end + + describe '#file_info' do + it 'returns file info from upload API' do + allow(upload_files_api).to receive(:info) + .with(file_id: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success({ 'uuid' => file_uuid, 'is_ready' => true })) + + result = router.file_info(file_id: file_uuid) + expect(result['uuid']).to eq(file_uuid) + end + end +end diff --git a/spec/uploadcare/param/authentication_header_spec.rb b/spec/uploadcare/param/authentication_header_spec.rb deleted file mode 100644 index b011bf91..00000000 --- a/spec/uploadcare/param/authentication_header_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'param/authentication_header' - -module Uploadcare - RSpec.describe Param::AuthenticationHeader do - subject { Param::AuthenticationHeader } - - before do - allow(Param::SimpleAuthHeader).to receive(:call).and_return('SimpleAuth called') - allow(Param::SecureAuthHeader).to receive(:call).and_return('SecureAuth called') - end - - it 'decides which header to use depending on configuration' do - Uploadcare.config.auth_type = 'Uploadcare.Simple' - expect(subject.call).to eq('SimpleAuth called') - Uploadcare.config.auth_type = 'Uploadcare' - expect(subject.call).to eq('SecureAuth called') - end - - it 'raise argument error if public_key is blank' do - Uploadcare.config.public_key = '' - expect { subject.call }.to raise_error(AuthError, 'Public Key is blank.') - end - - it 'raise argument error if secret_key is blank' do - Uploadcare.config.secret_key = '' - expect { subject.call }.to raise_error(AuthError, 'Secret Key is blank.') - end - end -end diff --git a/spec/uploadcare/param/conversion/document/processing_job_url_builder_spec.rb b/spec/uploadcare/param/conversion/document/processing_job_url_builder_spec.rb deleted file mode 100644 index 18b61fd9..00000000 --- a/spec/uploadcare/param/conversion/document/processing_job_url_builder_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'param/conversion/document/processing_job_url_builder' - -module Uploadcare - module Param - module Conversion - module Document - RSpec.describe Uploadcare::Param::Conversion::Document::ProcessingJobUrlBuilder do - subject { described_class.call(**arguments) } - - let(:uuid) { 'b054825b-17f2-4746-9f0c-8feee4d81ca1' } - let(:arguments) do - { - uuid: uuid, - format: 'png' - } - end - - shared_examples 'URL building' do - it 'builds a URL' do - expect(subject).to eq expected_url - end - end - - context 'when building an URL' do - context 'and when only the :format param is present' do - let(:expected_url) do - "#{uuid}/document/-/format/#{arguments[:format]}/" - end - - it_behaves_like 'URL building' - end - - context 'and when :format and :page params are present' do - let(:arguments) { super().merge(page: 1) } - let(:expected_url) do - "#{uuid}/document/-/format/#{arguments[:format]}/-/page/#{arguments[:page]}/" - end - - it_behaves_like 'URL building' - end - end - end - end - end - end -end diff --git a/spec/uploadcare/param/conversion/video/processing_job_url_builder_spec.rb b/spec/uploadcare/param/conversion/video/processing_job_url_builder_spec.rb deleted file mode 100644 index 50aecd8a..00000000 --- a/spec/uploadcare/param/conversion/video/processing_job_url_builder_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'param/conversion/video/processing_job_url_builder' - -module Uploadcare - module Param - module Conversion - module Video - RSpec.describe Uploadcare::Param::Conversion::Video::ProcessingJobUrlBuilder do - subject { described_class.call(**arguments) } - - let(:uuid) { 'b054825b-17f2-4746-9f0c-8feee4d81ca1' } - let(:arguments) do - { - uuid: uuid, - size: { resize_mode: 'preserve_ratio', width: '600', height: '400' }, - quality: 'best', - format: 'ogg', - cut: { start_time: '1:1:1.1', length: '2:1:1.1' }, - thumbs: { N: 20, number: 4 } - } - end - - shared_examples 'URL building' do - it 'builds a URL' do - expect(subject).to eq expected_url - end - end - - context 'when building an URL' do - context 'and when all operations are present' do - let(:expected_url) do - "#{uuid}/video/-" \ - "/size/#{arguments[:size][:width]}x#{arguments[:size][:height]}/#{arguments[:size][:resize_mode]}/-" \ - "/quality/#{arguments[:quality]}/-" \ - "/format/#{arguments[:format]}/-" \ - "/cut/#{arguments[:cut][:start_time]}/#{arguments[:cut][:length]}/-" \ - "/thumbs~#{arguments[:thumbs][:N]}/#{arguments[:thumbs][:number]}/" - end - - it_behaves_like 'URL building' - end - - context 'and when only the :size operation is present' do - let(:arguments) { super().slice(:uuid, :size) } - let(:expected_url) do - "#{uuid}/video/-" \ - "/size/#{arguments[:size][:width]}x#{arguments[:size][:height]}/#{arguments[:size][:resize_mode]}/" - end - - it_behaves_like 'URL building' - end - - %i[quality format].each do |param| - context "and when only the :#{param} operation is present" do - let(:arguments) { super().slice(:uuid, param) } - let(:expected_url) { "#{uuid}/video/-/#{param}/#{arguments[param]}/" } - - it_behaves_like 'URL building' - end - end - - context 'and when only the :cut operation is present' do - let(:arguments) { super().slice(:uuid, :cut) } - let(:expected_url) do - "#{uuid}/video/-/cut/#{arguments[:cut][:start_time]}/#{arguments[:cut][:length]}/" - end - - it_behaves_like 'URL building' - end - - context 'and when only the :thumbs operation is present' do - let(:arguments) { super().slice(:uuid, :thumbs) } - let(:expected_url) do - "#{uuid}/video/-/thumbs~#{arguments[:thumbs][:N]}/#{arguments[:thumbs][:number]}/" - end - - it_behaves_like 'URL building' - end - end - end - end - end - end -end diff --git a/spec/uploadcare/param/secure_auth_header_spec.rb b/spec/uploadcare/param/secure_auth_header_spec.rb deleted file mode 100644 index 3f2b6e90..00000000 --- a/spec/uploadcare/param/secure_auth_header_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'param/secure_auth_header' - -module Uploadcare - RSpec.describe Param::SecureAuthHeader do - subject { Param::SecureAuthHeader } - describe 'signature' do - before(:each) do - allow(Time).to receive(:now).and_return(Time.parse('2017.02.02 12:58:50 +0000')) - Uploadcare.config.public_key = 'pub' - Uploadcare.config.secret_key = 'priv' - end - - it 'returns correct headers for complex authentication' do - headers = subject.call(method: 'POST', uri: '/path', content_type: 'application/x-www-form-urlencoded') - expected = '47af79c7f800de03b9e0f2dbb1e589cba7b210c2' - expect(headers[:Authorization]).to eq "Uploadcare pub:#{expected}" - end - end - end -end diff --git a/spec/uploadcare/param/simple_auth_header_spec.rb b/spec/uploadcare/param/simple_auth_header_spec.rb deleted file mode 100644 index d7532d39..00000000 --- a/spec/uploadcare/param/simple_auth_header_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'param/simple_auth_header' - -module Uploadcare - RSpec.describe Param::SimpleAuthHeader do - subject { Uploadcare::Param::SimpleAuthHeader } - describe 'Uploadcare.Simple' do - before do - Uploadcare.config.public_key = 'foo' - Uploadcare.config.secret_key = 'bar' - Uploadcare.config.auth_type = 'Uploadcare.Simple' - end - - it 'returns correct headers for simple authentication' do - expect(subject.call).to eq(Authorization: 'Uploadcare.Simple foo:bar') - end - end - end -end diff --git a/spec/uploadcare/param/upload/signature_generator_spec.rb b/spec/uploadcare/param/upload/signature_generator_spec.rb deleted file mode 100644 index 93103ae2..00000000 --- a/spec/uploadcare/param/upload/signature_generator_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -# @see https://uploadcare.com/docs/api_reference/upload/signed_uploads/ - -require 'spec_helper' -require 'param/upload/signature_generator' - -module Uploadcare - module Param - module Upload - RSpec.describe Uploadcare::Param::Upload::SignatureGenerator do - let!(:expires_at) { 1_454_903_856 } - let!(:expected_result) { { signature: '46f70d2b4fb6196daeb2c16bf44a7f1e', expire: expires_at } } - - before do - allow(Time).to receive(:now).and_return(expires_at - (60 * 30)) - Uploadcare.config.secret_key = 'project_secret_key' - end - - it 'generates body params needed for signing uploads' do - signature_body = SignatureGenerator.call - expect(signature_body).to eq expected_result - end - end - end - end -end diff --git a/spec/uploadcare/param/upload/upload_params_generator_spec.rb b/spec/uploadcare/param/upload/upload_params_generator_spec.rb deleted file mode 100644 index 862c0d0d..00000000 --- a/spec/uploadcare/param/upload/upload_params_generator_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -# @see https://uploadcare.com/docs/api_reference/upload/signed_uploads/ - -require 'spec_helper' -require 'param/upload/upload_params_generator' - -module Uploadcare - module Param - module Upload - RSpec.describe Uploadcare::Param::Upload::UploadParamsGenerator do - subject { Uploadcare::Param::Upload::UploadParamsGenerator } - - it 'generates basic upload params headers' do - params = subject.call - expect(params['UPLOADCARE_PUB_KEY']).not_to be_nil - expect(params['UPLOADCARE_STORE']).not_to be_nil - end - end - end - end -end diff --git a/spec/uploadcare/param/user_agent_spec.rb b/spec/uploadcare/param/user_agent_spec.rb deleted file mode 100644 index c2936a8b..00000000 --- a/spec/uploadcare/param/user_agent_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'param/user_agent' - -module Uploadcare - RSpec.describe Param::UserAgent do - subject { Param::UserAgent } - - it 'contains gem version' do - user_agent_string = subject.call - expect(user_agent_string).to include(Uploadcare::VERSION) - end - - it 'contains framework data when it is specified' do - Uploadcare.config.framework_data = 'Rails' - expect(subject.call).to include('; Rails') - Uploadcare.config.framework_data = '' - expect(subject.call).not_to include(';') - end - end -end diff --git a/spec/uploadcare/resources/addon_execution_spec.rb b/spec/uploadcare/resources/addon_execution_spec.rb new file mode 100644 index 00000000..e6f6049e --- /dev/null +++ b/spec/uploadcare/resources/addon_execution_spec.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::AddonExecution do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_addons) { instance_double(Uploadcare::Api::Rest::Addons) } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest) } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + let(:request_id) { 'req-abc-123' } + let(:execute_response) { { 'request_id' => request_id } } + let(:status_response) { { 'status' => 'done', 'result' => { 'labels' => [] } } } + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:addons).and_return(rest_addons) + end + + describe '.aws_rekognition_detect_labels' do + it 'executes label detection and returns an AddonExecution' do + allow(rest_addons).to receive(:aws_rekognition_detect_labels) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success(execute_response)) + + result = described_class.aws_rekognition_detect_labels(uuid: file_uuid, client: client) + expect(result).to be_a(described_class) + expect(result.request_id).to eq(request_id) + end + end + + describe '.aws_rekognition_detect_labels_status' do + it 'checks label detection status' do + allow(rest_addons).to receive(:aws_rekognition_detect_labels_status) + .with(request_id: request_id, request_options: {}) + .and_return(Uploadcare::Result.success(status_response)) + + result = described_class.aws_rekognition_detect_labels_status(request_id: request_id, client: client) + expect(result).to be_a(described_class) + expect(result.status).to eq('done') + end + end + + describe '.aws_rekognition_detect_moderation_labels' do + it 'executes moderation label detection' do + allow(rest_addons).to receive(:aws_rekognition_detect_moderation_labels) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success(execute_response)) + + result = described_class.aws_rekognition_detect_moderation_labels(uuid: file_uuid, client: client) + expect(result).to be_a(described_class) + expect(result.request_id).to eq(request_id) + end + end + + describe '.aws_rekognition_detect_moderation_labels_status' do + it 'checks moderation label detection status' do + allow(rest_addons).to receive(:aws_rekognition_detect_moderation_labels_status) + .with(request_id: request_id, request_options: {}) + .and_return(Uploadcare::Result.success(status_response)) + + result = described_class.aws_rekognition_detect_moderation_labels_status( + request_id: request_id, client: client + ) + expect(result).to be_a(described_class) + expect(result.status).to eq('done') + end + end + + describe '.uc_clamav_virus_scan' do + it 'executes virus scan with optional params' do + allow(rest_addons).to receive(:uc_clamav_virus_scan) + .with(uuid: file_uuid, params: { purge_infected: true }, request_options: {}) + .and_return(Uploadcare::Result.success(execute_response)) + + result = described_class.uc_clamav_virus_scan( + uuid: file_uuid, params: { purge_infected: true }, client: client + ) + expect(result).to be_a(described_class) + expect(result.request_id).to eq(request_id) + end + + it 'defaults params to empty hash' do + allow(rest_addons).to receive(:uc_clamav_virus_scan) + .with(uuid: file_uuid, params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(execute_response)) + + result = described_class.uc_clamav_virus_scan(uuid: file_uuid, client: client) + expect(result).to be_a(described_class) + end + end + + describe '.uc_clamav_virus_scan_status' do + it 'checks virus scan status' do + allow(rest_addons).to receive(:uc_clamav_virus_scan_status) + .with(request_id: request_id, request_options: {}) + .and_return(Uploadcare::Result.success(status_response)) + + result = described_class.uc_clamav_virus_scan_status(request_id: request_id, client: client) + expect(result).to be_a(described_class) + expect(result.status).to eq('done') + end + end + + describe '.remove_bg' do + it 'executes background removal with optional params' do + allow(rest_addons).to receive(:remove_bg) + .with(uuid: file_uuid, params: { type: 'auto' }, request_options: {}) + .and_return(Uploadcare::Result.success(execute_response)) + + result = described_class.remove_bg(uuid: file_uuid, params: { type: 'auto' }, client: client) + expect(result).to be_a(described_class) + expect(result.request_id).to eq(request_id) + end + + it 'defaults params to empty hash' do + allow(rest_addons).to receive(:remove_bg) + .with(uuid: file_uuid, params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(execute_response)) + + result = described_class.remove_bg(uuid: file_uuid, client: client) + expect(result).to be_a(described_class) + end + end + + describe '.remove_bg_status' do + it 'checks background removal status' do + allow(rest_addons).to receive(:remove_bg_status) + .with(request_id: request_id, request_options: {}) + .and_return(Uploadcare::Result.success(status_response)) + + result = described_class.remove_bg_status(request_id: request_id, client: client) + expect(result).to be_a(described_class) + expect(result.status).to eq('done') + end + end + + describe 'attributes' do + it 'exposes request_id, status, and result' do + addon = described_class.new( + { 'request_id' => request_id, 'status' => 'in_progress', 'result' => nil }, + client + ) + expect(addon.request_id).to eq(request_id) + expect(addon.status).to eq('in_progress') + expect(addon.result).to be_nil + end + end +end diff --git a/spec/uploadcare/resources/base_resource_spec.rb b/spec/uploadcare/resources/base_resource_spec.rb new file mode 100644 index 00000000..b867de78 --- /dev/null +++ b/spec/uploadcare/resources/base_resource_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::BaseResource do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + + describe '#initialize' do + it 'accepts attributes and a client' do + resource = described_class.new({}, client) + expect(resource.client).to eq(client) + expect(resource.config).to eq(client.config) + end + + it 'raises when no client or config is given' do + expect do + described_class.new({}) + end.to raise_error(ArgumentError, /client or config is required/) + end + + it 'resolves a Configuration into a client' do + resource = described_class.new({}, config) + expect(resource.client).to be_a(Uploadcare::Client) + expect(resource.config.public_key).to eq('demopublickey') + end + end + + describe '.resolve_client' do + it 'returns the explicit client when provided' do + result = described_class.resolve_client(client: client) + expect(result).to eq(client) + end + + it 'wraps a Configuration in a client' do + result = described_class.resolve_client(config) + expect(result).to be_a(Uploadcare::Client) + expect(result.config.public_key).to eq('demopublickey') + end + + it 'wraps a Client passed as first argument' do + result = described_class.resolve_client(client) + expect(result).to eq(client) + end + + it 'raises for nil' do + expect do + described_class.resolve_client(nil) + end.to raise_error(ArgumentError, /client or config is required/) + end + + it 'uses the config keyword when provided' do + result = described_class.resolve_client(config: config) + expect(result).to be_a(Uploadcare::Client) + expect(result.config.public_key).to eq('demopublickey') + end + end + + describe '#assign_attributes' do + it 'sets attributes via setter methods' do + klass = Class.new(described_class) do + attr_accessor :name, :value + end + + resource = klass.new({ 'name' => 'test', 'value' => 42 }, client) + expect(resource.name).to eq('test') + expect(resource.value).to eq(42) + end + + it 'ignores attributes without setter methods' do + expect do + described_class.new({ 'nonexistent_attr' => 'ignored' }, client) + end.not_to raise_error + end + end +end diff --git a/spec/uploadcare/resources/document_conversion_spec.rb b/spec/uploadcare/resources/document_conversion_spec.rb new file mode 100644 index 00000000..1e63a68e --- /dev/null +++ b/spec/uploadcare/resources/document_conversion_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::DocumentConversion do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_doc_conversions) { double('document_conversions') } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest) } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:document_conversions).and_return(rest_doc_conversions) + end + + describe '.convert_document' do + it 'converts a document to the specified format' do + conversion_response = { + 'result' => [{ 'uuid' => 'converted-uuid', 'token' => 'job-token-123' }], + 'problems' => {} + } + + allow(rest_doc_conversions).to receive(:convert) + .with( + paths: ["#{file_uuid}/document/-/format/pdf/"], + options: {}, + request_options: {} + ) + .and_return(Uploadcare::Result.success(conversion_response)) + + result = described_class.convert_document( + params: { uuid: file_uuid, format: 'pdf' }, + client: client + ) + expect(result).to be_a(Hash) + expect(result['result'].first['uuid']).to eq('converted-uuid') + end + + it 'handles multiple UUIDs' do + uuids = %w[uuid-1 uuid-2] + conversion_response = { + 'result' => [ + { 'uuid' => 'converted-1' }, + { 'uuid' => 'converted-2' } + ], + 'problems' => {} + } + + allow(rest_doc_conversions).to receive(:convert) + .with( + paths: ['uuid-1/document/-/format/pdf/', 'uuid-2/document/-/format/pdf/'], + options: {}, + request_options: {} + ) + .and_return(Uploadcare::Result.success(conversion_response)) + + result = described_class.convert_document( + params: { uuid: uuids, format: 'pdf' }, + client: client + ) + expect(result['result'].length).to eq(2) + end + + it 'passes options through' do + allow(rest_doc_conversions).to receive(:convert) + .with( + paths: ["#{file_uuid}/document/-/format/png/"], + options: { store: true }, + request_options: {} + ) + .and_return(Uploadcare::Result.success({ 'result' => [] })) + + result = described_class.convert_document( + params: { uuid: file_uuid, format: 'png' }, + options: { store: true }, + client: client + ) + + expect(result).to eq({ 'result' => [] }) + end + end + + describe '#info' do + it 'fetches document format information' do + conversion = described_class.new({ 'uuid' => file_uuid }, client) + info_response = { + 'format' => 'pdf', + 'error' => nil + } + + allow(rest_doc_conversions).to receive(:info) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success(info_response)) + + result = conversion.info + expect(result).to eq(conversion) + expect(conversion.format).to eq('pdf') + expect(conversion.error).to be_nil + end + + it 'raises when uuid is missing' do + conversion = described_class.new({}, client) + + expect do + conversion.info + end.to raise_error(ArgumentError, 'uuid is required') + end + end + + describe '#fetch_status' do + it 'fetches conversion job status' do + conversion = described_class.new({}, client) + status_response = { + 'status' => 'finished', + 'result' => [{ 'uuid' => 'converted-uuid' }], + 'error' => nil + } + + allow(rest_doc_conversions).to receive(:status) + .with(token: 'job-token-123', request_options: {}) + .and_return(Uploadcare::Result.success(status_response)) + + result = conversion.fetch_status(token: 'job-token-123') + expect(result).to eq(conversion) + expect(conversion.status).to eq('finished') + expect(conversion.result).to eq([{ 'uuid' => 'converted-uuid' }]) + end + end + + describe 'attributes' do + it 'exposes error, format, converted_groups, status, and result' do + conversion = described_class.new( + { + 'error' => nil, + 'format' => 'pdf', + 'status' => 'pending', + 'result' => [], + 'converted_groups' => [] + }, + client + ) + expect(conversion.error).to be_nil + expect(conversion.format).to eq('pdf') + expect(conversion.status).to eq('pending') + expect(conversion.result).to eq([]) + expect(conversion.converted_groups).to eq([]) + end + end +end diff --git a/spec/uploadcare/resources/file_metadata_spec.rb b/spec/uploadcare/resources/file_metadata_spec.rb new file mode 100644 index 00000000..86c099d7 --- /dev/null +++ b/spec/uploadcare/resources/file_metadata_spec.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::FileMetadata do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_metadata) { instance_double(Uploadcare::Api::Rest::FileMetadata) } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest) } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:file_metadata).and_return(rest_metadata) + end + + describe '#initialize' do + it 'initializes with empty metadata hash' do + metadata = described_class.new({}, client) + expect(metadata.to_h).to eq({}) + end + end + + describe 'class methods' do + describe '.index' do + it 'returns metadata hash for a file' do + allow(rest_metadata).to receive(:index) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success({ 'key1' => 'value1', 'key2' => 'value2' })) + + result = described_class.index(uuid: file_uuid, client: client) + expect(result).to eq({ 'key1' => 'value1', 'key2' => 'value2' }) + end + end + + describe '.show' do + it 'returns a single metadata value' do + allow(rest_metadata).to receive(:show) + .with(uuid: file_uuid, key: 'key1', request_options: {}) + .and_return(Uploadcare::Result.success('value1')) + + result = described_class.show(uuid: file_uuid, key: 'key1', client: client) + expect(result).to eq('value1') + end + end + + describe '.update' do + it 'updates a metadata key and returns the value' do + allow(rest_metadata).to receive(:update) + .with(uuid: file_uuid, key: 'key1', value: 'new-value', request_options: {}) + .and_return(Uploadcare::Result.success('new-value')) + + result = described_class.update(uuid: file_uuid, key: 'key1', value: 'new-value', client: client) + expect(result).to eq('new-value') + end + end + + describe '.delete' do + it 'deletes a metadata key' do + allow(rest_metadata).to receive(:delete) + .with(uuid: file_uuid, key: 'key1', request_options: {}) + .and_return(Uploadcare::Result.success(nil)) + + expect do + described_class.delete(uuid: file_uuid, key: 'key1', client: client) + end.not_to raise_error + end + end + end + + describe 'instance methods' do + let(:metadata_instance) do + described_class.new({ 'uuid' => file_uuid }, client) + end + + it 'assigns uuid from initialization attributes' do + expect(metadata_instance.uuid).to eq(file_uuid) + end + + describe '#index' do + it 'fetches all metadata and stores it internally' do + allow(rest_metadata).to receive(:index) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success({ 'key1' => 'value1' })) + + result = metadata_instance.index + expect(result).to eq(metadata_instance) + expect(metadata_instance['key1']).to eq('value1') + expect(metadata_instance.to_h).to eq({ 'key1' => 'value1' }) + end + + it 'normalizes metadata keys to strings' do + allow(rest_metadata).to receive(:index) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success({ key1: 'value1' })) + + metadata_instance.index + + expect(metadata_instance['key1']).to eq('value1') + expect(metadata_instance.to_h).to eq({ 'key1' => 'value1' }) + end + end + + describe '#[] and #[]=' do + it 'gets and sets metadata locally' do + metadata_instance['color'] = 'red' + expect(metadata_instance['color']).to eq('red') + end + + it 'converts symbol keys to strings' do + metadata_instance[:shape] = 'circle' + expect(metadata_instance[:shape]).to eq('circle') + expect(metadata_instance['shape']).to eq('circle') + end + end + + describe '#to_h' do + it 'returns a copy of the metadata hash' do + metadata_instance['a'] = '1' + metadata_instance['b'] = '2' + h = metadata_instance.to_h + expect(h).to eq({ 'a' => '1', 'b' => '2' }) + + h['c'] = '3' + expect(metadata_instance['c']).to be_nil + end + end + + describe '#update' do + it 'persists a key-value pair to the server' do + allow(rest_metadata).to receive(:update) + .with(uuid: file_uuid, key: 'color', value: 'blue', request_options: {}) + .and_return(Uploadcare::Result.success('blue')) + + result = metadata_instance.update(key: 'color', value: 'blue') + expect(result).to eq('blue') + expect(metadata_instance['color']).to eq('blue') + end + + it 'does not update local metadata when uuid differs' do + allow(rest_metadata).to receive(:update) + .with(uuid: 'other-uuid', key: 'color', value: 'blue', request_options: {}) + .and_return(Uploadcare::Result.success('blue')) + + metadata_instance.update(key: 'color', value: 'blue', uuid: 'other-uuid') + expect(metadata_instance['color']).to be_nil + end + end + + describe '#show' do + it 'retrieves a single metadata value from the server' do + allow(rest_metadata).to receive(:show) + .with(uuid: file_uuid, key: 'color', request_options: {}) + .and_return(Uploadcare::Result.success('green')) + + result = metadata_instance.show(key: 'color') + expect(result).to eq('green') + expect(metadata_instance['color']).to eq('green') + end + + it 'does not update local metadata when uuid differs' do + allow(rest_metadata).to receive(:show) + .with(uuid: 'other-uuid', key: 'color', request_options: {}) + .and_return(Uploadcare::Result.success('green')) + + result = metadata_instance.show(key: 'color', uuid: 'other-uuid') + expect(result).to eq('green') + expect(metadata_instance['color']).to be_nil + end + end + + describe '#delete' do + it 'deletes a metadata key on the server' do + metadata_instance['temp'] = 'data' + allow(rest_metadata).to receive(:delete) + .with(uuid: file_uuid, key: 'temp', request_options: {}) + .and_return(Uploadcare::Result.success(nil)) + + metadata_instance.delete(key: 'temp') + expect(metadata_instance['temp']).to be_nil + end + + it 'does not delete local metadata when uuid differs' do + metadata_instance['temp'] = 'data' + allow(rest_metadata).to receive(:delete) + .with(uuid: 'other-uuid', key: 'temp', request_options: {}) + .and_return(Uploadcare::Result.success(nil)) + + metadata_instance.delete(key: 'temp', uuid: 'other-uuid') + expect(metadata_instance['temp']).to eq('data') + end + end + end +end diff --git a/spec/uploadcare/resources/file_spec.rb b/spec/uploadcare/resources/file_spec.rb new file mode 100644 index 00000000..9adfc490 --- /dev/null +++ b/spec/uploadcare/resources/file_spec.rb @@ -0,0 +1,457 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::File do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_files) { instance_double(Uploadcare::Api::Rest::Files) } + let(:upload_api) { instance_double(Uploadcare::Api::Upload) } + let(:upload_files) { double('upload_files') } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest, upload: upload_api) } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + let(:file_attrs) do + { + 'uuid' => file_uuid, + 'original_filename' => 'photo.jpg', + 'size' => 12_345, + 'mime_type' => 'image/jpeg', + 'is_image' => true, + 'is_ready' => true, + 'url' => "https://ucarecdn.com/#{file_uuid}/", + 'datetime_uploaded' => '2025-01-01T00:00:00Z', + 'datetime_stored' => '2025-01-01T00:00:01Z', + 'datetime_removed' => nil, + 'original_file_url' => "https://ucarecdn.com/#{file_uuid}/photo.jpg", + 'variations' => nil, + 'content_info' => {}, + 'metadata' => {}, + 'appdata' => nil, + 'source' => nil + } + end + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:files).and_return(rest_files) + end + + describe 'ATTRIBUTES' do + it 'defines expected attributes' do + expected = %i[ + datetime_removed datetime_stored datetime_uploaded is_image is_ready mime_type original_file_url + original_filename size url uuid variations content_info metadata appdata source + ] + expect(described_class::ATTRIBUTES).to match_array(expected) + end + end + + describe '#initialize' do + it 'assigns attributes from a hash' do + file = described_class.new(file_attrs, client) + expect(file.uuid).to eq(file_uuid) + expect(file.original_filename).to eq('photo.jpg') + expect(file.size).to eq(12_345) + expect(file.mime_type).to eq('image/jpeg') + expect(file.is_image).to be true + expect(file.is_ready).to be true + end + + it 'stores client reference' do + file = described_class.new(file_attrs, client) + expect(file.client).to eq(client) + end + end + + describe '.find' do + it 'fetches file info by UUID' do + allow(rest_files).to receive(:info) + .with(uuid: file_uuid, params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(file_attrs)) + + file = described_class.find(uuid: file_uuid, client: client) + expect(file).to be_a(described_class) + expect(file.uuid).to eq(file_uuid) + expect(file.original_filename).to eq('photo.jpg') + end + + it 'passes params when provided' do + allow(rest_files).to receive(:info) + .with(uuid: file_uuid, params: { include: 'appdata' }, request_options: {}) + .and_return(Uploadcare::Result.success(file_attrs)) + + file = described_class.find(uuid: file_uuid, params: { include: 'appdata' }, client: client) + expect(file.uuid).to eq(file_uuid) + end + + it 'is aliased as retrieve and info' do + expect(described_class).to respond_to(:retrieve) + expect(described_class).to respond_to(:info) + end + end + + describe '.list' do + let(:list_response) do + { + 'results' => [file_attrs], + 'next' => 'https://api.uploadcare.com/files/?limit=10&offset=10', + 'previous' => nil, + 'per_page' => 10, + 'total' => 25 + } + end + + it 'returns a Paginated collection' do + allow(rest_files).to receive(:list) + .with(params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(list_response)) + + result = described_class.list(client: client) + expect(result).to be_a(Uploadcare::Collections::Paginated) + expect(result.resources.length).to eq(1) + expect(result.resources.first).to be_a(described_class) + expect(result.resources.first.uuid).to eq(file_uuid) + expect(result.total).to eq(25) + expect(result.per_page).to eq(10) + end + + it 'passes options as params' do + allow(rest_files).to receive(:list) + .with(params: { limit: 5 }, request_options: {}) + .and_return(Uploadcare::Result.success(list_response)) + + result = described_class.list(options: { limit: 5 }, client: client) + + expect(result).to be_a(Uploadcare::Collections::Paginated) + end + end + + describe '.upload' do + it 'delegates to upload router' do + uploads = instance_double(Uploadcare::Operations::UploadRouter) + allow(client).to receive(:uploads).and_return(uploads) + file_io = double('file') + result_file = described_class.new(file_attrs, client) + + allow(uploads).to receive(:upload_file) + .with(file: file_io, request_options: {}, store: true) + .and_return(result_file) + + result = described_class.upload(file_io, client: client, store: true) + expect(result).to eq(result_file) + end + end + + describe '.upload_many' do + it 'delegates to upload router' do + uploads = instance_double(Uploadcare::Operations::UploadRouter) + allow(client).to receive(:uploads).and_return(uploads) + files = [double('file1'), double('file2')] + result_files = [described_class.new(file_attrs, client)] + + allow(uploads).to receive(:upload_files) + .with(files: files, request_options: {}) + .and_return(result_files) + + result = described_class.upload_many(files, client: client) + expect(result).to eq(result_files) + end + end + + describe '.upload_url' do + it 'delegates to upload router' do + uploads = instance_double(Uploadcare::Operations::UploadRouter) + allow(client).to receive(:uploads).and_return(uploads) + result_file = described_class.new(file_attrs, client) + + allow(uploads).to receive(:upload_from_url) + .with(url: 'https://example.com/img.jpg', request_options: {}) + .and_return(result_file) + + result = described_class.upload_url('https://example.com/img.jpg', client: client) + expect(result).to eq(result_file) + end + + it 'is aliased as upload_from_url' do + expect(described_class).to respond_to(:upload_from_url) + end + end + + describe '.batch_store' do + let(:batch_response) do + { + 'status' => 'ok', + 'result' => [file_attrs], + 'problems' => {} + } + end + + it 'stores files in batch and returns BatchResult' do + allow(rest_files).to receive(:batch_store) + .with(uuids: [file_uuid], request_options: {}) + .and_return(Uploadcare::Result.success(batch_response)) + + result = described_class.batch_store(uuids: [file_uuid], client: client) + expect(result).to be_a(Uploadcare::Collections::BatchResult) + expect(result.status).to eq('ok') + expect(result.result.length).to eq(1) + expect(result.result.first).to be_a(described_class) + expect(result.problems).to eq({}) + end + end + + describe '.batch_delete' do + let(:batch_response) do + { + 'status' => 'ok', + 'result' => [file_attrs], + 'problems' => { 'bad-uuid' => 'Not found' } + } + end + + it 'deletes files in batch and returns BatchResult' do + allow(rest_files).to receive(:batch_delete) + .with(uuids: [file_uuid, 'bad-uuid'], request_options: {}) + .and_return(Uploadcare::Result.success(batch_response)) + + result = described_class.batch_delete(uuids: [file_uuid, 'bad-uuid'], client: client) + expect(result).to be_a(Uploadcare::Collections::BatchResult) + expect(result.problems).to eq({ 'bad-uuid' => 'Not found' }) + end + end + + describe '.local_copy' do + it 'copies file to local storage' do + copy_response = { 'result' => file_attrs } + allow(rest_files).to receive(:local_copy) + .with(source: file_uuid, options: {}, request_options: {}) + .and_return(Uploadcare::Result.success(copy_response)) + + result = described_class.local_copy(source: file_uuid, client: client) + expect(result).to be_a(described_class) + expect(result.uuid).to eq(file_uuid) + end + + it 'is aliased as copy_to_local' do + expect(described_class).to respond_to(:copy_to_local) + end + end + + describe '.remote_copy' do + it 'copies file to remote storage and returns result URL' do + remote_url = 's3://bucket/file.jpg' + copy_response = { 'result' => remote_url } + allow(rest_files).to receive(:remote_copy) + .with(source: file_uuid, target: 'my-storage', options: {}, request_options: {}) + .and_return(Uploadcare::Result.success(copy_response)) + + result = described_class.remote_copy(source: file_uuid, target: 'my-storage', client: client) + expect(result).to eq(remote_url) + end + + it 'is aliased as copy_to_remote' do + expect(described_class).to respond_to(:copy_to_remote) + end + end + + describe '#store' do + it 'stores the file and updates attributes' do + file = described_class.new(file_attrs.merge('datetime_stored' => nil), client) + stored_attrs = file_attrs.merge('datetime_stored' => '2025-06-01T12:00:00Z') + + allow(rest_files).to receive(:store) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success(stored_attrs)) + + result = file.store + expect(result).to eq(file) + expect(file.datetime_stored).to eq('2025-06-01T12:00:00Z') + end + end + + describe '#delete' do + it 'deletes the file and updates attributes' do + file = described_class.new(file_attrs, client) + deleted_attrs = file_attrs.merge('datetime_removed' => '2025-06-01T12:00:00Z') + + allow(rest_files).to receive(:delete) + .with(uuid: file_uuid, request_options: {}) + .and_return(Uploadcare::Result.success(deleted_attrs)) + + result = file.delete + expect(result).to eq(file) + expect(file.datetime_removed).to eq('2025-06-01T12:00:00Z') + end + end + + describe '#reload' do + it 'reloads file info from API' do + file = described_class.new(file_attrs, client) + updated_attrs = file_attrs.merge('original_filename' => 'renamed.jpg') + + allow(rest_files).to receive(:info) + .with(uuid: file_uuid, params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(updated_attrs)) + + result = file.reload + expect(result).to eq(file) + expect(file.original_filename).to eq('renamed.jpg') + end + + it 'is aliased as load' do + file = described_class.new(file_attrs, client) + expect(file).to respond_to(:load) + end + end + + describe '#copy_to_local' do + it 'copies this file to local storage' do + file = described_class.new(file_attrs, client) + copy_response = { 'result' => file_attrs.merge('uuid' => 'new-uuid-copy') } + + allow(rest_files).to receive(:local_copy) + .with(source: file_uuid, options: {}, request_options: {}) + .and_return(Uploadcare::Result.success(copy_response)) + + result = file.copy_to_local + expect(result).to be_a(described_class) + end + end + + describe '#copy_to_remote' do + it 'copies this file to remote storage' do + file = described_class.new(file_attrs, client) + remote_url = 's3://bucket/file.jpg' + copy_response = { 'result' => remote_url } + + allow(rest_files).to receive(:remote_copy) + .with(source: file_uuid, target: 'my-storage', options: {}, request_options: {}) + .and_return(Uploadcare::Result.success(copy_response)) + + result = file.copy_to_remote(target: 'my-storage') + expect(result).to eq(remote_url) + end + end + + describe '#convert_to_document' do + let(:rest_doc_conversions) { double('document_conversions') } + let(:file) { described_class.new(file_attrs, client) } + + before do + allow(rest).to receive(:document_conversions).and_return(rest_doc_conversions) + end + + it 'raises ArgumentError when params is not a Hash' do + expect do + file.convert_to_document(params: 'not-a-hash') + end.to raise_error(ArgumentError, 'The first argument must be a Hash') + end + + it 'converts with document conversion' do + conversion_response = { + 'result' => [{ 'uuid' => 'converted-uuid' }] + } + allow(Uploadcare::Resources::DocumentConversion).to receive(:convert_document) + .and_return(conversion_response) + + allow(rest_files).to receive(:info) + .and_return(Uploadcare::Result.success(file_attrs.merge('uuid' => 'converted-uuid'))) + + result = file.convert_to_document(params: { format: 'pdf' }) + expect(result).to be_a(described_class) + end + end + + describe '#convert_to_video' do + let(:rest_video_conversions) { double('video_conversions') } + let(:file) { described_class.new(file_attrs, client) } + + before do + allow(rest).to receive(:video_conversions).and_return(rest_video_conversions) + end + + it 'raises ArgumentError when params is not a Hash' do + expect do + file.convert_to_video(params: 'not-a-hash') + end.to raise_error(ArgumentError, 'The first argument must be a Hash') + end + end + + describe '#uuid' do + it 'returns the assigned uuid' do + file = described_class.new({ 'uuid' => file_uuid }, client) + expect(file.uuid).to eq(file_uuid) + end + + it 'extracts uuid from url when uuid not set directly' do + file = described_class.new({ 'url' => "https://ucarecdn.com/#{file_uuid}/" }, client) + expect(file.uuid).to eq(file_uuid) + end + + it 'extracts uuid from original_file_url when url not set' do + file = described_class.new( + { 'original_file_url' => "https://ucarecdn.com/#{file_uuid}/photo.jpg" }, + client + ) + expect(file.uuid).to eq(file_uuid) + end + + it 'does not extract uuid from non-Uploadcare original_file_url' do + file = described_class.new( + { 'original_file_url' => "https://example.com/#{file_uuid}/photo.jpg" }, + client + ) + expect(file.uuid).to be_nil + end + + it 'returns nil when no uuid source is available' do + file = described_class.new({}, client) + expect(file.uuid).to be_nil + end + end + + describe '#cdn_url' do + it 'returns the CDN url attribute when set' do + file = described_class.new(file_attrs, client) + expect(file.cdn_url).to eq("https://ucarecdn.com/#{file_uuid}/") + end + + it 'builds CDN URL from config and uuid' do + file = described_class.new({ 'uuid' => file_uuid }, client) + expect(file.cdn_url).to eq("https://ucarecdn.com/#{file_uuid}/") + end + + it 'falls back to Uploadcare original_file_url when url is not a CDN URL' do + file = described_class.new( + { + 'url' => "https://api.uploadcare.com/files/#{file_uuid}/", + 'original_file_url' => "https://ucarecdn.com/#{file_uuid}/photo.jpg" + }, + client + ) + expect(file.cdn_url).to eq("https://ucarecdn.com/#{file_uuid}/photo.jpg") + end + + it 'ignores non-Uploadcare original_file_url values' do + file = described_class.new( + { 'uuid' => file_uuid, 'original_file_url' => 'https://example.com/photo.jpg' }, + client + ) + expect(file.cdn_url).to eq("https://ucarecdn.com/#{file_uuid}/") + end + + it 'returns nil when no CDN url or uuid is available' do + file = described_class.new({ 'original_file_url' => 'https://example.com/photo.jpg' }, client) + + expect(file.cdn_url).to be_nil + end + end +end diff --git a/spec/uploadcare/resources/group_spec.rb b/spec/uploadcare/resources/group_spec.rb new file mode 100644 index 00000000..f90e0a98 --- /dev/null +++ b/spec/uploadcare/resources/group_spec.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::Group do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_groups) { instance_double(Uploadcare::Api::Rest::Groups) } + let(:upload_api) { instance_double(Uploadcare::Api::Upload) } + let(:upload_groups) { double('upload_groups') } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest, upload: upload_api) } + + let(:group_id) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890~3' } + let(:group_attrs) do + { + 'id' => group_id, + 'datetime_created' => '2025-01-01T00:00:00Z', + 'datetime_stored' => nil, + 'files_count' => 3, + 'cdn_url' => "https://ucarecdn.com/#{group_id}/", + 'url' => "https://api.uploadcare.com/groups/#{group_id}/", + 'files' => [] + } + end + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:groups).and_return(rest_groups) + allow(upload_api).to receive(:groups).and_return(upload_groups) + end + + describe 'ATTRIBUTES' do + it 'defines expected attributes' do + expect(described_class::ATTRIBUTES).to include(:id, :files_count, :cdn_url, :files, :datetime_created) + end + end + + describe '.find' do + it 'fetches group info by ID' do + allow(rest_groups).to receive(:info) + .with(uuid: group_id, request_options: {}) + .and_return(Uploadcare::Result.success(group_attrs)) + + group = described_class.find(group_id: group_id, client: client) + expect(group).to be_a(described_class) + expect(group.id).to eq(group_id) + expect(group.files_count).to eq(3) + end + + it 'is aliased as retrieve and info' do + expect(described_class).to respond_to(:retrieve) + expect(described_class).to respond_to(:info) + end + end + + describe '.list' do + let(:list_response) do + { + 'results' => [group_attrs], + 'next' => nil, + 'previous' => nil, + 'per_page' => 10, + 'total' => 1 + } + end + + it 'returns a Paginated collection of groups' do + allow(rest_groups).to receive(:list) + .with(params: {}, request_options: {}) + .and_return(Uploadcare::Result.success(list_response)) + + result = described_class.list(client: client) + expect(result).to be_a(Uploadcare::Collections::Paginated) + expect(result.resources.length).to eq(1) + expect(result.resources.first).to be_a(described_class) + expect(result.total).to eq(1) + end + + it 'passes params through' do + allow(rest_groups).to receive(:list) + .with(params: { limit: 5 }, request_options: {}) + .and_return(Uploadcare::Result.success(list_response)) + + result = described_class.list(params: { limit: 5 }, client: client) + + expect(result).to be_a(Uploadcare::Collections::Paginated) + end + end + + describe '.create' do + it 'creates a group from file UUIDs' do + uuids = %w[uuid-1 uuid-2 uuid-3] + allow(upload_groups).to receive(:create) + .with(files: uuids, request_options: {}) + .and_return(Uploadcare::Result.success(group_attrs)) + + group = described_class.create(uuids: uuids, client: client) + expect(group).to be_a(described_class) + expect(group.id).to eq(group_id) + expect(group.files_count).to eq(3) + end + end + + describe '#reload' do + it 'reloads group info from the API' do + group = described_class.new(group_attrs, client) + updated_attrs = group_attrs.merge('files_count' => 5) + + allow(rest_groups).to receive(:info) + .with(uuid: group_id, request_options: {}) + .and_return(Uploadcare::Result.success(updated_attrs)) + + result = group.reload + expect(result).to eq(group) + expect(group.files_count).to eq(5) + end + + it 'is aliased as load' do + group = described_class.new(group_attrs, client) + expect(group).to respond_to(:load) + end + end + + describe '#delete' do + it 'deletes the group' do + group = described_class.new(group_attrs, client) + + allow(rest_groups).to receive(:delete) + .with(uuid: group_id, request_options: {}) + .and_return(Uploadcare::Result.success(nil)) + + expect { group.delete }.not_to raise_error + end + end + + describe '#id' do + it 'returns the id attribute when set' do + group = described_class.new(group_attrs, client) + expect(group.id).to eq(group_id) + end + + it 'falls back to uuid when id not set' do + group = described_class.new({ 'uuid' => 'some-uuid' }, client) + expect(group.id).to eq('some-uuid') + end + + it 'extracts id from cdn_url when id and uuid not set' do + group = described_class.new({ 'cdn_url' => "https://ucarecdn.com/#{group_id}/" }, client) + expect(group.id).to eq(group_id) + end + + it 'returns nil when no id source is available' do + group = described_class.new({}, client) + expect(group.id).to be_nil + end + end + + describe '#cdn_url' do + it 'returns the cdn_url attribute when set' do + group = described_class.new(group_attrs, client) + expect(group.cdn_url).to eq("https://ucarecdn.com/#{group_id}/") + end + + it 'builds CDN URL from config and id when cdn_url not set' do + group = described_class.new({ 'id' => group_id }, client) + expect(group.cdn_url).to eq("https://ucarecdn.com/#{group_id}/") + end + end + + describe '#file_cdn_urls' do + it 'returns array of nth CDN URLs for each file' do + group = described_class.new(group_attrs, client) + urls = group.file_cdn_urls + expect(urls.length).to eq(3) + expect(urls[0]).to eq("https://ucarecdn.com/#{group_id}/nth/0/") + expect(urls[1]).to eq("https://ucarecdn.com/#{group_id}/nth/1/") + expect(urls[2]).to eq("https://ucarecdn.com/#{group_id}/nth/2/") + end + + it 'returns empty array when files_count is nil' do + group = described_class.new({ 'id' => group_id }, client) + expect(group.file_cdn_urls).to eq([]) + end + end +end diff --git a/spec/uploadcare/resources/project_spec.rb b/spec/uploadcare/resources/project_spec.rb new file mode 100644 index 00000000..f452fcb3 --- /dev/null +++ b/spec/uploadcare/resources/project_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::Project do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_project) { instance_double(Uploadcare::Api::Rest::Project) } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest) } + + let(:project_attrs) do + { + 'name' => 'My Project', + 'pub_key' => 'demopublickey', + 'autostore_enabled' => true, + 'collaborators' => [ + { 'name' => 'Alice', 'email' => 'alice@example.com' } + ] + } + end + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:project).and_return(rest_project) + end + + describe '.current' do + it 'fetches current project info' do + allow(rest_project).to receive(:show) + .with(request_options: {}) + .and_return(Uploadcare::Result.success(project_attrs)) + + project = described_class.current(client: client) + expect(project).to be_a(described_class) + expect(project.name).to eq('My Project') + expect(project.pub_key).to eq('demopublickey') + expect(project.autostore_enabled).to be true + expect(project.collaborators).to be_an(Array) + expect(project.collaborators.first['name']).to eq('Alice') + end + + it 'is aliased as show' do + expect(described_class).to respond_to(:show) + end + end + + describe 'attributes' do + it 'exposes name, pub_key, autostore_enabled, collaborators' do + project = described_class.new(project_attrs, client) + expect(project.name).to eq('My Project') + expect(project.pub_key).to eq('demopublickey') + expect(project.autostore_enabled).to be true + expect(project.collaborators).to be_an(Array) + end + end +end diff --git a/spec/uploadcare/resources/video_conversion_spec.rb b/spec/uploadcare/resources/video_conversion_spec.rb new file mode 100644 index 00000000..aea433c8 --- /dev/null +++ b/spec/uploadcare/resources/video_conversion_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::VideoConversion do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_video_conversions) { double('video_conversions') } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest) } + + let(:file_uuid) { 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' } + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:video_conversions).and_return(rest_video_conversions) + end + + describe '.convert' do + it 'converts a video to the specified format and quality' do + conversion_response = { + 'result' => [{ 'uuid' => 'converted-uuid', 'token' => 'job-token-123' }], + 'problems' => {} + } + + allow(rest_video_conversions).to receive(:convert) + .with( + paths: ["#{file_uuid}/video/-/format/mp4/-/quality/best/"], + options: {}, + request_options: {} + ) + .and_return(Uploadcare::Result.success(conversion_response)) + + result = described_class.convert( + params: { uuid: file_uuid, format: 'mp4', quality: 'best' }, + client: client + ) + expect(result).to be_a(described_class) + end + + it 'raises ArgumentError when uuid is missing' do + expect do + described_class.convert(params: { format: 'mp4', quality: 'best' }, client: client) + end.to raise_error(ArgumentError, 'params must include :uuid') + end + + it 'raises ArgumentError when format is missing' do + expect do + described_class.convert(params: { uuid: file_uuid, quality: 'best' }, client: client) + end.to raise_error(ArgumentError, 'params must include :format') + end + + it 'raises ArgumentError when quality is missing' do + expect do + described_class.convert(params: { uuid: file_uuid, format: 'mp4' }, client: client) + end.to raise_error(ArgumentError, 'params must include :quality') + end + + it 'handles multiple UUIDs' do + uuids = %w[uuid-1 uuid-2] + conversion_response = { 'result' => [], 'problems' => {} } + + allow(rest_video_conversions).to receive(:convert) + .with( + paths: [ + 'uuid-1/video/-/format/webm/-/quality/normal/', + 'uuid-2/video/-/format/webm/-/quality/normal/' + ], + options: {}, + request_options: {} + ) + .and_return(Uploadcare::Result.success(conversion_response)) + + result = described_class.convert( + params: { uuid: uuids, format: 'webm', quality: 'normal' }, + client: client + ) + + expect(result).to be_a(described_class) + expect(result.result).to eq([]) + expect(result.problems).to eq({}) + end + + it 'passes options through' do + allow(rest_video_conversions).to receive(:convert) + .with( + paths: ["#{file_uuid}/video/-/format/mp4/-/quality/best/"], + options: { store: true }, + request_options: {} + ) + .and_return(Uploadcare::Result.success({ 'result' => [], 'problems' => {} })) + + result = described_class.convert( + params: { uuid: file_uuid, format: 'mp4', quality: 'best' }, + options: { store: true }, + client: client + ) + + expect(result).to be_a(described_class) + expect(result.result).to eq([]) + expect(result.problems).to eq({}) + end + end + + describe '#fetch_status' do + it 'fetches conversion job status' do + conversion = described_class.new({}, client) + status_response = { + 'status' => 'finished', + 'result' => [{ 'uuid' => 'converted-uuid' }], + 'error' => nil + } + + allow(rest_video_conversions).to receive(:status) + .with(token: 'job-token-123', request_options: {}) + .and_return(Uploadcare::Result.success(status_response)) + + result = conversion.fetch_status(token: 'job-token-123') + expect(result).to eq(conversion) + expect(conversion.status).to eq('finished') + expect(conversion.result).to eq([{ 'uuid' => 'converted-uuid' }]) + end + end + + describe 'attributes' do + it 'exposes problems, status, error, and result' do + conversion = described_class.new( + { 'problems' => {}, 'status' => 'processing', 'error' => nil, 'result' => [] }, + client + ) + expect(conversion.problems).to eq({}) + expect(conversion.status).to eq('processing') + expect(conversion.error).to be_nil + expect(conversion.result).to eq([]) + end + end +end diff --git a/spec/uploadcare/resources/webhook_spec.rb b/spec/uploadcare/resources/webhook_spec.rb new file mode 100644 index 00000000..fb947580 --- /dev/null +++ b/spec/uploadcare/resources/webhook_spec.rb @@ -0,0 +1,199 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare::Resources::Webhook do + let(:config) do + Uploadcare::Configuration.new( + public_key: 'demopublickey', + secret_key: 'demosecretkey', + auth_type: 'Uploadcare.Simple' + ) + end + let(:client) { Uploadcare::Client.new(config: config) } + let(:rest) { instance_double(Uploadcare::Api::Rest) } + let(:rest_webhooks) { instance_double(Uploadcare::Api::Rest::Webhooks) } + let(:api) { instance_double(Uploadcare::Client::Api, rest: rest) } + + let(:webhook_attrs) do + { + 'id' => 123, + 'project' => 456, + 'created' => '2025-01-01T00:00:00Z', + 'updated' => '2025-01-01T00:00:00Z', + 'event' => 'file.uploaded', + 'target_url' => 'https://example.com/webhook', + 'is_active' => true, + 'signing_secret' => 'secret123', + 'version' => '0.7' + } + end + + before do + allow(client).to receive(:api).and_return(api) + allow(rest).to receive(:webhooks).and_return(rest_webhooks) + end + + describe '.list' do + it 'returns an array of Webhook resources' do + allow(rest_webhooks).to receive(:list) + .with(request_options: {}) + .and_return(Uploadcare::Result.success([webhook_attrs])) + + result = described_class.list(client: client) + expect(result).to be_an(Array) + expect(result.length).to eq(1) + expect(result.first).to be_a(described_class) + expect(result.first.id).to eq(123) + expect(result.first.target_url).to eq('https://example.com/webhook') + expect(result.first.event).to eq('file.uploaded') + end + + it 'returns empty array when no webhooks exist' do + allow(rest_webhooks).to receive(:list) + .with(request_options: {}) + .and_return(Uploadcare::Result.success([])) + + result = described_class.list(client: client) + expect(result).to eq([]) + end + end + + describe '.create' do + it 'creates a webhook with default event and is_active' do + allow(rest_webhooks).to receive(:create) + .with( + options: { target_url: 'https://example.com/hook', event: 'file.uploaded', is_active: true }, + request_options: {} + ) + .and_return(Uploadcare::Result.success(webhook_attrs)) + + webhook = described_class.create(target_url: 'https://example.com/hook', client: client) + expect(webhook).to be_a(described_class) + expect(webhook.target_url).to eq('https://example.com/webhook') + end + + it 'creates a webhook with custom event and is_active' do + allow(rest_webhooks).to receive(:create) + .with( + options: { target_url: 'https://example.com/hook', event: 'file.stored', is_active: false }, + request_options: {} + ) + .and_return(Uploadcare::Result.success(webhook_attrs.merge('event' => 'file.stored', 'is_active' => false))) + + webhook = described_class.create( + target_url: 'https://example.com/hook', + event: 'file.stored', + is_active: false, + client: client + ) + expect(webhook).to be_a(described_class) + end + + it 'includes signing_secret when provided' do + allow(rest_webhooks).to receive(:create) + .with( + options: { + target_url: 'https://example.com/hook', + event: 'file.uploaded', + is_active: true, + signing_secret: 'my-secret' + }, + request_options: {} + ) + .and_return(Uploadcare::Result.success(webhook_attrs)) + + webhook = described_class.create( + target_url: 'https://example.com/hook', + signing_secret: 'my-secret', + client: client + ) + + expect(webhook).to be_a(described_class) + end + + it 'includes version when provided' do + allow(rest_webhooks).to receive(:create) + .with( + options: { + target_url: 'https://example.com/hook', + event: 'file.uploaded', + is_active: true, + version: '0.7' + }, + request_options: {} + ) + .and_return(Uploadcare::Result.success(webhook_attrs)) + + webhook = described_class.create( + target_url: 'https://example.com/hook', + version: '0.7', + client: client + ) + + expect(webhook).to be_a(described_class) + end + end + + describe '.update' do + it 'updates a webhook by id' do + updated_attrs = webhook_attrs.merge('target_url' => 'https://new-url.com/hook') + + allow(rest_webhooks).to receive(:update) + .with( + id: 123, + options: { target_url: 'https://new-url.com/hook' }, + request_options: {} + ) + .and_return(Uploadcare::Result.success(updated_attrs)) + + webhook = described_class.update(id: 123, target_url: 'https://new-url.com/hook', client: client) + expect(webhook).to be_a(described_class) + expect(webhook.target_url).to eq('https://new-url.com/hook') + end + + it 'can update is_active' do + allow(rest_webhooks).to receive(:update) + .with( + id: 123, + options: { is_active: false }, + request_options: {} + ) + .and_return(Uploadcare::Result.success(webhook_attrs.merge('is_active' => false))) + + webhook = described_class.update(id: 123, is_active: false, client: client) + expect(webhook.is_active).to be false + end + end + + describe '.delete' do + it 'deletes a webhook by target_url' do + allow(rest_webhooks).to receive(:delete) + .with(target_url: 'https://example.com/webhook', request_options: {}) + .and_return(Uploadcare::Result.success(nil)) + + expect do + described_class.delete(target_url: 'https://example.com/webhook', client: client) + end.not_to raise_error + end + + it 'is aliased as unsubscribe' do + expect(described_class).to respond_to(:unsubscribe) + end + end + + describe 'attributes' do + it 'exposes all webhook attributes' do + webhook = described_class.new(webhook_attrs, client) + expect(webhook.id).to eq(123) + expect(webhook.project).to eq(456) + expect(webhook.created).to eq('2025-01-01T00:00:00Z') + expect(webhook.updated).to eq('2025-01-01T00:00:00Z') + expect(webhook.event).to eq('file.uploaded') + expect(webhook.target_url).to eq('https://example.com/webhook') + expect(webhook.is_active).to be true + expect(webhook.signing_secret).to eq('secret123') + expect(webhook.version).to eq('0.7') + end + end +end diff --git a/spec/uploadcare/result_spec.rb b/spec/uploadcare/result_spec.rb new file mode 100644 index 00000000..82e513e5 --- /dev/null +++ b/spec/uploadcare/result_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +RSpec.describe Uploadcare::Result do + it 'returns success result' do + result = described_class.success('ok') + + expect(result.success?).to be(true) + expect(result.failure?).to be(false) + expect(result.success).to eq('ok') + expect(result.value!).to eq('ok') + end + + it 'returns failure result' do + error = StandardError.new('boom') + result = described_class.failure(error) + + expect(result.success?).to be(false) + expect(result.failure?).to be(true) + expect(result.failure).to eq(error) + expect(result.error_message).to eq('boom') + end + + it 'captures exceptions' do + result = described_class.capture { raise StandardError, 'nope' } + + expect(result.failure?).to be(true) + expect(result.error_message).to eq('nope') + end + + it 'returns nil error_message when no error' do + result = described_class.success('ok') + + expect(result.error_message).to be_nil + end + + it 'handles non-exception errors' do + result = described_class.failure('boom') + + expect(result.error_message).to eq('boom') + end + + it 'unwraps non-result values' do + expect(described_class.unwrap('raw')).to eq('raw') + end + + it 'raises when accessing value on failure' do + error = StandardError.new('nope') + result = described_class.failure(error) + + expect { result.value! }.to raise_error(StandardError, 'nope') + end + + it 'raises stringified value for non-exception errors' do + error = { error: 'boom' } + result = described_class.failure(error) + + expect { result.value! }.to raise_error(RuntimeError, error.to_s) + end +end diff --git a/spec/uploadcare/signed_url_generators/akamai_generator_spec.rb b/spec/uploadcare/signed_url_generators/akamai_generator_spec.rb index c4efde57..2a2c7f28 100644 --- a/spec/uploadcare/signed_url_generators/akamai_generator_spec.rb +++ b/spec/uploadcare/signed_url_generators/akamai_generator_spec.rb @@ -1,77 +1,35 @@ # frozen_string_literal: true -require 'spec_helper' -require 'signed_url_generators/akamai_generator' +require 'openssl' -module Uploadcare - RSpec.describe SignedUrlGenerators::AkamaiGenerator do - subject { described_class.new(cdn_host: 'example.com', secret_key: secret_key) } +RSpec.describe Uploadcare::SignedUrlGenerators::AkamaiGenerator do + it 'generates signed url' do + allow(Time).to receive(:now).and_return(Time.at(1000)) + uuid = 'e1fe0a80-0000-4000-8000-000000000000' + secret_key = '0123456789abcdef' - let(:default_ttl) { 300 } - let(:default_algorithm) { 'sha256' } - let(:uuid) { 'a7d5645e-5cd7-4046-819f-a6a2933bafe3' } - let(:unixtime) { '1649343600' } - let(:secret_key) { 'secret_key' } + generator = described_class.new(cdn_host: 'cdn.test', secret_key: secret_key, ttl: 300, algorithm: 'sha256') - describe '#generate_url' do - before do - allow(Time).to receive(:now).and_return(unixtime) - end + signature_data = "exp=1300~acl=/#{uuid}/" + secret_key_bin = Array(secret_key.delete(" \t\r\n")).pack('H*') + expected_hmac = OpenSSL::HMAC.hexdigest('sha256', secret_key_bin, signature_data) + expected = "https://cdn.test/#{uuid}/?token=exp=1300~acl=/#{uuid}/~hmac=#{expected_hmac}" - context 'when acl not present' do - it 'returns correct url' do - expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=d8b4919d595805fd8923258bb647065b7d7201dad8f475d6f5c430e3bffa8122' - expect(subject.generate_url(uuid)).to eq expected_url - end - end - - context 'when uuid with transformations' do - let(:uuid) { "#{super()}/-/resize/640x/other/transformations/" } - - it 'returns correct url' do - expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/-/resize/640x/other/transformations/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/-/resize/640x/other/transformations/~hmac=64dd1754c71bf194fcc81d49c413afeb3bbe0e6d703ed4c9b30a8a48c1782f53' - expect(subject.generate_url(uuid)).to eq expected_url - end - end - - context 'when acl present' do - it 'returns correct url' do - acl = '/*/' - expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/*/~hmac=984914950bccbfe22f542aa1891300fb2624def1208452335fc72520c934c4c3' - expect(subject.generate_url(uuid, acl)).to eq expected_url - end - end - - context 'when uuid not valid' do - it 'returns exception' do - expect { subject.generate_url(SecureRandom.hex) }.to raise_error ArgumentError - end - end - - context 'when wildcard is true' do - it 'returns correct url' do - expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/*~hmac=6f032220422cdaea5fe0b58f9dcf681269591bb5d1231aa1c4a38741d7cc2fe5' - expect(subject.generate_url(uuid, nil, wildcard: true)).to eq expected_url - end - end + expect(generator.generate_url(uuid)).to eq(expected) + end - context 'works with group' do - let(:uuid) { '83a8994a-e0b4-4091-9a10-5a847298e493~4' } + it 'generates signed url with wildcard acl' do + allow(Time).to receive(:now).and_return(Time.at(1000)) + uuid = 'e1fe0a80-0000-4000-8000-000000000000' + secret_key = '0123456789abcdef' - it 'returns correct url' do - expected_url = 'https://example.com/83a8994a-e0b4-4091-9a10-5a847298e493~4/?token=exp=1649343900~acl=/83a8994a-e0b4-4091-9a10-5a847298e493%7e4/*~hmac=f4d4c5da93324dffa2b5bb42d8a6cc693789077212cbdf599fe3220b9d37749d' - expect(subject.generate_url(uuid, nil, wildcard: true)).to eq expected_url - end - end + generator = described_class.new(cdn_host: 'cdn.test', secret_key: secret_key, ttl: 300, algorithm: 'sha256') - context 'works with nth file type notation for files within a group' do - let(:uuid) { '83a8994a-e0b4-4091-9a10-5a847298e493~4/nth/0/-/crop/250x250/1000,1000' } + signature_data = "exp=1300~acl=/#{uuid}/*" + secret_key_bin = Array(secret_key.delete(" \t\r\n")).pack('H*') + expected_hmac = OpenSSL::HMAC.hexdigest('sha256', secret_key_bin, signature_data) + expected = "https://cdn.test/#{uuid}/?token=exp=1300~acl=/#{uuid}/*~hmac=#{expected_hmac}" - it 'returns correct url' do - expected_url = 'https://example.com/83a8994a-e0b4-4091-9a10-5a847298e493~4/nth/0/-/crop/250x250/1000,1000/?token=exp=1649343900~acl=/83a8994a-e0b4-4091-9a10-5a847298e493%7e4/nth/0/-/crop/250x250/1000,1000/*~hmac=d483cfa64cffe617c1cc72d6f1d3287a74d27cb608bbf08dc07d3d61e29cd4be' - expect(subject.generate_url(uuid, nil, wildcard: true)).to eq expected_url - end - end - end + expect(generator.generate_url(uuid, 'ignored', wildcard: true)).to eq(expected) end end diff --git a/spec/uploadcare/signed_url_generators/base_generator_spec.rb b/spec/uploadcare/signed_url_generators/base_generator_spec.rb new file mode 100644 index 00000000..c80ae99f --- /dev/null +++ b/spec/uploadcare/signed_url_generators/base_generator_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.describe Uploadcare::SignedUrlGenerators::BaseGenerator do + it 'raises not implemented' do + generator = described_class.new(cdn_host: 'example.com', secret_key: 'abc') + + expect { generator.generate_url }.to raise_error(NotImplementedError) + end +end diff --git a/spec/uploadcare/version_spec.rb b/spec/uploadcare/version_spec.rb new file mode 100644 index 00000000..f0518adb --- /dev/null +++ b/spec/uploadcare/version_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe Uploadcare::VERSION do + it 'has a version number' do + expect(Uploadcare::VERSION).to eq('5.0.0') + end +end diff --git a/spec/uploadcare/param/webhook_signature_verifier_spec.rb b/spec/uploadcare/webhook_signature_verifier_spec.rb similarity index 55% rename from spec/uploadcare/param/webhook_signature_verifier_spec.rb rename to spec/uploadcare/webhook_signature_verifier_spec.rb index 2f988c2a..43ebb880 100644 --- a/spec/uploadcare/param/webhook_signature_verifier_spec.rb +++ b/spec/uploadcare/webhook_signature_verifier_spec.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'param/simple_auth_header' module Uploadcare - RSpec.describe Param::WebhookSignatureVerifier do + RSpec.describe WebhookSignatureVerifier do subject(:signature_valid?) { described_class.valid?(**params) } let(:webhook_body) do @@ -62,17 +61,55 @@ module Uploadcare } end - context 'when a signature is valid' do - it 'returns true' do - expect(signature_valid?).to be_truthy + describe '.valid?' do + context 'when a signature is valid' do + it 'returns true' do + expect(signature_valid?).to be_truthy + end + end + + context 'when a signature is invalid' do + let(:params) { super().merge(signing_secret: '12345') } + + it 'returns false' do + expect(signature_valid?).to be_falsey + end + end + + context 'when signing_secret is missing and UC_SIGNING_SECRET env var is set' do + before { ENV['UC_SIGNING_SECRET'] = '12345X' } + after { ENV.delete('UC_SIGNING_SECRET') } + + let(:params) { super().except(:signing_secret) } + + it 'uses environment variable and returns true' do + expect(signature_valid?).to be_truthy + end + end + + context 'with invalid parameters' do + let(:params) { { signing_secret: 'secret', x_uc_signature_header: 'invalid', webhook_body: 'body' } } + + it 'returns false for mismatched signature' do + expect(signature_valid?).to be_falsey + end + end + + context 'when webhook body is missing' do + let(:params) { super().merge(webhook_body: nil) } + + it 'returns false' do + expect(signature_valid?).to be_falsey + end end end - context 'when a signature is invalid' do - let(:params) { super().merge(signing_secret: '12345') } + describe '.secure_compare?' do + it 'falls back to byte comparison when OpenSSL helper is missing' do + allow(OpenSSL).to receive(:fixed_length_secure_compare).and_raise(NoMethodError) - it 'returns false' do - expect(signature_valid?).to be_falsey + expect(described_class.secure_compare?('abc', 'abc')).to be(true) + expect(described_class.secure_compare?('abc', 'abd')).to be(false) end end end diff --git a/spec/uploadcare_spec.rb b/spec/uploadcare_spec.rb new file mode 100644 index 00000000..0796823c --- /dev/null +++ b/spec/uploadcare_spec.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Uploadcare do + describe '.configure' do + it 'yields configuration block' do + expect { |b| described_class.configure(&b) }.to yield_with_args(described_class.configuration) + end + + it 'allows setting configuration values' do + described_class.configure do |config| + config.public_key = 'test_key' + config.upload_timeout = 120 + end + + expect(described_class.configuration.public_key).to eq('test_key') + expect(described_class.configuration.upload_timeout).to eq(120) + end + + it 'resets the memoized client' do + client1 = described_class.client + described_class.configure { |c| c.public_key = 'new_key' } + client2 = described_class.client + expect(client1).not_to equal(client2) + end + + it 'resets the memoized client even when the block raises' do + client1 = described_class.client + + expect do + described_class.configure do |config| + config.public_key = 'broken' + raise 'boom' + end + end.to raise_error(RuntimeError, 'boom') + + client2 = described_class.client + expect(client1).not_to equal(client2) + expect(client2.config.public_key).to eq('broken') + end + end + + describe '.configuration' do + it 'returns a Configuration instance' do + expect(described_class.configuration).to be_a(Uploadcare::Configuration) + end + + it 'memoizes the configuration' do + config1 = described_class.configuration + config2 = described_class.configuration + expect(config1).to be(config2) + end + end + + describe '.client' do + it 'returns a Client instance' do + expect(described_class.client).to be_a(Uploadcare::Client) + end + + it 'memoizes the default client' do + client1 = described_class.client + client2 = described_class.client + expect(client1).to equal(client2) + end + + it 'creates new client with custom config' do + custom = Uploadcare::Configuration.new(public_key: 'custom') + c = described_class.client(config: custom) + expect(c.config.public_key).to eq('custom') + end + + it 'creates new client with option overrides' do + c = described_class.client(public_key: 'override') + expect(c.config.public_key).to eq('override') + end + end + + describe '.files' do + it 'returns a FilesAccessor' do + expect(described_class.files).to be_a(Uploadcare::Client::FilesAccessor) + end + end + + describe '.groups' do + it 'returns a GroupsAccessor' do + expect(described_class.groups).to be_a(Uploadcare::Client::GroupsAccessor) + end + end + + describe '.uploads' do + it 'returns an UploadRouter' do + expect(described_class.uploads).to be_a(Uploadcare::Operations::UploadRouter) + end + end + + describe '.project' do + it 'returns a ProjectAccessor' do + expect(described_class.project).to be_a(Uploadcare::Client::ProjectAccessor) + end + end + + describe '.eager_load!' do + it 'does not raise errors' do + expect { Uploadcare.eager_load! }.not_to raise_error + end + end + + describe 'top-level constants' do + it 'aliases Resources::File as File' do + expect(Uploadcare::File).to eq(Uploadcare::Resources::File) + end + + it 'aliases Resources::Group as Group' do + expect(Uploadcare::Group).to eq(Uploadcare::Resources::Group) + end + + it 'aliases Resources::Project as Project' do + expect(Uploadcare::Project).to eq(Uploadcare::Resources::Project) + end + + it 'aliases Resources::Webhook as Webhook' do + expect(Uploadcare::Webhook).to eq(Uploadcare::Resources::Webhook) + end + + it 'aliases Resources::AddonExecution as AddonExecution' do + expect(Uploadcare::AddonExecution).to eq(Uploadcare::Resources::AddonExecution) + end + + it 'aliases Resources::DocumentConversion as DocumentConversion' do + expect(Uploadcare::DocumentConversion).to eq(Uploadcare::Resources::DocumentConversion) + end + + it 'aliases Resources::VideoConversion as VideoConversion' do + expect(Uploadcare::VideoConversion).to eq(Uploadcare::Resources::VideoConversion) + end + end +end diff --git a/uploadcare-ruby.gemspec b/uploadcare-ruby.gemspec index 49376008..b5daf475 100644 --- a/uploadcare-ruby.gemspec +++ b/uploadcare-ruby.gemspec @@ -2,7 +2,7 @@ lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'uploadcare/ruby/version' +require 'uploadcare/version' Gem::Specification.new do |spec| spec.name = 'uploadcare-ruby' @@ -41,12 +41,13 @@ Gem::Specification.new do |spec| end spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ['lib', 'lib/uploadcare', 'lib/uploadcare/rest'] + spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 3.0' + spec.required_ruby_version = '>= 3.3' # Compatible with Ruby 3.3+ and Ruby 4.0+ - spec.add_dependency 'mimemagic', '~> 0.4' - spec.add_dependency 'parallel', '~> 1.22' - spec.add_dependency 'retries', '~> 0.0' - spec.add_dependency 'uploadcare-api_struct', '>= 1.1', '< 2' + spec.add_dependency 'addressable', '~> 2.8' + spec.add_dependency 'faraday', '~> 2.14' + spec.add_dependency 'faraday-multipart', '~> 1.0' + spec.add_dependency 'mime-types', '~> 3.7' + spec.add_dependency 'zeitwerk', '~> 2.7' end