Skip to content

Add ImportFromCurl (closes #124)#135

Draft
dennisdoomen wants to merge 1 commit into
mainfrom
dennisdoomen/import-from-curl
Draft

Add ImportFromCurl (closes #124)#135
dennisdoomen wants to merge 1 commit into
mainfrom
dennisdoomen/import-from-curl

Conversation

@dennisdoomen

Copy link
Copy Markdown
Owner

Closes #124

Summary

Adds HttpMock.ImportFromCurl(string curlCommand) to bootstrap a request mock from an existing curl command — for example the output of a browser's "Copy as cURL" or a snippet from API docs. The parsed request is translated into the equivalent fluent matching configuration and a RequestMockBuilder is returned so a response can be attached:

mock.ImportFromCurl("""
    curl -X POST https://api.example.com/users \
      -H 'Content-Type: application/json' \
      --data-raw '{"name":"mockly"}'
    """)
    .RespondsWithStatus(HttpStatusCode.Created);

This PR is scoped to cURL only (Phase 1). HAR import is deferred to a follow-up PR (see the issue's Phase 2). No new runtime dependency is introduced — parsing uses only the BCL and System.Text.Json.

New public API

  • Mockly.HttpMock.ImportFromCurl(string curlCommand) : RequestMockBuilder

(API verification files under Mockly.ApiVerificationTests/ApprovedApi were regenerated via AcceptApiChanges.ps1.)

What gets parsed

  • Method-X/--request. Defaults to POST when a body is present, otherwise GET.
  • URL — the bare argument or --url; scheme, host, path and query are mapped onto the builder.
  • Headers-H/--header (plus -A/--user-agent, -e/--referer, -b/--cookie). Content-Type is matched against the request's content type (parameters such as charset ignored).
  • Body-d/--data/--data-raw/--data-ascii/--data-binary/--data-urlencode. JSON bodies are matched whitespace-insensitively; other bodies are matched exactly. Multiple data options are joined with &.

Shell syntax handling

  • Single quotes, double quotes (with \" \\ \$ \`` escapes), and line continuations (`, ^, ` followed by a newline).
  • A leading curl token is ignored.
  • Options known to take a value (e.g. -u user:pass, -o, --connect-timeout) are consumed so their value is not mistaken for the URL. Other unknown flags are ignored.
  • Malformed input (empty command, missing URL, missing option value, unterminated quote, malformed header) throws a clear ArgumentException (ArgumentNullException for null).

Tests

Added Mockly.Specs/CurlImportSpecs.cs (xUnit + FluentAssertions, AAA, nested classes) covering GET, query strings, explicit/lowercase/defaulted methods, single & multiple headers, JSON and form-encoded bodies, exact-body negative matching, value-taking option handling, line continuations and double quotes, plus malformed-input cases. Full suite is green on net8.0 (111) and net472 (110).

Notes for reviewers

  • Header strictness: faithful to the issue, every imported -H becomes a matcher. Browser "Copy as cURL" output includes volatile headers (User-Agent, Accept, Sec-*), so an imported mock can be stricter than a plain HttpClient request. Happy to soften this (e.g. only match a curated subset by default) if preferred.
  • Unsupported content headers (e.g. Content-Length, Content-Encoding) are skipped rather than turned into matchers that could never pass.
  • Semantic edge cases such as --data @file, --data-urlencode encoding, and -G are not specially handled yet; can be added if in scope.

🤖 Generated with GitHub Copilot CLI

…124)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dennisdoomen dennisdoomen added the enhancement New feature or request label May 30, 2026
Comment thread Mockly/CurlImport.cs
request.Headers.Add(new KeyValuePair<string, string>(name, value));
}

private CurlRequest Build(string curlCommand)
Comment thread Mockly/HttpMock.cs

if (request.Headers.TryGetValues(name, out IEnumerable<string>? values))
{
return values.Any(v => string.Equals(v, value, StringComparison.Ordinal)) ||
Comment thread Mockly/HttpMock.cs
if (request.Headers.TryGetValues(name, out IEnumerable<string>? values))
{
return values.Any(v => string.Equals(v, value, StringComparison.Ordinal)) ||
string.Equals(string.Join(", ", values), value, StringComparison.Ordinal);
Comment thread Mockly/CurlImport.cs
private bool tokenStarted;
private int position;

public CurlTokenizer(string text)
@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown

Test Results

  3 files  ± 0    3 suites  ±0   7s ⏱️ -1s
113 tests +24  113 ✅ +24  0 💤 ±0  0 ❌ ±0 
223 runs  +48  223 ✅ +48  0 💤 ±0  0 ❌ ±0 

Results for commit 3c4dc4a. ± Comparison against base commit 5239fa0.

♻️ This comment has been updated with latest results.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 26678625011

Coverage decreased (-0.9%) to 82.733%

Details

  • Coverage decreased (-0.9%) from the base build.
  • Patch coverage: 52 uncovered changes across 2 files (283 of 335 lines covered, 84.48%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
Mockly/CurlImport.cs 256 212 82.81%
Mockly/HttpMock.cs 79 71 89.87%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1345
Covered Lines: 1182
Line Coverage: 87.88%
Relevant Branches: 543
Covered Branches: 380
Branch Coverage: 69.98%
Branches in Coverage %: Yes
Coverage Strength: 290.12 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add import helpers to build mocks from curl commands (and optionally HAR files)

3 participants