Skip to content

Back/sc 4525 fix content type conflict in file uploads#28

Open
RandomGenericUsername wants to merge 3 commits into
masterfrom
back/SC-4525__fix_content_type_conflict_in_file_uploads
Open

Back/sc 4525 fix content type conflict in file uploads#28
RandomGenericUsername wants to merge 3 commits into
masterfrom
back/SC-4525__fix_content_type_conflict_in_file_uploads

Conversation

@RandomGenericUsername

@RandomGenericUsername RandomGenericUsername commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

This PR addresses the issue reported at https://app.shortcut.com/ubidots/story/4525/ubidots-cli-file-upload-requests-fail-with-json-parse-error-due-to-content-type-conflict

Summary by CodeRabbit

  • Bug Fixes

    • Corrected content-type header handling across file upload endpoints to ensure proper multipart/form-data formatting while preserving authentication headers.
  • Tests

    • Added tests for file upload endpoints to verify correct content-type handling and authentication header preservation.

Juan Tangarife and others added 2 commits June 22, 2026 16:00
…file uploads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@RandomGenericUsername, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 59 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aef0921f-85c2-49aa-8547-b0c3bff46c47

📥 Commits

Reviewing files that changed from the base of the PR and between d363a69 and 616c8e7.

📒 Files selected for processing (2)
  • cli/functions/tests/test_auth_dispatch.py
  • pyproject.toml
📝 Walkthrough

Walkthrough

Three upload handlers (add_function, UploadFileStep.execute, upload_page_code) are updated to strip any Content-Type header from the caller-supplied headers before issuing a multipart ZIP POST, preventing application/json from overriding the multipart boundary. Tests are added or extended to assert this behavior across all three paths.

Changes

Content-Type stripping for ZIP uploads

Layer / File(s) Summary
Strip Content-Type in upload handlers and pipeline
cli/functions/handlers.py, cli/functions/pipelines.py, cli/pages/handlers.py
Each upload site now builds upload_headers by copying the input headers and removing any Content-Type key (case-insensitive) before calling client.post with the multipart files payload.
Tests asserting multipart Content-Type and header preservation
cli/functions/tests/test_pipelines.py, cli/functions/tests/test_auth_dispatch.py, cli/pages/tests/test_handlers.py
New TestUploadFileStep class and extended helpers assert that upload requests carry multipart/form-data, do not retain application/json, and still forward auth tokens correctly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐇 A header snuck in, claiming JSON its name,
But the ZIP had a multipart boundary to claim.
With a snip and a filter, the type was stripped clean,
The form-data flew free — no conflict between!
Hop hop, the uploads now work as they should. 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly references the main change: fixing content-type conflicts in file uploads, which aligns with the primary objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch back/SC-4525__fix_content_type_conflict_in_file_uploads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@RandomGenericUsername RandomGenericUsername force-pushed the back/SC-4525__fix_content_type_conflict_in_file_uploads branch from 10ec295 to 616c8e7 Compare June 22, 2026 21:22

@jdavidagudelo jdavidagudelo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tests working and no issues with the code.

@gajaguar gajaguar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Los hallazgos son oportunidades de mejora que no bloquean la funcionalidad. Se aprueba el PR.

Comment thread cli/pages/handlers.py
}
client = httpx.Client(follow_redirects=True)
return client.post(url=url, headers=headers, files=files)
upload_headers = {k: v for k, v in headers.items() if k.lower() != "content-type"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Encapsulamiento: La misma expresión de filtrado de cabeceras aparece literalmente en cli/functions/handlers.py, cli/functions/pipelines.py y aquí. Extraerla a una pequeña función utilitaria (por ejemplo, strip_content_type(headers: dict) -> dict en un módulo compartido) concentra la regla en un solo lugar. Si algún día se necesita excluir más cabeceras, basta un único cambio en vez de tres ediciones coordinadas.

assert "multipart/form-data" in content_type
assert "application/json" not in content_type

@respx.mock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Legibilidad: test_does_not_send_json_content_type y test_preserves_auth_header comparten un setup idéntico — URL, ruta mockeada, project_metadata y diccionario de entrada — duplicado verbatim. Extraer un fixture o helper compartido acorta ambos tests y reduce el riesgo de divergencia si cambia la firma de UploadFileStep.execute.

assert route.called
assert route.calls.last.request.headers.get("x-auth-token") == "my-token"


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mantenibilidad: Este módulo tiene 1117 líneas y cubre 14 clases de prueba sin relación directa entre sí. A partir de las ~500 líneas, la navegación y el alcance de fallos se vuelven costosos. Considera dividirlo por agrupación funcional — por ejemplo, un archivo por paso o por dominio (test_upload_steps.py, test_invoke_steps.py) — siguiendo la estructura del módulo pipelines.py que testea.

self.assertTrue(route.called)
content_type = route.calls.last.request.headers.get("content-type", "")
self.assertIn("multipart/form-data", content_type, "upload must use multipart/form-data")
self.assertNotIn("application/json", content_type, "upload must not send Content-Type: application/json")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Contrato: Comparar route.calls.last.request.headers contra un diccionario esperado completo elimina la variable intermedia, hace explícito el contrato total de cabeceras que debe enviar el upload, y convierte dos assertIn/assertNotIn parciales en una sola aserción que falla con un diferencial legible.

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.

3 participants