diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8a97884..db52955 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -45,7 +45,8 @@ "Bash(SKIP_COVERAGE_MINIMUMS=true AWS_REGION=us-east-1 bundle exec rspec:*)", "Bash(env SKIP_COVERAGE_MINIMUMS=true AWS_REGION=us-east-1 bundle exec rspec:*)", "Skill(simplecov)", - "Bash(then grep -A 5 \"covered_percent\\|app.rb\\|request_validator\\|response_builder\\|s3_url_parser\\|url_validator\\|webhook_notifier\" coverage/index.html)" + "Bash(then grep -A 5 \"covered_percent\\|app.rb\\|request_validator\\|response_builder\\|s3_url_parser\\|url_validator\\|webhook_notifier\" coverage/index.html)", + "SlashCommand(/run-prompt:*)" ], "deny": [], "ask": [] diff --git a/CLAUDE.md b/CLAUDE.md index baddb2c..58728ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -99,14 +99,14 @@ The Lambda function is configured with: ### POST /convert -Converts a PDF to images. +Converts a PDF to images and delivers them as a zip file. **Request Body:** ```json { "source": "https://s3.amazonaws.com/bucket/input.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...", - "destination": "https://s3.amazonaws.com/bucket/output/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...", + "destination": "https://s3.amazonaws.com/bucket/output.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...", "webhook": "https://example.com/webhook", "unique_id": "client-123" } @@ -119,15 +119,14 @@ Converts a PDF to images. - **Client control**: Clients generate URLs with their own AWS credentials, maintaining data sovereignty - **Audit trail**: All S3 access is logged under the client's AWS account +**Note on destination URL:** The destination URL should be a pre-signed PUT URL for a zip file (e.g., `output.zip`), not a folder path. The service will create a zip file containing all converted images. + **Response:** ```json { - "message": "PDF conversion and upload completed", - "images": [ - "https://s3.amazonaws.com/bucket/output/client-123-0.png?...", - "https://s3.amazonaws.com/bucket/output/client-123-1.png?..." - ], + "message": "PDF conversion and zip upload completed", + "images": "https://s3.amazonaws.com/bucket/output.zip", "unique_id": "client-123", "status": "completed", "pages_converted": 2, @@ -139,4 +138,6 @@ Converts a PDF to images. } ``` -**Note:** The service processes PDFs synchronously and returns the converted images in the response. If a webhook URL is provided, a notification is also sent asynchronously (fire-and-forget) upon completion. +**Zip File Contents:** The zip file contains PNG images named as `{unique_id}-0.png`, `{unique_id}-1.png`, etc., corresponding to each page of the PDF. + +**Note:** The service processes PDFs synchronously and returns the zip file URL in the response. If a webhook URL is provided, a notification is also sent asynchronously (fire-and-forget) upon completion. diff --git a/README.md b/README.md index 12296bc..c1bfaa7 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ print(f"Authorization: Bearer {token}") ### Step 6: Test Your Deployment -Create pre-signed S3 URLs for source (PDF) and destination (images), then call the API: +Create pre-signed S3 URLs for source (PDF) and destination (zip file), then call the API: ```bash # Example using curl (replace with your actual URLs and token) @@ -149,12 +149,14 @@ curl -X POST https://your-api-endpoint.amazonaws.com/Prod/convert \ -H "Content-Type: application/json" \ -d '{ "source": "https://s3.amazonaws.com/your-bucket/input.pdf?X-Amz-...", - "destination": "https://s3.amazonaws.com/your-bucket/output/?X-Amz-...", + "destination": "https://s3.amazonaws.com/your-bucket/output.zip?X-Amz-...", "webhook": "https://your-webhook-endpoint.com/notify", "unique_id": "test-123" }' ``` +**Note:** The destination URL should be a pre-signed PUT URL for a `.zip` file, not a folder. The service will upload a single zip file containing all converted PNG images. + For instructions on generating pre-signed S3 URLs, see the [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html). ### Testing Scripts @@ -241,14 +243,14 @@ sam delete --stack-name content_processing # Delete the deployed stack ### POST /convert -Converts a PDF to images. +Converts a PDF to images and delivers them as a zip file. **Request Body:** ```json { "source": "https://s3.amazonaws.com/bucket/input.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...", - "destination": "https://s3.amazonaws.com/bucket/output/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...", + "destination": "https://s3.amazonaws.com/bucket/output.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...", "webhook": "https://example.com/webhook", "unique_id": "client-123" } @@ -261,15 +263,14 @@ Converts a PDF to images. - **Client control**: Clients generate URLs with their own AWS credentials, maintaining data sovereignty - **Audit trail**: All S3 access is logged under the client's AWS account +**Note on destination URL:** The destination URL should be a pre-signed PUT URL for a zip file (e.g., `output.zip`), not a folder path. The service will create a zip file containing all converted images. + **Response:** ```json { - "message": "PDF conversion and upload completed", - "images": [ - "https://s3.amazonaws.com/bucket/output/client-123-0.png?...", - "https://s3.amazonaws.com/bucket/output/client-123-1.png?..." - ], + "message": "PDF conversion and zip upload completed", + "images": "https://s3.amazonaws.com/bucket/output.zip", "unique_id": "client-123", "status": "completed", "pages_converted": 2, @@ -281,7 +282,9 @@ Converts a PDF to images. } ``` -**Note:** The service processes PDFs synchronously and returns the converted images in the response. If a webhook URL is provided, a notification is also sent asynchronously (fire-and-forget) upon completion. +**Zip File Contents:** The zip file contains PNG images named as `{unique_id}-0.png`, `{unique_id}-1.png`, etc., corresponding to each page of the PDF. + +**Note:** The service processes PDFs synchronously and returns the zip file URL in the response. If a webhook URL is provided, a notification is also sent asynchronously (fire-and-forget) upon completion. ## Architecture @@ -311,6 +314,7 @@ The Lambda function uses these environment variables: - **aws-sdk-secretsmanager (~> 1)**: AWS SDK for secure key retrieval - **json (~> 2.9)**: JSON parsing and generation - **ruby-vips (~> 2.2)**: Ruby bindings for libvips image processing library +- **rubyzip (~> 2.3)**: Zip file creation and manipulation - **async (~> 2.6)**: Asynchronous processing for batch uploads ### Testing diff --git a/pdf_converter/Gemfile b/pdf_converter/Gemfile index ff7edec..6b1f071 100644 --- a/pdf_converter/Gemfile +++ b/pdf_converter/Gemfile @@ -24,6 +24,9 @@ gem 'aws-sdk-secretsmanager', '~> 1' # PDF to image conversion gem 'ruby-vips', '~> 2.2' +# Zip file creation +gem 'rubyzip', '~> 2.3' + # Async processing for batch uploads gem 'async', '~> 2.6' diff --git a/pdf_converter/Gemfile.lock b/pdf_converter/Gemfile.lock index 0e87beb..e101f40 100644 --- a/pdf_converter/Gemfile.lock +++ b/pdf_converter/Gemfile.lock @@ -177,6 +177,7 @@ GEM simplecov (>= 0.22.0) tty-which (~> 0.5.0) virtus (~> 2.0) + rubyzip (2.4.1) sexp_processor (4.17.4) simplecov (0.22.0) docile (~> 1.1) @@ -214,6 +215,7 @@ DEPENDENCIES rubocop (~> 1.81) ruby-vips (~> 2.2) rubycritic (~> 4.9) + rubyzip (~> 2.3) simplecov (~> 0.22) webmock (~> 3.19) diff --git a/pdf_converter/app.rb b/pdf_converter/app.rb index d12b369..857051a 100644 --- a/pdf_converter/app.rb +++ b/pdf_converter/app.rb @@ -66,21 +66,21 @@ def process_pdf_conversion(request_body, start_time, response_builder) page_count = images.size puts "PDF converted successfully: #{page_count} pages" - # Upload images - upload_result = ImageUploader.new.upload_images_from_files(request_body['destination'], images) - return handle_failure(upload_result, response_builder, 'Image upload', output_dir) unless upload_result[:success] + # Upload images as zip file + upload_result = ImageUploader.new.upload_images_from_files(request_body['destination'], images, unique_id) + return handle_failure(upload_result, response_builder, 'Zip upload', output_dir) unless upload_result[:success] - uploaded_urls = upload_result[:uploaded_urls] - puts "Images uploaded successfully: #{uploaded_urls.size} files" + zip_url = upload_result[:zip_url] + puts "Zip file uploaded successfully: #{zip_url}" # Send webhook notification - notify_webhook(request_body['webhook'], unique_id, uploaded_urls, page_count, start_time) + notify_webhook(request_body['webhook'], unique_id, zip_url, page_count, start_time) # Clean up and return success FileUtils.rm_rf(output_dir) response_builder.success_response( unique_id: unique_id, - uploaded_urls: uploaded_urls, + zip_url: zip_url, page_count: page_count, metadata: conversion_result[:metadata] ) @@ -104,23 +104,23 @@ def handle_failure(result, response_builder, operation, output_dir = nil) # # @param webhook_url [String, nil] Webhook URL # @param unique_id [String] Unique identifier -# @param uploaded_urls [Array] Uploaded image URLs +# @param zip_url [String] URL of the uploaded zip file # @param page_count [Integer] Number of pages # @param start_time [Float] Processing start time -def notify_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) +def notify_webhook(webhook_url, unique_id, zip_url, page_count, start_time) return unless webhook_url - send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) end # Sends webhook notification asynchronously (non-blocking). # # @param webhook_url [String] The URL to send the notification to # @param unique_id [String] Unique identifier for this conversion -# @param uploaded_urls [Array] Array of uploaded image URLs +# @param zip_url [String] URL of the uploaded zip file # @param page_count [Integer] Number of pages converted # @param start_time [Float] Start time of the conversion process -def send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) +def send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) notifier = WebhookNotifier.new end_time = Time.now.to_f processing_time_ms = ((end_time - start_time) * 1000).to_i @@ -129,7 +129,7 @@ def send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) webhook_url: webhook_url, unique_id: unique_id, status: 'completed', - images: uploaded_urls, + images: zip_url, page_count: page_count, processing_time_ms: processing_time_ms ) diff --git a/pdf_converter/app/image_uploader.rb b/pdf_converter/app/image_uploader.rb index 77eb72a..cbccf82 100644 --- a/pdf_converter/app/image_uploader.rb +++ b/pdf_converter/app/image_uploader.rb @@ -8,6 +8,7 @@ require 'async/semaphore' require_relative '../lib/retry_handler' require_relative '../lib/url_utils' +require_relative '../lib/zip_builder' # ImageUploader handles uploading images to S3 using pre-signed URLs # with proper error handling, retries, and concurrent upload support @@ -47,10 +48,11 @@ def upload(url, content, content_type = 'image/png') error_result('Invalid URL format') rescue StandardError => e # Provide better error message for 403 errors - if e.message.include?('403') + error_message = e.message + if error_message.include?('403') error_result('Access denied - URL may be expired or invalid') else - error_result("Upload failed: #{e.message}") + error_result("Upload failed: #{error_message}") end end @@ -82,28 +84,41 @@ def upload_batch(urls, images, content_type = 'image/png') end # Sort results by index to maintain order - results.sort_by! { |r| r[:index] } + results.sort_by! { |result| result[:index] } - successful = results.count { |r| r[:success] } + successful = results.count { |result| result[:success] } log_info("Batch upload completed: #{successful}/#{results.size} successful") results end - # Uploads image files to S3 destination using pre-signed URL - # @param destination_url [String] Pre-signed S3 destination URL + # Uploads image files to S3 destination as a zip file using pre-signed URL + # @param destination_url [String] Pre-signed S3 destination URL for the zip file # @param image_paths [Array] Array of image file paths - # @return [Hash] Result with :success, :uploaded_urls, :etags, or :error - def upload_images_from_files(destination_url, image_paths) - base_uri = parse_destination_url(destination_url) - image_urls, image_contents = prepare_images_for_upload(image_paths, base_uri) + # @param unique_id [String] Unique identifier for naming images in the zip + # @return [Hash] Result with :success, :zip_url, :etag, or :error + def upload_images_from_files(destination_url, image_paths, unique_id) + log_info("Creating zip file with #{image_paths.size} images") - upload_results = upload_batch(image_urls, image_contents, 'image/png') - process_upload_results(upload_results, image_urls) + # Create zip file in memory + zip_content = ZipBuilder.create_from_images(image_paths, unique_id) + + log_info("Zip file created, size: #{zip_content.bytesize} bytes") + + # Upload zip file to S3 + upload_result = upload(destination_url, zip_content, 'application/zip') + + return { success: false, error: upload_result[:error] } unless upload_result[:success] + + { + success: true, + zip_url: UrlUtils.strip_query_params([destination_url]).first, + etag: upload_result[:etag] + } rescue StandardError => e { success: false, - error: "Upload error: #{e.message}" + error: "Zip upload error: #{e.message}" } end @@ -165,58 +180,4 @@ def log_info(message) def log_error(message) @logger&.error(message) || puts("ERROR: #{message}") end - - # Parses the destination URL and returns a base URI with proper path. - # - # @param destination_url [String] Destination URL - # @return [URI] Base URI with normalized path - def parse_destination_url(destination_url) - uri = URI.parse(destination_url) - uri_path = uri.path - uri.path = uri_path.end_with?('/') ? uri_path : "#{uri_path}/" - uri - end - - # Prepares image URLs and contents for batch upload. - # - # @param image_paths [Array] Image file paths - # @param base_uri [URI] Base URI for uploads - # @return [Array] Two arrays: URLs and contents - def prepare_images_for_upload(image_paths, base_uri) - image_urls = [] - image_contents = [] - - image_paths.each_with_index do |image_path, index| - image_uri = base_uri.dup - image_uri.path = "#{base_uri.path}page-#{index + 1}.png" - - image_urls << image_uri.to_s - image_contents << File.read(image_path, mode: 'rb') - end - - [image_urls, image_contents] - end - - # Processes upload results and returns success or failure hash. - # - # @param upload_results [Array] Upload results - # @param image_urls [Array] Image URLs - # @return [Hash] Result with :success, :uploaded_urls, :etags, or :error - def process_upload_results(upload_results, image_urls) - failed_uploads = upload_results.reject { |result| result[:success] } - - if failed_uploads.any? - error_messages = failed_uploads.map { |result| result[:error] }.uniq.join(', ') - return { - success: false, - error: "Failed to upload #{failed_uploads.size} images: #{error_messages}" - } - end - - { - success: true, - uploaded_urls: UrlUtils.strip_query_params(image_urls), - etags: upload_results.map { |result| result[:etag] } - } - end end diff --git a/pdf_converter/app/jwt_authenticator.rb b/pdf_converter/app/jwt_authenticator.rb index 5e6236f..d3c0a29 100644 --- a/pdf_converter/app/jwt_authenticator.rb +++ b/pdf_converter/app/jwt_authenticator.rb @@ -40,8 +40,9 @@ def authenticate(headers) log_debug('Authentication successful') { authenticated: true, payload: validation_result[:payload] } else - log_error("Authentication failed: #{validation_result[:error]}") - { authenticated: false, error: validation_result[:error] } + error_message = validation_result[:error] + log_error("Authentication failed: #{error_message}") + { authenticated: false, error: error_message } end end @@ -110,11 +111,12 @@ def retrieve_secret def build_client_config config = { region: ENV['AWS_REGION'] || 'us-east-1' } - return config unless ENV['AWS_ENDPOINT_URL'] + endpoint_url = ENV['AWS_ENDPOINT_URL'] + return config unless endpoint_url # Configure for LocalStack testing environment config.merge( - endpoint: ENV['AWS_ENDPOINT_URL'], + endpoint: endpoint_url, access_key_id: ENV['AWS_ACCESS_KEY_ID'] || 'test', secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] || 'test', ssl_verify_peer: false diff --git a/pdf_converter/app/pdf_converter.rb b/pdf_converter/app/pdf_converter.rb index a06674d..b0effdb 100644 --- a/pdf_converter/app/pdf_converter.rb +++ b/pdf_converter/app/pdf_converter.rb @@ -33,14 +33,15 @@ def convert_to_images(pdf_content:, output_dir:, unique_id:, **options) temp_pdf = create_temp_pdf(pdf_content) begin - page_count = get_page_count_from_file(temp_pdf.path) + temp_pdf_path = temp_pdf.path + page_count = get_page_count_from_file(temp_pdf_path) # Validate page count validation_error = validate_page_count(page_count) return validation_error if validation_error # Convert all pages to images - images = convert_all_pages(temp_pdf.path, page_count, output_dir, unique_id, conversion_dpi) + images = convert_all_pages(temp_pdf_path, page_count, output_dir, unique_id, conversion_dpi) success_result(images, page_count, conversion_dpi) rescue StandardError => e @@ -92,10 +93,11 @@ def convert_all_pages(pdf_path, page_count, output_dir, unique_id, dpi) (0...page_count).each do |page_index| image_path = convert_page(pdf_path, page_index, output_dir, unique_id, dpi) images << image_path - log_info("Converted page #{page_index + 1}/#{page_count}") + page_number = page_index + 1 + log_info("Converted page #{page_number}/#{page_count}") # Force garbage collection every 10 pages for memory management - GC.start if ((page_index + 1) % 10).zero? + GC.start if (page_number % 10).zero? end images @@ -166,7 +168,7 @@ def convert_page(pdf_path, page_index, output_dir, unique_id, dpi) output_path rescue StandardError => e - log_error("Failed to convert page #{page_index + 1}: #{e.message}") + log_error("Failed to convert page #{page_number}: #{e.message}") raise end diff --git a/pdf_converter/app/request_validator.rb b/pdf_converter/app/request_validator.rb index 947a579..db96648 100644 --- a/pdf_converter/app/request_validator.rb +++ b/pdf_converter/app/request_validator.rb @@ -25,10 +25,11 @@ def initialize # @return [Hash] The parsed request body # @raise [JSON::ParserError] If the body is not valid JSON def parse_request(event) - if event['body'].is_a?(String) - JSON.parse(event['body']) - elsif event['body'].is_a?(Hash) - event['body'] + body = event['body'] + if body.is_a?(String) + JSON.parse(body) + elsif body.is_a?(Hash) + body else event end @@ -78,7 +79,8 @@ def validate(body, response_builder) end # Validate webhook URL if provided - if body['webhook'] && !@url_validator.valid_url?(body['webhook']) + webhook_url = body['webhook'] + if webhook_url && !@url_validator.valid_url?(webhook_url) return response_builder.error_response(400, 'Invalid webhook URL format') end diff --git a/pdf_converter/app/response_builder.rb b/pdf_converter/app/response_builder.rb index 4a5059a..c64875b 100644 --- a/pdf_converter/app/response_builder.rb +++ b/pdf_converter/app/response_builder.rb @@ -47,17 +47,17 @@ def authentication_error_response(error_message) # Builds a success response for completed PDF conversion. # # @param unique_id [String] The unique identifier for this conversion request - # @param uploaded_urls [Array] Array of uploaded image URLs + # @param zip_url [String] URL of the uploaded zip file containing all converted images # @param page_count [Integer] Number of pages converted # @param metadata [Hash] Additional metadata from the conversion process # @return [Hash] Lambda response hash with statusCode, headers, and body - def success_response(unique_id:, uploaded_urls:, page_count:, metadata:) + def success_response(unique_id:, zip_url:, page_count:, metadata:) { statusCode: 200, headers: CORS_HEADERS, body: { - message: 'PDF conversion and upload completed', - images: uploaded_urls, + message: 'PDF conversion and zip upload completed', + images: zip_url, unique_id: unique_id, status: 'completed', pages_converted: page_count, diff --git a/pdf_converter/lib/aws_config.rb b/pdf_converter/lib/aws_config.rb index b9d6832..79dfcdc 100644 --- a/pdf_converter/lib/aws_config.rb +++ b/pdf_converter/lib/aws_config.rb @@ -11,9 +11,10 @@ def self.secrets_manager_client } # Support LocalStack and custom endpoints - if ENV['AWS_ENDPOINT_URL'] - puts "DEBUG: Using custom endpoint: #{ENV['AWS_ENDPOINT_URL']}" - config[:endpoint] = ENV['AWS_ENDPOINT_URL'] + endpoint_url = ENV['AWS_ENDPOINT_URL'] + if endpoint_url + puts "DEBUG: Using custom endpoint: #{endpoint_url}" + config[:endpoint] = endpoint_url config[:credentials] = Aws::Credentials.new('test', 'test') config[:force_path_style] = true # Required for LocalStack S3 end diff --git a/pdf_converter/lib/retry_handler.rb b/pdf_converter/lib/retry_handler.rb index 2467202..398254a 100644 --- a/pdf_converter/lib/retry_handler.rb +++ b/pdf_converter/lib/retry_handler.rb @@ -25,6 +25,9 @@ module RetryHandler NoMemoryError ].freeze + # Custom error raised when all retry attempts have been exhausted. + # This error wraps the original exception and provides context about the retry failure. + # Typically raised after max_attempts unsuccessful retries. class RetryError < StandardError; end # Executes a block with retry logic diff --git a/pdf_converter/lib/zip_builder.rb b/pdf_converter/lib/zip_builder.rb new file mode 100644 index 0000000..ada8832 --- /dev/null +++ b/pdf_converter/lib/zip_builder.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'zip' + +# ZipBuilder creates zip files in memory from image files +# Extracted to reduce complexity and improve separation of concerns +class ZipBuilder + # Creates a zip file in memory from image files + # @param image_paths [Array] Array of image file paths + # @param unique_id [String] Unique identifier for naming images + # @return [String] Binary content of the zip file + def self.create_from_images(image_paths, unique_id) + zip_stream = Zip::OutputStream.write_buffer do |zip| + add_images_to_zip(zip, image_paths, unique_id) + end + + zip_stream.rewind + zip_stream.read + end + + # Adds images to a zip stream + # @param zip [Zip::OutputStream] The zip stream to write to + # @param image_paths [Array] Array of image file paths + # @param unique_id [String] Unique identifier for naming images + def self.add_images_to_zip(zip, image_paths, unique_id) + image_paths.each_with_index do |image_path, index| + entry_name = build_entry_name(unique_id, index) + zip.put_next_entry(entry_name) + zip.write(File.read(image_path, mode: 'rb')) + end + end + + # Builds the entry name for an image in the zip + # @param unique_id [String] Unique identifier + # @param index [Integer] Image index + # @return [String] Entry name (e.g., "unique_id-0.png") + def self.build_entry_name(unique_id, index) + "#{unique_id}-#{index}.png" + end + + private_class_method :add_images_to_zip, :build_entry_name +end diff --git a/pdf_converter/spec/.rspec_status b/pdf_converter/spec/.rspec_status index 98befce..6ebaf2a 100644 --- a/pdf_converter/spec/.rspec_status +++ b/pdf_converter/spec/.rspec_status @@ -1,622 +1,613 @@ example_id | status | run_time | -------------------------------------------------------- | ------ | --------------- | -./spec/app/image_uploader_spec.rb[1:1:1] | passed | 0.00005 seconds | -./spec/app/image_uploader_spec.rb[1:2:1] | passed | 0.00005 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:1] | passed | 0.00041 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:2] | passed | 0.00042 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:3] | passed | 0.00025 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:4] | passed | 0.00026 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:5] | passed | 0.00031 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:6] | passed | 0.00034 seconds | -./spec/app/image_uploader_spec.rb[1:3:1:7] | passed | 0.00033 seconds | -./spec/app/image_uploader_spec.rb[1:3:2:1] | passed | 0.00026 seconds | -./spec/app/image_uploader_spec.rb[1:3:3:1] | passed | 0.00028 seconds | -./spec/app/image_uploader_spec.rb[1:3:4:1] | passed | 0.00034 seconds | -./spec/app/image_uploader_spec.rb[1:3:5:1] | passed | 0.00005 seconds | +./spec/app/image_uploader_spec.rb[1:1:1] | passed | 0.00003 seconds | +./spec/app/image_uploader_spec.rb[1:2:1] | passed | 0.00003 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:1] | passed | 0.00091 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:2] | passed | 0.0006 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:3] | passed | 0.00069 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:4] | passed | 0.00209 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:5] | passed | 0.00239 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:6] | passed | 0.00089 seconds | +./spec/app/image_uploader_spec.rb[1:3:1:7] | passed | 0.00161 seconds | +./spec/app/image_uploader_spec.rb[1:3:2:1] | passed | 0.00545 seconds | +./spec/app/image_uploader_spec.rb[1:3:3:1] | passed | 0.00064 seconds | +./spec/app/image_uploader_spec.rb[1:3:4:1] | passed | 0.00036 seconds | +./spec/app/image_uploader_spec.rb[1:3:5:1] | passed | 0.00012 seconds | ./spec/app/image_uploader_spec.rb[1:3:6:1] | passed | 0.00005 seconds | -./spec/app/image_uploader_spec.rb[1:3:7:1] | passed | 0.00006 seconds | -./spec/app/image_uploader_spec.rb[1:3:8:1] | passed | 0.00037 seconds | -./spec/app/image_uploader_spec.rb[1:3:9:1] | passed | 0.00012 seconds | -./spec/app/image_uploader_spec.rb[1:3:10:1] | passed | 0.0003 seconds | -./spec/app/image_uploader_spec.rb[1:3:10:2] | passed | 0.00029 seconds | -./spec/app/image_uploader_spec.rb[1:3:11:1] | passed | 0.00039 seconds | +./spec/app/image_uploader_spec.rb[1:3:7:1] | passed | 0.00013 seconds | +./spec/app/image_uploader_spec.rb[1:3:8:1] | passed | 0.00011 seconds | +./spec/app/image_uploader_spec.rb[1:3:9:1] | passed | 0.00006 seconds | +./spec/app/image_uploader_spec.rb[1:3:10:1] | passed | 0.0008 seconds | +./spec/app/image_uploader_spec.rb[1:3:10:2] | passed | 0.00145 seconds | +./spec/app/image_uploader_spec.rb[1:3:11:1] | passed | 0.00085 seconds | ./spec/app/image_uploader_spec.rb[1:3:12:1] | passed | 3.01 seconds | -./spec/app/image_uploader_spec.rb[1:3:13:1] | passed | 3.01 seconds | -./spec/app/image_uploader_spec.rb[1:3:14:1] | passed | 0.00071 seconds | -./spec/app/image_uploader_spec.rb[1:3:15:1] | passed | 0.00058 seconds | -./spec/app/image_uploader_spec.rb[1:3:16:1] | passed | 0.00057 seconds | -./spec/app/image_uploader_spec.rb[1:4:1:1] | passed | 0.00093 seconds | -./spec/app/image_uploader_spec.rb[1:4:1:2] | passed | 0.00155 seconds | -./spec/app/image_uploader_spec.rb[1:4:1:3] | passed | 0.0009 seconds | -./spec/app/image_uploader_spec.rb[1:4:1:4] | passed | 0.00265 seconds | -./spec/app/image_uploader_spec.rb[1:4:2:1] | passed | 0.00016 seconds | -./spec/app/image_uploader_spec.rb[1:4:3:1] | passed | 0.00092 seconds | -./spec/app/image_uploader_spec.rb[1:4:3:2] | passed | 0.00103 seconds | +./spec/app/image_uploader_spec.rb[1:3:13:1] | passed | 3.02 seconds | +./spec/app/image_uploader_spec.rb[1:3:14:1] | passed | 0.00033 seconds | +./spec/app/image_uploader_spec.rb[1:3:15:1] | passed | 0.00033 seconds | +./spec/app/image_uploader_spec.rb[1:3:16:1] | passed | 0.0013 seconds | +./spec/app/image_uploader_spec.rb[1:4:1:1] | passed | 0.00176 seconds | +./spec/app/image_uploader_spec.rb[1:4:1:2] | passed | 0.00199 seconds | +./spec/app/image_uploader_spec.rb[1:4:1:3] | passed | 0.0016 seconds | +./spec/app/image_uploader_spec.rb[1:4:1:4] | passed | 0.0016 seconds | +./spec/app/image_uploader_spec.rb[1:4:2:1] | passed | 0.00032 seconds | +./spec/app/image_uploader_spec.rb[1:4:3:1] | passed | 0.00213 seconds | +./spec/app/image_uploader_spec.rb[1:4:3:2] | passed | 0.00278 seconds | ./spec/app/image_uploader_spec.rb[1:4:4:1] | passed | 3.01 seconds | ./spec/app/image_uploader_spec.rb[1:4:4:2] | passed | 3.01 seconds | -./spec/app/image_uploader_spec.rb[1:5:1:1] | passed | 0.00147 seconds | -./spec/app/image_uploader_spec.rb[1:5:1:2] | passed | 0.00142 seconds | -./spec/app/image_uploader_spec.rb[1:5:1:3] | passed | 0.00797 seconds | -./spec/app/image_uploader_spec.rb[1:5:1:4] | passed | 0.00102 seconds | -./spec/app/image_uploader_spec.rb[1:5:2:1] | passed | 0.00085 seconds | -./spec/app/image_uploader_spec.rb[1:5:3:1] | passed | 0.00134 seconds | -./spec/app/image_uploader_spec.rb[1:5:4:1] | passed | 0.00093 seconds | -./spec/app/image_uploader_spec.rb[1:5:4:2] | passed | 0.00087 seconds | -./spec/app/image_uploader_spec.rb[1:5:5:1] | passed | 0.00049 seconds | -./spec/app/image_uploader_spec.rb[1:5:6:1] | passed | 0.00029 seconds | -./spec/app/image_uploader_spec.rb[1:6:1:1] | passed | 0.00007 seconds | -./spec/app/image_uploader_spec.rb[1:6:2:1] | passed | 0.00006 seconds | -./spec/app/image_uploader_spec.rb[1:6:3:1] | passed | 0.00005 seconds | -./spec/app/image_uploader_spec.rb[1:6:4:1] | passed | 0.00015 seconds | -./spec/app/image_uploader_spec.rb[1:6:5:1] | passed | 0.00009 seconds | -./spec/app/image_uploader_spec.rb[1:7:1:1] | passed | 0.00132 seconds | +./spec/app/image_uploader_spec.rb[1:5:1:1] | passed | 0.00855 seconds | +./spec/app/image_uploader_spec.rb[1:5:1:2] | passed | 0.00106 seconds | +./spec/app/image_uploader_spec.rb[1:5:1:3] | passed | 0.00148 seconds | +./spec/app/image_uploader_spec.rb[1:5:1:4] | passed | 0.00077 seconds | +./spec/app/image_uploader_spec.rb[1:5:1:5] | passed | 0.00175 seconds | +./spec/app/image_uploader_spec.rb[1:5:2:1] | passed | 0.00331 seconds | +./spec/app/image_uploader_spec.rb[1:5:3:1] | passed | 0.00036 seconds | +./spec/app/image_uploader_spec.rb[1:5:4:1] | passed | 0.00237 seconds | +./spec/app/image_uploader_spec.rb[1:6:1:1] | passed | 0.00017 seconds | +./spec/app/image_uploader_spec.rb[1:6:2:1] | passed | 0.0001 seconds | +./spec/app/image_uploader_spec.rb[1:6:3:1] | passed | 0.00011 seconds | +./spec/app/image_uploader_spec.rb[1:6:4:1] | passed | 0.0001 seconds | +./spec/app/image_uploader_spec.rb[1:6:5:1] | passed | 0.00015 seconds | +./spec/app/image_uploader_spec.rb[1:7:1:1] | passed | 0.00024 seconds | ./spec/app/image_uploader_spec.rb[1:7:2:1] | passed | 3.01 seconds | -./spec/app/image_uploader_spec.rb[1:8:1:1] | passed | 0.00084 seconds | -./spec/app/image_uploader_spec.rb[1:8:2:1] | passed | 0.00024 seconds | -./spec/app/image_uploader_spec.rb[1:8:3:1] | passed | 0.0011 seconds | -./spec/app/image_uploader_spec.rb[1:8:4:1] | passed | 0.00042 seconds | -./spec/app/image_uploader_spec.rb[1:8:5:1] | passed | 0.00053 seconds | -./spec/app/image_uploader_spec.rb[1:8:6:1] | passed | 0.00035 seconds | -./spec/app/image_uploader_spec.rb[1:8:7:1] | passed | 0.00054 seconds | -./spec/app/image_uploader_spec.rb[1:9:1:1] | passed | 0.0001 seconds | -./spec/app/image_uploader_spec.rb[1:9:2:1] | passed | 0.00147 seconds | -./spec/app/image_uploader_spec.rb[1:10:1] | passed | 0.00072 seconds | -./spec/app/image_uploader_spec.rb[1:10:2] | passed | 0.00046 seconds | -./spec/app/image_uploader_spec.rb[1:10:3] | passed | 0.00036 seconds | -./spec/app/image_uploader_spec.rb[1:11:1:1] | passed | 0.00005 seconds | -./spec/app/image_uploader_spec.rb[1:11:1:2] | passed | 0.00006 seconds | -./spec/app/image_uploader_spec.rb[1:11:1:3] | passed | 0.00014 seconds | -./spec/app/image_uploader_spec.rb[1:11:2:1] | passed | 0.00025 seconds | -./spec/app/image_uploader_spec.rb[1:11:2:2] | passed | 0.00011 seconds | -./spec/app/image_uploader_spec.rb[1:11:2:3] | passed | 0.00014 seconds | -./spec/app/image_uploader_spec.rb[1:11:3:1] | passed | 0.00004 seconds | -./spec/app/image_uploader_spec.rb[1:12:1] | passed | 0.00008 seconds | -./spec/app/image_uploader_spec.rb[1:12:2] | passed | 0.00008 seconds | -./spec/app/image_uploader_spec.rb[1:13:1] | passed | 0.00009 seconds | -./spec/app/image_uploader_spec.rb[1:14:1] | passed | 0.00009 seconds | -./spec/app/jwt_authenticator_spec.rb[1:1:1] | passed | 0.00018 seconds | -./spec/app/jwt_authenticator_spec.rb[1:1:2] | passed | 0.00016 seconds | -./spec/app/jwt_authenticator_spec.rb[1:1:3:1] | passed | 0.00022 seconds | -./spec/app/jwt_authenticator_spec.rb[1:1:4:1] | passed | 0.00145 seconds | -./spec/app/jwt_authenticator_spec.rb[1:1:5:1] | passed | 0.00021 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:1:1] | passed | 0.0002 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:1:2] | passed | 0.0002 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:2:1] | passed | 0.00075 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:3:1] | passed | 0.00017 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:3:2] | passed | 0.00023 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:4:1] | passed | 0.00016 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:4:2] | passed | 0.00016 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:5:1] | passed | 0.0002 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:5:2] | passed | 0.0002 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:6:1] | passed | 0.00022 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:6:2] | passed | 0.00034 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:7:1] | passed | 0.00031 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:7:2] | passed | 0.00018 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:8:1] | passed | 0.0003 seconds | -./spec/app/jwt_authenticator_spec.rb[1:2:8:2] | passed | 0.00022 seconds | -./spec/app/jwt_authenticator_spec.rb[1:3:1:1] | passed | 0.00017 seconds | -./spec/app/jwt_authenticator_spec.rb[1:3:2:1] | passed | 0.00026 seconds | -./spec/app/jwt_authenticator_spec.rb[1:3:3:1] | passed | 0.00016 seconds | +./spec/app/image_uploader_spec.rb[1:8:1:1] | passed | 0.0008 seconds | +./spec/app/image_uploader_spec.rb[1:8:2:1] | passed | 0.00061 seconds | +./spec/app/image_uploader_spec.rb[1:8:3:1] | passed | 0.00058 seconds | +./spec/app/image_uploader_spec.rb[1:8:4:1] | passed | 0.00048 seconds | +./spec/app/image_uploader_spec.rb[1:8:5:1] | passed | 0.00066 seconds | +./spec/app/image_uploader_spec.rb[1:8:6:1] | passed | 0.00029 seconds | +./spec/app/image_uploader_spec.rb[1:8:7:1] | passed | 0.00034 seconds | +./spec/app/image_uploader_spec.rb[1:9:1] | passed | 0.00006 seconds | +./spec/app/image_uploader_spec.rb[1:9:2] | passed | 0.00006 seconds | +./spec/app/image_uploader_spec.rb[1:10:1] | passed | 0.00008 seconds | +./spec/app/image_uploader_spec.rb[1:11:1] | passed | 0.0001 seconds | +./spec/app/jwt_authenticator_spec.rb[1:1:1] | passed | 0.00022 seconds | +./spec/app/jwt_authenticator_spec.rb[1:1:2] | passed | 0.00019 seconds | +./spec/app/jwt_authenticator_spec.rb[1:1:3:1] | passed | 0.00194 seconds | +./spec/app/jwt_authenticator_spec.rb[1:1:4:1] | passed | 0.00022 seconds | +./spec/app/jwt_authenticator_spec.rb[1:1:5:1] | passed | 0.0002 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:1:1] | passed | 0.00022 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:1:2] | passed | 0.00023 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:2:1] | passed | 0.00023 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:3:1] | passed | 0.00021 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:3:2] | passed | 0.00021 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:4:1] | passed | 0.0002 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:4:2] | passed | 0.00018 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:5:1] | passed | 0.00022 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:5:2] | passed | 0.00021 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:6:1] | passed | 0.00044 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:6:2] | passed | 0.00021 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:7:1] | passed | 0.00019 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:7:2] | passed | 0.0002 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:8:1] | passed | 0.00026 seconds | +./spec/app/jwt_authenticator_spec.rb[1:2:8:2] | passed | 0.00023 seconds | +./spec/app/jwt_authenticator_spec.rb[1:3:1:1] | passed | 0.00016 seconds | +./spec/app/jwt_authenticator_spec.rb[1:3:2:1] | passed | 0.00017 seconds | +./spec/app/jwt_authenticator_spec.rb[1:3:3:1] | passed | 0.00017 seconds | ./spec/app/jwt_authenticator_spec.rb[1:3:4:1] | passed | 0.00016 seconds | -./spec/app/jwt_authenticator_spec.rb[1:3:5:1] | passed | 0.00018 seconds | -./spec/app/jwt_authenticator_spec.rb[1:3:6:1] | passed | 0.00016 seconds | +./spec/app/jwt_authenticator_spec.rb[1:3:5:1] | passed | 0.00016 seconds | +./spec/app/jwt_authenticator_spec.rb[1:3:6:1] | passed | 0.00017 seconds | ./spec/app/jwt_authenticator_spec.rb[1:3:7:1] | passed | 0.00017 seconds | -./spec/app/jwt_authenticator_spec.rb[1:3:8:1] | passed | 0.00016 seconds | -./spec/app/jwt_authenticator_spec.rb[1:4:1:1] | passed | 0.00024 seconds | -./spec/app/jwt_authenticator_spec.rb[1:4:2:1] | passed | 0.00021 seconds | +./spec/app/jwt_authenticator_spec.rb[1:3:8:1] | passed | 0.00017 seconds | +./spec/app/jwt_authenticator_spec.rb[1:4:1:1] | passed | 0.0002 seconds | +./spec/app/jwt_authenticator_spec.rb[1:4:2:1] | passed | 0.00017 seconds | ./spec/app/jwt_authenticator_spec.rb[1:4:3:1] | passed | 0.00017 seconds | -./spec/app/jwt_authenticator_spec.rb[1:4:4:1] | passed | 0.0003 seconds | -./spec/app/jwt_authenticator_spec.rb[1:4:5:1] | passed | 0.00023 seconds | -./spec/app/jwt_authenticator_spec.rb[1:4:6:1] | passed | 0.00018 seconds | -./spec/app/jwt_authenticator_spec.rb[1:4:7:1] | passed | 0.00024 seconds | +./spec/app/jwt_authenticator_spec.rb[1:4:4:1] | passed | 0.00022 seconds | +./spec/app/jwt_authenticator_spec.rb[1:4:5:1] | passed | 0.0002 seconds | +./spec/app/jwt_authenticator_spec.rb[1:4:6:1] | passed | 0.00019 seconds | +./spec/app/jwt_authenticator_spec.rb[1:4:7:1] | passed | 0.00022 seconds | ./spec/app/jwt_authenticator_spec.rb[1:5:1:1] | passed | 0.0002 seconds | -./spec/app/jwt_authenticator_spec.rb[1:5:2:1] | passed | 0.0002 seconds | -./spec/app/jwt_authenticator_spec.rb[1:5:3:1] | passed | 0.00019 seconds | -./spec/app/jwt_authenticator_spec.rb[1:5:4:1] | passed | 0.00022 seconds | -./spec/app/jwt_authenticator_spec.rb[1:6:1] | passed | 0.00019 seconds | -./spec/app/jwt_authenticator_spec.rb[1:7:1] | passed | 0.00017 seconds | -./spec/app/jwt_authenticator_spec.rb[1:7:2] | passed | 0.00021 seconds | -./spec/app/jwt_authenticator_spec.rb[1:8:1] | passed | 0.00014 seconds | -./spec/app/pdf_converter_spec.rb[1:1:1] | passed | 0.00114 seconds | -./spec/app/pdf_converter_spec.rb[1:1:2] | passed | 0.00021 seconds | -./spec/app/pdf_converter_spec.rb[1:1:3] | passed | 0.0002 seconds | -./spec/app/pdf_converter_spec.rb[1:2:1:1] | passed | 0.00018 seconds | -./spec/app/pdf_converter_spec.rb[1:2:1:2] | passed | 0.00018 seconds | +./spec/app/jwt_authenticator_spec.rb[1:5:2:1] | passed | 0.00022 seconds | +./spec/app/jwt_authenticator_spec.rb[1:5:3:1] | passed | 0.02079 seconds | +./spec/app/jwt_authenticator_spec.rb[1:5:4:1] | passed | 0.00021 seconds | +./spec/app/jwt_authenticator_spec.rb[1:6:1] | passed | 0.00033 seconds | +./spec/app/jwt_authenticator_spec.rb[1:7:1] | passed | 0.00019 seconds | +./spec/app/jwt_authenticator_spec.rb[1:7:2] | passed | 0.00023 seconds | +./spec/app/jwt_authenticator_spec.rb[1:8:1] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:1:1] | passed | 0.00069 seconds | +./spec/app/pdf_converter_spec.rb[1:1:2] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:1:3] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:2:1:1] | passed | 0.00019 seconds | +./spec/app/pdf_converter_spec.rb[1:2:1:2] | passed | 0.00016 seconds | ./spec/app/pdf_converter_spec.rb[1:2:1:3] | passed | 0.00017 seconds | -./spec/app/pdf_converter_spec.rb[1:2:2:1] | passed | 0.00019 seconds | -./spec/app/pdf_converter_spec.rb[1:2:2:2] | passed | 0.00019 seconds | -./spec/app/pdf_converter_spec.rb[1:2:2:3] | passed | 0.00032 seconds | -./spec/app/pdf_converter_spec.rb[1:2:3:1] | passed | 0.00021 seconds | -./spec/app/pdf_converter_spec.rb[1:2:4:1] | passed | 0.00021 seconds | -./spec/app/pdf_converter_spec.rb[1:3:1:1] | passed | 0.00065 seconds | -./spec/app/pdf_converter_spec.rb[1:3:1:2] | passed | 0.00048 seconds | -./spec/app/pdf_converter_spec.rb[1:3:1:3] | passed | 0.0006 seconds | -./spec/app/pdf_converter_spec.rb[1:3:1:4] | passed | 0.00057 seconds | -./spec/app/pdf_converter_spec.rb[1:3:1:5] | passed | 0.00096 seconds | -./spec/app/pdf_converter_spec.rb[1:3:2:1] | passed | 0.00054 seconds | -./spec/app/pdf_converter_spec.rb[1:3:2:2] | passed | 0.00074 seconds | -./spec/app/pdf_converter_spec.rb[1:3:3:1] | passed | 0.00076 seconds | -./spec/app/pdf_converter_spec.rb[1:3:3:2] | passed | 0.00066 seconds | -./spec/app/pdf_converter_spec.rb[1:3:4:1] | passed | 0.00051 seconds | -./spec/app/pdf_converter_spec.rb[1:3:5:1] | passed | 0.0006 seconds | -./spec/app/pdf_converter_spec.rb[1:3:6:1] | passed | 0.00034 seconds | -./spec/app/pdf_converter_spec.rb[1:3:7:1] | passed | 0.00045 seconds | -./spec/app/pdf_converter_spec.rb[1:3:8:1] | passed | 0.00046 seconds | -./spec/app/pdf_converter_spec.rb[1:3:9:1] | passed | 0.00062 seconds | -./spec/app/pdf_converter_spec.rb[1:3:9:2] | passed | 0.00069 seconds | -./spec/app/pdf_converter_spec.rb[1:4:1:1] | passed | 0.00052 seconds | -./spec/app/pdf_converter_spec.rb[1:4:1:2] | passed | 0.00051 seconds | -./spec/app/pdf_converter_spec.rb[1:4:2:1] | passed | 0.00037 seconds | -./spec/app/pdf_converter_spec.rb[1:4:3:1] | passed | 0.0005 seconds | -./spec/app/pdf_converter_spec.rb[1:4:3:2] | passed | 0.00047 seconds | -./spec/app/pdf_converter_spec.rb[1:4:4:1] | passed | 0.00055 seconds | -./spec/app/pdf_converter_spec.rb[1:4:4:2] | passed | 0.00053 seconds | -./spec/app/pdf_converter_spec.rb[1:5:1:1] | passed | 0.00017 seconds | -./spec/app/pdf_converter_spec.rb[1:5:2:1] | passed | 0.0002 seconds | -./spec/app/pdf_converter_spec.rb[1:5:2:2] | passed | 0.0002 seconds | -./spec/app/pdf_converter_spec.rb[1:5:3:1] | passed | 0.00028 seconds | -./spec/app/pdf_converter_spec.rb[1:5:3:2] | passed | 0.00019 seconds | -./spec/app/pdf_converter_spec.rb[1:5:3:3] | passed | 0.00016 seconds | -./spec/app/pdf_converter_spec.rb[1:6:1:1] | passed | 0.00046 seconds | -./spec/app/pdf_converter_spec.rb[1:6:1:2] | passed | 0.00053 seconds | -./spec/app/pdf_converter_spec.rb[1:6:2:1] | passed | 0.00451 seconds | -./spec/app/pdf_converter_spec.rb[1:6:3:1] | passed | 0.00116 seconds | -./spec/app/pdf_converter_spec.rb[1:7:1:1] | passed | 0.00019 seconds | -./spec/app/pdf_converter_spec.rb[1:7:1:2] | passed | 0.00028 seconds | -./spec/app/pdf_converter_spec.rb[1:7:2:1] | passed | 0.00018 seconds | -./spec/app/pdf_converter_spec.rb[1:7:2:2] | passed | 0.00016 seconds | -./spec/app/pdf_converter_spec.rb[1:7:2:3] | passed | 0.00016 seconds | -./spec/app/pdf_converter_spec.rb[1:8:1] | passed | 0.00037 seconds | -./spec/app/pdf_converter_spec.rb[1:8:2] | passed | 0.00062 seconds | -./spec/app/pdf_converter_spec.rb[1:8:3] | passed | 0.00036 seconds | -./spec/app/pdf_converter_spec.rb[1:9:1:1] | passed | 0.00051 seconds | -./spec/app/pdf_converter_spec.rb[1:9:2:1] | passed | 0.00056 seconds | -./spec/app/pdf_converter_spec.rb[1:10:1:1] | passed | 0.00043 seconds | -./spec/app/pdf_converter_spec.rb[1:10:1:2] | passed | 0.00045 seconds | -./spec/app/pdf_converter_spec.rb[1:10:1:3] | passed | 0.0006 seconds | -./spec/app/pdf_converter_spec.rb[1:10:1:4] | passed | 0.00046 seconds | -./spec/app/pdf_converter_spec.rb[1:10:2:1] | passed | 0.00053 seconds | -./spec/app/pdf_converter_spec.rb[1:10:2:2] | passed | 0.00044 seconds | -./spec/app/pdf_converter_spec.rb[1:11:1:1] | passed | 0.00118 seconds | -./spec/app/pdf_converter_spec.rb[1:11:1:2] | passed | 0.00033 seconds | -./spec/app/pdf_converter_spec.rb[1:11:2:1] | passed | 0.00018 seconds | -./spec/app/pdf_converter_spec.rb[1:12:1] | passed | 0.00018 seconds | -./spec/app/pdf_converter_spec.rb[1:12:2] | passed | 0.0002 seconds | -./spec/app/pdf_converter_spec.rb[1:12:3] | passed | 0.00018 seconds | -./spec/app/pdf_converter_spec.rb[1:12:4] | passed | 0.00015 seconds | -./spec/app/pdf_converter_spec.rb[1:12:5] | passed | 0.00018 seconds | -./spec/app/pdf_converter_spec.rb[1:13:1] | passed | 0.00019 seconds | -./spec/app/pdf_downloader_spec.rb[1:1:1] | passed | 0.00003 seconds | -./spec/app/pdf_downloader_spec.rb[1:2:1] | passed | 0.00002 seconds | -./spec/app/pdf_downloader_spec.rb[1:3:1] | passed | 0.00006 seconds | -./spec/app/pdf_downloader_spec.rb[1:3:2] | passed | 0.00007 seconds | -./spec/app/pdf_downloader_spec.rb[1:3:3] | passed | 0.00008 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:1:1] | passed | 0.00094 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:1:2] | passed | 0.00051 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:1:3] | passed | 0.00089 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:1:4] | passed | 0.00026 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:2:1] | passed | 0.00075 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:3:1] | passed | 0.00125 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:4:1] | passed | 0.00021 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:5:1] | passed | 0.00005 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:6:1] | passed | 0.00018 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:7:1] | passed | 0.00007 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:8:1] | passed | 0.00026 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:8:2] | passed | 0.00052 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:9:1] | passed | 0.00033 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:10:1] | passed | 0.0006 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:10:2] | passed | 0.0006 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:11:1] | passed | 0.00304 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:12:1] | passed | 0.00033 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:13:1] | passed | 0.00043 seconds | +./spec/app/pdf_converter_spec.rb[1:2:2:1] | passed | 0.00017 seconds | +./spec/app/pdf_converter_spec.rb[1:2:2:2] | passed | 0.0002 seconds | +./spec/app/pdf_converter_spec.rb[1:2:2:3] | passed | 0.00015 seconds | +./spec/app/pdf_converter_spec.rb[1:2:3:1] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:2:4:1] | passed | 0.00018 seconds | +./spec/app/pdf_converter_spec.rb[1:3:1:1] | passed | 0.00053 seconds | +./spec/app/pdf_converter_spec.rb[1:3:1:2] | passed | 0.00058 seconds | +./spec/app/pdf_converter_spec.rb[1:3:1:3] | passed | 0.00043 seconds | +./spec/app/pdf_converter_spec.rb[1:3:1:4] | passed | 0.00046 seconds | +./spec/app/pdf_converter_spec.rb[1:3:1:5] | passed | 0.00061 seconds | +./spec/app/pdf_converter_spec.rb[1:3:2:1] | passed | 0.00044 seconds | +./spec/app/pdf_converter_spec.rb[1:3:2:2] | passed | 0.00052 seconds | +./spec/app/pdf_converter_spec.rb[1:3:3:1] | passed | 0.00062 seconds | +./spec/app/pdf_converter_spec.rb[1:3:3:2] | passed | 0.00061 seconds | +./spec/app/pdf_converter_spec.rb[1:3:4:1] | passed | 0.00044 seconds | +./spec/app/pdf_converter_spec.rb[1:3:5:1] | passed | 0.00046 seconds | +./spec/app/pdf_converter_spec.rb[1:3:6:1] | passed | 0.00032 seconds | +./spec/app/pdf_converter_spec.rb[1:3:7:1] | passed | 0.0003 seconds | +./spec/app/pdf_converter_spec.rb[1:3:8:1] | passed | 0.00031 seconds | +./spec/app/pdf_converter_spec.rb[1:3:9:1] | passed | 0.00085 seconds | +./spec/app/pdf_converter_spec.rb[1:3:9:2] | passed | 0.00328 seconds | +./spec/app/pdf_converter_spec.rb[1:4:1:1] | passed | 0.0004 seconds | +./spec/app/pdf_converter_spec.rb[1:4:1:2] | passed | 0.00052 seconds | +./spec/app/pdf_converter_spec.rb[1:4:2:1] | passed | 0.00027 seconds | +./spec/app/pdf_converter_spec.rb[1:4:3:1] | passed | 0.00042 seconds | +./spec/app/pdf_converter_spec.rb[1:4:3:2] | passed | 0.00036 seconds | +./spec/app/pdf_converter_spec.rb[1:4:4:1] | passed | 0.00071 seconds | +./spec/app/pdf_converter_spec.rb[1:4:4:2] | passed | 0.00063 seconds | +./spec/app/pdf_converter_spec.rb[1:5:1:1] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:5:2:1] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:5:2:2] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:5:3:1] | passed | 0.00012 seconds | +./spec/app/pdf_converter_spec.rb[1:5:3:2] | passed | 0.00012 seconds | +./spec/app/pdf_converter_spec.rb[1:5:3:3] | passed | 0.00012 seconds | +./spec/app/pdf_converter_spec.rb[1:6:1:1] | passed | 0.00038 seconds | +./spec/app/pdf_converter_spec.rb[1:6:1:2] | passed | 0.00049 seconds | +./spec/app/pdf_converter_spec.rb[1:6:2:1] | passed | 0.00071 seconds | +./spec/app/pdf_converter_spec.rb[1:6:3:1] | passed | 0.00101 seconds | +./spec/app/pdf_converter_spec.rb[1:7:1:1] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:7:1:2] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:7:2:1] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:7:2:2] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:7:2:3] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:8:1] | passed | 0.00024 seconds | +./spec/app/pdf_converter_spec.rb[1:8:2] | passed | 0.00023 seconds | +./spec/app/pdf_converter_spec.rb[1:8:3] | passed | 0.00024 seconds | +./spec/app/pdf_converter_spec.rb[1:9:1:1] | passed | 0.00045 seconds | +./spec/app/pdf_converter_spec.rb[1:9:2:1] | passed | 0.00039 seconds | +./spec/app/pdf_converter_spec.rb[1:10:1:1] | passed | 0.00066 seconds | +./spec/app/pdf_converter_spec.rb[1:10:1:2] | passed | 0.00032 seconds | +./spec/app/pdf_converter_spec.rb[1:10:1:3] | passed | 0.00033 seconds | +./spec/app/pdf_converter_spec.rb[1:10:1:4] | passed | 0.00034 seconds | +./spec/app/pdf_converter_spec.rb[1:10:2:1] | passed | 0.00041 seconds | +./spec/app/pdf_converter_spec.rb[1:10:2:2] | passed | 0.00037 seconds | +./spec/app/pdf_converter_spec.rb[1:11:1:1] | passed | 0.00022 seconds | +./spec/app/pdf_converter_spec.rb[1:11:1:2] | passed | 0.00022 seconds | +./spec/app/pdf_converter_spec.rb[1:11:2:1] | passed | 0.00012 seconds | +./spec/app/pdf_converter_spec.rb[1:12:1] | passed | 0.00013 seconds | +./spec/app/pdf_converter_spec.rb[1:12:2] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:12:3] | passed | 0.00012 seconds | +./spec/app/pdf_converter_spec.rb[1:12:4] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:12:5] | passed | 0.00014 seconds | +./spec/app/pdf_converter_spec.rb[1:13:1] | passed | 0.00027 seconds | +./spec/app/pdf_downloader_spec.rb[1:1:1] | passed | 0.00008 seconds | +./spec/app/pdf_downloader_spec.rb[1:2:1] | passed | 0.00007 seconds | +./spec/app/pdf_downloader_spec.rb[1:3:1] | passed | 0.00008 seconds | +./spec/app/pdf_downloader_spec.rb[1:3:2] | passed | 0.00008 seconds | +./spec/app/pdf_downloader_spec.rb[1:3:3] | passed | 0.00014 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:1:1] | passed | 0.00031 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:1:2] | passed | 0.0007 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:1:3] | passed | 0.00055 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:1:4] | passed | 0.00037 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:2:1] | passed | 0.00026 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:3:1] | passed | 0.00092 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:4:1] | passed | 0.00017 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:5:1] | passed | 0.00017 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:6:1] | passed | 0.0005 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:7:1] | passed | 0.00022 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:8:1] | passed | 0.00166 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:8:2] | passed | 0.0011 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:9:1] | passed | 0.00047 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:10:1] | passed | 0.00081 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:10:2] | passed | 0.00165 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:11:1] | passed | 0.00444 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:12:1] | passed | 0.0011 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:13:1] | passed | 0.00168 seconds | ./spec/app/pdf_downloader_spec.rb[1:4:14:1] | passed | 3.01 seconds | ./spec/app/pdf_downloader_spec.rb[1:4:15:1] | passed | 3.01 seconds | ./spec/app/pdf_downloader_spec.rb[1:4:16:1] | passed | 3.01 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:17:1] | passed | 0.00055 seconds | -./spec/app/pdf_downloader_spec.rb[1:4:18:1] | passed | 0.00039 seconds | -./spec/app/pdf_downloader_spec.rb[1:5:1:1] | passed | 0.00008 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:17:1] | passed | 0.00118 seconds | +./spec/app/pdf_downloader_spec.rb[1:4:18:1] | passed | 0.00124 seconds | +./spec/app/pdf_downloader_spec.rb[1:5:1:1] | passed | 0.00004 seconds | ./spec/app/pdf_downloader_spec.rb[1:5:2:1] | passed | 0.00003 seconds | -./spec/app/pdf_downloader_spec.rb[1:5:3:1] | passed | 0.00009 seconds | -./spec/app/pdf_downloader_spec.rb[1:5:4:1] | passed | 0.00007 seconds | -./spec/app/pdf_downloader_spec.rb[1:5:5:1] | passed | 0.00003 seconds | -./spec/app/pdf_downloader_spec.rb[1:5:6:1] | passed | 0.00008 seconds | +./spec/app/pdf_downloader_spec.rb[1:5:3:1] | passed | 0.00003 seconds | +./spec/app/pdf_downloader_spec.rb[1:5:4:1] | passed | 0.00003 seconds | +./spec/app/pdf_downloader_spec.rb[1:5:5:1] | passed | 0.00004 seconds | +./spec/app/pdf_downloader_spec.rb[1:5:6:1] | passed | 0.00003 seconds | ./spec/app/pdf_downloader_spec.rb[1:5:7:1] | passed | 0.00003 seconds | -./spec/app/pdf_downloader_spec.rb[1:6:1:1] | passed | 0.00004 seconds | +./spec/app/pdf_downloader_spec.rb[1:6:1:1] | passed | 0.00005 seconds | ./spec/app/pdf_downloader_spec.rb[1:6:2:1] | passed | 0.00005 seconds | -./spec/app/pdf_downloader_spec.rb[1:6:3:1] | passed | 0.00004 seconds | +./spec/app/pdf_downloader_spec.rb[1:6:3:1] | passed | 0.00005 seconds | ./spec/app/pdf_downloader_spec.rb[1:6:4:1] | passed | 0.00004 seconds | -./spec/app/pdf_downloader_spec.rb[1:6:5:1] | passed | 0.00012 seconds | -./spec/app/pdf_downloader_spec.rb[1:6:6:1] | passed | 0.00005 seconds | -./spec/app/pdf_downloader_spec.rb[1:7:1:1] | passed | 0.00076 seconds | +./spec/app/pdf_downloader_spec.rb[1:6:5:1] | passed | 0.00007 seconds | +./spec/app/pdf_downloader_spec.rb[1:6:6:1] | passed | 0.0001 seconds | +./spec/app/pdf_downloader_spec.rb[1:7:1:1] | passed | 0.00035 seconds | ./spec/app/pdf_downloader_spec.rb[1:7:2:1] | passed | 3.01 seconds | -./spec/app/pdf_downloader_spec.rb[1:8:1:1] | passed | 0.00056 seconds | -./spec/app/pdf_downloader_spec.rb[1:8:2:1] | passed | 0.0008 seconds | +./spec/app/pdf_downloader_spec.rb[1:8:1:1] | passed | 0.00035 seconds | +./spec/app/pdf_downloader_spec.rb[1:8:2:1] | passed | 0.00117 seconds | ./spec/app/pdf_downloader_spec.rb[1:8:3:1] | passed | 0.00017 seconds | -./spec/app/pdf_downloader_spec.rb[1:8:4:1] | passed | 0.00055 seconds | -./spec/app/pdf_downloader_spec.rb[1:8:5:1] | passed | 0.00054 seconds | -./spec/app/pdf_downloader_spec.rb[1:8:6:1] | passed | 0.00055 seconds | -./spec/app/pdf_downloader_spec.rb[1:9:1:1] | passed | 0.00095 seconds | -./spec/app/pdf_downloader_spec.rb[1:9:1:2] | passed | 0.0003 seconds | -./spec/app/pdf_downloader_spec.rb[1:9:2:1] | passed | 0.0005 seconds | +./spec/app/pdf_downloader_spec.rb[1:8:4:1] | passed | 0.0004 seconds | +./spec/app/pdf_downloader_spec.rb[1:8:5:1] | passed | 0.00096 seconds | +./spec/app/pdf_downloader_spec.rb[1:8:6:1] | passed | 0.00029 seconds | +./spec/app/pdf_downloader_spec.rb[1:9:1:1] | passed | 0.00053 seconds | +./spec/app/pdf_downloader_spec.rb[1:9:1:2] | passed | 0.00054 seconds | +./spec/app/pdf_downloader_spec.rb[1:9:2:1] | passed | 0.00046 seconds | ./spec/app/pdf_downloader_spec.rb[1:10:1] | passed | 0.00005 seconds | -./spec/app/pdf_downloader_spec.rb[1:10:2] | passed | 0.00005 seconds | -./spec/app/pdf_downloader_spec.rb[1:11:1] | passed | 0.00007 seconds | -./spec/app/pdf_downloader_spec.rb[1:12:1] | passed | 0.00004 seconds | -./spec/app/request_validator_spec.rb[1:1:1] | passed | 0.00006 seconds | -./spec/app/request_validator_spec.rb[1:1:2] | passed | 0.00006 seconds | -./spec/app/request_validator_spec.rb[1:1:3] | passed | 0.00007 seconds | -./spec/app/request_validator_spec.rb[1:1:4] | passed | 0.0001 seconds | -./spec/app/request_validator_spec.rb[1:1:5] | passed | 0.00012 seconds | -./spec/app/request_validator_spec.rb[1:2:1] | passed | 0.00006 seconds | -./spec/app/request_validator_spec.rb[1:2:2] | passed | 0.00006 seconds | -./spec/app/request_validator_spec.rb[1:2:3] | passed | 0.00006 seconds | -./spec/app/request_validator_spec.rb[1:2:4] | passed | 0.00007 seconds | +./spec/app/pdf_downloader_spec.rb[1:10:2] | passed | 0.00008 seconds | +./spec/app/pdf_downloader_spec.rb[1:11:1] | passed | 0.00006 seconds | +./spec/app/pdf_downloader_spec.rb[1:12:1] | passed | 0.00015 seconds | +./spec/app/request_validator_spec.rb[1:1:1] | passed | 0.0001 seconds | +./spec/app/request_validator_spec.rb[1:1:2] | passed | 0.0001 seconds | +./spec/app/request_validator_spec.rb[1:1:3] | passed | 0.00009 seconds | +./spec/app/request_validator_spec.rb[1:1:4] | passed | 0.00009 seconds | +./spec/app/request_validator_spec.rb[1:1:5] | passed | 0.00018 seconds | +./spec/app/request_validator_spec.rb[1:2:1] | passed | 0.0001 seconds | +./spec/app/request_validator_spec.rb[1:2:2] | passed | 0.00017 seconds | +./spec/app/request_validator_spec.rb[1:2:3] | passed | 0.0001 seconds | +./spec/app/request_validator_spec.rb[1:2:4] | passed | 0.00011 seconds | ./spec/app/request_validator_spec.rb[1:2:5] | passed | 0.00012 seconds | ./spec/app/request_validator_spec.rb[1:2:6] | passed | 0.00013 seconds | -./spec/app/request_validator_spec.rb[1:3:1:1] | passed | 0.00009 seconds | -./spec/app/request_validator_spec.rb[1:3:2:1] | passed | 0.00014 seconds | -./spec/app/request_validator_spec.rb[1:3:3:1] | passed | 0.00008 seconds | -./spec/app/request_validator_spec.rb[1:3:4:1] | passed | 0.00008 seconds | -./spec/app/request_validator_spec.rb[1:4:1:1] | passed | 0.0002 seconds | -./spec/app/request_validator_spec.rb[1:4:2:1] | passed | 0.00019 seconds | -./spec/app/request_validator_spec.rb[1:4:3:1] | passed | 0.00009 seconds | -./spec/app/request_validator_spec.rb[1:4:4:1] | passed | 0.00382 seconds | -./spec/app/request_validator_spec.rb[1:5:1:1] | passed | 0.00019 seconds | -./spec/app/request_validator_spec.rb[1:5:1:2] | passed | 0.00049 seconds | -./spec/app/request_validator_spec.rb[1:5:1:3] | passed | 0.00039 seconds | -./spec/app/request_validator_spec.rb[1:5:1:4] | passed | 0.00021 seconds | -./spec/app/request_validator_spec.rb[1:5:2:1] | passed | 0.00016 seconds | -./spec/app/request_validator_spec.rb[1:5:3:1] | passed | 0.00021 seconds | -./spec/app/request_validator_spec.rb[1:5:4:1] | passed | 0.0004 seconds | -./spec/app/request_validator_spec.rb[1:5:5:1] | passed | 0.0004 seconds | -./spec/app/request_validator_spec.rb[1:5:6:1] | passed | 0.0003 seconds | -./spec/app/request_validator_spec.rb[1:5:7:1] | passed | 0.00036 seconds | -./spec/app/request_validator_spec.rb[1:5:8:1] | passed | 0.00046 seconds | -./spec/app/request_validator_spec.rb[1:5:9:1] | passed | 0.00039 seconds | -./spec/app/request_validator_spec.rb[1:5:10:1] | passed | 0.00021 seconds | -./spec/app/request_validator_spec.rb[1:5:11:1] | passed | 0.00052 seconds | -./spec/app/request_validator_spec.rb[1:5:12:1] | passed | 0.00049 seconds | -./spec/app/request_validator_spec.rb[1:5:13:1] | passed | 0.00051 seconds | -./spec/app/request_validator_spec.rb[1:5:14:1] | passed | 0.0004 seconds | -./spec/app/request_validator_spec.rb[1:5:15:1] | passed | 0.00051 seconds | -./spec/app/request_validator_spec.rb[1:5:16:1] | passed | 0.00024 seconds | -./spec/app/request_validator_spec.rb[1:5:16:2] | passed | 0.00018 seconds | -./spec/app/request_validator_spec.rb[1:6:1] | passed | 0.00021 seconds | +./spec/app/request_validator_spec.rb[1:3:1:1] | passed | 0.00013 seconds | +./spec/app/request_validator_spec.rb[1:3:2:1] | passed | 0.00012 seconds | +./spec/app/request_validator_spec.rb[1:3:3:1] | passed | 0.00012 seconds | +./spec/app/request_validator_spec.rb[1:3:4:1] | passed | 0.00012 seconds | +./spec/app/request_validator_spec.rb[1:4:1:1] | passed | 0.00013 seconds | +./spec/app/request_validator_spec.rb[1:4:2:1] | passed | 0.00013 seconds | +./spec/app/request_validator_spec.rb[1:4:3:1] | passed | 0.00018 seconds | +./spec/app/request_validator_spec.rb[1:4:4:1] | passed | 0.00021 seconds | +./spec/app/request_validator_spec.rb[1:5:1:1] | passed | 0.00029 seconds | +./spec/app/request_validator_spec.rb[1:5:1:2] | passed | 0.00034 seconds | +./spec/app/request_validator_spec.rb[1:5:1:3] | passed | 0.00031 seconds | +./spec/app/request_validator_spec.rb[1:5:1:4] | passed | 0.00045 seconds | +./spec/app/request_validator_spec.rb[1:5:2:1] | passed | 0.00023 seconds | +./spec/app/request_validator_spec.rb[1:5:3:1] | passed | 0.00017 seconds | +./spec/app/request_validator_spec.rb[1:5:4:1] | passed | 0.0003 seconds | +./spec/app/request_validator_spec.rb[1:5:5:1] | passed | 0.00023 seconds | +./spec/app/request_validator_spec.rb[1:5:6:1] | passed | 0.00027 seconds | +./spec/app/request_validator_spec.rb[1:5:7:1] | passed | 0.00026 seconds | +./spec/app/request_validator_spec.rb[1:5:8:1] | passed | 0.00018 seconds | +./spec/app/request_validator_spec.rb[1:5:9:1] | passed | 0.00041 seconds | +./spec/app/request_validator_spec.rb[1:5:10:1] | passed | 0.00026 seconds | +./spec/app/request_validator_spec.rb[1:5:11:1] | passed | 0.0002 seconds | +./spec/app/request_validator_spec.rb[1:5:12:1] | passed | 0.00024 seconds | +./spec/app/request_validator_spec.rb[1:5:13:1] | passed | 0.00044 seconds | +./spec/app/request_validator_spec.rb[1:5:14:1] | passed | 0.00044 seconds | +./spec/app/request_validator_spec.rb[1:5:15:1] | passed | 0.0004 seconds | +./spec/app/request_validator_spec.rb[1:5:16:1] | passed | 0.00023 seconds | +./spec/app/request_validator_spec.rb[1:5:16:2] | passed | 0.00025 seconds | +./spec/app/request_validator_spec.rb[1:6:1] | passed | 0.00023 seconds | ./spec/app/response_builder_spec.rb[1:1:1] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:1:2] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:1:3] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:2:1:1] | passed | 0.00008 seconds | -./spec/app/response_builder_spec.rb[1:2:1:2] | passed | 0.00004 seconds | -./spec/app/response_builder_spec.rb[1:2:1:3] | passed | 0.00003 seconds | +./spec/app/response_builder_spec.rb[1:2:1:1] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:2:1:2] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:2:1:3] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:2:1:4] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:2:2:1] | passed | 0.00003 seconds | +./spec/app/response_builder_spec.rb[1:2:2:1] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:2:2:2] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:2:3:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:2:3:2] | passed | 0.00003 seconds | +./spec/app/response_builder_spec.rb[1:2:3:2] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:2:4:1] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:2:5:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:3:1:1] | passed | 0.00003 seconds | +./spec/app/response_builder_spec.rb[1:3:1:1] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:3:1:2] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:3:1:3] | passed | 0.00004 seconds | +./spec/app/response_builder_spec.rb[1:3:1:3] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:3:1:4] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:3:2:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:3:2:2] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:3:3:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:3:4:1] | passed | 0.00011 seconds | +./spec/app/response_builder_spec.rb[1:3:2:2] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:3:3:1] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:3:4:1] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:3:5:1] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:3:6:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:4:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:4:2] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:4:3] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:4:4] | passed | 0.00003 seconds | +./spec/app/response_builder_spec.rb[1:4:1] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:4:2] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:4:3] | passed | 0.00002 seconds | +./spec/app/response_builder_spec.rb[1:4:4] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:4:5] | passed | 0.00002 seconds | -./spec/app/response_builder_spec.rb[1:4:6] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:4:7] | passed | 0.00002 seconds | -./spec/app/response_builder_spec.rb[1:4:8] | passed | 0.00009 seconds | +./spec/app/response_builder_spec.rb[1:4:6] | passed | 0.00005 seconds | +./spec/app/response_builder_spec.rb[1:4:7] | passed | 0.00003 seconds | +./spec/app/response_builder_spec.rb[1:4:8] | passed | 0.00002 seconds | ./spec/app/response_builder_spec.rb[1:4:9] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:4:10:1] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:4:11:1] | passed | 0.00003 seconds | ./spec/app/response_builder_spec.rb[1:4:12:1] | passed | 0.00003 seconds | -./spec/app/response_builder_spec.rb[1:5:1] | passed | 0.00008 seconds | -./spec/app/response_builder_spec.rb[1:5:2] | passed | 0.00008 seconds | +./spec/app/response_builder_spec.rb[1:5:1] | passed | 0.00013 seconds | +./spec/app/response_builder_spec.rb[1:5:2] | passed | 0.00005 seconds | ./spec/app/url_validator_spec.rb[1:1:1] | passed | 0.00004 seconds | -./spec/app/url_validator_spec.rb[1:1:2] | passed | 0.00004 seconds | -./spec/app/url_validator_spec.rb[1:2:1] | passed | 0.00006 seconds | -./spec/app/url_validator_spec.rb[1:2:2] | passed | 0.00007 seconds | +./spec/app/url_validator_spec.rb[1:1:2] | passed | 0.00005 seconds | +./spec/app/url_validator_spec.rb[1:2:1] | passed | 0.00009 seconds | +./spec/app/url_validator_spec.rb[1:2:2] | passed | 0.00009 seconds | ./spec/app/url_validator_spec.rb[1:3:1:1] | passed | 0.00012 seconds | -./spec/app/url_validator_spec.rb[1:3:2:1] | passed | 0.00012 seconds | +./spec/app/url_validator_spec.rb[1:3:2:1] | passed | 0.00015 seconds | ./spec/app/url_validator_spec.rb[1:3:3:1] | passed | 0.00004 seconds | -./spec/app/url_validator_spec.rb[1:3:4:1] | passed | 0.00009 seconds | +./spec/app/url_validator_spec.rb[1:3:4:1] | passed | 0.00004 seconds | ./spec/app/url_validator_spec.rb[1:3:5:1] | passed | 0.00013 seconds | -./spec/app/url_validator_spec.rb[1:4:1:1] | passed | 0.0004 seconds | -./spec/app/url_validator_spec.rb[1:4:2:1] | passed | 0.00014 seconds | -./spec/app/url_validator_spec.rb[1:4:3:1] | passed | 0.00045 seconds | -./spec/app/url_validator_spec.rb[1:4:4:1] | passed | 0.0001 seconds | -./spec/app/url_validator_spec.rb[1:4:5:1] | passed | 0.00004 seconds | -./spec/app/url_validator_spec.rb[1:4:6:1] | passed | 0.00007 seconds | -./spec/app/url_validator_spec.rb[1:5:1:1] | passed | 0.00005 seconds | -./spec/app/url_validator_spec.rb[1:5:2:1] | passed | 0.00006 seconds | +./spec/app/url_validator_spec.rb[1:4:1:1] | passed | 0.00022 seconds | +./spec/app/url_validator_spec.rb[1:4:2:1] | passed | 0.00044 seconds | +./spec/app/url_validator_spec.rb[1:4:3:1] | passed | 0.00013 seconds | +./spec/app/url_validator_spec.rb[1:4:4:1] | passed | 0.00004 seconds | +./spec/app/url_validator_spec.rb[1:4:5:1] | passed | 0.00005 seconds | +./spec/app/url_validator_spec.rb[1:4:6:1] | passed | 0.0001 seconds | +./spec/app/url_validator_spec.rb[1:5:1:1] | passed | 0.0001 seconds | +./spec/app/url_validator_spec.rb[1:5:2:1] | passed | 0.0001 seconds | ./spec/app/url_validator_spec.rb[1:5:3:1] | passed | 0.0001 seconds | -./spec/app/url_validator_spec.rb[1:5:4:1] | passed | 0.00007 seconds | -./spec/app/url_validator_spec.rb[1:5:5:1] | passed | 0.00007 seconds | -./spec/app/url_validator_spec.rb[1:5:6:1] | passed | 0.00005 seconds | -./spec/app/url_validator_spec.rb[1:5:7:1] | passed | 0.00006 seconds | -./spec/app/url_validator_spec.rb[1:6:1:1] | passed | 0.00024 seconds | -./spec/app/url_validator_spec.rb[1:6:1:2] | passed | 0.00025 seconds | -./spec/app/url_validator_spec.rb[1:6:2:1] | passed | 0.00018 seconds | -./spec/app/url_validator_spec.rb[1:6:2:2] | passed | 0.00019 seconds | -./spec/app/url_validator_spec.rb[1:7:1:1:1] | passed | 0.00012 seconds | -./spec/app/url_validator_spec.rb[1:7:1:2:1] | passed | 0.0002 seconds | -./spec/app/url_validator_spec.rb[1:7:1:3:1] | passed | 0.00012 seconds | -./spec/app/url_validator_spec.rb[1:7:2:1:1] | passed | 0.0002 seconds | -./spec/app/url_validator_spec.rb[1:7:2:2:1] | passed | 0.00017 seconds | -./spec/app/url_validator_spec.rb[1:7:2:3:1] | passed | 0.00015 seconds | +./spec/app/url_validator_spec.rb[1:5:4:1] | passed | 0.00005 seconds | +./spec/app/url_validator_spec.rb[1:5:5:1] | passed | 0.00008 seconds | +./spec/app/url_validator_spec.rb[1:5:6:1] | passed | 0.00011 seconds | +./spec/app/url_validator_spec.rb[1:5:7:1] | passed | 0.00004 seconds | +./spec/app/url_validator_spec.rb[1:6:1:1] | passed | 0.00016 seconds | +./spec/app/url_validator_spec.rb[1:6:1:2] | passed | 0.00017 seconds | +./spec/app/url_validator_spec.rb[1:6:2:1] | passed | 0.00012 seconds | +./spec/app/url_validator_spec.rb[1:6:2:2] | passed | 0.00015 seconds | +./spec/app/url_validator_spec.rb[1:7:1:1:1] | passed | 0.00022 seconds | +./spec/app/url_validator_spec.rb[1:7:1:2:1] | passed | 0.00025 seconds | +./spec/app/url_validator_spec.rb[1:7:1:3:1] | passed | 0.00039 seconds | +./spec/app/url_validator_spec.rb[1:7:2:1:1] | passed | 0.00027 seconds | +./spec/app/url_validator_spec.rb[1:7:2:2:1] | passed | 0.00014 seconds | +./spec/app/url_validator_spec.rb[1:7:2:3:1] | passed | 0.0003 seconds | ./spec/app/url_validator_spec.rb[1:7:3:1:1] | passed | 0.00012 seconds | -./spec/app/url_validator_spec.rb[1:7:3:2:1] | passed | 0.00013 seconds | -./spec/app/url_validator_spec.rb[1:7:4:1:1] | passed | 0.00012 seconds | -./spec/app/url_validator_spec.rb[1:7:4:2:1] | passed | 0.00014 seconds | -./spec/app/url_validator_spec.rb[1:7:4:3:1] | passed | 0.00014 seconds | -./spec/app/url_validator_spec.rb[1:7:4:4:1] | passed | 0.00018 seconds | -./spec/app/url_validator_spec.rb[1:8:1:1] | passed | 0.00029 seconds | -./spec/app/url_validator_spec.rb[1:8:2:1] | passed | 0.00018 seconds | +./spec/app/url_validator_spec.rb[1:7:3:2:1] | passed | 0.00017 seconds | +./spec/app/url_validator_spec.rb[1:7:4:1:1] | passed | 0.00013 seconds | +./spec/app/url_validator_spec.rb[1:7:4:2:1] | passed | 0.00026 seconds | +./spec/app/url_validator_spec.rb[1:7:4:3:1] | passed | 0.00023 seconds | +./spec/app/url_validator_spec.rb[1:7:4:4:1] | passed | 0.00014 seconds | +./spec/app/url_validator_spec.rb[1:8:1:1] | passed | 0.00015 seconds | +./spec/app/url_validator_spec.rb[1:8:2:1] | passed | 0.00014 seconds | ./spec/app/url_validator_spec.rb[1:9:1:1] | passed | 0.00014 seconds | -./spec/app/url_validator_spec.rb[1:9:2:1] | passed | 0.00017 seconds | -./spec/app/url_validator_spec.rb[1:10:1:1] | passed | 0.00022 seconds | -./spec/app/url_validator_spec.rb[1:10:2:1] | passed | 0.00024 seconds | -./spec/app/url_validator_spec.rb[1:10:3:1] | passed | 0.00021 seconds | +./spec/app/url_validator_spec.rb[1:9:2:1] | passed | 0.00033 seconds | +./spec/app/url_validator_spec.rb[1:10:1:1] | passed | 0.00025 seconds | +./spec/app/url_validator_spec.rb[1:10:2:1] | passed | 0.00025 seconds | +./spec/app/url_validator_spec.rb[1:10:3:1] | passed | 0.00024 seconds | ./spec/app/url_validator_spec.rb[1:11:1:1] | passed | 0.00022 seconds | -./spec/app/url_validator_spec.rb[1:11:2:1] | passed | 0.00015 seconds | -./spec/app/url_validator_spec.rb[1:12:1:1] | passed | 0.00008 seconds | -./spec/app/webhook_notifier_spec.rb[1:1:1] | passed | 0.00003 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:1:1] | passed | 0.00069 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:1:2] | passed | 0.00061 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:1:3] | passed | 0.0003 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:1:4] | passed | 0.00108 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:1:5] | passed | 0.00033 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:1:6] | passed | 0.0007 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:2:1:1] | passed | 0.00049 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:2:2:1] | passed | 0.00071 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:2:3:1] | passed | 0.00063 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:2:4:1] | passed | 0.00049 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:3:1:1] | passed | 0.00144 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:3:1:2] | passed | 0.00074 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:3:2:1] | passed | 0.00094 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:3:3:1] | passed | 0.00055 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:4:1:1] | passed | 0.00055 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:5:1:1] | passed | 0.00027 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:5:2:1] | passed | 0.00046 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:5:3:1] | passed | 0.00025 seconds | +./spec/app/url_validator_spec.rb[1:11:2:1] | passed | 0.00024 seconds | +./spec/app/url_validator_spec.rb[1:12:1:1] | passed | 0.00012 seconds | +./spec/app/webhook_notifier_spec.rb[1:1:1] | passed | 0.00002 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:1:1] | passed | 0.00023 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:1:2] | passed | 0.0004 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:1:3] | passed | 0.00029 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:1:4] | passed | 0.00034 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:1:5] | passed | 0.00054 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:1:6] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:2:1:1] | passed | 0.00022 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:2:2:1] | passed | 0.0025 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:2:3:1] | passed | 0.00021 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:2:4:1] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:3:1:1] | passed | 0.00025 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:3:1:2] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:3:2:1] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:3:3:1] | passed | 0.00034 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:4:1:1] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:5:1:1] | passed | 0.00034 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:5:2:1] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:5:3:1] | passed | 0.0003 seconds | ./spec/app/webhook_notifier_spec.rb[1:2:5:4:1] | passed | 0.00024 seconds | ./spec/app/webhook_notifier_spec.rb[1:2:6:1] | passed | 0.00005 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:7:1] | passed | 0.00059 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:7:2] | passed | 0.00055 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:8:1] | passed | 0.00036 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:8:2] | passed | 0.0007 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:9:1] | passed | 0.00105 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:10:1] | passed | 0.00093 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:11:1] | passed | 0.00066 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:12:1] | passed | 0.00054 seconds | -./spec/app/webhook_notifier_spec.rb[1:2:13:1] | passed | 0.00143 seconds | -./spec/app/webhook_notifier_spec.rb[1:3:1:1] | passed | 0.00004 seconds | -./spec/app/webhook_notifier_spec.rb[1:3:2:1] | passed | 0.00004 seconds | -./spec/app_spec.rb[1:1:1:1] | passed | 0.0007 seconds | -./spec/app_spec.rb[1:1:1:2] | passed | 0.00576 seconds | -./spec/app_spec.rb[1:1:1:3] | passed | 0.00074 seconds | -./spec/app_spec.rb[1:1:1:4] | passed | 0.0007 seconds | -./spec/app_spec.rb[1:1:1:5] | passed | 0.0011 seconds | -./spec/app_spec.rb[1:1:2:1] | passed | 0.00045 seconds | -./spec/app_spec.rb[1:1:2:2] | passed | 0.00044 seconds | -./spec/app_spec.rb[1:1:2:3] | passed | 0.00047 seconds | -./spec/app_spec.rb[1:1:3:1] | passed | 0.00087 seconds | -./spec/app_spec.rb[1:1:3:2] | passed | 0.00043 seconds | -./spec/app_spec.rb[1:1:3:3] | passed | 0.00042 seconds | -./spec/app_spec.rb[1:1:4:1] | passed | 0.00072 seconds | -./spec/app_spec.rb[1:1:4:2] | passed | 0.00048 seconds | -./spec/app_spec.rb[1:2:1:1] | passed | 0.00057 seconds | -./spec/app_spec.rb[1:2:1:2] | passed | 0.00061 seconds | -./spec/app_spec.rb[1:2:1:3] | passed | 0.00058 seconds | -./spec/app_spec.rb[1:2:1:4] | passed | 0.00061 seconds | -./spec/app_spec.rb[1:2:1:5] | passed | 0.00059 seconds | -./spec/app_spec.rb[1:2:1:6] | passed | 0.00056 seconds | -./spec/app_spec.rb[1:2:1:7] | passed | 0.00057 seconds | -./spec/app_spec.rb[1:2:2:1] | passed | 0.00042 seconds | -./spec/app_spec.rb[1:2:2:2] | passed | 0.00045 seconds | -./spec/app_spec.rb[1:2:2:3] | passed | 0.00044 seconds | -./spec/app_spec.rb[1:2:2:4] | passed | 0.00045 seconds | -./spec/app_spec.rb[1:2:3:1] | passed | 0.00048 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:7:1] | passed | 0.0003 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:7:2] | passed | 0.00035 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:8:1] | passed | 0.00024 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:8:2] | passed | 0.0005 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:9:1] | passed | 0.0006 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:10:1] | passed | 0.00057 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:11:1] | passed | 0.0003 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:12:1] | passed | 0.00034 seconds | +./spec/app/webhook_notifier_spec.rb[1:2:13:1] | passed | 0.00029 seconds | +./spec/app/webhook_notifier_spec.rb[1:3:1:1] | passed | 0.00003 seconds | +./spec/app/webhook_notifier_spec.rb[1:3:2:1] | passed | 0.00005 seconds | +./spec/app_spec.rb[1:1:1:1] | passed | 0.00067 seconds | +./spec/app_spec.rb[1:1:1:2] | passed | 0.00078 seconds | +./spec/app_spec.rb[1:1:1:3] | passed | 0.00067 seconds | +./spec/app_spec.rb[1:1:1:4] | passed | 0.00069 seconds | +./spec/app_spec.rb[1:1:1:5] | passed | 0.00074 seconds | +./spec/app_spec.rb[1:1:2:1] | passed | 0.00048 seconds | +./spec/app_spec.rb[1:1:2:2] | passed | 0.00048 seconds | +./spec/app_spec.rb[1:1:2:3] | passed | 0.00045 seconds | +./spec/app_spec.rb[1:1:3:1] | passed | 0.00044 seconds | +./spec/app_spec.rb[1:1:3:2] | passed | 0.00042 seconds | +./spec/app_spec.rb[1:1:3:3] | passed | 0.00041 seconds | +./spec/app_spec.rb[1:1:4:1] | passed | 0.00046 seconds | +./spec/app_spec.rb[1:1:4:2] | passed | 0.00049 seconds | +./spec/app_spec.rb[1:2:1:1] | passed | 0.00053 seconds | +./spec/app_spec.rb[1:2:1:2] | passed | 0.00053 seconds | +./spec/app_spec.rb[1:2:1:3] | passed | 0.00077 seconds | +./spec/app_spec.rb[1:2:1:4] | passed | 0.00055 seconds | +./spec/app_spec.rb[1:2:1:5] | passed | 0.00062 seconds | +./spec/app_spec.rb[1:2:1:6] | passed | 0.00054 seconds | +./spec/app_spec.rb[1:2:1:7] | passed | 0.00056 seconds | +./spec/app_spec.rb[1:2:2:1] | passed | 0.00067 seconds | +./spec/app_spec.rb[1:2:2:2] | passed | 0.00046 seconds | +./spec/app_spec.rb[1:2:2:3] | passed | 0.00058 seconds | +./spec/app_spec.rb[1:2:2:4] | passed | 0.00054 seconds | +./spec/app_spec.rb[1:2:3:1] | passed | 0.0005 seconds | ./spec/app_spec.rb[1:2:3:2] | passed | 0.00049 seconds | ./spec/app_spec.rb[1:2:3:3] | passed | 0.0005 seconds | -./spec/app_spec.rb[1:2:4:1] | passed | 0.00056 seconds | -./spec/app_spec.rb[1:2:4:2] | passed | 0.00056 seconds | +./spec/app_spec.rb[1:2:4:1] | passed | 0.0005 seconds | +./spec/app_spec.rb[1:2:4:2] | passed | 0.00051 seconds | ./spec/app_spec.rb[1:2:4:3] | passed | 0.00054 seconds | -./spec/app_spec.rb[1:2:5:1] | passed | 0.00062 seconds | -./spec/app_spec.rb[1:2:6:1] | passed | 0.00064 seconds | -./spec/app_spec.rb[1:3:1:1] | passed | 0.0005 seconds | -./spec/app_spec.rb[1:3:1:2] | passed | 0.0004 seconds | -./spec/app_spec.rb[1:3:1:3] | passed | 0.00041 seconds | -./spec/app_spec.rb[1:3:2:1] | passed | 0.00039 seconds | -./spec/app_spec.rb[1:3:2:2] | passed | 0.0004 seconds | -./spec/app_spec.rb[1:3:3:1] | passed | 0.00042 seconds | -./spec/app_spec.rb[1:3:3:2] | passed | 0.00044 seconds | -./spec/app_spec.rb[1:4:1:1] | passed | 0.00038 seconds | -./spec/app_spec.rb[1:4:1:2] | passed | 0.00039 seconds | -./spec/app_spec.rb[1:4:1:3] | passed | 0.00121 seconds | -./spec/app_spec.rb[1:4:2:1] | passed | 0.00032 seconds | +./spec/app_spec.rb[1:2:5:1] | passed | 0.00058 seconds | +./spec/app_spec.rb[1:2:6:1] | passed | 0.00401 seconds | +./spec/app_spec.rb[1:3:1:1] | passed | 0.00038 seconds | +./spec/app_spec.rb[1:3:1:2] | passed | 0.00037 seconds | +./spec/app_spec.rb[1:3:1:3] | passed | 0.00035 seconds | +./spec/app_spec.rb[1:3:2:1] | passed | 0.01132 seconds | +./spec/app_spec.rb[1:3:2:2] | passed | 0.0006 seconds | +./spec/app_spec.rb[1:3:3:1] | passed | 0.00038 seconds | +./spec/app_spec.rb[1:3:3:2] | passed | 0.00048 seconds | +./spec/app_spec.rb[1:4:1:1] | passed | 0.00041 seconds | +./spec/app_spec.rb[1:4:1:2] | passed | 0.00038 seconds | +./spec/app_spec.rb[1:4:1:3] | passed | 0.00055 seconds | +./spec/app_spec.rb[1:4:2:1] | passed | 0.00045 seconds | ./spec/app_spec.rb[1:4:2:2] | passed | 0.00034 seconds | -./spec/app_spec.rb[1:5:1:1] | passed | 0.00037 seconds | -./spec/app_spec.rb[1:5:1:2] | passed | 0.00037 seconds | -./spec/app_spec.rb[1:5:1:3] | passed | 0.00037 seconds | -./spec/app_spec.rb[1:5:2:1] | passed | 0.00089 seconds | -./spec/app_spec.rb[1:5:2:2] | passed | 0.00041 seconds | -./spec/app_spec.rb[1:5:2:3] | passed | 0.00038 seconds | -./spec/app_spec.rb[1:6:1:1] | passed | 0.00307 seconds | -./spec/app_spec.rb[1:6:1:2] | passed | 0.00046 seconds | -./spec/app_spec.rb[1:6:1:3] | passed | 0.0006 seconds | -./spec/app_spec.rb[1:6:1:4] | passed | 0.00051 seconds | -./spec/app_spec.rb[1:6:2:1] | passed | 0.00043 seconds | -./spec/app_spec.rb[1:6:3:1] | passed | 0.00038 seconds | -./spec/app_spec.rb[1:6:4:1] | passed | 0.0004 seconds | -./spec/app_spec.rb[1:6:4:2] | passed | 0.00108 seconds | -./spec/app_spec.rb[1:6:5:1] | passed | 0.0004 seconds | -./spec/app_spec.rb[1:6:5:2] | passed | 0.0004 seconds | -./spec/integration/localstack_integration_spec.rb[1:1:1] | failed | 0.06558 seconds | -./spec/integration/localstack_integration_spec.rb[1:1:2] | failed | 0.02875 seconds | -./spec/lib/aws_config_spec.rb[1:1:1:1] | passed | 0.00052 seconds | -./spec/lib/aws_config_spec.rb[1:1:1:2] | passed | 0.00877 seconds | -./spec/lib/aws_config_spec.rb[1:1:2:1] | passed | 0.00396 seconds | -./spec/lib/aws_config_spec.rb[1:1:3:1] | passed | 0.00059 seconds | -./spec/lib/aws_config_spec.rb[1:1:4:1] | passed | 0.00009 seconds | -./spec/lib/aws_config_spec.rb[1:1:4:2] | passed | 0.00009 seconds | -./spec/lib/aws_config_spec.rb[1:1:4:3] | passed | 0.00011 seconds | -./spec/lib/aws_config_spec.rb[1:1:4:4] | passed | 0.00096 seconds | -./spec/lib/aws_config_spec.rb[1:1:5:1] | passed | 0.00016 seconds | -./spec/lib/aws_config_spec.rb[1:1:5:2] | passed | 0.02389 seconds | -./spec/lib/aws_config_spec.rb[1:2:1:1] | passed | 0.00003 seconds | +./spec/app_spec.rb[1:5:1:1] | passed | 0.0006 seconds | +./spec/app_spec.rb[1:5:1:2] | passed | 0.0004 seconds | +./spec/app_spec.rb[1:5:1:3] | passed | 0.00033 seconds | +./spec/app_spec.rb[1:5:2:1] | passed | 0.00222 seconds | +./spec/app_spec.rb[1:5:2:2] | passed | 0.00142 seconds | +./spec/app_spec.rb[1:5:2:3] | passed | 0.00132 seconds | +./spec/app_spec.rb[1:6:1:1] | passed | 0.00035 seconds | +./spec/app_spec.rb[1:6:1:2] | passed | 0.00036 seconds | +./spec/app_spec.rb[1:6:1:3] | passed | 0.00046 seconds | +./spec/app_spec.rb[1:6:1:4] | passed | 0.00035 seconds | +./spec/app_spec.rb[1:6:2:1] | passed | 0.00036 seconds | +./spec/app_spec.rb[1:6:3:1] | passed | 0.00035 seconds | +./spec/app_spec.rb[1:6:4:1] | passed | 0.00048 seconds | +./spec/app_spec.rb[1:6:4:2] | passed | 0.00035 seconds | +./spec/app_spec.rb[1:6:5:1] | passed | 0.00046 seconds | +./spec/app_spec.rb[1:6:5:2] | passed | 0.00037 seconds | +./spec/integration/localstack_integration_spec.rb[1:1:1] | failed | 0.06587 seconds | +./spec/integration/localstack_integration_spec.rb[1:1:2] | failed | 0.02457 seconds | +./spec/lib/aws_config_spec.rb[1:1:1:1] | passed | 0.0112 seconds | +./spec/lib/aws_config_spec.rb[1:1:1:2] | passed | 0.00067 seconds | +./spec/lib/aws_config_spec.rb[1:1:2:1] | passed | 0.00052 seconds | +./spec/lib/aws_config_spec.rb[1:1:3:1] | passed | 0.00048 seconds | +./spec/lib/aws_config_spec.rb[1:1:4:1] | passed | 0.00014 seconds | +./spec/lib/aws_config_spec.rb[1:1:4:2] | passed | 0.00011 seconds | +./spec/lib/aws_config_spec.rb[1:1:4:3] | passed | 0.00012 seconds | +./spec/lib/aws_config_spec.rb[1:1:4:4] | passed | 0.00012 seconds | +./spec/lib/aws_config_spec.rb[1:1:5:1] | passed | 0.00011 seconds | +./spec/lib/aws_config_spec.rb[1:1:5:2] | passed | 0.0001 seconds | +./spec/lib/aws_config_spec.rb[1:2:1:1] | passed | 0.00002 seconds | ./spec/lib/aws_config_spec.rb[1:2:2:1] | passed | 0.00003 seconds | -./spec/lib/aws_config_spec.rb[1:2:3:1] | passed | 0.00004 seconds | -./spec/lib/aws_config_spec.rb[1:2:4:1] | passed | 0.00064 seconds | +./spec/lib/aws_config_spec.rb[1:2:3:1] | passed | 0.00002 seconds | +./spec/lib/aws_config_spec.rb[1:2:4:1] | passed | 0.00002 seconds | ./spec/lib/aws_config_spec.rb[1:3:1:1] | passed | 0.00003 seconds | -./spec/lib/aws_config_spec.rb[1:3:2:1] | passed | 0.00003 seconds | +./spec/lib/aws_config_spec.rb[1:3:2:1] | passed | 0.00002 seconds | ./spec/lib/aws_config_spec.rb[1:3:3:1] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:1:1:1] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:1:1:2] | passed | 0.00004 seconds | -./spec/lib/retry_handler_spec.rb[1:1:1:3] | passed | 0.00009 seconds | +./spec/lib/retry_handler_spec.rb[1:1:1:1] | passed | 0.00008 seconds | +./spec/lib/retry_handler_spec.rb[1:1:1:2] | passed | 0.00008 seconds | +./spec/lib/retry_handler_spec.rb[1:1:1:3] | passed | 0.00026 seconds | ./spec/lib/retry_handler_spec.rb[1:1:2:1] | passed | 3.01 seconds | ./spec/lib/retry_handler_spec.rb[1:1:2:2] | passed | 1 second | ./spec/lib/retry_handler_spec.rb[1:1:2:3] | passed | 1.01 seconds | ./spec/lib/retry_handler_spec.rb[1:1:2:4] | passed | 1 second | ./spec/lib/retry_handler_spec.rb[1:1:2:5] | passed | 1.01 seconds | ./spec/lib/retry_handler_spec.rb[1:1:2:6] | passed | 1 second | -./spec/lib/retry_handler_spec.rb[1:1:2:7] | passed | 1.01 seconds | +./spec/lib/retry_handler_spec.rb[1:1:2:7] | passed | 1 second | ./spec/lib/retry_handler_spec.rb[1:1:2:8] | passed | 1.01 seconds | -./spec/lib/retry_handler_spec.rb[1:1:2:9] | passed | 0.00018 seconds | +./spec/lib/retry_handler_spec.rb[1:1:2:9] | passed | 0.00046 seconds | ./spec/lib/retry_handler_spec.rb[1:1:2:10] | passed | 3.01 seconds | ./spec/lib/retry_handler_spec.rb[1:1:3:1] | passed | 3.01 seconds | ./spec/lib/retry_handler_spec.rb[1:1:3:2] | passed | 3.01 seconds | -./spec/lib/retry_handler_spec.rb[1:1:3:3] | passed | 1 second | -./spec/lib/retry_handler_spec.rb[1:1:4:1] | passed | 0.00026 seconds | -./spec/lib/retry_handler_spec.rb[1:1:4:2] | passed | 0.00045 seconds | -./spec/lib/retry_handler_spec.rb[1:1:5:1] | passed | 0.00028 seconds | -./spec/lib/retry_handler_spec.rb[1:1:5:2] | passed | 0.00019 seconds | -./spec/lib/retry_handler_spec.rb[1:1:5:3] | passed | 0.00067 seconds | -./spec/lib/retry_handler_spec.rb[1:1:6:1] | passed | 15.01 seconds | -./spec/lib/retry_handler_spec.rb[1:1:6:2] | passed | 0.00047 seconds | +./spec/lib/retry_handler_spec.rb[1:1:3:3] | passed | 1.01 seconds | +./spec/lib/retry_handler_spec.rb[1:1:4:1] | passed | 0.00024 seconds | +./spec/lib/retry_handler_spec.rb[1:1:4:2] | passed | 0.00094 seconds | +./spec/lib/retry_handler_spec.rb[1:1:5:1] | passed | 0.00017 seconds | +./spec/lib/retry_handler_spec.rb[1:1:5:2] | passed | 0.00016 seconds | +./spec/lib/retry_handler_spec.rb[1:1:5:3] | passed | 0.00022 seconds | +./spec/lib/retry_handler_spec.rb[1:1:6:1] | passed | 15.02 seconds | +./spec/lib/retry_handler_spec.rb[1:1:6:2] | passed | 0.0006 seconds | ./spec/lib/retry_handler_spec.rb[1:1:6:3] | passed | 1 second | -./spec/lib/retry_handler_spec.rb[1:1:7:1] | passed | 1 second | -./spec/lib/retry_handler_spec.rb[1:2:1:1] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:1:2] | passed | 0.00003 seconds | +./spec/lib/retry_handler_spec.rb[1:1:7:1] | passed | 1.01 seconds | +./spec/lib/retry_handler_spec.rb[1:2:1:1] | passed | 0.00002 seconds | +./spec/lib/retry_handler_spec.rb[1:2:1:2] | passed | 0.00002 seconds | ./spec/lib/retry_handler_spec.rb[1:2:1:3] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:1:4] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:2:1] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:2:2] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:2:3] | passed | 0.00003 seconds | +./spec/lib/retry_handler_spec.rb[1:2:1:4] | passed | 0.00002 seconds | +./spec/lib/retry_handler_spec.rb[1:2:2:1] | passed | 0.00002 seconds | +./spec/lib/retry_handler_spec.rb[1:2:2:2] | passed | 0.00002 seconds | +./spec/lib/retry_handler_spec.rb[1:2:2:3] | passed | 0.00002 seconds | ./spec/lib/retry_handler_spec.rb[1:2:2:4] | passed | 0.00003 seconds | ./spec/lib/retry_handler_spec.rb[1:2:2:5] | passed | 0.00003 seconds | ./spec/lib/retry_handler_spec.rb[1:2:3:1] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:3:2] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:2:3:3] | passed | 0.00003 seconds | +./spec/lib/retry_handler_spec.rb[1:2:3:2] | passed | 0.00004 seconds | +./spec/lib/retry_handler_spec.rb[1:2:3:3] | passed | 0.00008 seconds | ./spec/lib/retry_handler_spec.rb[1:2:3:4] | passed | 0.00003 seconds | ./spec/lib/retry_handler_spec.rb[1:2:3:5] | passed | 0.00003 seconds | ./spec/lib/retry_handler_spec.rb[1:2:3:6] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:3:1] | passed | 0.00013 seconds | +./spec/lib/retry_handler_spec.rb[1:3:1] | passed | 0.00008 seconds | ./spec/lib/retry_handler_spec.rb[1:3:2] | passed | 0.00008 seconds | -./spec/lib/retry_handler_spec.rb[1:3:3] | passed | 0.00008 seconds | -./spec/lib/retry_handler_spec.rb[1:3:4] | passed | 0.00009 seconds | -./spec/lib/retry_handler_spec.rb[1:3:5] | passed | 0.00008 seconds | -./spec/lib/retry_handler_spec.rb[1:4:1:1] | passed | 0.00011 seconds | +./spec/lib/retry_handler_spec.rb[1:3:3] | passed | 0.00014 seconds | +./spec/lib/retry_handler_spec.rb[1:3:4] | passed | 0.00008 seconds | +./spec/lib/retry_handler_spec.rb[1:3:5] | passed | 0.00015 seconds | +./spec/lib/retry_handler_spec.rb[1:4:1:1] | passed | 0.00015 seconds | ./spec/lib/retry_handler_spec.rb[1:4:1:2] | passed | 0.00007 seconds | -./spec/lib/retry_handler_spec.rb[1:4:2:1] | passed | 0.00003 seconds | -./spec/lib/retry_handler_spec.rb[1:4:2:2] | passed | 0.00004 seconds | -./spec/lib/retry_handler_spec.rb[1:5:1] | passed | 0.00004 seconds | -./spec/lib/retry_handler_spec.rb[1:5:2] | passed | 0.00004 seconds | -./spec/lib/retry_handler_spec.rb[1:6:1] | passed | 0.00003 seconds | +./spec/lib/retry_handler_spec.rb[1:4:2:1] | passed | 0.00008 seconds | +./spec/lib/retry_handler_spec.rb[1:4:2:2] | passed | 0.00005 seconds | +./spec/lib/retry_handler_spec.rb[1:5:1] | passed | 0.00007 seconds | +./spec/lib/retry_handler_spec.rb[1:5:2] | passed | 0.00006 seconds | +./spec/lib/retry_handler_spec.rb[1:6:1] | passed | 0.00002 seconds | ./spec/lib/retry_handler_spec.rb[1:6:2] | passed | 0.00002 seconds | -./spec/lib/retry_handler_spec.rb[1:6:3] | passed | 0.00002 seconds | +./spec/lib/retry_handler_spec.rb[1:6:3] | passed | 0.00003 seconds | ./spec/lib/retry_handler_spec.rb[1:6:4] | passed | 0.00003 seconds | ./spec/lib/retry_handler_spec.rb[1:6:5] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:1:1:1] | passed | 0.00269 seconds | -./spec/lib/s3_url_parser_spec.rb[1:1:1:2] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:1:1:1] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:1:1:2] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:1:1:3] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:1:1:4] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:1:1:5] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:1:1:6] | passed | 0.00002 seconds | +./spec/lib/s3_url_parser_spec.rb[1:1:1:6] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:1:2:1] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:1:2:2] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:1:2:3] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:1:2:4] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:2:1:1] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:2:1:2] | passed | 0.00002 seconds | +./spec/lib/s3_url_parser_spec.rb[1:1:2:4] | passed | 0.00002 seconds | +./spec/lib/s3_url_parser_spec.rb[1:2:1:1] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:2:1:2] | passed | 0.00009 seconds | ./spec/lib/s3_url_parser_spec.rb[1:2:1:3] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:2:1:4] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:2:2:1] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:2:2:2] | passed | 0.00004 seconds | +./spec/lib/s3_url_parser_spec.rb[1:2:2:2] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:2:2:3] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:3:1:1] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:3:1:2] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:3:1:3] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:3:1:3] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:3:2:1] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:3:2:2] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:3:2:3] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:4:1:1] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:4:1:2] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:4:1:3] | passed | 0.00002 seconds | +./spec/lib/s3_url_parser_spec.rb[1:4:1:2] | passed | 0.00023 seconds | +./spec/lib/s3_url_parser_spec.rb[1:4:1:3] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:4:2:1] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:4:2:2] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:4:2:3] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:4:2:2] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:4:2:3] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:1:1] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:1:2] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:1:3] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:1:4] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:1:5] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:5:2:1] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:5:2:1] | passed | 0.00006 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:2:2] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:2:3] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:5:2:4] | passed | 0.00004 seconds | -./spec/lib/s3_url_parser_spec.rb[1:5:2:5] | passed | 0.00004 seconds | +./spec/lib/s3_url_parser_spec.rb[1:5:2:4] | passed | 0.00008 seconds | +./spec/lib/s3_url_parser_spec.rb[1:5:2:5] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:2:6] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:3:1] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:3:2] | passed | 0.00002 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:3:3] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:5:3:4] | passed | 0.00004 seconds | +./spec/lib/s3_url_parser_spec.rb[1:5:3:4] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:3:5] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:5:4:1] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:5:4:2] | passed | 0.00005 seconds | +./spec/lib/s3_url_parser_spec.rb[1:5:4:2] | passed | 0.00004 seconds | ./spec/lib/s3_url_parser_spec.rb[1:6:1:1] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:6:1:2] | passed | 0.00002 seconds | +./spec/lib/s3_url_parser_spec.rb[1:6:1:2] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:6:1:3] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:6:2:1] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:6:2:2] | passed | 0.00002 seconds | +./spec/lib/s3_url_parser_spec.rb[1:6:2:1] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:6:2:2] | passed | 0.00003 seconds | ./spec/lib/s3_url_parser_spec.rb[1:6:2:3] | passed | 0.00002 seconds | -./spec/lib/s3_url_parser_spec.rb[1:7:1] | passed | 0.00004 seconds | +./spec/lib/s3_url_parser_spec.rb[1:7:1] | passed | 0.0004 seconds | ./spec/lib/s3_url_parser_spec.rb[1:7:2] | passed | 0.00003 seconds | -./spec/lib/s3_url_parser_spec.rb[1:8:1:1] | passed | 0.00005 seconds | -./spec/lib/s3_url_parser_spec.rb[1:8:2:1] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:1] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:2] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:3] | passed | 0.00004 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:4] | passed | 0.00004 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:5] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:6] | passed | 0.00003 seconds | +./spec/lib/s3_url_parser_spec.rb[1:8:1:1] | passed | 0.00079 seconds | +./spec/lib/s3_url_parser_spec.rb[1:8:2:1] | passed | 0.00004 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:1] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:2] | passed | 0.00006 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:3] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:4] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:5] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:6] | passed | 0.00008 seconds | ./spec/lib/url_utils_spec.rb[1:1:1:7] | passed | 0.00005 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:8] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:1:9] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:2:1] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:2:2] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:2:3] | passed | 0.00005 seconds | -./spec/lib/url_utils_spec.rb[1:1:2:4] | passed | 0.00003 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:8] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:1:9] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:2:1] | passed | 0.00004 seconds | +./spec/lib/url_utils_spec.rb[1:1:2:2] | passed | 0.00004 seconds | +./spec/lib/url_utils_spec.rb[1:1:2:3] | passed | 0.00004 seconds | +./spec/lib/url_utils_spec.rb[1:1:2:4] | passed | 0.00004 seconds | ./spec/lib/url_utils_spec.rb[1:1:2:5] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:3:1] | passed | 0.00004 seconds | -./spec/lib/url_utils_spec.rb[1:1:3:2] | passed | 0.00004 seconds | -./spec/lib/url_utils_spec.rb[1:1:3:3] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:1:3:4] | passed | 0.00004 seconds | +./spec/lib/url_utils_spec.rb[1:1:3:1] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:3:2] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:3:3] | passed | 0.00005 seconds | +./spec/lib/url_utils_spec.rb[1:1:3:4] | passed | 0.00006 seconds | ./spec/lib/url_utils_spec.rb[1:2:1:1] | passed | 0.00002 seconds | -./spec/lib/url_utils_spec.rb[1:2:1:2] | passed | 0.00004 seconds | -./spec/lib/url_utils_spec.rb[1:2:1:3] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:2:1:4] | passed | 0.00002 seconds | -./spec/lib/url_utils_spec.rb[1:2:1:5] | passed | 0.00003 seconds | -./spec/lib/url_utils_spec.rb[1:2:2:1] | passed | 0.00002 seconds | +./spec/lib/url_utils_spec.rb[1:2:1:2] | passed | 0.00002 seconds | +./spec/lib/url_utils_spec.rb[1:2:1:3] | passed | 0.00002 seconds | +./spec/lib/url_utils_spec.rb[1:2:1:4] | passed | 0.00008 seconds | +./spec/lib/url_utils_spec.rb[1:2:1:5] | passed | 0.00002 seconds | +./spec/lib/url_utils_spec.rb[1:2:2:1] | passed | 0.00003 seconds | ./spec/lib/url_utils_spec.rb[1:2:2:2] | passed | 0.00002 seconds | -./spec/lib/url_utils_spec.rb[1:2:2:3] | passed | 0.00002 seconds | +./spec/lib/url_utils_spec.rb[1:2:2:3] | passed | 0.00003 seconds | ./spec/lib/url_utils_spec.rb[1:2:2:4] | passed | 0.00002 seconds | ./spec/lib/url_utils_spec.rb[1:2:3:1] | passed | 0.00002 seconds | -./spec/lib/url_utils_spec.rb[1:2:3:2] | passed | 0.00002 seconds | +./spec/lib/url_utils_spec.rb[1:2:3:2] | passed | 0.00003 seconds | +./spec/lib/zip_builder_spec.rb[1:1:1] | passed | 0.00032 seconds | +./spec/lib/zip_builder_spec.rb[1:1:2] | passed | 0.00044 seconds | +./spec/lib/zip_builder_spec.rb[1:1:3] | passed | 0.00066 seconds | +./spec/lib/zip_builder_spec.rb[1:1:4] | passed | 0.00027 seconds | +./spec/lib/zip_builder_spec.rb[1:1:5] | passed | 0.00036 seconds | diff --git a/pdf_converter/spec/app/image_uploader_spec.rb b/pdf_converter/spec/app/image_uploader_spec.rb index ac89ecc..2c47560 100644 --- a/pdf_converter/spec/app/image_uploader_spec.rb +++ b/pdf_converter/spec/app/image_uploader_spec.rb @@ -338,7 +338,8 @@ end describe '#upload_images_from_files' do - let(:destination_url) { 'https://s3.amazonaws.com/bucket/output/?signed=true' } + let(:destination_url) { 'https://s3.amazonaws.com/bucket/output.zip?signed=true' } + let(:unique_id) { 'test-123' } let(:temp_files) do [ Tempfile.new(['page-1', '.png']), @@ -353,7 +354,7 @@ file.rewind end - stub_request(:put, %r{https://s3.amazonaws.com/bucket/output/page-\d+.png}) + stub_request(:put, destination_url) .to_return(status: 200, headers: { 'ETag' => '"test-etag"' }) end @@ -362,70 +363,43 @@ end context 'with successful upload' do - it 'returns success with uploaded URLs' do - result = uploader.upload_images_from_files(destination_url, image_paths) + it 'returns success with zip URL' do + result = uploader.upload_images_from_files(destination_url, image_paths, unique_id) expect(result[:success]).to be true - expect(result[:uploaded_urls]).to be_an(Array) - expect(result[:uploaded_urls].size).to eq(2) + expect(result[:zip_url]).to be_a(String) end - it 'returns ETags for uploaded images' do - result = uploader.upload_images_from_files(destination_url, image_paths) - expect(result[:etags]).to be_an(Array) - expect(result[:etags].size).to eq(2) + it 'returns ETag for uploaded zip' do + result = uploader.upload_images_from_files(destination_url, image_paths, unique_id) + expect(result[:etag]).to eq('"test-etag"') end - it 'strips query parameters from URLs' do - result = uploader.upload_images_from_files(destination_url, image_paths) - result[:uploaded_urls].each do |url| - expect(url).not_to include('signed=') - end + it 'strips query parameters from zip URL' do + result = uploader.upload_images_from_files(destination_url, image_paths, unique_id) + expect(result[:zip_url]).not_to include('signed=') + expect(result[:zip_url]).to include('output.zip') end - it 'generates sequential page names' do - result = uploader.upload_images_from_files(destination_url, image_paths) - expect(result[:uploaded_urls][0]).to include('page-1.png') - expect(result[:uploaded_urls][1]).to include('page-2.png') + it 'logs zip creation' do + expect { uploader.upload_images_from_files(destination_url, image_paths, unique_id) } + .to output(/Creating zip file with 2 images/).to_stdout end - end - - context 'with destination URL ending in slash' do - let(:destination_url) { 'https://s3.amazonaws.com/bucket/output/?signed=true' } - - it 'handles URL with trailing slash' do - result = uploader.upload_images_from_files(destination_url, image_paths) - expect(result[:success]).to be true - end - end - - context 'with destination URL not ending in slash' do - let(:destination_url) { 'https://s3.amazonaws.com/bucket/output?signed=true' } - it 'adds trailing slash to path' do - result = uploader.upload_images_from_files(destination_url, image_paths) - expect(result[:success]).to be true - result[:uploaded_urls].each do |url| - expect(url).to match(%r{/output/page-\d+\.png}) - end + it 'logs zip upload' do + expect { uploader.upload_images_from_files(destination_url, image_paths, unique_id) } + .to output(/Zip file created/).to_stdout end end - context 'with some failed uploads' do + context 'with failed upload' do before do - stub_request(:put, %r{https://s3.amazonaws.com/bucket/output/page-1.png}) - .to_return(status: 200, headers: { 'ETag' => '"etag-1"' }) - stub_request(:put, %r{https://s3.amazonaws.com/bucket/output/page-2.png}) + stub_request(:put, destination_url) .to_return(status: 403, body: 'Forbidden') end - it 'returns error with count' do - result = uploader.upload_images_from_files(destination_url, image_paths) + it 'returns error' do + result = uploader.upload_images_from_files(destination_url, image_paths, unique_id) expect(result[:success]).to be false - expect(result[:error]).to include('Failed to upload 1 images') - end - - it 'includes error messages' do - result = uploader.upload_images_from_files(destination_url, image_paths) expect(result[:error]).to include('Access denied') end end @@ -434,9 +408,9 @@ let(:invalid_paths) { ['/nonexistent/file1.png', '/nonexistent/file2.png'] } it 'returns error for missing files' do - result = uploader.upload_images_from_files(destination_url, invalid_paths) + result = uploader.upload_images_from_files(destination_url, invalid_paths, unique_id) expect(result[:success]).to be false - expect(result[:error]).to include('Upload error') + expect(result[:error]).to include('Zip upload error') end end @@ -444,9 +418,9 @@ let(:invalid_url) { 'not a valid url' } it 'returns error for invalid destination URL' do - result = uploader.upload_images_from_files(invalid_url, image_paths) + result = uploader.upload_images_from_files(invalid_url, image_paths, unique_id) expect(result[:success]).to be false - expect(result[:error]).to include('Upload error') + expect(result[:error]).to match(/Zip upload error|Invalid URL format/) end end end @@ -606,137 +580,6 @@ end end - describe '#parse_destination_url (private)' do - context 'with URL ending in slash' do - let(:url_with_slash) { 'https://s3.amazonaws.com/bucket/output/' } - - it 'keeps trailing slash' do - uri = uploader.send(:parse_destination_url, url_with_slash) - expect(uri.path).to end_with('/') - end - end - - context 'with URL not ending in slash' do - let(:url_without_slash) { 'https://s3.amazonaws.com/bucket/output' } - - it 'adds trailing slash' do - uri = uploader.send(:parse_destination_url, url_without_slash) - expect(uri.path).to end_with('/') - end - end - end - - describe '#prepare_images_for_upload (private)' do - let(:base_uri) { URI.parse('https://s3.amazonaws.com/bucket/output/') } - let(:temp_files) do - [ - Tempfile.new(['test-1', '.png']), - Tempfile.new(['test-2', '.png']) - ] - end - let(:image_paths) { temp_files.map(&:path) } - - before do - temp_files.each_with_index do |file, index| - file.write("content-#{index + 1}") - file.rewind - end - end - - after do - temp_files.each(&:close!) - end - - it 'returns arrays of URLs and contents' do - urls, contents = uploader.send(:prepare_images_for_upload, image_paths, base_uri) - expect(urls).to be_an(Array) - expect(contents).to be_an(Array) - expect(urls.size).to eq(2) - expect(contents.size).to eq(2) - end - - it 'generates sequential page names' do - urls, _contents = uploader.send(:prepare_images_for_upload, image_paths, base_uri) - expect(urls[0]).to include('page-1.png') - expect(urls[1]).to include('page-2.png') - end - - it 'reads file contents' do - _urls, contents = uploader.send(:prepare_images_for_upload, image_paths, base_uri) - expect(contents[0]).to eq('content-1') - expect(contents[1]).to eq('content-2') - end - end - - describe '#process_upload_results (private)' do - let(:image_urls) { ['https://s3.amazonaws.com/bucket/page-1.png?signed=true', 'https://s3.amazonaws.com/bucket/page-2.png?signed=true'] } - - context 'with all successful uploads' do - let(:upload_results) do - [ - { success: true, etag: '"etag-1"', index: 0 }, - { success: true, etag: '"etag-2"', index: 1 } - ] - end - - it 'returns success result' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - expect(result[:success]).to be true - end - - it 'returns uploaded URLs without query params' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - result[:uploaded_urls].each do |url| - expect(url).not_to include('signed=') - end - end - - it 'returns all ETags' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - expect(result[:etags]).to eq(['"etag-1"', '"etag-2"']) - end - end - - context 'with some failed uploads' do - let(:upload_results) do - [ - { success: true, etag: '"etag-1"', index: 0 }, - { success: false, error: 'Access denied - URL may be expired or invalid', index: 1 } - ] - end - - it 'returns failure result' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - expect(result[:success]).to be false - end - - it 'includes error count' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - expect(result[:error]).to include('Failed to upload 1 images') - end - - it 'includes error messages' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - expect(result[:error]).to include('Access denied') - end - end - - context 'with multiple duplicate errors' do - let(:upload_results) do - [ - { success: false, error: 'HTTP 403: Forbidden', index: 0 }, - { success: false, error: 'HTTP 403: Forbidden', index: 1 } - ] - end - - it 'deduplicates error messages' do - result = uploader.send(:process_upload_results, upload_results, image_urls) - # Should only include the error message once - expect(result[:error].scan('HTTP 403').count).to eq(1) - end - end - end - describe '#error_result (private)' do it 'returns hash with success false' do result = uploader.send(:error_result, 'Test error') diff --git a/pdf_converter/spec/app/response_builder_spec.rb b/pdf_converter/spec/app/response_builder_spec.rb index 5bdab47..8e0ef7a 100644 --- a/pdf_converter/spec/app/response_builder_spec.rb +++ b/pdf_converter/spec/app/response_builder_spec.rb @@ -165,7 +165,7 @@ let(:params) do { unique_id: 'test-123', - uploaded_urls: ['https://s3.amazonaws.com/bucket/page-1.png'], + zip_url: 'https://s3.amazonaws.com/bucket/output.zip', page_count: 1, metadata: { dpi: 300, compression: 6 } } @@ -182,7 +182,7 @@ it 'includes success message in body' do body = JSON.parse(response[:body]) - expect(body['message']).to eq('PDF conversion and upload completed') + expect(body['message']).to eq('PDF conversion and zip upload completed') end it 'includes unique_id in body' do @@ -195,9 +195,9 @@ expect(body['status']).to eq('completed') end - it 'includes images array in body' do + it 'includes zip URL in body' do body = JSON.parse(response[:body]) - expect(body['images']).to eq(['https://s3.amazonaws.com/bucket/page-1.png']) + expect(body['images']).to eq('https://s3.amazonaws.com/bucket/output.zip') end it 'includes page count in body' do @@ -214,23 +214,19 @@ expect { JSON.parse(response[:body]) }.not_to raise_error end - context 'with multiple images' do + context 'with multiple pages' do let(:params) do { unique_id: 'multi-page-456', - uploaded_urls: [ - 'https://s3.amazonaws.com/bucket/page-1.png', - 'https://s3.amazonaws.com/bucket/page-2.png', - 'https://s3.amazonaws.com/bucket/page-3.png' - ], + zip_url: 'https://s3.amazonaws.com/bucket/output.zip', page_count: 3, metadata: { dpi: 150, compression: 9 } } end - it 'includes all uploaded URLs' do + it 'includes zip URL and page count' do body = JSON.parse(response[:body]) - expect(body['images'].size).to eq(3) + expect(body['images']).to eq('https://s3.amazonaws.com/bucket/output.zip') expect(body['pages_converted']).to eq(3) end end @@ -239,7 +235,7 @@ let(:params) do { unique_id: 'test-789', - uploaded_urls: ['https://s3.amazonaws.com/bucket/page-1.png'], + zip_url: 'https://s3.amazonaws.com/bucket/output.zip', page_count: 1, metadata: {} } @@ -255,7 +251,7 @@ let(:params) do { unique_id: 'complex-metadata', - uploaded_urls: ['https://s3.amazonaws.com/bucket/page-1.png'], + zip_url: 'https://s3.amazonaws.com/bucket/output.zip', page_count: 1, metadata: { dpi: 300, @@ -280,7 +276,7 @@ auth_error_resp = response_builder.authentication_error_response('Auth Error') success_resp = response_builder.success_response( unique_id: 'test', - uploaded_urls: [], + zip_url: 'https://s3.amazonaws.com/bucket/output.zip', page_count: 0, metadata: {} ) @@ -298,7 +294,7 @@ auth_error_resp = response_builder.authentication_error_response('Auth Error') success_resp = response_builder.success_response( unique_id: 'test', - uploaded_urls: [], + zip_url: 'https://s3.amazonaws.com/bucket/output.zip', page_count: 0, metadata: {} ) diff --git a/pdf_converter/spec/app_spec.rb b/pdf_converter/spec/app_spec.rb index 29ab400..f381ac1 100644 --- a/pdf_converter/spec/app_spec.rb +++ b/pdf_converter/spec/app_spec.rb @@ -58,7 +58,7 @@ let(:upload_result) do { success: true, - uploaded_urls: ['https://s3.amazonaws.com/bucket/output/page-1.png'] + zip_url: 'https://s3.amazonaws.com/bucket/output.zip' } end let(:success_response) do @@ -230,10 +230,7 @@ let(:upload_result) do { success: true, - uploaded_urls: [ - 'https://s3.amazonaws.com/bucket/output/page-1.png', - 'https://s3.amazonaws.com/bucket/output/page-2.png' - ] + zip_url: 'https://s3.amazonaws.com/bucket/output.zip' } end let(:success_response) do @@ -268,11 +265,12 @@ )) end - it 'uploads images to destination' do + it 'uploads images as zip to destination' do process_pdf_conversion(request_body, start_time, response_builder) expect(image_uploader).to have_received(:upload_images_from_files) .with('https://s3.amazonaws.com/bucket/output/?signed', - ['/tmp/test-123/page-1.png', '/tmp/test-123/page-2.png']) + ['/tmp/test-123/page-1.png', '/tmp/test-123/page-2.png'], + 'test-123') end it 'sends webhook notification' do @@ -390,7 +388,7 @@ { statusCode: 422, headers: { 'Content-Type' => 'application/json' }, - body: JSON.generate({ error: 'Image upload failed: S3 access denied' }) + body: JSON.generate({ error: 'Zip upload failed: S3 access denied' }) } end @@ -399,7 +397,7 @@ allow(pdf_converter).to receive(:convert_to_images).and_return(conversion_result) allow(image_uploader).to receive(:upload_images_from_files).and_return(upload_result) allow(response_builder).to receive(:error_response) - .with(422, 'Image upload failed: S3 access denied') + .with(422, 'Zip upload failed: S3 access denied') .and_return(error_response) end @@ -422,7 +420,7 @@ context 'with custom DPI from environment' do let(:download_result) { { success: true, content: 'pdf-binary-content' } } let(:conversion_result) { { success: true, images: ['/tmp/test-123/page-1.png'], metadata: {} } } - let(:upload_result) { { success: true, uploaded_urls: ['https://s3.amazonaws.com/bucket/output/page-1.png'] } } + let(:upload_result) { { success: true, zip_url: 'https://s3.amazonaws.com/bucket/output.zip' } } before do ENV['CONVERSION_DPI'] = '150' @@ -447,7 +445,7 @@ context 'with default DPI when environment variable not set' do let(:download_result) { { success: true, content: 'pdf-binary-content' } } let(:conversion_result) { { success: true, images: ['/tmp/test-123/page-1.png'], metadata: {} } } - let(:upload_result) { { success: true, uploaded_urls: ['https://s3.amazonaws.com/bucket/output/page-1.png'] } } + let(:upload_result) { { success: true, zip_url: 'https://s3.amazonaws.com/bucket/output.zip' } } before do ENV.delete('CONVERSION_DPI') @@ -541,7 +539,7 @@ describe '#notify_webhook' do let(:webhook_url) { 'https://example.com/webhook' } let(:unique_id) { 'test-123' } - let(:uploaded_urls) { ['https://s3.amazonaws.com/bucket/output/page-1.png'] } + let(:zip_url) { 'https://s3.amazonaws.com/bucket/output.zip' } let(:page_count) { 1 } let(:start_time) { Time.now.to_f - 5.5 } @@ -551,24 +549,24 @@ context 'when webhook URL is provided' do it 'sends webhook notification' do - notify_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + notify_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(webhook_notifier).to have_received(:notify) end it 'passes all parameters to webhook notifier' do - notify_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + notify_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(webhook_notifier).to have_received(:notify) .with(hash_including( webhook_url: webhook_url, unique_id: unique_id, status: 'completed', - images: uploaded_urls, + images: zip_url, page_count: page_count )) end it 'calculates processing time in milliseconds' do - notify_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + notify_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(webhook_notifier).to have_received(:notify) do |args| processing_time = args[:processing_time_ms] expect(processing_time).to be_a(Integer) @@ -581,12 +579,12 @@ let(:webhook_url) { nil } it 'does not send webhook notification' do - notify_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + notify_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(webhook_notifier).not_to have_received(:notify) end it 'returns nil early' do - result = notify_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + result = notify_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(result).to be_nil end end @@ -595,7 +593,7 @@ describe '#send_webhook' do let(:webhook_url) { 'https://example.com/webhook' } let(:unique_id) { 'test-123' } - let(:uploaded_urls) { ['https://s3.amazonaws.com/bucket/output/page-1.png'] } + let(:zip_url) { 'https://s3.amazonaws.com/bucket/output.zip' } let(:page_count) { 1 } let(:start_time) { Time.now.to_f - 3.5 } @@ -605,19 +603,19 @@ end it 'sends notification with correct parameters' do - send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(webhook_notifier).to have_received(:notify) .with(hash_including( webhook_url: webhook_url, unique_id: unique_id, status: 'completed', - images: uploaded_urls, + images: zip_url, page_count: page_count )) end it 'calculates processing time correctly' do - send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(webhook_notifier).to have_received(:notify) do |args| processing_time = args[:processing_time_ms] expect(processing_time).to be >= 3500 @@ -626,7 +624,7 @@ end it 'does not return early' do - result = send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + result = send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(result).to be_nil end end @@ -639,18 +637,18 @@ it 'does not raise error' do expect do - send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) end.not_to raise_error end it 'logs warning message' do expect do - send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) end.to output(/WARNING: Webhook notification failed: Connection timeout/).to_stdout end it 'continues without failing the request' do - result = send_webhook(webhook_url, unique_id, uploaded_urls, page_count, start_time) + result = send_webhook(webhook_url, unique_id, zip_url, page_count, start_time) expect(result).to be_nil end end diff --git a/pdf_converter/spec/integration/localstack_integration_spec.rb b/pdf_converter/spec/integration/localstack_integration_spec.rb index 43b172b..d2c9d81 100644 --- a/pdf_converter/spec/integration/localstack_integration_spec.rb +++ b/pdf_converter/spec/integration/localstack_integration_spec.rb @@ -80,20 +80,15 @@ dest_url = s3_presigner.presigned_url( :put_object, bucket: bucket_name, - key: 'output/page-1.png', + key: 'output/result.zip', expires_in: 3600 ) - # Extract base path for destination - URI.parse(dest_url) - # Reconstruct with query params from one of the presigned URLs - dest_base_with_params = dest_url.sub('/page-1.png', '/') - # Create Lambda event event = { 'body' => { 'source' => source_url, - 'destination' => dest_base_with_params, + 'destination' => dest_url, 'webhook' => 'http://localhost:3000/webhook', 'unique_id' => 'test-localstack-123' }.to_json, @@ -112,22 +107,18 @@ expect(response[:statusCode]).to eq(200) body = JSON.parse(response[:body]) expect(body['status']).to eq('completed') - expect(body['images']).to be_an(Array) - expect(body['images'].size).to be > 0 + expect(body['images']).to be_a(String) + expect(body['images']).to include('output/result.zip') expect(body['pages_converted']).to be > 0 - # Verify images were uploaded to S3 - body['images'].each_with_index do |_image_url, index| - key = "output/page-#{index + 1}.png" - - # Check if object exists in S3 - begin - response = s3_client.head_object(bucket: bucket_name, key: key) - expect(response.content_type).to eq('image/png') - expect(response.content_length).to be > 0 - rescue Aws::S3::Errors::NotFound - raise "Expected image #{key} not found in S3" - end + # Verify zip file was uploaded to S3 + zip_key = 'output/result.zip' + begin + zip_response = s3_client.head_object(bucket: bucket_name, key: zip_key) + expect(zip_response.content_type).to eq('application/zip') + expect(zip_response.content_length).to be > 0 + rescue Aws::S3::Errors::NotFound + raise "Expected zip file #{zip_key} not found in S3" end end diff --git a/pdf_converter/spec/lib/zip_builder_spec.rb b/pdf_converter/spec/lib/zip_builder_spec.rb new file mode 100644 index 0000000..75353d4 --- /dev/null +++ b/pdf_converter/spec/lib/zip_builder_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'zip' +require 'tempfile' +require_relative '../../lib/zip_builder' + +RSpec.describe ZipBuilder do + describe '.create_from_images' do + let(:unique_id) { 'test-123' } + let(:temp_files) do + [ + Tempfile.new(['test-1', '.png']), + Tempfile.new(['test-2', '.png']) + ] + end + let(:image_paths) { temp_files.map(&:path) } + + before do + temp_files.each_with_index do |file, index| + file.write("content-#{index + 1}") + file.rewind + end + end + + after do + temp_files.each(&:close!) + end + + it 'creates a zip file with images' do + zip_content = described_class.create_from_images(image_paths, unique_id) + expect(zip_content).to be_a(String) + expect(zip_content).not_to be_empty + end + + it 'names images with unique_id prefix' do + zip_content = described_class.create_from_images(image_paths, unique_id) + + # Read the zip to verify entry names + zip_stream = StringIO.new(zip_content) + Zip::File.open_buffer(zip_stream) do |zip_file| + entries = zip_file.entries.map(&:name) + expect(entries).to include('test-123-0.png') + expect(entries).to include('test-123-1.png') + end + end + + it 'includes image contents in zip' do + zip_content = described_class.create_from_images(image_paths, unique_id) + + # Read the zip to verify contents + zip_stream = StringIO.new(zip_content) + Zip::File.open_buffer(zip_stream) do |zip_file| + expect(zip_file.read('test-123-0.png')).to eq('content-1') + expect(zip_file.read('test-123-1.png')).to eq('content-2') + end + end + + it 'handles empty image paths array' do + zip_content = described_class.create_from_images([], unique_id) + + zip_stream = StringIO.new(zip_content) + Zip::File.open_buffer(zip_stream) do |zip_file| + expect(zip_file.entries).to be_empty + end + end + + it 'correctly orders images by index' do + zip_content = described_class.create_from_images(image_paths, unique_id) + + zip_stream = StringIO.new(zip_content) + Zip::File.open_buffer(zip_stream) do |zip_file| + entry_names = zip_file.entries.map(&:name) + expect(entry_names[0]).to eq('test-123-0.png') + expect(entry_names[1]).to eq('test-123-1.png') + end + end + end +end diff --git a/prompts/completed/001-zip-images-stream-to-s3.md b/prompts/completed/001-zip-images-stream-to-s3.md new file mode 100644 index 0000000..5661e15 --- /dev/null +++ b/prompts/completed/001-zip-images-stream-to-s3.md @@ -0,0 +1,97 @@ + +Modify the PDF converter to zip all generated images and stream the zip file directly to the destination S3 URL instead of uploading individual images. Update all documentation to reflect that the destination pre-signed URL should be for a zip file (e.g., output.zip). + +This change simplifies client integration by providing a single zip file containing all converted pages, reducing API response complexity and improving download efficiency. + + + +This is a serverless PDF to image conversion service built with AWS SAM and Ruby Lambda functions. The service currently: +- Accepts pre-signed S3 URLs for source (PDF) and destination (folder for images) +- Converts PDF pages to PNG images +- Uploads each image individually to S3 + +We need to change the upload behavior to create a zip file containing all images and stream it to the destination URL. + +Review the current implementation: +- @pdf_converter/app.rb - Main Lambda handler and conversion logic +- @pdf_converter/lib/image_uploader.rb - Current image upload implementation +- @CLAUDE.md - Project conventions and setup +- @README.md - User-facing documentation + + + +1. **Code Changes:** + - Modify the image upload process to create a zip file containing all converted PNG images + - Stream the zip file directly to the destination S3 URL (no temporary local file storage if possible, or clean up if needed) + - Maintain the naming convention for images inside the zip: `{unique_id}-0.png`, `{unique_id}-1.png`, etc. + - Update the response format to reflect the new behavior (single zip URL instead of array of image URLs) + - Ensure proper error handling for zip creation and upload + +2. **Response Format Changes:** + - Change `images` field from an array of URLs to a single string containing the destination zip URL + - Update the response message to reflect zip delivery + - Keep all other response fields (unique_id, status, pages_converted, metadata) + +3. **Documentation Updates:** + - Update CLAUDE.md API specification to show destination should be a zip file URL + - Update README.md examples to show pre-signed URLs for zip files + - Update any inline code comments to reflect the new behavior + - Ensure all documentation consistently refers to "zip file" as the output format + +4. **Dependencies:** + - Add the 'rubyzip' gem to handle zip file creation + - Update Gemfile and ensure bundle install is reflected in Dockerfile if needed + + + +Approach: +1. Add rubyzip gem dependency +2. Modify ImageUploader or create a ZipUploader class to: + - Accept an array of image file paths and the destination URL + - Create a zip file in memory or in /tmp + - Stream/upload the zip to the pre-signed S3 URL via HTTP PUT + - Clean up temporary files +3. Update app.rb to use the new zip upload functionality +4. Modify the response JSON structure +5. Update all documentation files + +Constraints: +- Maintain the synchronous processing model - the response should include the zip URL immediately +- Do NOT change the authentication mechanism (JWT) or request validation +- Preserve the webhook notification functionality +- Maintain backwards compatibility with existing error handling patterns +- Use streaming or temporary file cleanup to avoid Lambda /tmp storage limits (512MB max) + + + +Modify/create these files: +- `pdf_converter/Gemfile` - Add rubyzip gem +- `pdf_converter/lib/image_uploader.rb` - Modify to create and upload zip, or create new ZipUploader class +- `pdf_converter/app.rb` - Update to use zip upload and modify response format +- `CLAUDE.md` - Update API specification examples +- `README.md` - Update usage examples and pre-signed URL generation instructions +- Any other files that reference the output format + +Add/update tests: +- `pdf_converter/spec/unit/image_uploader_spec.rb` - Test zip creation and upload +- `pdf_converter/spec/integration/*` - Update integration tests for new response format + + + +Before declaring complete, verify: +1. Run the test suite: `cd pdf_converter && AWS_REGION=us-east-1 bundle exec rspec` +2. Check that all tests pass with the new zip functionality +3. Verify the response JSON structure matches the updated documentation +4. Confirm CLAUDE.md and README.md consistently refer to zip file destinations +5. Ensure Gemfile includes rubyzip and it's properly integrated +6. Check that temporary files are cleaned up after processing + + + +- Code successfully creates a zip file containing all converted PNG images +- Zip file is streamed/uploaded to the destination pre-signed S3 URL +- Response returns a single zip URL instead of an array of image URLs +- All tests pass +- Documentation consistently reflects the new zip-based output format +- No regression in error handling, authentication, or webhook functionality +