Skip to content

Conversation

@evgeni
Copy link
Member

@evgeni evgeni commented Jun 6, 2025

What are the changes introduced in this pull request?

Considerations taken when implementing this change?

What are the testing steps for this pull request?

Summary by Sourcery

Read file data when chunk-uploading content via ActionDispatch::Http::UploadedFile and ensure proper file upload behavior

Bug Fixes:

  • Read file contents from UploadedFile in content uploads to correctly pass raw data to the backend service.

Tests:

  • Add test_update_file to verify that file uploads are read and uploaded correctly during update operations.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jun 6, 2025

Reviewer's Guide

Ensures uploaded files are properly read into raw data before being passed to the backend upload_chunk service, and adds a dedicated test to validate this behavior.

File-Level Changes

Change Details Files
Read file-like content before invoking upload_chunk
  • Introduce a local content variable that checks for read and extracts raw data when possible
  • Modify the upload_chunk call to use the raw content instead of the original params[:content]
app/controllers/katello/api/v2/content_uploads_controller.rb
Add test coverage for file chunk upload
  • Implement test_update_file to exercise the update endpoint with a Rack::Test::UploadedFile
  • Stub the backend service’s upload_chunk to expect correct id, offset, content, and size
  • Send a PUT request with uploaded file and assert a successful response
test/controllers/api/v2/content_uploads_controller_test.rb

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @evgeni - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@evgeni
Copy link
Member Author

evgeni commented Jun 6, 2025

To illustrate the difference, look how a RestClient request ends up with data in form, while requests in files

require 'rest-client'
x = RestClient.post('https://httpbin.org/post', {:content => '123', :multipart => true})
puts(x.body)
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "content": "123"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", 
    "Content-Length": "137", 
    "Content-Type": "multipart/form-data; boundary=----RubyFormBoundarye6tKlTzVaGTnwPlq", 
    "Host": "httpbin.org", 
    "User-Agent": "rest-client/2.1.0 (linux x86_64) ruby/3.4.2p28", 
    "X-Amzn-Trace-Id": "Root=1-6842c1d5-651aa08573ee4cb73499da57"
  }, 
  "json": null, 
  "origin": "134.19.2.92", 
  "url": "https://httpbin.org/post"
}
import requests
x = requests.post('https://httpbin.org/post', files={'content': '123'})
print(x.content.decode())
{
  "args": {}, 
  "data": "", 
  "files": {
    "content": "123"
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "149", 
    "Content-Type": "multipart/form-data; boundary=6dcb82266e32ffc47cce9726a4347f68", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.32.3", 
    "X-Amzn-Trace-Id": "Root=1-6842c209-3494e6b43f90173d396017a1"
  }, 
  "json": null, 
  "origin": "134.19.2.92", 
  "url": "https://httpbin.org/post"
}

To get RestClient to use files, you need to pass a "real" File object:

irb(main):028> x = RestClient.post('https://httpbin.org/post', {:content => File.open('/tmp/l')})
=> <RestClient::Response 200 "{\n  \"args\":...">
irb(main):029> puts(x.body)
{
  "args": {}, 
  "data": "", 
  "files": {
    "content": "lol\n"
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", 
    "Content-Length": "178", 
    "Content-Type": "multipart/form-data; boundary=----RubyFormBoundaryPpuABAVAqyw2Gs30", 
    "Host": "httpbin.org", 
    "User-Agent": "rest-client/2.1.0 (linux x86_64) ruby/3.4.2p28", 
    "X-Amzn-Trace-Id": "Root=1-6842c678-3f5616972cd8f1710e796615"
  }, 
  "json": null, 
  "origin": "134.19.2.92", 
  "url": "https://httpbin.org/post"
}

Technically, I think, using files= for Python is not exactly correct -- it's just a chunk, not a whole file.
But that's the only way to make requests send multipart-encoded things (passing data= results in form encoding), without doing the whole encoding/headers dance manually, which I'd really prefer to avoid.

@evgeni
Copy link
Member Author

evgeni commented Jun 6, 2025

The test failures do not seem related?

@ianballou
Copy link
Member

Out of curiosity, do you have an example about how to use this change with cURL or a Python client?

@evgeni
Copy link
Member Author

evgeni commented Jun 6, 2025

I have theforeman/foreman-ansible-modules#1871 -- does that suffice? Or would you like a pure Python implementation? :)

Copy link
Member

@ianballou ianballou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

@ianballou
Copy link
Member

Feel free to merge when ready

@evgeni evgeni merged commit 593de75 into Katello:master Jun 10, 2025
38 of 43 checks passed
@evgeni evgeni deleted the i38482 branch June 10, 2025 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants